diff --git a/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs b/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs index 81efda591..dd729a251 100644 --- a/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs +++ b/crates/codegen/runtime/cargo/src/runtime/bindings/mod.rs @@ -135,6 +135,18 @@ impl Bindings { handle, }) } + + pub fn cursor_to_handle(&self, cursor: &Cursor) -> Option> { + for (handle, handle_cursor) in self.cursors.iter() { + if handle_cursor == cursor { + return Some(Handle { + owner: self, + handle: *handle, + }); + } + } + return None; + } } pub struct Handle<'a> { diff --git a/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml b/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml index 84f8661a0..54976189c 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml +++ b/crates/solidity/outputs/cargo/slang_solidity/Cargo.toml @@ -33,7 +33,7 @@ required-features = ["cli"] [features] default = ["cli"] -cli = ["dep:anyhow", "dep:clap", "dep:serde_json", "__private_ariadne"] +cli = ["dep:clap", "dep:serde_json", "__private_ariadne"] # This is meant to be used by the CLI or internally only. __private_ariadne = ["dep:ariadne"] # For internal development only @@ -46,7 +46,6 @@ infra_utils = { workspace = true } # __REMOVE_THIS_LINE_DURING_CAR solidity_language = { workspace = true } # __REMOVE_THIS_LINE_DURING_CARGO_PUBLISH__ [dependencies] -anyhow = { workspace = true, optional = true } ariadne = { workspace = true, optional = true } clap = { workspace = true, optional = true } metaslang_cst = { workspace = true } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/assertions.rs b/crates/solidity/outputs/cargo/slang_solidity/src/assertions.rs index 73beb6a02..9366cdd5c 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/assertions.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/assertions.rs @@ -5,31 +5,127 @@ use std::collections::HashMap; use regex::Regex; use semver::Version; use thiserror::Error; -use anyhow::Result; use slang_solidity::bindings::Bindings; use slang_solidity::cursor::Cursor; use slang_solidity::kinds::TerminalKind; use slang_solidity::query::Query; -use slang_solidity::cli::commands::CommandError; use slang_solidity::cli::commands; +use slang_solidity::cli::commands::CommandError; -pub fn execute_check_assertions(file_path_string: &str, version: Version) -> Result<()> { +pub fn execute_check_assertions( + file_path_string: &str, + version: Version, +) -> Result<(), CommandError> { let mut bindings = Bindings::create(version.clone()); let parse_output = commands::parse::parse_source_file(file_path_string, version, |_| ())?; let tree_cursor = parse_output.create_tree_cursor(); bindings.add_file(file_path_string, tree_cursor.clone())?; - let assertions = collect_assertions(tree_cursor).map_err(|e| CommandError::Unknown(e.to_string()))?; - for assertion in assertions.iter() { - println!("{}", assertion); - } + + check_assertions(&bindings, &assertions)?; + Ok(()) } +fn check_assertions(bindings: &Bindings, assertions: &Assertions) -> Result<(), CommandError> { + let mut count = 0; + let mut success = 0; + + for assertion in assertions.definitions.values() { + count += 1; + + let Assertion::Definition { id: _, cursor } = assertion else { + unreachable!("{assertion} is not a definition assertion"); + }; + + let Some(handle) = bindings.cursor_to_handle(cursor) else { + eprintln!("{assertion} failed: not found"); + continue; + }; + if !handle.is_definition() { + eprintln!("{assertion} failed: not a definition"); + continue; + } + + success += 1; + } + + for assertion in assertions.references.iter() { + count += 1; + + let Assertion::Reference { id, cursor } = assertion else { + unreachable!("{assertion} is not a reference assertion"); + }; + + let Some(handle) = bindings.cursor_to_handle(cursor) else { + eprintln!("{assertion} failed: not found"); + continue; + }; + if !handle.is_reference() { + eprintln!("{assertion} failed: not a reference"); + continue; + } + + let Some(def_handle) = handle.jump_to_definition() else { + // couldn't jump to definition + if id.is_some() { + // but a binding resolution was expected + eprintln!("{assertion} failed: not resolved"); + } else { + // and we asserted an unresolved reference -> good + success += 1; + } + continue; + }; + let Some(id) = id else { + // expected an unresolved reference + eprintln!( + "{assertion} failed: reference did resolve to {}", + DisplayCursor(&def_handle.get_cursor().unwrap()) + ); + continue; + }; + + let Some(Assertion::Definition { + id: _, + cursor: def_cursor, + }) = assertions.definitions.get(id) + else { + eprintln!("{assertion} failed: definition assertion not found"); + continue; + }; + if let Some(ref_def_cursor) = def_handle.get_cursor() { + if ref_def_cursor != *def_cursor { + eprintln!( + "{assertion} failed: resolved to unexpected {}", + DisplayCursor(&ref_def_cursor) + ); + continue; + } + } else { + eprintln!("{assertion} failed: jumped to definition did not resolve to a cursor"); + continue; + } + + success += 1; + } + + if count > success { + eprintln!(); + Err(CommandError::Unknown(format!( + "Failed {failed} of {count} bindings assertions", + failed = count - success + ))) + } else { + println!("{count} binding assertions OK"); + Ok(()) + } +} + #[derive(Debug, Error)] enum AssertionError { #[error("Invalid assertion at {0}:{1}")] @@ -86,10 +182,6 @@ impl Assertions { } } } - - fn iter(&self) -> impl Iterator { - self.definitions.values().chain(self.references.iter()) - } } #[derive(Clone, Debug, PartialEq)] @@ -118,12 +210,20 @@ impl fmt::Display for Assertion { cursor } }; - let offset = cursor.text_offset(); - let range = cursor.text_range(); + write!(f, " {}", DisplayCursor(cursor)) + } +} + +struct DisplayCursor<'a>(&'a Cursor); + +impl<'a> fmt::Display for DisplayCursor<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let offset = self.0.text_offset(); + let range = self.0.text_range(); write!( f, - " `{}` at {}:{} [{}..{}]", - cursor.node().unparse(), + "`{}` at {}:{} [{}..{}]", + self.0.node().unparse(), offset.line + 1, offset.column + 1, range.start, diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/commands.rs b/crates/solidity/outputs/cargo/slang_solidity/src/commands.rs index 32d878fb6..f54a1d466 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/commands.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/commands.rs @@ -1,5 +1,5 @@ #[allow(unused_imports)] -use anyhow::Error; +use slang_solidity::cli::commands::CommandError; use clap::Subcommand; use std::process::ExitCode; @@ -24,7 +24,7 @@ impl CustomCommands { #[cfg(feature = "__experimental_bindings_api")] pub fn execute(self) -> ExitCode { - let result: Result<(), Error> = match self { + let result: Result<(), CommandError> = match self { Self::CheckAssertions { file_path, version } => { super::assertions::execute_check_assertions(&file_path, version) } diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs index 95348f325..38966574c 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/generated/bindings/mod.rs @@ -137,6 +137,18 @@ impl Bindings { handle, }) } + + pub fn cursor_to_handle(&self, cursor: &Cursor) -> Option> { + for (handle, handle_cursor) in self.cursors.iter() { + if handle_cursor == cursor { + return Some(Handle { + owner: self, + handle: *handle, + }); + } + } + return None; + } } pub struct Handle<'a> { diff --git a/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs b/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs index 9aee541d9..2b5fadfb7 100644 --- a/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs +++ b/crates/solidity/outputs/cargo/slang_solidity/src/lib.rs @@ -10,5 +10,5 @@ pub use generated::*; mod supress_cli_dependencies { #[cfg(feature = "__experimental_bindings_api")] use regex as _; - use {anyhow as _, ariadne as _, clap as _, serde_json as _}; + use {ariadne as _, clap as _, serde_json as _}; } diff --git a/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs b/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs index 95348f325..38966574c 100644 --- a/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs +++ b/crates/testlang/outputs/cargo/slang_testlang/src/generated/bindings/mod.rs @@ -137,6 +137,18 @@ impl Bindings { handle, }) } + + pub fn cursor_to_handle(&self, cursor: &Cursor) -> Option> { + for (handle, handle_cursor) in self.cursors.iter() { + if handle_cursor == cursor { + return Some(Handle { + owner: self, + handle: *handle, + }); + } + } + return None; + } } pub struct Handle<'a> {