From ced4a9ae644e1045dc01b75288f13257742be2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 17 Dec 2024 15:01:12 -0500 Subject: [PATCH] Inject extension scopes while running the resolution algorithm (#1170) This PR builds on top of #1149 This changes the way we handle extension scopes (ie. `using` directives) in bindings. Instead of using the scope stack from the stack graph, we hook into the resolution algorithm and inject new graph edges from nodes designated as extension hooks (usually the source unit's lexical scope) to extension scopes which are defined at each contract/library and contain the definition nodes from `using` directives. This simplifies the rules quite a bit and greatly improves performance, particularly in the case of Solidity < 0.7.0 where `using` directives are inherited from base contracts. This PR also moves built-ins parsing and ingestion to `slang_solidity` crate. Since the built-ins file needs to be pre-processed to transform the symbols as to ensure no conflicts can occur with user code, adding the built-ins requires a couple of manual steps that were replicated in every construction of Solidity bindings API. By encapsulating this functionality in the `slang_solidity` crate we remove a source of user error and make it easier to make changes to the built-ins ingestion code. --- .../bindings/generated/public_api.txt | 3 +- crates/metaslang/bindings/src/builder/mod.rs | 117 +++++++++- crates/metaslang/bindings/src/lib.rs | 48 +++-- crates/metaslang/bindings/src/resolver/mod.rs | 146 +++++++++++-- .../inputs/language/bindings/rules.msgb | 203 +++++------------- .../bindings/generated/binding_rules.rs | 203 +++++------------- .../outputs/cargo/tests/src/bindings.rs | 10 + .../src/bindings_assertions/assertions.rs | 2 +- .../tests/src/bindings_assertions/runner.rs | 4 +- .../tests/src/bindings_output/renderer.rs | 2 +- .../cargo/tests/src/bindings_output/runner.rs | 4 +- .../solidity/outputs/cargo/tests/src/lib.rs | 1 + .../testing/perf/src/tests/references.rs | 2 +- 13 files changed, 403 insertions(+), 342 deletions(-) create mode 100644 crates/solidity/outputs/cargo/tests/src/bindings.rs diff --git a/crates/metaslang/bindings/generated/public_api.txt b/crates/metaslang/bindings/generated/public_api.txt index 0e7a4e3bb8..e0fe3e3212 100644 --- a/crates/metaslang/bindings/generated/public_api.txt +++ b/crates/metaslang/bindings/generated/public_api.txt @@ -32,7 +32,6 @@ pub fn metaslang_bindings::BindingGraph::all_references(&self) -> impl core: pub fn metaslang_bindings::BindingGraph::create(version: semver::Version, binding_rules: &str, path_resolver: alloc::rc::Rc>) -> Self pub fn metaslang_bindings::BindingGraph::definition_at(&self, cursor: &metaslang_cst::cursor::Cursor) -> core::option::Option> pub fn metaslang_bindings::BindingGraph::get_context(&self) -> core::option::Option> -pub fn metaslang_bindings::BindingGraph::lookup_definition_by_name(&self, name: &str) -> core::option::Option> pub fn metaslang_bindings::BindingGraph::reference_at(&self, cursor: &metaslang_cst::cursor::Cursor) -> core::option::Option> pub fn metaslang_bindings::BindingGraph::set_context(&mut self, context: &metaslang_bindings::DefinitionHandle) pub struct metaslang_bindings::BuiltInLocation @@ -67,8 +66,8 @@ pub fn metaslang_bindings::Reference<'a, KT>::definitions(&self) -> alloc::vec:: pub fn metaslang_bindings::Reference<'a, KT>::get_cursor(&self) -> &metaslang_cst::cursor::Cursor pub fn metaslang_bindings::Reference<'a, KT>::get_file(&self) -> metaslang_bindings::FileDescriptor pub fn metaslang_bindings::Reference<'a, KT>::id(&self) -> usize -pub fn metaslang_bindings::Reference<'a, KT>::jump_to_definition(&self) -> core::result::Result, metaslang_bindings::ResolutionError<'a, KT>> pub fn metaslang_bindings::Reference<'a, KT>::location(&self) -> metaslang_bindings::BindingLocation +pub fn metaslang_bindings::Reference<'a, KT>::resolve_definition(&self) -> core::result::Result, metaslang_bindings::ResolutionError<'a, KT>> impl<'a, KT: core::clone::Clone + metaslang_cst::kinds::KindTypes + 'static> core::clone::Clone for metaslang_bindings::Reference<'a, KT> pub fn metaslang_bindings::Reference<'a, KT>::clone(&self) -> metaslang_bindings::Reference<'a, KT> impl core::cmp::PartialEq for metaslang_bindings::Reference<'_, KT> diff --git a/crates/metaslang/bindings/src/builder/mod.rs b/crates/metaslang/bindings/src/builder/mod.rs index 3a27e9b1cc..f768302d52 100644 --- a/crates/metaslang/bindings/src/builder/mod.rs +++ b/crates/metaslang/bindings/src/builder/mod.rs @@ -130,7 +130,7 @@ //! To do this, add a `source_node` attribute, whose value is a syntax node capture: //! //! ``` skip -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]]] { //! node def //! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition //! } @@ -161,7 +161,7 @@ //! `syntax_type` attribute, whose value is a string indicating the syntax type. //! //! ``` skip -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]]] { //! node def //! ; ... //! attr (def) syntax_type = "function" @@ -175,7 +175,7 @@ //! `definiens_node` attribute, whose value is a syntax node that spans the definiens. //! //! ``` skip -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ... @body [FunctionBody] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]] @body [FunctionBody]] { //! node def //! ; ... //! attr (def) definiens_node = @body @@ -189,7 +189,7 @@ //! To connect two stack graph nodes, use the `edge` statement to add an edge between them: //! //! ``` skip -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]]] { //! node def //! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition //! node body @@ -201,7 +201,7 @@ //! you can add a `precedence` attribute to each edge to indicate which paths are prioritized: //! //! ``` skip -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]]] { //! node def //! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition //! node body @@ -220,7 +220,7 @@ //! ``` skip //! global ROOT_NODE //! -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]]] { //! node def //! attr (def) type = "pop_symbol", symbol = (source-text @id), source_node = @func, is_definition //! edge ROOT_NODE -> def @@ -235,7 +235,7 @@ //! a scope node with a kind as follows: //! //! ``` skip -//! @func [FunctionDefinition ... [FunctionName @id [Identifier]] ...] { +//! @func [FunctionDefinition [FunctionName @id [Identifier]]] { //! ; ... //! node param_scope //! attr (param_scope) debug_kind = "param_scope" @@ -243,6 +243,72 @@ //! } //! ``` //! +//! ### Other node attributes introduced in Slang's usage of stack-graphs +//! +//! #### `tag` attribute +//! +//! This is used to attach a specific meaning to the node, to alter the ranking +//! algorithm used when attempting to disambiguate between multiple definitions +//! found for a reference. This is an optional string attribute. +//! +//! Possible values: +//! +//! - "alias": marks a definition node as a semantic alias of another definition +//! (eg. an import alias) +//! +//! - "c3": used to mark a function/method definition to be a candidate in +//! disambiguation using the C3 linearisation algorithm. In order for C3 +//! linearisation to be possible, type hierarchy attributes need to be provided +//! as well (see `parents` attribute below). +//! +//! - "super": marks a reference as a call to super virtual call. This modifies +//! the C3 linearisation algorithm by eliminating the candidates that are at +//! or further down the hierarchy of where the reference occurs. To determine +//! where the reference occurs, we also use the `parents` attribute. +//! +//! #### `parents` attribute +//! +//! Is used to convey semantic hierarchy. Can be applied to both definitions and +//! references. It's an optional, list of graph nodes attribute. +//! +//! For references it can indicate in which language context the reference +//! occurs (eg. in which method or class). For definitions it can indicate the +//! enclosing type of the definition, or parent classes in a class hierarchy. +//! The parent handles themselves can refer to definitions or references. In the +//! later case, generally speaking they will need to be resolved at resolution +//! time in order to be useful. +//! +//! #### `export_node` and `import_nodes` +//! +//! These are used to define static fixed edges to add via `set_context()`. +//! Using `set_context()` will modify the underlying stack graph by inserting +//! edges from the `import_nodes` of all parents (resolved recursively) of the +//! given context, to the `export_node` associated with the context. +//! +//! This can be used to inject virtual method implementations defined in +//! subclasses in the scope of their parent classes, which are otherwise +//! lexically inaccessible. +//! +//! `export_node` is an optional graph node attribute, and `import_nodes` is an +//! optional list of graph nodes. Both apply only to definition nodes. +//! +//! #### `extension_hook`, `extension_scope` and `inherit_extensions` +//! +//! These attributes enable the bindings API to resolve extension methods by +//! injecting specific scopes at potentially unrelated (lexically speaking) +//! nodes in the stack graph. Availability and application of extension scopes +//! depend on the call site (ie. the reference node). Thus, the extension scope +//! to (potentially) apply when resolving a reference is computed by looking up +//! the `parents` of the reference and then querying those parent nodes for +//! their `extension_scope` (an optional scope node). Any extension providing +//! node can also have the `inherit_extensions` attribute (a boolean) which +//! indicates that the algorithm should recurse and resolve its parents to +//! further look for other extensions scopes. +//! +//! Finally, the attribute `extension_hook` defines where in the graph should +//! these extension scopes be injected. This is typically the root lexical +//! scope. This attribute applies to any scope node and is boolean. +//! mod cancellation; mod functions; @@ -282,6 +348,9 @@ static IS_DEFINITION_ATTR: &str = "is_definition"; static IS_ENDPOINT_ATTR: &str = "is_endpoint"; static IS_EXPORTED_ATTR: &str = "is_exported"; static IS_REFERENCE_ATTR: &str = "is_reference"; +static EXTENSION_HOOK_ATTR: &str = "extension_hook"; +static EXTENSION_SCOPE_ATTR: &str = "extension_scope"; +static INHERIT_EXTENSIONS_ATTR: &str = "inherit_extensions"; static PARENTS_ATTR: &str = "parents"; static SCOPE_ATTR: &str = "scope"; static SOURCE_NODE_ATTR: &str = "source_node"; @@ -302,6 +371,8 @@ static POP_SCOPED_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { EXPORT_NODE_ATTR, IMPORT_NODES_ATTR, SYNTAX_TYPE_ATTR, + EXTENSION_SCOPE_ATTR, + INHERIT_EXTENSIONS_ATTR, ]) }); static POP_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { @@ -315,6 +386,8 @@ static POP_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { EXPORT_NODE_ATTR, IMPORT_NODES_ATTR, SYNTAX_TYPE_ATTR, + EXTENSION_SCOPE_ATTR, + INHERIT_EXTENSIONS_ATTR, ]) }); static PUSH_SCOPED_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { @@ -336,8 +409,14 @@ static PUSH_SYMBOL_ATTRS: Lazy> = Lazy::new(|| { PARENTS_ATTR, ]) }); -static SCOPE_ATTRS: Lazy> = - Lazy::new(|| HashSet::from([TYPE_ATTR, IS_EXPORTED_ATTR, IS_ENDPOINT_ATTR])); +static SCOPE_ATTRS: Lazy> = Lazy::new(|| { + HashSet::from([ + TYPE_ATTR, + IS_EXPORTED_ATTR, + IS_ENDPOINT_ATTR, + EXTENSION_HOOK_ATTR, + ]) +}); // Edge attribute names static PRECEDENCE_ATTR: &str = "precedence"; @@ -362,6 +441,7 @@ pub(crate) struct Builder<'a, KT: KindTypes + 'static> { cursors: HashMap, Cursor>, definitions_info: HashMap, DefinitionBindingInfo>, references_info: HashMap, ReferenceBindingInfo>, + extension_hooks: HashSet>, } pub(crate) struct BuildResult { @@ -370,6 +450,8 @@ pub(crate) struct BuildResult { pub cursors: HashMap, Cursor>, pub definitions_info: HashMap, DefinitionBindingInfo>, pub references_info: HashMap, ReferenceBindingInfo>, + // Nodes where we want to inject extensions + pub extension_hooks: HashSet>, } impl<'a, KT: KindTypes + 'static> Builder<'a, KT> { @@ -392,6 +474,7 @@ impl<'a, KT: KindTypes + 'static> Builder<'a, KT> { cursors: HashMap::new(), definitions_info: HashMap::new(), references_info: HashMap::new(), + extension_hooks: HashSet::new(), } } @@ -480,6 +563,7 @@ impl<'a, KT: KindTypes + 'static> Builder<'a, KT> { cursors: self.cursors, definitions_info: self.definitions_info, references_info: self.references_info, + extension_hooks: self.extension_hooks, }) } @@ -896,6 +980,15 @@ impl<'a, KT: KindTypes> Builder<'a, KT> { None => Vec::new(), }; + let extension_scope = match node.attributes.get(EXTENSION_SCOPE_ATTR) { + Some(extension_scope) => { + Some(self.node_handle_for_graph_node(extension_scope.as_graph_node_ref()?)) + } + None => None, + }; + + let inherit_extensions = Self::load_flag(node, INHERIT_EXTENSIONS_ATTR)?; + self.definitions_info.insert( node_handle, DefinitionBindingInfo { @@ -904,6 +997,8 @@ impl<'a, KT: KindTypes> Builder<'a, KT> { parents, export_node, import_nodes, + extension_scope, + inherit_extensions, }, ); } else if stack_graph_node.is_reference() { @@ -911,6 +1006,10 @@ impl<'a, KT: KindTypes> Builder<'a, KT> { .insert(node_handle, ReferenceBindingInfo { tag, parents }); } + if Self::load_flag(node, EXTENSION_HOOK_ATTR)? { + self.extension_hooks.insert(node_handle); + } + Ok(()) } diff --git a/crates/metaslang/bindings/src/lib.rs b/crates/metaslang/bindings/src/lib.rs index 6b655f2a33..e830f97004 100644 --- a/crates/metaslang/bindings/src/lib.rs +++ b/crates/metaslang/bindings/src/lib.rs @@ -12,7 +12,7 @@ use metaslang_cst::cursor::Cursor; use metaslang_cst::kinds::KindTypes; use metaslang_graph_builder::ast::File; use metaslang_graph_builder::functions::Functions; -use resolver::Resolver; +use resolver::{ResolveOptions, Resolver}; use semver::Version; use stack_graphs::graph::StackGraph; @@ -36,10 +36,10 @@ pub(crate) struct DefinitionBindingInfo { definiens: Option>, tag: Option, parents: Vec, - #[allow(dead_code)] export_node: Option, - #[allow(dead_code)] import_nodes: Vec, + extension_scope: Option, + inherit_extensions: bool, } pub(crate) struct ReferenceBindingInfo { @@ -57,6 +57,7 @@ pub struct BindingGraph { cursor_to_definitions: HashMap, cursor_to_references: HashMap, context: Option, + extension_hooks: HashSet, } pub enum FileDescriptor { @@ -140,6 +141,7 @@ impl BindingGraph { cursor_to_definitions: HashMap::new(), cursor_to_references: HashMap::new(), context: None, + extension_hooks: HashSet::new(), } } @@ -191,6 +193,7 @@ impl BindingGraph { self.definitions_info .extend(result.definitions_info.drain()); self.references_info.extend(result.references_info.drain()); + self.extension_hooks.extend(result.extension_hooks.drain()); result } @@ -258,17 +261,12 @@ impl BindingGraph { if self.stack_graph[*handle].is_definition() { self.to_definition(*handle) } else { - self.to_reference(*handle)?.jump_to_definition().ok() + self.to_reference(*handle)?.non_recursive_resolve().ok() } }) .collect() } - pub fn lookup_definition_by_name(&self, name: &str) -> Option> { - self.all_definitions() - .find(|definition| definition.get_cursor().node().unparse() == name) - } - pub fn get_context(&self) -> Option> { self.context.and_then(|handle| self.to_definition(handle)) } @@ -337,6 +335,10 @@ impl BindingGraph { } results } + + pub(crate) fn is_extension_hook(&self, node_handle: GraphHandle) -> bool { + self.extension_hooks.contains(&node_handle) + } } struct DisplayCursor<'a, KT: KindTypes + 'static> { @@ -428,6 +430,20 @@ impl<'a, KT: KindTypes + 'static> Definition<'a, KT> { .unwrap_or_default() } + pub(crate) fn get_extension_scope(&self) -> Option { + self.owner + .definitions_info + .get(&self.handle) + .and_then(|info| info.extension_scope) + } + + pub(crate) fn inherit_extensions(&self) -> bool { + self.owner + .definitions_info + .get(&self.handle) + .map_or(false, |info| info.inherit_extensions) + } + pub fn to_handle(self) -> DefinitionHandle { DefinitionHandle(self.handle) } @@ -509,12 +525,20 @@ impl<'a, KT: KindTypes + 'static> Reference<'a, KT> { .expect("Reference does not have a valid file descriptor") } - pub fn jump_to_definition(&self) -> Result, ResolutionError<'a, KT>> { - Resolver::build_for(self).first() + pub fn resolve_definition(&self) -> Result, ResolutionError<'a, KT>> { + Resolver::build_for(self, ResolveOptions::Full).first() } pub fn definitions(&self) -> Vec> { - Resolver::build_for(self).all() + Resolver::build_for(self, ResolveOptions::Full).all() + } + + pub(crate) fn non_recursive_resolve( + &self, + ) -> Result, ResolutionError<'a, KT>> { + // This was likely originated from a full resolution call, so cut + // recursion here by restricting the resolution algorithm. + Resolver::build_for(self, ResolveOptions::NonRecursive).first() } pub(crate) fn has_tag(&self, tag: Tag) -> bool { diff --git a/crates/metaslang/bindings/src/resolver/mod.rs b/crates/metaslang/bindings/src/resolver/mod.rs index 7019a73425..bcd01dc3b7 100644 --- a/crates/metaslang/bindings/src/resolver/mod.rs +++ b/crates/metaslang/bindings/src/resolver/mod.rs @@ -2,10 +2,14 @@ use std::collections::{HashMap, HashSet}; use std::iter::once; use metaslang_cst::kinds::KindTypes; +use stack_graphs::graph::{Degree, Edge, StackGraph}; use stack_graphs::partial::{PartialPath, PartialPaths}; -use stack_graphs::stitching::{ForwardPartialPathStitcher, GraphEdgeCandidates, StitcherConfig}; +use stack_graphs::stitching::{ + ForwardCandidates, ForwardPartialPathStitcher, GraphEdgeCandidates, GraphEdges, StitcherConfig, +}; +use stack_graphs::CancellationError; -use crate::{BindingGraph, Definition, Reference, ResolutionError, Tag}; +use crate::{BindingGraph, Definition, FileHandle, GraphHandle, Reference, ResolutionError, Tag}; mod c3; @@ -37,6 +41,13 @@ pub(crate) struct Resolver<'a, KT: KindTypes + 'static> { reference: Reference<'a, KT>, partials: PartialPaths, results: Vec>, + options: ResolveOptions, +} + +#[derive(Copy, Clone, Eq, PartialEq)] +pub(crate) enum ResolveOptions { + Full, + NonRecursive, } struct ResolvedPath<'a, KT: KindTypes + 'static> { @@ -51,13 +62,79 @@ impl<'a, KT: KindTypes + 'static> ResolvedPath<'a, KT> { } } +/// Candidates for the forward stitching resolution process. This will inject +/// edges to the the given extensions scopes at extension hook nodes when asked +/// for forward candidates (ie. `get_forward_candidates`) by the resolution +/// algorithm. Other than that, it's exactly the same as `GraphEdgeCandidates`. +struct ResolverCandidates<'a, KT: KindTypes + 'static> { + owner: &'a BindingGraph, + partials: &'a mut PartialPaths, + file: Option, + edges: GraphEdges, + extensions: &'a [GraphHandle], +} + +impl<'a, KT: KindTypes + 'static> ResolverCandidates<'a, KT> { + pub fn new( + owner: &'a BindingGraph, + partials: &'a mut PartialPaths, + file: Option, + extensions: &'a [GraphHandle], + ) -> Self { + Self { + owner, + partials, + file, + edges: GraphEdges, + extensions, + } + } +} + +impl ForwardCandidates + for ResolverCandidates<'_, KT> +{ + fn get_forward_candidates(&mut self, path: &PartialPath, result: &mut R) + where + R: std::iter::Extend, + { + let node = path.end_node; + result.extend(self.owner.stack_graph.outgoing_edges(node).filter(|e| { + self.file + .map_or(true, |file| self.owner.stack_graph[e.sink].is_in_file(file)) + })); + + if self.owner.is_extension_hook(node) { + // Inject edges from the extension hook node to each extension scope + let mut extension_edges = Vec::new(); + for extension in self.extensions { + extension_edges.push(Edge { + source: node, + sink: *extension, + precedence: 0, + }); + } + result.extend(extension_edges); + } + } + + fn get_joining_candidate_degree(&self, path: &PartialPath) -> Degree { + self.owner.stack_graph.incoming_edge_degree(path.end_node) + } + + fn get_graph_partials_and_db(&mut self) -> (&StackGraph, &mut PartialPaths, &GraphEdges) { + (&self.owner.stack_graph, self.partials, &self.edges) + } +} + impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> { - pub fn build_for(reference: &Reference<'a, KT>) -> Self { + pub fn build_for(reference: &Reference<'a, KT>, options: ResolveOptions) -> Self { let mut resolver = Self { owner: reference.owner, reference: reference.clone(), partials: PartialPaths::new(), results: Vec::new(), + options, }; resolver.resolve(); resolver @@ -65,25 +142,56 @@ impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> { fn resolve(&mut self) { let mut reference_paths = Vec::new(); - ForwardPartialPathStitcher::find_all_complete_partial_paths( - &mut GraphEdgeCandidates::new(&self.owner.stack_graph, &mut self.partials, None), - once(self.reference.handle), - StitcherConfig::default(), - &stack_graphs::NoCancellation, - |_graph, _paths, path| { - reference_paths.push(path.clone()); - }, - ) - .expect("should never be cancelled"); + if self.options == ResolveOptions::Full { + let ref_parents = self.reference.resolve_parents(); + let mut extensions = HashSet::new(); + for parent in &ref_parents { + if let Some(extension_scope) = parent.get_extension_scope() { + extensions.insert(extension_scope); + } + + if parent.inherit_extensions() { + #[allow(clippy::mutable_key_type)] + let grand_parents = Self::resolve_parents_all(parent.clone()); + for grand_parent in grand_parents.values().flatten() { + if let Some(extension_scope) = grand_parent.get_extension_scope() { + extensions.insert(extension_scope); + } + } + } + } + let extensions = extensions.drain().collect::>(); + + ForwardPartialPathStitcher::find_all_complete_partial_paths( + &mut ResolverCandidates::new(self.owner, &mut self.partials, None, &extensions), + once(self.reference.handle), + StitcherConfig::default(), + &stack_graphs::NoCancellation, + |_graph, _paths, path| { + reference_paths.push(path.clone()); + }, + ) + .expect("Should never be cancelled"); + } else { + ForwardPartialPathStitcher::find_all_complete_partial_paths( + &mut GraphEdgeCandidates::new(&self.owner.stack_graph, &mut self.partials, None), + once(self.reference.handle), + StitcherConfig::default(), + &stack_graphs::NoCancellation, + |_graph, _paths, path| { + reference_paths.push(path.clone()); + }, + ) + .expect("Should never be cancelled"); + }; let mut added_nodes = HashSet::new(); for reference_path in &reference_paths { let end_node = reference_path.end_node; - // Because of how we're using the scope stack to propagate dynamic - // scopes, we may get multiple results with different scope stack - // postconditions but reaching the exact same definition. We only - // care about the definition, so we check for uniqueness. + // There may be duplicate ending nodes with different + // post-conditions in the scope stack, but we only care about the + // definition itself. Hence we need to check for uniqueness. if !added_nodes.contains(&end_node) && reference_paths .iter() @@ -139,7 +247,9 @@ impl<'a, KT: KindTypes + 'static> Resolver<'a, KT> { } self.mark_down_aliases(); self.mark_down_built_ins(); - self.rank_c3_methods(); + if self.options == ResolveOptions::Full { + self.rank_c3_methods(); + } self.results.sort_by(|a, b| b.score.total_cmp(&a.score)); } diff --git a/crates/solidity/inputs/language/bindings/rules.msgb b/crates/solidity/inputs/language/bindings/rules.msgb index 4bab41894f..0033e7f90e 100644 --- a/crates/solidity/inputs/language/bindings/rules.msgb +++ b/crates/solidity/inputs/language/bindings/rules.msgb @@ -21,7 +21,6 @@ inherit .enclosing_def inherit .parent_scope inherit .lexical_scope -inherit .extended_scope ; Used to resolve extension methods for `using for *` directives ; This is used as a minor optimization to avoid introducing new possible paths @@ -68,13 +67,10 @@ inherit .star_extension ;; inherited) for contracts to resolve bases (both in inheritance lists and ;; override specifiers) let @source_unit.parent_scope = @source_unit.lexical_scope - ; FIXME: we probably need to make extended scope different than lexical scope - ; and push an extension scope on that path - let @source_unit.extended_scope = @source_unit.lexical_scope - ; We may jump to scope here to resolve using the extensions scope provided by - ; contract/libraries that contain `using` directives - edge @source_unit.lexical_scope -> JUMP_TO_SCOPE_NODE + ; This is used to indicate the resolution algorithm that here's where it + ; should inject any possible extension scopes + attr (@source_unit.lexical_scope) extension_hook ; Provide a default star extension sink node that gets inherited. This is ; connected to from expressions, and those can potentially happen anywhere. @@ -104,17 +100,6 @@ inherit .star_extension edge @unit_member.lexical_scope -> @source_unit.lexical_scope } -;; Definitions that need to resolve expressions do it through an extended_scope -@source_unit [SourceUnit [SourceUnitMembers - [SourceUnitMember @unit_member ( - [FunctionDefinition] - | [ConstantDefinition] - )] -]] { - edge @unit_member.extended_scope -> @source_unit.extended_scope -} - - ;; Special case for built-ins: we want to export all symbols in the contract: ;; functions, types and state variables. All built-in symbols are defined in an ;; internal contract named '%BuiltIns%' (renamed from '$BuiltIns$') so we need @@ -317,15 +302,6 @@ inherit .star_extension attr (ns_member) push_symbol = "." edge heir.ns -> ns_member edge ns_member -> @type_name.push_begin - - if (version-matches "< 0.7.0") { - ; `using` directives are inherited in Solidity < 0.7.0, so connect them to - ; our own extensions scope - node extensions_push_guard - attr (extensions_push_guard) push_symbol = "@extensions" - edge heir.extensions -> extensions_push_guard - edge extensions_push_guard -> @type_name.push_begin - } } ;; The next couple of rules setup a `.parent_refs` attribute to use in the @@ -355,7 +331,6 @@ inherit .star_extension @contract [ContractDefinition @name name: [Identifier]] { node @contract.lexical_scope - node @contract.extended_scope node @contract.extensions node @contract.def node @contract.members @@ -365,6 +340,8 @@ inherit .star_extension attr (@contract.def) node_definition = @name attr (@contract.def) definiens_node = @contract + ; The .extensions node is where `using` directives will hook the definitions + attr (@contract.def) extension_scope = @contract.extensions edge @contract.lexical_scope -> @contract.instance @@ -428,9 +405,12 @@ inherit .star_extension attr (push_typeof) push_symbol = "@typeof" node push_name attr (push_name) push_symbol = (source-text @name) + node hook + attr (hook) extension_hook + edge call -> push_typeof edge push_typeof -> push_name - edge push_name -> JUMP_TO_SCOPE_NODE + edge push_name -> hook if (version-matches "< 0.5.0") { ; For Solidity < 0.5.0 `this` also acts like an `address` @@ -448,22 +428,10 @@ inherit .star_extension attr (@contract.star_extension) push_symbol = "@*" if (version-matches "< 0.7.0") { - ; Expose extensions through an `@extensions` guard on Solidity < 0.7.0 so - ; that they can be accessed from sub contract extension scopes - node extensions_guard - attr (extensions_guard) pop_symbol = "@extensions" - edge @contract.def -> extensions_guard - edge extensions_guard -> @contract.extensions - - ; Since using directives are inherited, we need to *always* connect the push - ; extensions to the extended scope, regardless of whether this contract - ; contains any `using` directive. - edge @contract.extended_scope -> @contract.push_extensions - ; For Solidity < 0.7.0 using directives are inherited, so we need to connect ; always For newer versions, this connection only happens when there is a ; `using for *` directive in the contract (see rule below) - edge @contract.star_extension -> @contract.extended_scope + edge @contract.star_extension -> @contract.lexical_scope } ; Path to resolve the built-in type for type() expressions @@ -506,6 +474,9 @@ inherit .star_extension ; above to connect the instance scope of this contract to the parents. let @specifier.heir = @contract attr (@contract.def) parents = @specifier.parent_refs + if (version-matches "< 0.7.0") { + attr (@contract.def) inherit_extensions + } ; The rest of these statements deal with defining and connecting the `super` ; keyword path. @@ -567,7 +538,6 @@ inherit .star_extension )] ]] { edge @member.lexical_scope -> @contract.lexical_scope - edge @member.extended_scope -> @contract.extended_scope } @contract [ContractDefinition [ContractMembers @@ -575,11 +545,6 @@ inherit .star_extension ]] { ; Hook the using definition in the extensions scope edge @contract.extensions -> @using.def - - ; Connect the extensions push path (this can happen multiple times if there - ; are multiple `using` directives in the contract, but that's allowed by the - ; graph builder). - edge @contract.extended_scope -> @contract.push_extensions } @contract [ContractDefinition [ContractMembers @@ -655,7 +620,7 @@ inherit .star_extension ]]] { ; Connect the star extension node to the resolution extended scope if there is ; a `using for *` directive in the contract - edge @contract.star_extension -> @contract.extended_scope + edge @contract.star_extension -> @contract.lexical_scope } ; This applies to both state variables and function definitions @@ -683,9 +648,6 @@ inherit .star_extension edge @interface.lexical_scope -> @interface.instance - ; Interfaces don't contain expressions (or `using` directives), so the - ; extended scope is the same as the lexical scope - let @interface.extended_scope = @interface.lexical_scope ; The extensions node is required for the inheritance rules, but not used in interfaces let @interface.extensions = (node) @@ -717,7 +679,10 @@ inherit .star_extension attr (push_name) push_symbol = (source-text @name) edge call -> push_typeof edge push_typeof -> push_name - edge push_name -> JUMP_TO_SCOPE_NODE + node hook + attr (hook) extension_hook + edge push_name -> hook + ; edge push_name -> JUMP_TO_SCOPE_NODE ;; "namespace" like access path node ns_member @@ -767,7 +732,6 @@ inherit .star_extension item: [ContractMember @function variant: [FunctionDefinition]] ]] { edge @interface.members -> @function.def - edge @function.extended_scope -> @interface.extended_scope } [InterfaceDefinition [InterfaceMembers [ContractMember @using [UsingDirective]]]] { @@ -784,7 +748,6 @@ inherit .star_extension @library [LibraryDefinition @name name: [Identifier]] { node @library.lexical_scope - node @library.extended_scope node @library.extensions node @library.def node @library.ns @@ -792,9 +755,13 @@ inherit .star_extension attr (@library.def) node_definition = @name attr (@library.def) definiens_node = @library + ; The .extensions node is where `using` directives will hook the definitions + attr (@library.def) extension_scope = @library.extensions edge @library.lexical_scope -> @library.ns + let @library.enclosing_def = @library.def + node member attr (member) pop_symbol = "." edge @library.def -> member @@ -840,7 +807,6 @@ inherit .star_extension )] ]] { edge @member.lexical_scope -> @library.lexical_scope - edge @member.extended_scope -> @library.extended_scope edge @library.ns -> @member.def } @@ -849,7 +815,6 @@ inherit .star_extension ]] { edge @library.modifiers -> @modifier.def edge @modifier.lexical_scope -> @library.lexical_scope - edge @modifier.extended_scope -> @library.extended_scope } @library [LibraryDefinition [LibraryMembers @@ -857,9 +822,6 @@ inherit .star_extension ]] { ; Expose the using directive from the extensions scope edge @library.extensions -> @using.def - - ; Connect the extensions push path - edge @library.extended_scope -> @library.push_extensions } @library [LibraryDefinition [LibraryMembers [ContractMember @@ -867,58 +829,13 @@ inherit .star_extension ]]] { ; Connect the star extension node to the resolution extended scope if there is ; a `using for *` directive in the library - edge @library.star_extension -> @library.extended_scope -} - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Extensions scope rules -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; For contracts (and libraries) navigating to the source unit lexical scope -;; *also* needs to (optionally) propagate an extensions scope to be able to -;; correctly bind `using` attached functions. -@contract_or_library ([ContractDefinition] | [LibraryDefinition]) { - ; The `.extended_scope` resolution scope used by function bodies and other - ; expressions. From there we need to (optionally) push the extensions scope - ; (ie. `using` directives definitions) to the scope stack. - ; We will connect the path to push the extensions *if* the contract/library - ; has a `using` directive. Also, the extended scope links to the lexical scope - ; of the contract/library directly, regardless of whether there is a `using` - ; directive or not. - ; TODO: if we had a query negation operator to detect when there is no `using` - ; directive, could we avoid connecting directly when there are extensions? - edge @contract_or_library.extended_scope -> @contract_or_library.lexical_scope - - ; The .extensions node is where `using` directives will hook the definitions - attr (@contract_or_library.extensions) is_exported - - ; Now we define the path to push the .extensions scope into the scope stack. - ; We connect this to the extended scope only when there are extensions in the - ; contract/library. - node @contract_or_library.push_extensions - attr (@contract_or_library.push_extensions) push_scoped_symbol = "@extend" - attr (@contract_or_library.push_extensions) scope = @contract_or_library.extensions - node drop_scopes - attr (drop_scopes) type = "drop_scopes" - node pop_extensions - attr (pop_extensions) pop_scoped_symbol = "@extend" - - edge @contract_or_library.push_extensions -> drop_scopes - edge drop_scopes -> pop_extensions - edge pop_extensions -> @contract_or_library.lexical_scope + edge @library.star_extension -> @library.lexical_scope } - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Using directives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; The UsingDirective node requires the enclosing context to setup an -;; .extended_scope scoped variable for it to resolve both targets and subjects. -;; The resolution connects to the extended scope in order to (potentially) push -;; the same extension scope again, to resolve chained calls that all make use of -;; attached functions. - @using [UsingDirective] { ; This node acts as a definition in the sense that provides an entry point ; that pops the target type and pushes the library/functions to attach to the @@ -933,7 +850,7 @@ inherit .star_extension @using [UsingDirective [UsingClause @id_path [IdentifierPath]]] { ; resolve the library to be used in the directive - edge @id_path.push_end -> @using.extended_scope + edge @id_path.push_end -> @using.lexical_scope ; because we're using the whole library, we don't need to "consume" the ; attached function (as when using the deconstruction syntax), but we still @@ -955,7 +872,7 @@ inherit .star_extension ]] ]]] { ; resolve the function to be used in the directive - edge @id_path.push_end -> @using.extended_scope + edge @id_path.push_end -> @using.lexical_scope node dot attr (dot) pop_symbol = "." @@ -982,9 +899,8 @@ inherit .star_extension edge @type_name.pop_end -> cast edge cast -> @using.clause - ; resolve the target type of the directive on the extended scope so the - ; extension scope gets re-pushed - edge @type_name.type_ref -> @using.extended_scope + ; resolve the target type of the directive on the lexical scope + edge @type_name.type_ref -> @using.lexical_scope } [ContractMember @using [UsingDirective [UsingTarget [Asterisk]]]] { @@ -1448,7 +1364,6 @@ inherit .star_extension } node @function.lexical_scope - node @function.extended_scope node @function.def ; this path from the function definition to the scope allows attaching @@ -1468,12 +1383,12 @@ inherit .star_extension } @function [FunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @function.extended_scope + edge @params.lexical_scope -> @function.lexical_scope ;; Input parameters are available in the function scope - edge @function.extended_scope -> @params.defs + edge @function.lexical_scope -> @params.defs ;; ... and shadow other declarations - attr (@function.extended_scope -> @params.defs) precedence = 1 + attr (@function.lexical_scope -> @params.defs) precedence = 1 ;; Connect to paramaters for named argument resolution edge @function.def -> @params.names @@ -1482,12 +1397,12 @@ inherit .star_extension @function [FunctionDefinition returns: [ReturnsDeclaration @return_params [ParametersDeclaration] ]] { - edge @return_params.lexical_scope -> @function.extended_scope + edge @return_params.lexical_scope -> @function.lexical_scope ;; Return parameters are available in the function scope - edge @function.extended_scope -> @return_params.defs + edge @function.lexical_scope -> @return_params.defs ;; ... and shadow other declarations - attr (@function.extended_scope -> @return_params.defs) precedence = 1 + attr (@function.lexical_scope -> @return_params.defs) precedence = 1 } ;; Only functions that return a single value have an actual return type @@ -1504,13 +1419,13 @@ inherit .star_extension ;; Connect the function body's block lexical scope to the function @function [FunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @function.extended_scope + edge @block.lexical_scope -> @function.lexical_scope } @function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute @modifier [ModifierInvocation] ]]] { - edge @modifier.lexical_scope -> @function.extended_scope + edge @modifier.lexical_scope -> @function.lexical_scope } @modifier [ModifierInvocation @name [IdentifierPath]] { @@ -1534,18 +1449,17 @@ inherit .star_extension ;;; Unnamed functions (deprecated) @unnamed_function [UnnamedFunctionDefinition] { node @unnamed_function.lexical_scope - node @unnamed_function.extended_scope } @unnamed_function [UnnamedFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @unnamed_function.extended_scope + edge @params.lexical_scope -> @unnamed_function.lexical_scope - edge @unnamed_function.extended_scope -> @params.defs - attr (@unnamed_function.extended_scope -> @params.defs) precedence = 1 + edge @unnamed_function.lexical_scope -> @params.defs + attr (@unnamed_function.lexical_scope -> @params.defs) precedence = 1 } @unnamed_function [UnnamedFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @unnamed_function.extended_scope + edge @block.lexical_scope -> @unnamed_function.lexical_scope } @unnamed_function [UnnamedFunctionDefinition @@ -1562,17 +1476,16 @@ inherit .star_extension @constructor [ConstructorDefinition] { node @constructor.lexical_scope - node @constructor.extended_scope node @constructor.def } @constructor [ConstructorDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @constructor.extended_scope + edge @params.lexical_scope -> @constructor.lexical_scope ;; Input parameters are available in the constructor scope - edge @constructor.extended_scope -> @params.defs + edge @constructor.lexical_scope -> @params.defs ;; ... and shadow other declarations - attr (@constructor.extended_scope -> @params.defs) precedence = 1 + attr (@constructor.lexical_scope -> @params.defs) precedence = 1 ;; Connect to paramaters for named argument resolution edge @constructor.def -> @params.names @@ -1580,13 +1493,13 @@ inherit .star_extension ;; Connect the constructor body's block lexical scope to the constructor @constructor [ConstructorDefinition @block [Block]] { - edge @block.lexical_scope -> @constructor.extended_scope + edge @block.lexical_scope -> @constructor.lexical_scope } @constructor [ConstructorDefinition [ConstructorAttributes item: [ConstructorAttribute @modifier [ModifierInvocation] ]]] { - edge @modifier.lexical_scope -> @constructor.extended_scope + edge @modifier.lexical_scope -> @constructor.lexical_scope edge @modifier.identifier -> @constructor.lexical_scope } @@ -1637,15 +1550,14 @@ inherit .star_extension @fallback [FallbackFunctionDefinition] { node @fallback.lexical_scope - node @fallback.extended_scope } @fallback [FallbackFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @fallback.extended_scope + edge @params.lexical_scope -> @fallback.lexical_scope ;; Input parameters are available in the fallback function scope - edge @fallback.extended_scope -> @params.defs - attr (@fallback.extended_scope -> @params.defs) precedence = 1 + edge @fallback.lexical_scope -> @params.defs + attr (@fallback.lexical_scope -> @params.defs) precedence = 1 } @fallback [FallbackFunctionDefinition returns: [ReturnsDeclaration @@ -1654,12 +1566,12 @@ inherit .star_extension edge @return_params.lexical_scope -> @fallback.lexical_scope ;; Return parameters are available in the fallback function scope - edge @fallback.extended_scope -> @return_params.defs - attr (@fallback.extended_scope -> @return_params.defs) precedence = 1 + edge @fallback.lexical_scope -> @return_params.defs + attr (@fallback.lexical_scope -> @return_params.defs) precedence = 1 } @fallback [FallbackFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @fallback.extended_scope + edge @block.lexical_scope -> @fallback.lexical_scope } @fallback [FallbackFunctionDefinition [FallbackFunctionAttributes @@ -1670,11 +1582,10 @@ inherit .star_extension @receive [ReceiveFunctionDefinition] { node @receive.lexical_scope - node @receive.extended_scope } @receive [ReceiveFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @receive.extended_scope + edge @block.lexical_scope -> @receive.lexical_scope } @receive [ReceiveFunctionDefinition [ReceiveFunctionAttributes @@ -1691,7 +1602,6 @@ inherit .star_extension @modifier [ModifierDefinition] { node @modifier.def node @modifier.lexical_scope - node @modifier.extended_scope } @modifier [ModifierDefinition @@ -1701,7 +1611,7 @@ inherit .star_extension attr (@modifier.def) node_definition = @name attr (@modifier.def) definiens_node = @modifier - edge @body.lexical_scope -> @modifier.extended_scope + edge @body.lexical_scope -> @modifier.lexical_scope ; Special case: bind the place holder statement `_` to the built-in ; `%placeholder`. This only happens in the body of a modifier. @@ -1716,11 +1626,11 @@ inherit .star_extension } @modifier [ModifierDefinition @params [ParametersDeclaration]] { - edge @params.lexical_scope -> @modifier.extended_scope + edge @params.lexical_scope -> @modifier.lexical_scope ;; Input parameters are available in the modifier scope - edge @modifier.extended_scope -> @params.defs - attr (@modifier.extended_scope -> @params.defs) precedence = 1 + edge @modifier.lexical_scope -> @params.defs + attr (@modifier.lexical_scope -> @params.defs) precedence = 1 } @@ -2079,7 +1989,6 @@ inherit .star_extension @state_var [StateVariableDefinition] { node @state_var.lexical_scope - node @state_var.extended_scope node @state_var.def } @@ -2121,7 +2030,7 @@ inherit .star_extension @state_var [StateVariableDefinition [StateVariableDefinitionValue @value [Expression]] ] { - let @value.lexical_scope = @state_var.extended_scope + let @value.lexical_scope = @state_var.lexical_scope } diff --git a/crates/solidity/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs b/crates/solidity/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs index 59e4f7f07a..4817a55eb7 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs @@ -26,7 +26,6 @@ inherit .enclosing_def inherit .parent_scope inherit .lexical_scope -inherit .extended_scope ; Used to resolve extension methods for `using for *` directives ; This is used as a minor optimization to avoid introducing new possible paths @@ -73,13 +72,10 @@ inherit .star_extension ;; inherited) for contracts to resolve bases (both in inheritance lists and ;; override specifiers) let @source_unit.parent_scope = @source_unit.lexical_scope - ; FIXME: we probably need to make extended scope different than lexical scope - ; and push an extension scope on that path - let @source_unit.extended_scope = @source_unit.lexical_scope - ; We may jump to scope here to resolve using the extensions scope provided by - ; contract/libraries that contain `using` directives - edge @source_unit.lexical_scope -> JUMP_TO_SCOPE_NODE + ; This is used to indicate the resolution algorithm that here's where it + ; should inject any possible extension scopes + attr (@source_unit.lexical_scope) extension_hook ; Provide a default star extension sink node that gets inherited. This is ; connected to from expressions, and those can potentially happen anywhere. @@ -109,17 +105,6 @@ inherit .star_extension edge @unit_member.lexical_scope -> @source_unit.lexical_scope } -;; Definitions that need to resolve expressions do it through an extended_scope -@source_unit [SourceUnit [SourceUnitMembers - [SourceUnitMember @unit_member ( - [FunctionDefinition] - | [ConstantDefinition] - )] -]] { - edge @unit_member.extended_scope -> @source_unit.extended_scope -} - - ;; Special case for built-ins: we want to export all symbols in the contract: ;; functions, types and state variables. All built-in symbols are defined in an ;; internal contract named '%BuiltIns%' (renamed from '$BuiltIns$') so we need @@ -322,15 +307,6 @@ inherit .star_extension attr (ns_member) push_symbol = "." edge heir.ns -> ns_member edge ns_member -> @type_name.push_begin - - if (version-matches "< 0.7.0") { - ; `using` directives are inherited in Solidity < 0.7.0, so connect them to - ; our own extensions scope - node extensions_push_guard - attr (extensions_push_guard) push_symbol = "@extensions" - edge heir.extensions -> extensions_push_guard - edge extensions_push_guard -> @type_name.push_begin - } } ;; The next couple of rules setup a `.parent_refs` attribute to use in the @@ -360,7 +336,6 @@ inherit .star_extension @contract [ContractDefinition @name name: [Identifier]] { node @contract.lexical_scope - node @contract.extended_scope node @contract.extensions node @contract.def node @contract.members @@ -370,6 +345,8 @@ inherit .star_extension attr (@contract.def) node_definition = @name attr (@contract.def) definiens_node = @contract + ; The .extensions node is where `using` directives will hook the definitions + attr (@contract.def) extension_scope = @contract.extensions edge @contract.lexical_scope -> @contract.instance @@ -433,9 +410,12 @@ inherit .star_extension attr (push_typeof) push_symbol = "@typeof" node push_name attr (push_name) push_symbol = (source-text @name) + node hook + attr (hook) extension_hook + edge call -> push_typeof edge push_typeof -> push_name - edge push_name -> JUMP_TO_SCOPE_NODE + edge push_name -> hook if (version-matches "< 0.5.0") { ; For Solidity < 0.5.0 `this` also acts like an `address` @@ -453,22 +433,10 @@ inherit .star_extension attr (@contract.star_extension) push_symbol = "@*" if (version-matches "< 0.7.0") { - ; Expose extensions through an `@extensions` guard on Solidity < 0.7.0 so - ; that they can be accessed from sub contract extension scopes - node extensions_guard - attr (extensions_guard) pop_symbol = "@extensions" - edge @contract.def -> extensions_guard - edge extensions_guard -> @contract.extensions - - ; Since using directives are inherited, we need to *always* connect the push - ; extensions to the extended scope, regardless of whether this contract - ; contains any `using` directive. - edge @contract.extended_scope -> @contract.push_extensions - ; For Solidity < 0.7.0 using directives are inherited, so we need to connect ; always For newer versions, this connection only happens when there is a ; `using for *` directive in the contract (see rule below) - edge @contract.star_extension -> @contract.extended_scope + edge @contract.star_extension -> @contract.lexical_scope } ; Path to resolve the built-in type for type() expressions @@ -511,6 +479,9 @@ inherit .star_extension ; above to connect the instance scope of this contract to the parents. let @specifier.heir = @contract attr (@contract.def) parents = @specifier.parent_refs + if (version-matches "< 0.7.0") { + attr (@contract.def) inherit_extensions + } ; The rest of these statements deal with defining and connecting the `super` ; keyword path. @@ -572,7 +543,6 @@ inherit .star_extension )] ]] { edge @member.lexical_scope -> @contract.lexical_scope - edge @member.extended_scope -> @contract.extended_scope } @contract [ContractDefinition [ContractMembers @@ -580,11 +550,6 @@ inherit .star_extension ]] { ; Hook the using definition in the extensions scope edge @contract.extensions -> @using.def - - ; Connect the extensions push path (this can happen multiple times if there - ; are multiple `using` directives in the contract, but that's allowed by the - ; graph builder). - edge @contract.extended_scope -> @contract.push_extensions } @contract [ContractDefinition [ContractMembers @@ -660,7 +625,7 @@ inherit .star_extension ]]] { ; Connect the star extension node to the resolution extended scope if there is ; a `using for *` directive in the contract - edge @contract.star_extension -> @contract.extended_scope + edge @contract.star_extension -> @contract.lexical_scope } ; This applies to both state variables and function definitions @@ -688,9 +653,6 @@ inherit .star_extension edge @interface.lexical_scope -> @interface.instance - ; Interfaces don't contain expressions (or `using` directives), so the - ; extended scope is the same as the lexical scope - let @interface.extended_scope = @interface.lexical_scope ; The extensions node is required for the inheritance rules, but not used in interfaces let @interface.extensions = (node) @@ -722,7 +684,10 @@ inherit .star_extension attr (push_name) push_symbol = (source-text @name) edge call -> push_typeof edge push_typeof -> push_name - edge push_name -> JUMP_TO_SCOPE_NODE + node hook + attr (hook) extension_hook + edge push_name -> hook + ; edge push_name -> JUMP_TO_SCOPE_NODE ;; "namespace" like access path node ns_member @@ -772,7 +737,6 @@ inherit .star_extension item: [ContractMember @function variant: [FunctionDefinition]] ]] { edge @interface.members -> @function.def - edge @function.extended_scope -> @interface.extended_scope } [InterfaceDefinition [InterfaceMembers [ContractMember @using [UsingDirective]]]] { @@ -789,7 +753,6 @@ inherit .star_extension @library [LibraryDefinition @name name: [Identifier]] { node @library.lexical_scope - node @library.extended_scope node @library.extensions node @library.def node @library.ns @@ -797,9 +760,13 @@ inherit .star_extension attr (@library.def) node_definition = @name attr (@library.def) definiens_node = @library + ; The .extensions node is where `using` directives will hook the definitions + attr (@library.def) extension_scope = @library.extensions edge @library.lexical_scope -> @library.ns + let @library.enclosing_def = @library.def + node member attr (member) pop_symbol = "." edge @library.def -> member @@ -845,7 +812,6 @@ inherit .star_extension )] ]] { edge @member.lexical_scope -> @library.lexical_scope - edge @member.extended_scope -> @library.extended_scope edge @library.ns -> @member.def } @@ -854,7 +820,6 @@ inherit .star_extension ]] { edge @library.modifiers -> @modifier.def edge @modifier.lexical_scope -> @library.lexical_scope - edge @modifier.extended_scope -> @library.extended_scope } @library [LibraryDefinition [LibraryMembers @@ -862,9 +827,6 @@ inherit .star_extension ]] { ; Expose the using directive from the extensions scope edge @library.extensions -> @using.def - - ; Connect the extensions push path - edge @library.extended_scope -> @library.push_extensions } @library [LibraryDefinition [LibraryMembers [ContractMember @@ -872,58 +834,13 @@ inherit .star_extension ]]] { ; Connect the star extension node to the resolution extended scope if there is ; a `using for *` directive in the library - edge @library.star_extension -> @library.extended_scope -} - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Extensions scope rules -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; For contracts (and libraries) navigating to the source unit lexical scope -;; *also* needs to (optionally) propagate an extensions scope to be able to -;; correctly bind `using` attached functions. -@contract_or_library ([ContractDefinition] | [LibraryDefinition]) { - ; The `.extended_scope` resolution scope used by function bodies and other - ; expressions. From there we need to (optionally) push the extensions scope - ; (ie. `using` directives definitions) to the scope stack. - ; We will connect the path to push the extensions *if* the contract/library - ; has a `using` directive. Also, the extended scope links to the lexical scope - ; of the contract/library directly, regardless of whether there is a `using` - ; directive or not. - ; TODO: if we had a query negation operator to detect when there is no `using` - ; directive, could we avoid connecting directly when there are extensions? - edge @contract_or_library.extended_scope -> @contract_or_library.lexical_scope - - ; The .extensions node is where `using` directives will hook the definitions - attr (@contract_or_library.extensions) is_exported - - ; Now we define the path to push the .extensions scope into the scope stack. - ; We connect this to the extended scope only when there are extensions in the - ; contract/library. - node @contract_or_library.push_extensions - attr (@contract_or_library.push_extensions) push_scoped_symbol = "@extend" - attr (@contract_or_library.push_extensions) scope = @contract_or_library.extensions - node drop_scopes - attr (drop_scopes) type = "drop_scopes" - node pop_extensions - attr (pop_extensions) pop_scoped_symbol = "@extend" - - edge @contract_or_library.push_extensions -> drop_scopes - edge drop_scopes -> pop_extensions - edge pop_extensions -> @contract_or_library.lexical_scope + edge @library.star_extension -> @library.lexical_scope } - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Using directives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; The UsingDirective node requires the enclosing context to setup an -;; .extended_scope scoped variable for it to resolve both targets and subjects. -;; The resolution connects to the extended scope in order to (potentially) push -;; the same extension scope again, to resolve chained calls that all make use of -;; attached functions. - @using [UsingDirective] { ; This node acts as a definition in the sense that provides an entry point ; that pops the target type and pushes the library/functions to attach to the @@ -938,7 +855,7 @@ inherit .star_extension @using [UsingDirective [UsingClause @id_path [IdentifierPath]]] { ; resolve the library to be used in the directive - edge @id_path.push_end -> @using.extended_scope + edge @id_path.push_end -> @using.lexical_scope ; because we're using the whole library, we don't need to "consume" the ; attached function (as when using the deconstruction syntax), but we still @@ -960,7 +877,7 @@ inherit .star_extension ]] ]]] { ; resolve the function to be used in the directive - edge @id_path.push_end -> @using.extended_scope + edge @id_path.push_end -> @using.lexical_scope node dot attr (dot) pop_symbol = "." @@ -987,9 +904,8 @@ inherit .star_extension edge @type_name.pop_end -> cast edge cast -> @using.clause - ; resolve the target type of the directive on the extended scope so the - ; extension scope gets re-pushed - edge @type_name.type_ref -> @using.extended_scope + ; resolve the target type of the directive on the lexical scope + edge @type_name.type_ref -> @using.lexical_scope } [ContractMember @using [UsingDirective [UsingTarget [Asterisk]]]] { @@ -1453,7 +1369,6 @@ inherit .star_extension } node @function.lexical_scope - node @function.extended_scope node @function.def ; this path from the function definition to the scope allows attaching @@ -1473,12 +1388,12 @@ inherit .star_extension } @function [FunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @function.extended_scope + edge @params.lexical_scope -> @function.lexical_scope ;; Input parameters are available in the function scope - edge @function.extended_scope -> @params.defs + edge @function.lexical_scope -> @params.defs ;; ... and shadow other declarations - attr (@function.extended_scope -> @params.defs) precedence = 1 + attr (@function.lexical_scope -> @params.defs) precedence = 1 ;; Connect to paramaters for named argument resolution edge @function.def -> @params.names @@ -1487,12 +1402,12 @@ inherit .star_extension @function [FunctionDefinition returns: [ReturnsDeclaration @return_params [ParametersDeclaration] ]] { - edge @return_params.lexical_scope -> @function.extended_scope + edge @return_params.lexical_scope -> @function.lexical_scope ;; Return parameters are available in the function scope - edge @function.extended_scope -> @return_params.defs + edge @function.lexical_scope -> @return_params.defs ;; ... and shadow other declarations - attr (@function.extended_scope -> @return_params.defs) precedence = 1 + attr (@function.lexical_scope -> @return_params.defs) precedence = 1 } ;; Only functions that return a single value have an actual return type @@ -1509,13 +1424,13 @@ inherit .star_extension ;; Connect the function body's block lexical scope to the function @function [FunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @function.extended_scope + edge @block.lexical_scope -> @function.lexical_scope } @function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute @modifier [ModifierInvocation] ]]] { - edge @modifier.lexical_scope -> @function.extended_scope + edge @modifier.lexical_scope -> @function.lexical_scope } @modifier [ModifierInvocation @name [IdentifierPath]] { @@ -1539,18 +1454,17 @@ inherit .star_extension ;;; Unnamed functions (deprecated) @unnamed_function [UnnamedFunctionDefinition] { node @unnamed_function.lexical_scope - node @unnamed_function.extended_scope } @unnamed_function [UnnamedFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @unnamed_function.extended_scope + edge @params.lexical_scope -> @unnamed_function.lexical_scope - edge @unnamed_function.extended_scope -> @params.defs - attr (@unnamed_function.extended_scope -> @params.defs) precedence = 1 + edge @unnamed_function.lexical_scope -> @params.defs + attr (@unnamed_function.lexical_scope -> @params.defs) precedence = 1 } @unnamed_function [UnnamedFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @unnamed_function.extended_scope + edge @block.lexical_scope -> @unnamed_function.lexical_scope } @unnamed_function [UnnamedFunctionDefinition @@ -1567,17 +1481,16 @@ inherit .star_extension @constructor [ConstructorDefinition] { node @constructor.lexical_scope - node @constructor.extended_scope node @constructor.def } @constructor [ConstructorDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @constructor.extended_scope + edge @params.lexical_scope -> @constructor.lexical_scope ;; Input parameters are available in the constructor scope - edge @constructor.extended_scope -> @params.defs + edge @constructor.lexical_scope -> @params.defs ;; ... and shadow other declarations - attr (@constructor.extended_scope -> @params.defs) precedence = 1 + attr (@constructor.lexical_scope -> @params.defs) precedence = 1 ;; Connect to paramaters for named argument resolution edge @constructor.def -> @params.names @@ -1585,13 +1498,13 @@ inherit .star_extension ;; Connect the constructor body's block lexical scope to the constructor @constructor [ConstructorDefinition @block [Block]] { - edge @block.lexical_scope -> @constructor.extended_scope + edge @block.lexical_scope -> @constructor.lexical_scope } @constructor [ConstructorDefinition [ConstructorAttributes item: [ConstructorAttribute @modifier [ModifierInvocation] ]]] { - edge @modifier.lexical_scope -> @constructor.extended_scope + edge @modifier.lexical_scope -> @constructor.lexical_scope edge @modifier.identifier -> @constructor.lexical_scope } @@ -1642,15 +1555,14 @@ inherit .star_extension @fallback [FallbackFunctionDefinition] { node @fallback.lexical_scope - node @fallback.extended_scope } @fallback [FallbackFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @fallback.extended_scope + edge @params.lexical_scope -> @fallback.lexical_scope ;; Input parameters are available in the fallback function scope - edge @fallback.extended_scope -> @params.defs - attr (@fallback.extended_scope -> @params.defs) precedence = 1 + edge @fallback.lexical_scope -> @params.defs + attr (@fallback.lexical_scope -> @params.defs) precedence = 1 } @fallback [FallbackFunctionDefinition returns: [ReturnsDeclaration @@ -1659,12 +1571,12 @@ inherit .star_extension edge @return_params.lexical_scope -> @fallback.lexical_scope ;; Return parameters are available in the fallback function scope - edge @fallback.extended_scope -> @return_params.defs - attr (@fallback.extended_scope -> @return_params.defs) precedence = 1 + edge @fallback.lexical_scope -> @return_params.defs + attr (@fallback.lexical_scope -> @return_params.defs) precedence = 1 } @fallback [FallbackFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @fallback.extended_scope + edge @block.lexical_scope -> @fallback.lexical_scope } @fallback [FallbackFunctionDefinition [FallbackFunctionAttributes @@ -1675,11 +1587,10 @@ inherit .star_extension @receive [ReceiveFunctionDefinition] { node @receive.lexical_scope - node @receive.extended_scope } @receive [ReceiveFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @receive.extended_scope + edge @block.lexical_scope -> @receive.lexical_scope } @receive [ReceiveFunctionDefinition [ReceiveFunctionAttributes @@ -1696,7 +1607,6 @@ inherit .star_extension @modifier [ModifierDefinition] { node @modifier.def node @modifier.lexical_scope - node @modifier.extended_scope } @modifier [ModifierDefinition @@ -1706,7 +1616,7 @@ inherit .star_extension attr (@modifier.def) node_definition = @name attr (@modifier.def) definiens_node = @modifier - edge @body.lexical_scope -> @modifier.extended_scope + edge @body.lexical_scope -> @modifier.lexical_scope ; Special case: bind the place holder statement `_` to the built-in ; `%placeholder`. This only happens in the body of a modifier. @@ -1721,11 +1631,11 @@ inherit .star_extension } @modifier [ModifierDefinition @params [ParametersDeclaration]] { - edge @params.lexical_scope -> @modifier.extended_scope + edge @params.lexical_scope -> @modifier.lexical_scope ;; Input parameters are available in the modifier scope - edge @modifier.extended_scope -> @params.defs - attr (@modifier.extended_scope -> @params.defs) precedence = 1 + edge @modifier.lexical_scope -> @params.defs + attr (@modifier.lexical_scope -> @params.defs) precedence = 1 } @@ -2084,7 +1994,6 @@ inherit .star_extension @state_var [StateVariableDefinition] { node @state_var.lexical_scope - node @state_var.extended_scope node @state_var.def } @@ -2126,7 +2035,7 @@ inherit .star_extension @state_var [StateVariableDefinition [StateVariableDefinitionValue @value [Expression]] ] { - let @value.lexical_scope = @state_var.extended_scope + let @value.lexical_scope = @state_var.lexical_scope } diff --git a/crates/solidity/outputs/cargo/tests/src/bindings.rs b/crates/solidity/outputs/cargo/tests/src/bindings.rs new file mode 100644 index 0000000000..c5a9cf60dc --- /dev/null +++ b/crates/solidity/outputs/cargo/tests/src/bindings.rs @@ -0,0 +1,10 @@ +use slang_solidity::bindings::{BindingGraph, Definition}; + +pub fn lookup_definition_by_name<'a>( + binding_graph: &'a BindingGraph, + name: &str, +) -> Option> { + binding_graph + .all_definitions() + .find(|definition| definition.get_cursor().node().unparse() == name) +} diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_assertions/assertions.rs b/crates/solidity/outputs/cargo/tests/src/bindings_assertions/assertions.rs index 94aeca6f2d..fcc4d19547 100644 --- a/crates/solidity/outputs/cargo/tests/src/bindings_assertions/assertions.rs +++ b/crates/solidity/outputs/cargo/tests/src/bindings_assertions/assertions.rs @@ -482,7 +482,7 @@ fn find_and_resolve_reference<'a>( // For the purpose of binding assertions, any failure to resolve to a single // definition will be treated as if it was unresolved - Ok(reference.jump_to_definition().ok()) + Ok(reference.resolve_definition().ok()) } fn lookup_referenced_definition<'a>( diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_assertions/runner.rs b/crates/solidity/outputs/cargo/tests/src/bindings_assertions/runner.rs index 2da4fe4aff..f593e1937d 100644 --- a/crates/solidity/outputs/cargo/tests/src/bindings_assertions/runner.rs +++ b/crates/solidity/outputs/cargo/tests/src/bindings_assertions/runner.rs @@ -7,6 +7,7 @@ use semver::Version; use slang_solidity::parser::Parser; use slang_solidity::{bindings, diagnostic}; +use crate::bindings::lookup_definition_by_name; use crate::bindings_assertions::assertions::{ check_assertions, collect_assertions_into, Assertions, }; @@ -65,8 +66,7 @@ fn check_assertions_with_version(version: &Version, contents: &str) -> Result<() } if let Some(context) = multi_part.context { - let context_definition = binding_graph - .lookup_definition_by_name(context) + let context_definition = lookup_definition_by_name(&binding_graph, context) .expect("context definition to be found") .to_handle(); binding_graph.set_context(&context_definition); diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs b/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs index 423b7c9910..b347b541d7 100644 --- a/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs +++ b/crates/solidity/outputs/cargo/tests/src/bindings_output/renderer.rs @@ -111,7 +111,7 @@ fn build_report_for_part<'a>( start..end }; - let definition = reference.jump_to_definition(); + let definition = reference.resolve_definition(); let message = match definition { Ok(definition) => { if definition.get_file().is_system() { diff --git a/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs b/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs index 35ee7d233b..5daaf93288 100644 --- a/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs +++ b/crates/solidity/outputs/cargo/tests/src/bindings_output/runner.rs @@ -13,6 +13,7 @@ use slang_solidity::parser::{ParseOutput, Parser}; use super::graph::graphviz::render as render_graphviz_graph; use super::graph::mermaid::render as render_mermaid_graph; use super::renderer::render_bindings; +use crate::bindings::lookup_definition_by_name; use crate::generated::VERSION_BREAKS; use crate::multi_part_file::{split_multi_file, Part}; use crate::resolver::TestsPathResolver; @@ -66,8 +67,7 @@ pub fn run(group_name: &str, test_name: &str) -> Result<()> { } if let Some(context) = multi_part.context { - let context_definition = binding_graph - .lookup_definition_by_name(context) + let context_definition = lookup_definition_by_name(&binding_graph, context) .expect("context definition to be found") .to_handle(); binding_graph.set_context(&context_definition); diff --git a/crates/solidity/outputs/cargo/tests/src/lib.rs b/crates/solidity/outputs/cargo/tests/src/lib.rs index 519c795d76..b62d53c1a2 100644 --- a/crates/solidity/outputs/cargo/tests/src/lib.rs +++ b/crates/solidity/outputs/cargo/tests/src/lib.rs @@ -3,6 +3,7 @@ use metaslang_bindings as _; mod binding_rules; +mod bindings; mod bindings_assertions; mod bindings_output; mod built_ins; diff --git a/crates/solidity/testing/perf/src/tests/references.rs b/crates/solidity/testing/perf/src/tests/references.rs index fed8fb3bec..c039149a6d 100644 --- a/crates/solidity/testing/perf/src/tests/references.rs +++ b/crates/solidity/testing/perf/src/tests/references.rs @@ -17,7 +17,7 @@ pub fn run(binding_graph: BindingGraph) { } reference_count += 1; - let resolution = reference.jump_to_definition(); + let resolution = reference.resolve_definition(); if resolution.is_ok() { resolved_references += 1; }