From 1e31da44ed57d22d214074df08b98a1c6a330d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Mon, 12 Aug 2024 15:29:18 -0400 Subject: [PATCH] Add bindings API function to retrieve *all* definitions from a reference --- crates/metaslang/bindings/src/lib.rs | 104 ++++++++++++++---- .../inputs/language/bindings/rules.msgb | 8 +- .../bindings/generated/binding_rules.rs | 8 +- .../contracts/virtual_methods.sol | 4 +- 4 files changed, 97 insertions(+), 27 deletions(-) diff --git a/crates/metaslang/bindings/src/lib.rs b/crates/metaslang/bindings/src/lib.rs index 05d2d47f0..c039f961f 100644 --- a/crates/metaslang/bindings/src/lib.rs +++ b/crates/metaslang/bindings/src/lib.rs @@ -1,8 +1,7 @@ pub mod builder; use std::collections::HashMap; -use std::fmt; -use std::fmt::Debug; +use std::fmt::{self, Display}; use std::iter::once; use std::sync::Arc; @@ -13,7 +12,7 @@ use metaslang_graph_builder::ast::File; use metaslang_graph_builder::functions::Functions; use semver::Version; use stack_graphs::graph::StackGraph; -use stack_graphs::partial::PartialPaths; +use stack_graphs::partial::{PartialPath, PartialPaths}; use stack_graphs::stitching::{ Appendable, ForwardPartialPathStitcher, GraphEdgeCandidates, StitcherConfig, }; @@ -141,6 +140,25 @@ impl Bindings { } } +struct DisplayCursor<'a, KT: KindTypes + 'static> { + cursor: &'a Cursor, + file: Option<&'a str>, +} + +impl<'a, KT: KindTypes + 'static> fmt::Display for DisplayCursor<'a, KT> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let offset = self.cursor.text_offset(); + write!( + f, + "`{}` at {}:{}:{}", + self.cursor.node().unparse(), + self.file.unwrap_or(""), + offset.line + 1, + offset.column + 1, + ) + } +} + #[derive(Clone)] pub struct Definition<'a, KT: KindTypes + 'static> { owner: &'a Bindings, @@ -163,9 +181,20 @@ impl<'a, KT: KindTypes + 'static> Definition<'a, KT> { } } -impl Debug for Definition<'_, KT> { +impl Display for Definition<'_, KT> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("BindingsHandle").field(&self.handle).finish() + if let Some(cursor) = self.get_cursor() { + write!( + f, + "definition {}", + DisplayCursor { + cursor: &cursor, + file: self.get_file() + } + ) + } else { + write!(f, "{}", self.handle.display(&self.owner.stack_graph)) + } } } @@ -213,7 +242,42 @@ impl<'a, KT: KindTypes + 'static> Reference<'a, KT> { /// path to the actual definition; hence why this function will return /// the longest path available. /// + /// 3. Overriden virtual methods: a reference will find valid paths to all + /// available definitions in the class hierarchy. + /// pub fn jump_to_definition(&self) -> Option> { + let definitions = self.resolve(); + if definitions.len() > 1 { + println!( + "WARN: More than one definition found for {self}, will return the longest path", + ); + + for (index, result) in definitions.iter().enumerate() { + let definition = Definition { + owner: self.owner, + handle: result.end_node(), + }; + println!( + " {index}. {definition} (length {length})", + index = index + 1, + length = result.edges.len(), + ); + } + } + definitions.first().map(|path| Definition { + owner: self.owner, + handle: path.end_node(), + }) + } + + pub fn definitions(&self) -> Vec> { + self.resolve().into_iter().map(|path| Definition { + owner: self.owner, + handle: path.end_node(), + }).collect() + } + + fn resolve(&self) -> Vec { let mut partials = PartialPaths::new(); let mut reference_paths = Vec::new(); ForwardPartialPathStitcher::find_all_complete_partial_paths( @@ -233,26 +297,28 @@ impl<'a, KT: KindTypes + 'static> Reference<'a, KT> { .iter() .all(|other| !other.shadows(&mut partials, reference_path)) { - results.push(reference_path); + results.push(reference_path.clone()); } } - if results.len() > 1 { - println!( - "WARN: More than one definition found for {}, will return the longest path", - self.handle.display(&self.owner.stack_graph) - ); - results.sort_by(|a, b| b.edges.len().cmp(&a.edges.len())); - } - results.first().map(|path| Definition { - owner: self.owner, - handle: path.end_node(), - }) + results.sort_by(|a, b| b.edges.len().cmp(&a.edges.len())); + results } } -impl Debug for Reference<'_, KT> { +impl Display for Reference<'_, KT> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("BindingsHandle").field(&self.handle).finish() + if let Some(cursor) = self.get_cursor() { + write!( + f, + "reference {}", + DisplayCursor { + cursor: &cursor, + file: self.get_file() + } + ) + } else { + write!(f, "{}", self.handle.display(&self.owner.stack_graph)) + } } } diff --git a/crates/solidity/inputs/language/bindings/rules.msgb b/crates/solidity/inputs/language/bindings/rules.msgb index 2d2d97aff..b992ae4b7 100644 --- a/crates/solidity/inputs/language/bindings/rules.msgb +++ b/crates/solidity/inputs/language/bindings/rules.msgb @@ -300,7 +300,7 @@ attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, i edge @contract.lexical_scope -> super } -@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes @type [InheritanceType +@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes @_type [InheritanceType @type_name [IdentifierPath] ]]]] { ;; Resolve contract bases names through the parent scope of the contract (aka @@ -327,9 +327,11 @@ attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, i ;; The base contract defs are directly accesible through our special super scope edge @contract.super_scope -> @type_name.right + ;; Precedence order for bases defined from right to left (ie. rightmost base has higher precedence) - let p = (plus 1 (named-child-index @type)) - attr (@contract.super_scope -> @type_name.right) precedence = p + ;; NOTE: this will overriden implementations of other bases than the first + ; let p = (named-child-index @type) + ; attr (@contract.super_scope -> @type_name.right) precedence = p } @interface [InterfaceDefinition @name name: [Identifier]] { diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs index 0c2d9ba88..f937b1558 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/generated/binding_rules.rs @@ -305,7 +305,7 @@ attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, i edge @contract.lexical_scope -> super } -@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes @type [InheritanceType +@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes @_type [InheritanceType @type_name [IdentifierPath] ]]]] { ;; Resolve contract bases names through the parent scope of the contract (aka @@ -332,9 +332,11 @@ attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, i ;; The base contract defs are directly accesible through our special super scope edge @contract.super_scope -> @type_name.right + ;; Precedence order for bases defined from right to left (ie. rightmost base has higher precedence) - let p = (plus 1 (named-child-index @type)) - attr (@contract.super_scope -> @type_name.right) precedence = p + ;; NOTE: this will overriden implementations of other bases than the first + ; let p = (named-child-index @type) + ; attr (@contract.super_scope -> @type_name.right) precedence = p } @interface [InterfaceDefinition @name name: [Identifier]] { diff --git a/crates/solidity/testing/snapshots/bindings_assertions/contracts/virtual_methods.sol b/crates/solidity/testing/snapshots/bindings_assertions/contracts/virtual_methods.sol index 343684487..cc476b0a4 100644 --- a/crates/solidity/testing/snapshots/bindings_assertions/contracts/virtual_methods.sol +++ b/crates/solidity/testing/snapshots/bindings_assertions/contracts/virtual_methods.sol @@ -27,7 +27,7 @@ contract D is B, C { // D.foo() returns "C" function foo() public pure override(B, C) returns (string memory) { return super.foo(); - // ^ref:3 (>=0.6.0) + // ^FIXME ref:3 (>=0.6.0) } } @@ -35,6 +35,6 @@ contract E is C, B { // E.foo() returns "B" function foo() public pure override(C, B) returns (string memory) { return super.foo(); - // ^ref:2 (>=0.6.0) + // ^FIXME ref:2 (>=0.6.0) } }