Skip to content

Commit

Permalink
Add checks for unbound identifiers in sanctuary runner
Browse files Browse the repository at this point in the history
  • Loading branch information
ggiraldez committed Dec 20, 2024
1 parent 894aed7 commit 9b52fa4
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ fn check_bindings_coverage<'a>(
.with_config(Config::default().with_color(false));

let query = Query::parse("@identifier ([Identifier] | [YulIdentifier])").unwrap();
let cursor = part.parse_output.create_tree_cursor();
for result in cursor.query(vec![query]) {
let tree_cursor = part.parse_output.create_tree_cursor();
for result in tree_cursor.query(vec![query]) {
let identifier_cursor = result.captures.get("identifier").unwrap().first().unwrap();
let parent = {
let mut parent_cursor = identifier_cursor.spawn();
Expand Down
70 changes: 53 additions & 17 deletions crates/solidity/testing/sanctuary/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use metaslang_bindings::PathResolver;
use semver::Version;
use slang_solidity::bindings;
use slang_solidity::bindings::BindingGraph;
use slang_solidity::cst::{Cursor, KindTypes, NonterminalKind, TextRange};
use slang_solidity::cst::{Cursor, KindTypes, NonterminalKind, Query, TextRange};
use slang_solidity::diagnostic::{Diagnostic, Severity};
use slang_solidity::parser::{ParseOutput, Parser};
use slang_solidity::utils::LanguageFacts;
Expand Down Expand Up @@ -122,11 +122,11 @@ pub fn run_test(file: &SourceFile, events: &Events, check_bindings: bool) -> Res
}

if check_bindings {
let unresolved_references = run_bindings_check(&version, source_id, &output)?;
if !unresolved_references.is_empty() {
for unresolved in &unresolved_references {
let binding_errors = run_bindings_check(&version, source_id, &output)?;
if !binding_errors.is_empty() {
for error in &binding_errors {
let report =
slang_solidity::diagnostic::render(unresolved, source_id, &source, with_color);
slang_solidity::diagnostic::render(error, source_id, &source, with_color);
events.bindings_error(format!("[{version}] {report}"));
}

Expand Down Expand Up @@ -185,8 +185,8 @@ fn run_bindings_check(
version: &Version,
source_id: &str,
output: &ParseOutput,
) -> Result<Vec<UnresolvedReference>> {
let mut unresolved = Vec::new();
) -> Result<Vec<BindingError>> {
let mut errors = Vec::new();
let binding_graph = create_bindings(version, source_id, output)?;

for reference in binding_graph.all_references() {
Expand All @@ -198,11 +198,32 @@ fn run_bindings_check(
// to, so we lookup all of them and fail if we find none.
if reference.definitions().is_empty() {
let cursor = reference.get_cursor().to_owned();
unresolved.push(UnresolvedReference { cursor });
errors.push(BindingError::UnresolvedReference(cursor));
}
}

Ok(unresolved)
// Check that all `Identifier` and `YulIdentifier` nodes are bound to either
// a definition or a reference
let query = Query::parse("@identifier ([Identifier] | [YulIdentifier])").unwrap();
let tree_cursor = output.create_tree_cursor();
for result in tree_cursor.query(vec![query]) {
let identifier_cursor = result.captures.get("identifier").unwrap().first().unwrap();
let parent = {
let mut parent_cursor = identifier_cursor.spawn();
parent_cursor.go_to_parent();
parent_cursor.node()
};
if parent.is_nonterminal_with_kind(NonterminalKind::ExperimentalFeature) {
// ignore identifiers in `pragma experimental` directives
continue;
}
if binding_graph.definition_at(identifier_cursor).is_none()
&& binding_graph.reference_at(identifier_cursor).is_none()
{
errors.push(BindingError::UnboundIdentifier(identifier_cursor.clone()));
}
}
Ok(errors)
}

fn create_bindings(
Expand Down Expand Up @@ -235,23 +256,38 @@ impl PathResolver<KindTypes> for SingleFileResolver {
}
}

struct UnresolvedReference {
pub cursor: Cursor,
enum BindingError {
UnresolvedReference(Cursor),
UnboundIdentifier(Cursor),
}

impl Diagnostic for UnresolvedReference {
impl Diagnostic for BindingError {
fn text_range(&self) -> TextRange {
self.cursor.text_range()
let cursor = match self {
Self::UnboundIdentifier(cursor) => cursor,
Self::UnresolvedReference(cursor) => cursor,
};
cursor.text_range()
}

fn severity(&self) -> Severity {
Severity::Error
}

fn message(&self) -> String {
format!(
"Unresolved reference to `{symbol}`",
symbol = self.cursor.node().unparse()
)
match self {
Self::UnresolvedReference(cursor) => {
format!(
"Unresolved reference to `{symbol}`",
symbol = cursor.node().unparse()
)
}
Self::UnboundIdentifier(cursor) => {
format!(
"Missing identifier or definition for `{symbol}`",
symbol = cursor.node().unparse()
)
}
}
}
}

0 comments on commit 9b52fa4

Please sign in to comment.