Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Definition::references() #1243

Merged
merged 7 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/famous-monkeys-move.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

add `definition.references()` API to find all references that resolve to a definition.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ interface bindings {
/// Returns the location of the definition's definiens.
/// For `contract X {}`, that is the location of the parent `ContractDefinition` node.
definiens-location: func() -> binding-location;

/// Returns a list of all references this definition binds to.
references: func() -> list<reference>;
}

/// Represents a reference in the binding graph.
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ define_wrapper! { Definition {
fn definiens_location(&self) -> ffi::BindingLocation {
self._borrow_ffi().definiens_location()._into_ffi()
}

fn references(&self) -> Vec<ffi::Reference> {
self._borrow_ffi().references().iter().cloned().map(IntoFFI::_into_ffi).collect()
}
} }

//================================================
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/metaslang/bindings/generated/public_api.txt

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion crates/metaslang/bindings/src/graph/definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::rc::Rc;
use metaslang_cst::cursor::Cursor;
use metaslang_cst::kinds::KindTypes;

use super::{BindingGraph, BindingLocation};
use super::{BindingGraph, BindingLocation, Reference};
use crate::builder::{FileDescriptor, GraphHandle};
use crate::graph::DisplayCursor;

Expand Down Expand Up @@ -57,6 +57,10 @@ impl<KT: KindTypes + 'static> Definition<KT> {
.get_file_descriptor(self.handle)
.expect("Definition to have a valid file descriptor")
}

pub fn references(&self) -> Vec<Reference<KT>> {
self.owner.resolve_definition(self.handle)
}
}

impl<KT: KindTypes + 'static> Display for Definition<KT> {
Expand Down
13 changes: 13 additions & 0 deletions crates/metaslang/bindings/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ impl<KT: KindTypes + 'static> BindingGraph<KT> {
})
.collect()
}

fn resolve_definition(self: &Rc<Self>, handle: GraphHandle) -> Vec<Reference<KT>> {
let mut resolver = self.resolver.borrow_mut();
resolver.ensure_all_references_resolved(&self.graph);
let references = resolver.definition_to_references(handle);
references
.iter()
.map(|handle| Reference {
owner: Rc::clone(self),
handle: *handle,
})
.collect()
}
}

struct DisplayCursor<'a, KT: KindTypes + 'static> {
Expand Down
43 changes: 43 additions & 0 deletions crates/metaslang/bindings/src/graph/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub(crate) struct Resolver {
partials: PartialPaths,
database: Database,
references: HashMap<GraphHandle, Vec<GraphHandle>>,
definitions: Option<HashMap<GraphHandle, Vec<GraphHandle>>>,
}

impl Resolver {
Expand All @@ -27,6 +28,7 @@ impl Resolver {
partials,
database,
references: HashMap::new(),
definitions: None,
};
resolver.build(graph);
resolver
Expand Down Expand Up @@ -182,6 +184,47 @@ impl Resolver {
Vec::new()
}
}

pub(crate) fn ensure_all_references_resolved<KT: KindTypes + 'static>(
&mut self,
graph: &ExtendedStackGraph<KT>,
) {
if self.definitions.is_some() {
return;
}

// Resolve all references
for handle in graph.iter_references() {
if !self.references.contains_key(&handle)
&& graph
.get_file_descriptor(handle)
.is_some_and(|file| file.is_user())
{
let definition_handles = self.resolve_internal(graph, handle, true);
self.references.insert(handle, definition_handles);
}
}

// Build reverse mapping from definitions to reference handles
let mut definitions: HashMap<GraphHandle, Vec<GraphHandle>> = HashMap::new();
for (reference, resolved_definitions) in &self.references {
for definition in resolved_definitions {
if let Some(references) = definitions.get_mut(definition) {
references.push(*reference);
} else {
definitions.insert(*definition, vec![*reference]);
}
}
}
self.definitions = Some(definitions);
}

pub(crate) fn definition_to_references(&self, handle: GraphHandle) -> Vec<GraphHandle> {
let Some(ref definitions) = self.definitions else {
unreachable!("All references have been resolved");
};
definitions.get(&handle).cloned().unwrap_or_default()
}
}

// This is a partial paths database, but we also need to keep track of edges
Expand Down
120 changes: 120 additions & 0 deletions crates/solidity/outputs/cargo/tests/src/binding_resolver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use std::rc::Rc;

use anyhow::Result;
use semver::Version;
use slang_solidity::bindings;
use slang_solidity::cst::{Cursor, Query};
use slang_solidity::parser::Parser;

use crate::resolver::TestsPathResolver;

const TEST_VERSION: Version = Version::new(0, 8, 26);

const INPUT_FILE: &str = r##"
contract Base {}
contract Middle is Base {}
contract Test is Base, Middle {}
"##;

fn find_first_match(root_cursor: Cursor, query_string: &str, capture_name: &str) -> Result<Cursor> {
let query = Query::create(query_string)?;
let query_match = root_cursor.query(vec![query]).next();
let query_match = query_match.expect("query to succeed");
Ok(query_match.captures[capture_name]
.first()
.expect("query to capture identifier")
.clone())
}

fn find_all_matches(
root_cursor: Cursor,
query_string: &str,
capture_name: &str,
) -> Result<Vec<Cursor>> {
let query = Query::create(query_string)?;
let mut results = Vec::new();
for query_match in root_cursor.query(vec![query]) {
let cursor = query_match.captures[capture_name]
.first()
.expect("query to capture identifier");
results.push(cursor.clone());
}
Ok(results)
}

#[test]
fn test_resolve_references_from_definition() -> Result<()> {
let version = TEST_VERSION;
let parser = Parser::create(version.clone())?;
let mut builder =
bindings::create_with_resolver(version.clone(), Rc::new(TestsPathResolver {}))?;

let parse_output = parser.parse_file_contents(INPUT_FILE);
builder.add_user_file("input.sol", parse_output.create_tree_cursor());

let binding_graph = builder.build();
let root_cursor = parse_output.create_tree_cursor();

// "Base" identifier
let base_cursor = find_first_match(
root_cursor.clone(),
"[ContractDefinition @identifier [\"Base\"]]",
"identifier",
)?;
let base_definition = binding_graph
.definition_at(&base_cursor)
.expect("Base definition to be found");
let base_references = base_definition.references();
assert_eq!(2, base_references.len());

let base_ref_cursors = find_all_matches(
root_cursor.clone(),
"[IdentifierPath @identifier [\"Base\"]]",
"identifier",
)?;
for base_ref in &base_references {
assert!(base_ref_cursors.contains(base_ref.get_cursor()));
}

// "Middle" identifier
let middle_cursor = find_first_match(
root_cursor.clone(),
"[ContractDefinition @identifier [\"Middle\"]]",
"identifier",
)?;
let middle_definition = binding_graph
.definition_at(&middle_cursor)
.expect("Middle definition to be found");
let middle_references = middle_definition.references();
assert_eq!(1, middle_references.len());

let middle_ref_cursors = find_all_matches(
root_cursor.clone(),
"[IdentifierPath @identifier [\"Middle\"]]",
"identifier",
)?;
for middle_ref in &middle_references {
assert!(middle_ref_cursors.contains(middle_ref.get_cursor()));
}

// "Test" identifier
let test_cursor = find_first_match(
root_cursor.clone(),
"[ContractDefinition @identifier [\"Test\"]]",
"identifier",
)?;
let test_definition = binding_graph
.definition_at(&test_cursor)
.expect("Test definition to be found");
let test_references = test_definition.references();
assert_eq!(0, test_references.len());

let test_ref_cursors = find_all_matches(
root_cursor.clone(),
"[IdentifierPath @identifier [\"Test\"]]",
"identifier",
)?;
assert!(test_ref_cursors.is_empty());

Ok(())
}
1 change: 1 addition & 0 deletions crates/solidity/outputs/cargo/tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use metaslang_bindings as _;

mod binding_resolver;
mod binding_rules;
mod bindings_output;
mod built_ins;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ test("binding graph", async () => {
const definition = unit.bindingGraph.definitionAt(cursor)!;
assertUserFileLocation(definition.nameLocation, "child.sol", TerminalKind.Identifier, 3);
assertUserFileLocation(definition.definiensLocation, "child.sol", NonterminalKind.ImportDeconstructionSymbol, 3);

const refs = definition.references();
assert.equal(refs.length, 1);

assertUserFileLocation(refs[0]!.location, "child.sol", TerminalKind.Identifier, 5);
}

{
Expand All @@ -40,6 +45,8 @@ test("binding graph", async () => {
const definition = unit.bindingGraph.definitionAt(cursor)!;
assertUserFileLocation(definition.nameLocation, "child.sol", TerminalKind.Identifier, 5);
assertUserFileLocation(definition.definiensLocation, "child.sol", NonterminalKind.ContractDefinition, 4);
const refs = definition.references();
assert.equal(refs.length, 0);
}

{
Expand Down Expand Up @@ -78,6 +85,9 @@ test("binding graph", async () => {
const definition = unit.bindingGraph.definitionAt(cursor)!;
assertUserFileLocation(definition.nameLocation, "child.sol", TerminalKind.Identifier, 6);
assertUserFileLocation(definition.definiensLocation, "child.sol", NonterminalKind.FunctionDefinition, 6);

const refs = definition.references();
assert.equal(refs.length, 0);
}

{
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading