From 192eb723c53cddb829b3d5186439690204a2f868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Wed, 27 Nov 2024 18:17:23 -0500 Subject: [PATCH 1/6] Change `binding_rules_file` to `binding_rules_dir` and default to `rules.msgb` --- crates/codegen/language/definition/src/model/manifest.rs | 2 +- .../src/fail/definitions/duplicate_expression_name/test.rs | 2 +- .../tests/src/fail/definitions/duplicate_item_name/test.rs | 2 +- .../src/fail/definitions/duplicate_variant_name/test.rs | 2 +- .../tests/src/fail/definitions/operator_mismatch/test.rs | 2 +- .../tests/src/fail/parsing/duplicate_map_key/test.rs | 2 +- .../tests/src/fail/parsing/duplicate_set_entry/test.rs | 2 +- .../language/tests/src/fail/parsing/empty_string/test.rs | 2 +- .../language/tests/src/fail/parsing/missing_field/test.rs | 2 +- .../tests/src/fail/parsing/unrecognized_field/test.rs | 2 +- .../tests/src/fail/parsing/unrecognized_variant/test.rs | 2 +- .../language/tests/src/fail/reachability/unreachable/test.rs | 2 +- .../tests/src/fail/reachability/unused_version/test.rs | 2 +- .../tests/src/fail/references/disabled_too_late/test.rs | 2 +- .../tests/src/fail/references/enabled_too_early/test.rs | 2 +- .../tests/src/fail/references/invalid_filter/test.rs | 2 +- .../tests/src/fail/references/invalid_version/test.rs | 2 +- .../tests/src/fail/references/unknown_reference/test.rs | 2 +- .../tests/src/fail/references/unordered_version_pair/test.rs | 2 +- .../tests/src/fail/references/version_not_found/test.rs | 2 +- crates/codegen/language/tests/src/pass/tiny_language.rs | 4 ++-- crates/codegen/runtime/generator/src/bindings/mod.rs | 5 ++--- crates/solidity/inputs/language/src/definition.rs | 2 +- crates/testlang/inputs/language/src/definition.rs | 2 +- 24 files changed, 26 insertions(+), 27 deletions(-) diff --git a/crates/codegen/language/definition/src/model/manifest.rs b/crates/codegen/language/definition/src/model/manifest.rs index cfe1ef7c82..bf66d16da9 100644 --- a/crates/codegen/language/definition/src/model/manifest.rs +++ b/crates/codegen/language/definition/src/model/manifest.rs @@ -14,7 +14,7 @@ pub struct Language { pub name: Identifier, pub documentation_dir: PathBuf, - pub binding_rules_file: PathBuf, + pub binding_rules_dir: PathBuf, pub file_extension: Option, pub root_item: Identifier, diff --git a/crates/codegen/language/tests/src/fail/definitions/duplicate_expression_name/test.rs b/crates/codegen/language/tests/src/fail/definitions/duplicate_expression_name/test.rs index 5b6ec3c74d..306ff82b76 100644 --- a/crates/codegen/language/tests/src/fail/definitions/duplicate_expression_name/test.rs +++ b/crates/codegen/language/tests/src/fail/definitions/duplicate_expression_name/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/definitions/duplicate_item_name/test.rs b/crates/codegen/language/tests/src/fail/definitions/duplicate_item_name/test.rs index 47e3448b32..31c6789e72 100644 --- a/crates/codegen/language/tests/src/fail/definitions/duplicate_item_name/test.rs +++ b/crates/codegen/language/tests/src/fail/definitions/duplicate_item_name/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar1, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/definitions/duplicate_variant_name/test.rs b/crates/codegen/language/tests/src/fail/definitions/duplicate_variant_name/test.rs index a3b7287263..955c55f296 100644 --- a/crates/codegen/language/tests/src/fail/definitions/duplicate_variant_name/test.rs +++ b/crates/codegen/language/tests/src/fail/definitions/duplicate_variant_name/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/definitions/operator_mismatch/test.rs b/crates/codegen/language/tests/src/fail/definitions/operator_mismatch/test.rs index 143be44999..30f5436896 100644 --- a/crates/codegen/language/tests/src/fail/definitions/operator_mismatch/test.rs +++ b/crates/codegen/language/tests/src/fail/definitions/operator_mismatch/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/parsing/duplicate_map_key/test.rs b/crates/codegen/language/tests/src/fail/parsing/duplicate_map_key/test.rs index aa53d5905d..daf3e7d444 100644 --- a/crates/codegen/language/tests/src/fail/parsing/duplicate_map_key/test.rs +++ b/crates/codegen/language/tests/src/fail/parsing/duplicate_map_key/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/parsing/duplicate_set_entry/test.rs b/crates/codegen/language/tests/src/fail/parsing/duplicate_set_entry/test.rs index 87236ac4b1..23c8790408 100644 --- a/crates/codegen/language/tests/src/fail/parsing/duplicate_set_entry/test.rs +++ b/crates/codegen/language/tests/src/fail/parsing/duplicate_set_entry/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/parsing/empty_string/test.rs b/crates/codegen/language/tests/src/fail/parsing/empty_string/test.rs index a14eec2aeb..45acb8290c 100644 --- a/crates/codegen/language/tests/src/fail/parsing/empty_string/test.rs +++ b/crates/codegen/language/tests/src/fail/parsing/empty_string/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/parsing/missing_field/test.rs b/crates/codegen/language/tests/src/fail/parsing/missing_field/test.rs index 3676713290..a94544b170 100644 --- a/crates/codegen/language/tests/src/fail/parsing/missing_field/test.rs +++ b/crates/codegen/language/tests/src/fail/parsing/missing_field/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/parsing/unrecognized_field/test.rs b/crates/codegen/language/tests/src/fail/parsing/unrecognized_field/test.rs index 82318d9f02..07f5812a7f 100644 --- a/crates/codegen/language/tests/src/fail/parsing/unrecognized_field/test.rs +++ b/crates/codegen/language/tests/src/fail/parsing/unrecognized_field/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/parsing/unrecognized_variant/test.rs b/crates/codegen/language/tests/src/fail/parsing/unrecognized_variant/test.rs index ec42e71b8c..8428b3ee73 100644 --- a/crates/codegen/language/tests/src/fail/parsing/unrecognized_variant/test.rs +++ b/crates/codegen/language/tests/src/fail/parsing/unrecognized_variant/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar1, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/reachability/unreachable/test.rs b/crates/codegen/language/tests/src/fail/reachability/unreachable/test.rs index a6af2b7398..dc396c5b22 100644 --- a/crates/codegen/language/tests/src/fail/reachability/unreachable/test.rs +++ b/crates/codegen/language/tests/src/fail/reachability/unreachable/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar1, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/reachability/unused_version/test.rs b/crates/codegen/language/tests/src/fail/reachability/unused_version/test.rs index d7363c8998..90f6716db9 100644 --- a/crates/codegen/language/tests/src/fail/reachability/unused_version/test.rs +++ b/crates/codegen/language/tests/src/fail/reachability/unused_version/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/disabled_too_late/test.rs b/crates/codegen/language/tests/src/fail/references/disabled_too_late/test.rs index d6c9e86c59..50613d5aa3 100644 --- a/crates/codegen/language/tests/src/fail/references/disabled_too_late/test.rs +++ b/crates/codegen/language/tests/src/fail/references/disabled_too_late/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = One, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/enabled_too_early/test.rs b/crates/codegen/language/tests/src/fail/references/enabled_too_early/test.rs index 15acba2144..65baf78a3e 100644 --- a/crates/codegen/language/tests/src/fail/references/enabled_too_early/test.rs +++ b/crates/codegen/language/tests/src/fail/references/enabled_too_early/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = One, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/invalid_filter/test.rs b/crates/codegen/language/tests/src/fail/references/invalid_filter/test.rs index 31a2df3036..86707db368 100644 --- a/crates/codegen/language/tests/src/fail/references/invalid_filter/test.rs +++ b/crates/codegen/language/tests/src/fail/references/invalid_filter/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/invalid_version/test.rs b/crates/codegen/language/tests/src/fail/references/invalid_version/test.rs index f01871269f..430368710e 100644 --- a/crates/codegen/language/tests/src/fail/references/invalid_version/test.rs +++ b/crates/codegen/language/tests/src/fail/references/invalid_version/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/unknown_reference/test.rs b/crates/codegen/language/tests/src/fail/references/unknown_reference/test.rs index 96cd096237..2cc4916024 100644 --- a/crates/codegen/language/tests/src/fail/references/unknown_reference/test.rs +++ b/crates/codegen/language/tests/src/fail/references/unknown_reference/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = Bar, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/unordered_version_pair/test.rs b/crates/codegen/language/tests/src/fail/references/unordered_version_pair/test.rs index c6e5120d50..6f9679964a 100644 --- a/crates/codegen/language/tests/src/fail/references/unordered_version_pair/test.rs +++ b/crates/codegen/language/tests/src/fail/references/unordered_version_pair/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = One, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/fail/references/version_not_found/test.rs b/crates/codegen/language/tests/src/fail/references/version_not_found/test.rs index f7ac4684b3..a066da8146 100644 --- a/crates/codegen/language/tests/src/fail/references/version_not_found/test.rs +++ b/crates/codegen/language/tests/src/fail/references/version_not_found/test.rs @@ -3,7 +3,7 @@ codegen_language_macros::compile!(Language( name = Foo, documentation_dir = "foo/bar", - binding_rules_file = "bindings/rules.msgb", + binding_rules_dir = "bindings", root_item = One, leading_trivia = Sequence([]), trailing_trivia = Sequence([]), diff --git a/crates/codegen/language/tests/src/pass/tiny_language.rs b/crates/codegen/language/tests/src/pass/tiny_language.rs index 6b5652634a..bd8ce189f5 100644 --- a/crates/codegen/language/tests/src/pass/tiny_language.rs +++ b/crates/codegen/language/tests/src/pass/tiny_language.rs @@ -10,7 +10,7 @@ use semver::Version; codegen_language_macros::compile!(Language( name = Tiny, documentation_dir = "tiny/docs", - binding_rules_file = "tiny/bindings/rules.msgb", + binding_rules_dir = "tiny/bindings", file_extension = ".tiny", root_item = Foo, leading_trivia = Sequence([]), @@ -50,7 +50,7 @@ fn definition() { Language { name: "Tiny".into(), documentation_dir: Path::repo_path("tiny/docs"), - binding_rules_file: Path::repo_path("tiny/bindings/rules.msgb"), + binding_rules_dir: Path::repo_path("tiny/bindings"), file_extension: Some(".tiny".into()), root_item: "Foo".into(), leading_trivia: TriviaParser::Sequence { parsers: [].into() }, diff --git a/crates/codegen/runtime/generator/src/bindings/mod.rs b/crates/codegen/runtime/generator/src/bindings/mod.rs index 61e9f5687e..5d9564472c 100644 --- a/crates/codegen/runtime/generator/src/bindings/mod.rs +++ b/crates/codegen/runtime/generator/src/bindings/mod.rs @@ -3,7 +3,6 @@ use std::collections::BTreeSet; use anyhow::Result; use codegen_language_definition::model; use infra_utils::codegen::CodegenFileSystem; -use infra_utils::paths::PathExtensions; use semver::Version; use serde::Serialize; @@ -17,9 +16,9 @@ pub struct BindingsModel { impl BindingsModel { pub fn from_language(language: &model::Language) -> Result { // We use `CodegenFileSystem` here to ensure the rules are rebuilt if the rules file changes - let binding_rules_dir = language.binding_rules_file.unwrap_parent(); + let binding_rules_dir = &language.binding_rules_dir; let mut fs = CodegenFileSystem::new(binding_rules_dir)?; - let binding_rules_source = fs.read_file(&language.binding_rules_file)?; + let binding_rules_source = fs.read_file(binding_rules_dir.join("rules.msgb"))?; let built_ins_versions = language.collect_built_ins_versions(); let file_extension = language.file_extension.clone().unwrap_or_default(); diff --git a/crates/solidity/inputs/language/src/definition.rs b/crates/solidity/inputs/language/src/definition.rs index 80830bc363..cf7d4dc373 100644 --- a/crates/solidity/inputs/language/src/definition.rs +++ b/crates/solidity/inputs/language/src/definition.rs @@ -3,7 +3,7 @@ pub use solidity::SolidityDefinition; codegen_language_macros::compile!(Language( name = Solidity, documentation_dir = "crates/solidity/inputs/language/docs", - binding_rules_file = "crates/solidity/inputs/language/bindings/rules.msgb", + binding_rules_dir = "crates/solidity/inputs/language/bindings", file_extension = ".sol", // TODO: This should be moved to the Solidity-specific 'extensions' sub-module. root_item = SourceUnit, // TODO(#1020): Define the end-of-file trivia explicitly rather than diff --git a/crates/testlang/inputs/language/src/definition.rs b/crates/testlang/inputs/language/src/definition.rs index 569348b328..2ca364e2aa 100644 --- a/crates/testlang/inputs/language/src/definition.rs +++ b/crates/testlang/inputs/language/src/definition.rs @@ -3,7 +3,7 @@ pub use testlang::TestlangDefinition; codegen_language_macros::compile!(Language( name = Testlang, documentation_dir = "crates/testlang/inputs/language/docs", - binding_rules_file = "crates/testlang/inputs/language/bindings/rules.msgb", + binding_rules_dir = "crates/testlang/inputs/language/bindings", root_item = SourceUnit, leading_trivia = OneOrMore(Choice([ Trivia(Whitespace), From 6e3fc5de9b30e8f820732c9ba57e1d8707f7a6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Fri, 29 Nov 2024 12:47:15 -0500 Subject: [PATCH 2/6] Compose the binding rules file from parts --- .../runtime/generator/src/bindings/mod.rs | 21 +++++++++++++++---- .../inputs/language/bindings/user-rules.parts | 1 + .../inputs/language/bindings/rules.msgb | 1 - .../inputs/language/bindings/user-rules.parts | 0 .../bindings/generated/binding_rules.rs | 3 +-- 5 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 crates/solidity/inputs/language/bindings/user-rules.parts delete mode 100644 crates/testlang/inputs/language/bindings/rules.msgb create mode 100644 crates/testlang/inputs/language/bindings/user-rules.parts diff --git a/crates/codegen/runtime/generator/src/bindings/mod.rs b/crates/codegen/runtime/generator/src/bindings/mod.rs index 5d9564472c..ed3b4c454f 100644 --- a/crates/codegen/runtime/generator/src/bindings/mod.rs +++ b/crates/codegen/runtime/generator/src/bindings/mod.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::{collections::BTreeSet, path::Path}; use anyhow::Result; use codegen_language_definition::model; @@ -15,10 +15,8 @@ pub struct BindingsModel { impl BindingsModel { pub fn from_language(language: &model::Language) -> Result { - // We use `CodegenFileSystem` here to ensure the rules are rebuilt if the rules file changes let binding_rules_dir = &language.binding_rules_dir; - let mut fs = CodegenFileSystem::new(binding_rules_dir)?; - let binding_rules_source = fs.read_file(binding_rules_dir.join("rules.msgb"))?; + let binding_rules_source = build_rules(binding_rules_dir, "user-rules.parts")?; let built_ins_versions = language.collect_built_ins_versions(); let file_extension = language.file_extension.clone().unwrap_or_default(); @@ -29,3 +27,18 @@ impl BindingsModel { }) } } + +// Builds a rules file by concatenating the file parts listed in the given `parts_file` +fn build_rules(rules_dir: &Path, parts_file: &str) -> Result { + // We use `CodegenFileSystem` here to ensure the rules are rebuilt if the rules file changes + let mut fs = CodegenFileSystem::new(rules_dir)?; + let parts_contents = fs.read_file(rules_dir.join(parts_file))?; + let mut parts = Vec::new(); + for part_name in parts_contents.lines() { + if part_name.is_empty() { + continue; + } + parts.push(fs.read_file(rules_dir.join(part_name))?); + } + Ok(parts.join("\n")) +} diff --git a/crates/solidity/inputs/language/bindings/user-rules.parts b/crates/solidity/inputs/language/bindings/user-rules.parts new file mode 100644 index 0000000000..631b2dabb8 --- /dev/null +++ b/crates/solidity/inputs/language/bindings/user-rules.parts @@ -0,0 +1 @@ +rules.msgb diff --git a/crates/testlang/inputs/language/bindings/rules.msgb b/crates/testlang/inputs/language/bindings/rules.msgb deleted file mode 100644 index 7a50d573f9..0000000000 --- a/crates/testlang/inputs/language/bindings/rules.msgb +++ /dev/null @@ -1 +0,0 @@ -// TODO(#982): add rules for TestLang here... diff --git a/crates/testlang/inputs/language/bindings/user-rules.parts b/crates/testlang/inputs/language/bindings/user-rules.parts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs b/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs index be1feee3c7..16e24e7bb4 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs @@ -3,6 +3,5 @@ #[allow(clippy::needless_raw_string_hashes)] #[allow(dead_code)] // TODO(#982): use to create the graph pub const BINDING_RULES_SOURCE: &str = r#####" - // TODO(#982): add rules for TestLang here... - + "#####; From 51c9b597d30a8276adc357e4f30e3c1c875f8bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Fri, 29 Nov 2024 13:41:25 -0500 Subject: [PATCH 3/6] Split Solidity binding rules in several parts This is both for maintainability and to be able to select a subset of the rules for processing built-ins. --- .../language/bindings/00-top-level.msgb | 267 ++ .../language/bindings/01-definitions.msgb | 807 +++++ .../bindings/02-other-definitions.msgb | 467 +++ .../inputs/language/bindings/03-types.msgb | 404 +++ .../language/bindings/04-statements.msgb | 354 ++ .../language/bindings/05-expressions.msgb | 320 ++ .../inputs/language/bindings/06-yul.msgb | 332 ++ .../inputs/language/bindings/rules.msgb | 2960 ----------------- .../inputs/language/bindings/user-rules.parts | 8 +- .../bindings/generated/binding_rules.rs | 2287 +++++++------ 10 files changed, 4100 insertions(+), 4106 deletions(-) create mode 100644 crates/solidity/inputs/language/bindings/00-top-level.msgb create mode 100644 crates/solidity/inputs/language/bindings/01-definitions.msgb create mode 100644 crates/solidity/inputs/language/bindings/02-other-definitions.msgb create mode 100644 crates/solidity/inputs/language/bindings/03-types.msgb create mode 100644 crates/solidity/inputs/language/bindings/04-statements.msgb create mode 100644 crates/solidity/inputs/language/bindings/05-expressions.msgb create mode 100644 crates/solidity/inputs/language/bindings/06-yul.msgb delete mode 100644 crates/solidity/inputs/language/bindings/rules.msgb diff --git a/crates/solidity/inputs/language/bindings/00-top-level.msgb b/crates/solidity/inputs/language/bindings/00-top-level.msgb new file mode 100644 index 0000000000..34d8d352e2 --- /dev/null +++ b/crates/solidity/inputs/language/bindings/00-top-level.msgb @@ -0,0 +1,267 @@ +global ROOT_NODE +global FILE_PATH +global JUMP_TO_SCOPE_NODE + +attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition +attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference +attribute node_symbol = node => symbol = (source-text node), source_node = node +attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol +attribute push_symbol = symbol => type = "push_symbol", symbol = symbol +attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition +attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference + +attribute scoped_node_definition = node => type = "pop_scoped_symbol", node_symbol = node, is_definition +attribute scoped_node_reference = node => type = "push_scoped_symbol", node_symbol = node, is_reference +attribute pop_scoped_symbol = symbol => type = "pop_scoped_symbol", symbol = symbol +attribute push_scoped_symbol = symbol => type = "push_scoped_symbol", symbol = symbol + +;; Keeps a link to the enclosing contract definition to provide a parent for +;; method calls (to correctly resolve virtual methods) +inherit .enclosing_def + +inherit .parent_scope +inherit .lexical_scope + +; Used to resolve extension methods for `using for *` directives +; This is used as a minor optimization to avoid introducing new possible paths +; when there are no `using for *` directives in the contract. +inherit .star_extension + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Source unit (aka .sol file) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@source_unit [SourceUnit] { + ;; All lexical_scope nodes eventually connect to the file's root scope + node @source_unit.lexical_scope + + ;; This provides all the exported symbols from the file + node @source_unit.defs + + ;; Connect to ROOT_NODE to export our symbols + node export + edge ROOT_NODE -> export + edge export -> @source_unit.defs + + if (is-system-file FILE_PATH) { + ; If this is a system file (aka. built-ins), export everything through this + ; special symbol (which is automatically imported below) + attr (export) pop_symbol = "@@built-ins@@" + + } else { + ; This is a user file, so we want to export under the file's path symbol + attr (export) pop_symbol = FILE_PATH + + ; ... and also import the global built-ins + node built_ins + attr (built_ins) push_symbol = "@@built-ins@@" + + edge @source_unit.lexical_scope -> built_ins + edge built_ins -> ROOT_NODE + } + + let @source_unit.enclosing_def = #null + + ;; This defines a parent_scope at the source unit level (this attribute is + ;; inherited) for contracts to resolve bases (both in inheritance lists and + ;; override specifiers) + let @source_unit.parent_scope = @source_unit.lexical_scope + + ; 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. + node @source_unit.star_extension +} + +;; Top-level definitions... +@source_unit [SourceUnit [SourceUnitMembers + [SourceUnitMember @unit_member ( + [ContractDefinition] + | [LibraryDefinition] + | [InterfaceDefinition] + | [StructDefinition] + | [EnumDefinition] + | [FunctionDefinition] + | [ConstantDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + | [EventDefinition] + )] +]] { + edge @source_unit.lexical_scope -> @unit_member.def + edge @source_unit.defs -> @unit_member.def + + ; In the general case, the lexical scope of the definition connects directly + ; to the source unit's + edge @unit_member.lexical_scope -> @source_unit.lexical_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 +;; to export all its members and type members directly as a source unit +;; definition. +;; __SLANG_SOLIDITY_BUILT_INS_CONTRACT_NAME__ keep in sync with built-ins generation. +@source_unit [SourceUnit [SourceUnitMembers + [SourceUnitMember @contract [ContractDefinition name: ["%BuiltIns%"]]] +]] { + if (is-system-file FILE_PATH) { + edge @source_unit.defs -> @contract.instance + } +} + +@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember @using [UsingDirective]]]] { + ; TODO: this is the hook for top-level extensions, but this should connect to + ; an extensions scope that gets pushed to the scope stack, as in the case of + ; contracts/libraries (defined further down below). + edge @source_unit.lexical_scope -> @using.def +} + +@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember + @using [UsingDirective [GlobalKeyword]] +]]] { + ; global using directives are exported by this source unit + edge @source_unit.defs -> @using.def +} + +;; Import connections to the source unit +@source_unit [SourceUnit [SourceUnitMembers + [SourceUnitMember [ImportDirective + [ImportClause @import ( + [PathImport] + | [NamedImport] + | [ImportDeconstruction] + )] + ]] +]] { + node @import.defs + edge @source_unit.defs -> @import.defs + edge @source_unit.lexical_scope -> @import.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Imports +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +[ImportClause + [_ + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] + ] +] { + ;; This node represents the imported file and the @path.import node is used by + ;; all subsequent import rules + node @path.import + let resolved_path = (resolve-path FILE_PATH @path) + attr (@path.import) push_symbol = resolved_path + edge @path.import -> ROOT_NODE +} + +;;; `import ` +@import [PathImport + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] +] { + ;; This is the "lexical" connection, which makes all symbols exported from the + ;; imported source unit available for resolution globally at this' source unit + ;; scope + edge @import.defs -> @path.import +} + +;;; `import as ` +@import [PathImport + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] + alias: [ImportAlias @alias [Identifier]] +] { + node def + attr (def) node_definition = @alias + attr (def) definiens_node = @import + edge @import.defs -> def + + node member + attr (member) pop_symbol = "." + edge def -> member + + ;; Lexical connection, which makes the import available as a member through + ;; the alias identifier + edge member -> @path.import +} + +;;; `import * as from ` +@import [NamedImport + alias: [ImportAlias @alias [Identifier]] + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] +] { + node def + attr (def) node_definition = @alias + attr (def) definiens_node = @import + edge @import.defs -> def + + node member + attr (member) pop_symbol = "." + edge def -> member + + ;; Lexical connection, which makes the import available as a member through + ;; the alias identifier + edge member -> @path.import +} + +;;; `import { [as ] ...} from ` +@import [ImportDeconstruction + symbols: [ImportDeconstructionSymbols @symbol [ImportDeconstructionSymbol]] + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] +] { + ;; We define these intermediate nodes for convenience only, to make the + ;; queries simpler in the two rules below + node @symbol.def + edge @import.defs -> @symbol.def + + node @symbol.import + edge @symbol.import -> @path.import +} + +@symbol [ImportDeconstructionSymbol @name name: [Identifier] .] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @symbol + attr (def) tag = "alias" ; deprioritize this definition + edge @symbol.def -> def + + node import + attr (import) node_reference = @name + edge def -> import + + edge import -> @symbol.import +} + +@symbol [ImportDeconstructionSymbol + @name name: [Identifier] + alias: [ImportAlias @alias [Identifier]] +] { + node def + attr (def) node_definition = @alias + attr (def) definiens_node = @symbol + attr (def) tag = "alias" ; deprioritize this definition + edge @symbol.def -> def + + node import + attr (import) node_reference = @name + edge def -> import + + edge import -> @symbol.import +} + + diff --git a/crates/solidity/inputs/language/bindings/01-definitions.msgb b/crates/solidity/inputs/language/bindings/01-definitions.msgb new file mode 100644 index 0000000000..ccaf1e2126 --- /dev/null +++ b/crates/solidity/inputs/language/bindings/01-definitions.msgb @@ -0,0 +1,807 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Contracts +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@contract [ContractDefinition @name name: [Identifier]] { + node @contract.lexical_scope + node @contract.extensions + node @contract.def + node @contract.members + node @contract.ns + node @contract.modifiers + node @contract.instance + + 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 + + ; Instance scope can also see members and our namespace definitions + edge @contract.instance -> @contract.members + edge @contract.instance -> @contract.ns + + let @contract.enclosing_def = @contract.def + + ;; External "instance" scope access: either member access through a variable + ;; of the contract's type, or through calling (which happens on `new` + ;; invocations or casting). These should access only externally accessible + ;; members, such as functions and public variables. + node member + attr (member) pop_symbol = "." + edge member -> @contract.instance + + node type_def + attr (type_def) pop_symbol = "@typeof" + edge @contract.def -> type_def + edge type_def -> member + + node call + attr (call) pop_symbol = "()" + edge @contract.def -> call + edge call -> member + + ;; "namespace" scope access + node ns_member + attr (ns_member) pop_symbol = "." + edge @contract.def -> ns_member + edge ns_member -> @contract.ns + + ; Finally there's an @instance guarded path used by derived contracts to + ; access instance accessible members + node instance + attr (instance) pop_symbol = "@instance" + edge @contract.def -> instance + edge instance -> @contract.instance + + ; "this" keyword is available in our lexical scope and can access any + ; externally available member + node this + attr (this) pop_symbol = "this" + edge @contract.lexical_scope -> this + edge this -> member + + ;; Modifiers are available as a contract type members through a special '@modifier' guard + node modifier + attr (modifier) pop_symbol = "@modifier" + edge @contract.ns -> modifier + edge modifier -> @contract.modifiers + + ; There may be attached functions to our type. For the general case of + ; variables of our type, that's already handled via normal lexical scope + ; resolution. But for casting/`new` invocations that we resolve through the + ; `()` guard above, we need to explicitly jump to the extension scope from + ; here to attempt resolving the attached function. We cannot jump back to the + ; parent scope because that would create a cycle in the graph. + node push_typeof + 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 -> hook + + if (version-matches "< 0.5.0") { + ; For Solidity < 0.5.0 `this` also acts like an `address` + node address_ref + attr (address_ref) push_symbol = "%address" + node address_typeof + attr (address_typeof) push_symbol = "@typeof" + edge this -> address_typeof + edge address_typeof -> address_ref + edge address_ref -> @contract.lexical_scope + } + + ; This is the connection point to resolve attached functions by `using for *` + node @contract.star_extension + attr (@contract.star_extension) push_symbol = "@*" + + if (version-matches "< 0.7.0") { + ; 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.lexical_scope + } + + ; Path to resolve the built-in type for type() expressions + node type + attr (type) pop_symbol = "@type" + node type_contract_type + attr (type_contract_type) push_symbol = "%ContractTypeType" + edge @contract.def -> type + edge type -> type_contract_type + edge type_contract_type -> @contract.parent_scope + + ; The following defines the connection nodes the resolution algorithm uses + ; *only when setting a compilation context/target*. + + ; This attribute defines the sink of edges added from base contracts when + ; setting this contract as the compilation context, and should provide access + ; to anything that can be reached through `super`. The instance scope is a bit + ; too broad, but `.members` is too narrow as it doesn't allow navigation to + ; parent contracts (and from the base we need to be able to reach all + ; contracts in the hierarchy). + attr (@contract.def) export_node = @contract.instance + + ; This node will eventually connect to the contract's members being compiled + ; and grants access to definitions in that contract and all its parents + ; (recursively). It only makes sense if `super` is defined (ie. if we have + ; parents), but we define it here to be able to use it in the declaration of + ; import nodes. This is the dual of the export_node above. + node @contract.super_import + attr (@contract.super_import) pop_symbol = "." + + ; This defines the source side of edges added to base contracts when setting + ; a contract as compilation context; this allows this contract (a base) to + ; access virtual methods in any sub-contract defined in the hierarchy (both + ; with and without `super`, hence the two connection points). + attr (@contract.def) import_nodes = [@contract.lexical_scope, @contract.super_import] +} + +@contract [ContractDefinition @specifier [InheritanceSpecifier]] { + ; The `.heir` scoped variable allows the rules for `InheritanceSpecifier` + ; 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. + + ; `super_scope` is where we hook all references to our parent contracts + node @contract.super_scope + + ; Define "super" in the lexical scope + node @contract.super + attr (@contract.super) pop_symbol = "super" + edge @contract.lexical_scope -> @contract.super + + ; This connects `super` to exported scopes from all contracts in the hierarchy + ; when setting a contract compilation target (see more detailed description + ; above on the definition of the `super_import` node). + edge @contract.super -> @contract.super_import + + ; Then connect it through an `@instance` guard to the parent contracts through + ; `super_scope`. This allows "instance"-like access to members of parents + ; through `super`. + node super_instance + attr (super_instance) push_symbol = "@instance" + edge @contract.super_import -> super_instance + edge super_instance -> @contract.super_scope +} + +@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes + [InheritanceType @type_name [IdentifierPath]] +]]] { + ;; The base contract defs are directly accesible through our super scope + edge @contract.super_scope -> @type_name.push_begin +} + +; Pure definitions that cannot contain expressions +@contract [ContractDefinition [ContractMembers + [ContractMember @member ( + [EnumDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + edge @member.lexical_scope -> @contract.lexical_scope +} + +; Definitions that can contain expressions need two scopes: +; - normal lexical scope for resolving types +; - extended scope (extended by using directives) for resolving expressions +@contract [ContractDefinition [ContractMembers + [ContractMember @member ( + [FunctionDefinition] + | [ConstructorDefinition] + | [ModifierDefinition] + | [FallbackFunctionDefinition] + | [ReceiveFunctionDefinition] + | [UnnamedFunctionDefinition] + | [StateVariableDefinition] + )] +]] { + edge @member.lexical_scope -> @contract.lexical_scope +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @using [UsingDirective]] +]] { + ; Hook the using definition in the extensions scope + edge @contract.extensions -> @using.def +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @member ( + [EnumDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + ; These definition go into the "namespace" scope and are accessible externally + ; via qualified naming (eg. `Contract.MyStruct`) + edge @contract.ns -> @member.def +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @state_var [StateVariableDefinition]] +]] { + ; State variables are available to derived contracts. + ; TODO: this also exposes private state variables to derived contracts, but we + ; can't easily filter them because we don't have negative assertions in our + ; query language (we would need to modify this query for anything *not* + ; containing a `PrivateKeyword` node) + edge @contract.instance -> @state_var.def +} + +;; Public state variables are also exposed as external member functions +@contract [ContractDefinition [ContractMembers + [ContractMember @state_var [StateVariableDefinition + [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] + ]] +]] { + edge @contract.members -> @state_var.def +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @function [FunctionDefinition]] +]] { + ;; Contract functions are also accessible for an instance of the contract + edge @contract.members -> @function.def + + ;; This may prioritize this definition (when there are multiple options) + ;; according to the C3 linerisation ordering + attr (@function.def) tag = "c3" + attr (@function.def) parents = [@contract.def] +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @function [FunctionDefinition + [FunctionAttributes [FunctionAttribute ([ExternalKeyword] | [PublicKeyword])]] + ]] +]] { + ; Public or external functions are also accessible through the contract type + ; (to retrieve their `.selector` for example) + edge @contract.ns -> @function.def +} + +@contract [ContractDefinition members: [ContractMembers + [ContractMember @modifier [ModifierDefinition]] +]] { + ; Modifiers live in their own special scope + edge @contract.modifiers -> @modifier.def + + ;; This may prioritize this definition (when there are multiple options) + ;; according to the C3 linerisation ordering + attr (@modifier.def) tag = "c3" + attr (@modifier.def) parents = [@contract.def] +} + +@contract [ContractDefinition [ContractMembers [ContractMember + [UsingDirective [UsingTarget [Asterisk]]] +]]] { + ; 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.lexical_scope +} + +; This applies to both state variables and function definitions +@override [OverrideSpecifier [OverridePathsDeclaration [OverridePaths + @base_ident [IdentifierPath] +]]] { + ;; Resolve overriden bases when listed in the function or modifiers modifiers + edge @base_ident.push_end -> @override.parent_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Interfaces +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@interface [InterfaceDefinition @name name: [Identifier]] { + node @interface.lexical_scope + node @interface.def + node @interface.members + node @interface.ns + node @interface.instance + + attr (@interface.def) node_definition = @name + attr (@interface.def) definiens_node = @interface + + edge @interface.lexical_scope -> @interface.instance + + ; The extensions node is required for the inheritance rules, but not used in interfaces + let @interface.extensions = (node) + + edge @interface.instance -> @interface.members + edge @interface.instance -> @interface.ns + + ;; External "instance" like access path, to access members of a variable of + ;; the interface's type or through a casting call. + node member + attr (member) pop_symbol = "." + edge member -> @interface.instance + + node typeof + attr (typeof) pop_symbol = "@typeof" + edge @interface.def -> typeof + edge typeof -> member + + node call + attr (call) pop_symbol = "()" + edge @interface.def -> call + edge call -> member + + ; From a call we may need to resolve using the extensions scope, in case there's + ; a `using` directive on our type. This path ends up jumping to scope just to + ; handle that case. + node push_typeof + attr (push_typeof) push_symbol = "@typeof" + node push_name + attr (push_name) push_symbol = (source-text @name) + edge call -> push_typeof + edge push_typeof -> push_name + node hook + attr (hook) extension_hook + edge push_name -> hook + ; edge push_name -> JUMP_TO_SCOPE_NODE + + ;; "namespace" like access path + node ns_member + attr (ns_member) pop_symbol = "." + edge @interface.def -> ns_member + edge ns_member -> @interface.ns + + ; Finally there's guarded `@instance` path used by derived contracts to access + ; instance accessible members + node instance + attr (instance) pop_symbol = "@instance" + edge @interface.def -> instance + edge instance -> @interface.instance + + ; Path to resolve the built-in type for type() expressions + node type + attr (type) pop_symbol = "@type" + node type_interface_type + attr (type_interface_type) push_symbol = "%InterfaceTypeType" + edge @interface.def -> type + edge type -> type_interface_type + edge type_interface_type -> @interface.parent_scope +} + +@interface [InterfaceDefinition @specifier [InheritanceSpecifier]] { + let @specifier.heir = @interface + attr (@interface.def) parents = @specifier.parent_refs +} + +@interface [InterfaceDefinition [InterfaceMembers + [ContractMember @member ( + [EnumDefinition] + | [FunctionDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + edge @member.lexical_scope -> @interface.lexical_scope + edge @interface.ns -> @member.def +} + +;; Allow references (eg. variables of the interface type) to the interface to +;; access functions +@interface [InterfaceDefinition members: [InterfaceMembers + item: [ContractMember @function variant: [FunctionDefinition]] +]] { + edge @interface.members -> @function.def +} + +[InterfaceDefinition [InterfaceMembers [ContractMember @using [UsingDirective]]]] { + ; using directives are not allowed in interfaces, but the grammar allows them + ; so we need to create an artificial node here to connect to created edges from + ; the instance nodes + let @using.lexical_scope = (node) +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Common inheritance rules (apply to contracts and interfaces) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@specifier [InheritanceSpecifier [InheritanceTypes + [InheritanceType @type_name [IdentifierPath]] +]] { + ;; This should point to the enclosing contract or interface definition + let heir = @specifier.heir + + ;; Resolve base names through the parent scope of our heir (contract or + ;; interface), aka the source unit + edge @type_name.push_end -> heir.parent_scope + + ; Access instance members of the inherited contract/interface, from the + ; instance scope of the inheriting contract/interface + node instance + attr (instance) push_symbol = "@instance" + edge heir.instance -> instance + edge instance -> @type_name.push_begin + + ; Base members can also be accessed (from the instance scope) qualified with + ; the base name (eg. `Base.something`) + node member_pop + attr (member_pop) pop_symbol = "." + edge heir.instance -> @type_name.pop_begin + edge @type_name.pop_end -> member_pop + edge member_pop -> instance + + ; Base namespace-like members (ie. enums, structs, etc) are also accessible as + ; our own namespace members + node ns_member + attr (ns_member) push_symbol = "." + edge heir.ns -> ns_member + edge ns_member -> @type_name.push_begin +} + +;; The next couple of rules setup a `.parent_refs` attribute to use in the +;; resolution algorithm to perform linearisation of a contract hierarchy. + +;; NOTE: we use anchors here to prevent the query engine from returning all the +;; sublists of possible parents +@specifier [InheritanceSpecifier [InheritanceTypes . @parents [_]+ .]] { + var parent_refs = [] + for parent in @parents { + if (eq (node-type parent) "InheritanceType") { + ;; this is intentionally reversed because of how Solidity linearised the contract bases + set parent_refs = (concat [parent.ref] parent_refs) + } + } + let @specifier.parent_refs = parent_refs +} + +@parent [InheritanceType @type_name [IdentifierPath]] { + let @parent.ref = @type_name.push_begin +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Libraries +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@library [LibraryDefinition @name name: [Identifier]] { + node @library.lexical_scope + node @library.extensions + node @library.def + node @library.ns + node @library.modifiers + + 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 + edge member -> @library.ns + + ; Access to modifiers is guarded by a @modifier symbol + node modifier + attr (modifier) pop_symbol = "@modifier" + edge @library.ns -> modifier + edge modifier -> @library.modifiers + + ; Path to resolve the built-in type for type() expressions (same as contracts) + node type + attr (type) pop_symbol = "@type" + node type_library_type + attr (type_library_type) push_symbol = "%ContractTypeType" + edge @library.def -> type + edge type -> type_library_type + edge type_library_type -> @library.lexical_scope + + ; This is the connection point to resolve attached functions by `using for *` + node @library.star_extension + attr (@library.star_extension) push_symbol = "@*" +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @member ( + [EnumDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + edge @member.lexical_scope -> @library.lexical_scope + edge @library.ns -> @member.def +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @member ( + [FunctionDefinition] + | [StateVariableDefinition [StateVariableAttributes [StateVariableAttribute [ConstantKeyword]]]] + )] +]] { + edge @member.lexical_scope -> @library.lexical_scope + edge @library.ns -> @member.def +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @modifier [ModifierDefinition]] +]] { + edge @library.modifiers -> @modifier.def + edge @modifier.lexical_scope -> @library.lexical_scope +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @using [UsingDirective]] +]] { + ; Expose the using directive from the extensions scope + edge @library.extensions -> @using.def +} + +@library [LibraryDefinition [LibraryMembers [ContractMember + [UsingDirective [UsingTarget [Asterisk]]] +]]] { + ; 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.lexical_scope +} + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function, parameter declarations and modifiers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@param [Parameter @type_name [TypeName]] { + node @param.lexical_scope + node @param.def + + edge @type_name.type_ref -> @param.lexical_scope + + node @param.typeof + attr (@param.typeof) push_symbol = "@typeof" + edge @param.typeof -> @type_name.output +} + +@param [Parameter @name [Identifier]] { + attr (@param.def) node_definition = @name + attr (@param.def) definiens_node = @param + + edge @param.def -> @param.typeof +} + +@params [ParametersDeclaration] { + node @params.lexical_scope + node @params.defs + + ;; This scope can be used to resolve named argument calls + node @params.names + attr (@params.names) pop_symbol = "@param_names" + edge @params.names -> @params.defs +} + +@params [ParametersDeclaration [Parameters @param item: [Parameter]]] { + edge @param.lexical_scope -> @params.lexical_scope + edge @params.defs -> @param.def +} + +@function [FunctionDefinition @attrs [FunctionAttributes]] { + var type_symbol = "%Function" + scan (source-text @attrs) { + "\\b(public|external)\\b" { + set type_symbol = "%ExternalFunction" + } + } + + node @function.lexical_scope + node @function.def + + ; this path from the function definition to the scope allows attaching + ; functions to this function's type + node typeof + attr (typeof) push_symbol = "@typeof" + node type_function + attr (type_function) push_symbol = type_symbol + edge @function.def -> typeof + edge typeof -> type_function + edge type_function -> @function.lexical_scope +} + +@function [FunctionDefinition name: [FunctionName @name [Identifier]]] { + attr (@function.def) node_definition = @name + attr (@function.def) definiens_node = @function +} + +@function [FunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @function.lexical_scope + + ;; Input parameters are available in the function scope + edge @function.lexical_scope -> @params.defs + ;; ... and shadow other declarations + attr (@function.lexical_scope -> @params.defs) precedence = 1 + + ;; Connect to paramaters for named argument resolution + edge @function.def -> @params.names +} + +@function [FunctionDefinition returns: [ReturnsDeclaration + @return_params [ParametersDeclaration] +]] { + edge @return_params.lexical_scope -> @function.lexical_scope + + ;; Return parameters are available in the function scope + edge @function.lexical_scope -> @return_params.defs + ;; ... and shadow other declarations + attr (@function.lexical_scope -> @return_params.defs) precedence = 1 +} + +;; Only functions that return a single value have an actual return type +;; since tuples are not actual types in Solidity +@function [FunctionDefinition returns: [ReturnsDeclaration + [ParametersDeclaration [Parameters . @param [Parameter] .]] +]] { + node call + attr (call) pop_symbol = "()" + + edge @function.def -> call + edge call -> @param.typeof +} + +;; Connect the function body's block lexical scope to the function +@function [FunctionDefinition [FunctionBody @block [Block]]] { + edge @block.lexical_scope -> @function.lexical_scope +} + +@function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute + @modifier [ModifierInvocation] +]]] { + edge @modifier.lexical_scope -> @function.lexical_scope +} + +@modifier [ModifierInvocation @name [IdentifierPath]] { + node @modifier.lexical_scope + + node modifier + attr (modifier) push_symbol = "@modifier" + + edge @name.push_end -> modifier + edge modifier -> @modifier.lexical_scope + + ; This allows resolving @name in the more general scope in constructors (since + ; calling a parent constructor is parsed as a modifier invocation) + let @modifier.identifier = @name.push_end +} + +@modifier [ModifierInvocation @args [ArgumentsDeclaration]] { + edge @args.lexical_scope -> @modifier.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; State Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@state_var [StateVariableDefinition] { + node @state_var.lexical_scope + node @state_var.def +} + +@state_var [StateVariableDefinition + @type_name type_name: [TypeName] + @name name: [Identifier] +] { + attr (@state_var.def) node_definition = @name + attr (@state_var.def) definiens_node = @state_var + + edge @type_name.type_ref -> @state_var.lexical_scope + + node @state_var.typeof + attr (@state_var.typeof) push_symbol = "@typeof" + + edge @state_var.def -> @state_var.typeof + edge @state_var.typeof -> @type_name.output +} + +@state_var [StateVariableDefinition + [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] +] { + ; Public state variables are used as functions when invoked from an external contract + node call + attr (call) pop_symbol = "()" + + ; In the general case using the getter can bind to the state variable's type + edge @state_var.def -> call + edge call -> @state_var.typeof + + ; Some complex types generate special getters (ie. arrays and mappings index + ; their contents, structs flatten most of their fields and return a tuple) + node getter + attr (getter) push_symbol = "@as_getter" + edge call -> getter + edge getter -> @state_var.typeof +} + +@state_var [StateVariableDefinition + [StateVariableDefinitionValue @value [Expression]] +] { + let @value.lexical_scope = @state_var.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Structure definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@struct [StructDefinition @name name: [Identifier]] { + node @struct.lexical_scope + node @struct.def + node @struct.members + + attr (@struct.def) node_definition = @name + attr (@struct.def) definiens_node = @struct + + ; Now connect normally to the struct members + node @struct.typeof + attr (@struct.typeof) pop_symbol = "@typeof" + node member + attr (member) pop_symbol = "." + edge @struct.def -> @struct.typeof + edge @struct.typeof -> member + edge member -> @struct.members + + ; Bind member names when using construction with named arguments + node param_names + attr (param_names) pop_symbol = "@param_names" + edge @struct.def -> param_names + edge param_names -> @struct.members + + ; Used as a function call (ie. casting), should bind to itself + node call + attr (call) pop_symbol = "()" + edge @struct.def -> call + edge call -> member +} + +@struct [StructDefinition [StructMembers + @member item: [StructMember @type_name [TypeName] @name name: [Identifier]] +]] { + node @member.def + attr (@member.def) node_definition = @name + attr (@member.def) definiens_node = @member + + edge @struct.members -> @member.def + + edge @type_name.type_ref -> @struct.lexical_scope + + node @member.typeof + attr (@member.typeof) push_symbol = "@typeof" + + edge @member.def -> @member.typeof + edge @member.typeof -> @type_name.output +} + +@struct [StructDefinition [StructMembers . @first_member [StructMember]]] { + ; As a public getter result, the value returned is a tuple with all our fields flattened + ; We only care about the first member for name binding, since tuples are not real types + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge @struct.typeof -> getter_call + edge getter_call -> @first_member.typeof +} diff --git a/crates/solidity/inputs/language/bindings/02-other-definitions.msgb b/crates/solidity/inputs/language/bindings/02-other-definitions.msgb new file mode 100644 index 0000000000..cacbf346ca --- /dev/null +++ b/crates/solidity/inputs/language/bindings/02-other-definitions.msgb @@ -0,0 +1,467 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Using directives +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@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 + ; target type + node @using.def + + ; This internal node connects the definition side of the clause to the target + ; for resolution, and allows handling the multiple cases of `using` syntax + ; easily + node @using.clause +} + +@using [UsingDirective [UsingClause @id_path [IdentifierPath]]] { + ; resolve the library to be used in the directive + 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 + ; need to verify that we're only using this path when resolving a function + ; access to the target type, not the target type itself + node dot_guard_pop + attr (dot_guard_pop) pop_symbol = "." + node dot_guard_push + attr (dot_guard_push) push_symbol = "." + + edge @using.clause -> dot_guard_pop + edge dot_guard_pop -> dot_guard_push + edge dot_guard_push -> @id_path.push_begin +} + +@using [UsingDirective [UsingClause [UsingDeconstruction + [UsingDeconstructionSymbols [UsingDeconstructionSymbol + @id_path [IdentifierPath] + ]] +]]] { + ; resolve the function to be used in the directive + edge @id_path.push_end -> @using.lexical_scope + + node dot + attr (dot) pop_symbol = "." + node last_identifier + attr (last_identifier) pop_symbol = (source-text @id_path.rightmost_identifier) + + edge @using.clause -> dot + edge dot -> last_identifier + edge last_identifier -> @id_path.push_begin +} + +@using [UsingDirective [UsingTarget @type_name [TypeName]]] { + ; pop the type symbols to connect to the attached function (via @using.clause) + node typeof + attr (typeof) pop_symbol = "@typeof" + node cast + attr (cast) pop_symbol = "()" + + ; We connect to all_pop_begin to be able to resolve both qualified and + ; unqualified instances of the target type + edge @using.def -> @type_name.all_pop_begin + edge @type_name.pop_end -> typeof + edge typeof -> @using.clause + edge @type_name.pop_end -> cast + edge cast -> @using.clause + + ; resolve the target type of the directive on the lexical scope + edge @type_name.type_ref -> @using.lexical_scope +} + +[ContractMember @using [UsingDirective [UsingTarget [Asterisk]]]] { + ; using X for * is only allowed inside contracts + node star + attr (star) pop_symbol = "@*" + edge @using.def -> star + edge star -> @using.clause +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Constructors +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@constructor [ConstructorDefinition] { + node @constructor.lexical_scope + node @constructor.def +} + +@constructor [ConstructorDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @constructor.lexical_scope + + ;; Input parameters are available in the constructor scope + edge @constructor.lexical_scope -> @params.defs + ;; ... and shadow other declarations + attr (@constructor.lexical_scope -> @params.defs) precedence = 1 + + ;; Connect to paramaters for named argument resolution + edge @constructor.def -> @params.names +} + +;; Connect the constructor body's block lexical scope to the constructor +@constructor [ConstructorDefinition @block [Block]] { + edge @block.lexical_scope -> @constructor.lexical_scope +} + +@constructor [ConstructorDefinition [ConstructorAttributes item: [ConstructorAttribute + @modifier [ModifierInvocation] +]]] { + edge @modifier.lexical_scope -> @constructor.lexical_scope + edge @modifier.identifier -> @constructor.lexical_scope +} + +@contract [ContractDefinition [ContractMembers [ContractMember + @constructor [ConstructorDefinition] +]]] { + ;; This link allows calling a constructor with the named parameters syntax + edge @contract.def -> @constructor.def +} + +;; Solidity < 0.5.0 constructors +;; They were declared as functions of the contract's name + +@contract [ContractDefinition + @contract_name [Identifier] + [ContractMembers [ContractMember [FunctionDefinition + [FunctionName @function_name [Identifier]] + @params [ParametersDeclaration] + ]]] +] { + if (version-matches "< 0.5.0") { + if (eq (source-text @contract_name) (source-text @function_name)) { + ; Connect to paramaters for named argument resolution + edge @contract.def -> @params.names + } + } +} + +[ContractDefinition + @contract_name [Identifier] + [ContractMembers [ContractMember @function [FunctionDefinition + [FunctionName @function_name [Identifier]] + [FunctionAttributes [FunctionAttribute @modifier [ModifierInvocation]]] + ]]] +] { + if (version-matches "< 0.5.0") { + if (eq (source-text @contract_name) (source-text @function_name)) { + ; Parent constructor calls are parsed as modifier invocations + edge @modifier.identifier -> @function.lexical_scope + } + } +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function modifiers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@modifier [ModifierDefinition] { + node @modifier.def + node @modifier.lexical_scope +} + +@modifier [ModifierDefinition + @name name: [Identifier] +] { + attr (@modifier.def) node_definition = @name + attr (@modifier.def) definiens_node = @modifier +} + +@modifier [ModifierDefinition + body: [FunctionBody @body [Block]] +] { + 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. + node placeholder_pop + attr (placeholder_pop) pop_symbol = "_" + node placeholder_ref + attr (placeholder_ref) push_symbol = "%placeholder" + + edge @body.lexical_scope -> placeholder_pop + edge placeholder_pop -> placeholder_ref + edge placeholder_ref -> @modifier.lexical_scope +} + +@modifier [ModifierDefinition @params [ParametersDeclaration]] { + edge @params.lexical_scope -> @modifier.lexical_scope + + ;; Input parameters are available in the modifier scope + edge @modifier.lexical_scope -> @params.defs + attr (@modifier.lexical_scope -> @params.defs) precedence = 1 +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Fallback and receive functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@fallback [FallbackFunctionDefinition] { + node @fallback.lexical_scope +} + +@fallback [FallbackFunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @fallback.lexical_scope + + ;; Input parameters are available in the fallback function scope + edge @fallback.lexical_scope -> @params.defs + attr (@fallback.lexical_scope -> @params.defs) precedence = 1 +} + +@fallback [FallbackFunctionDefinition returns: [ReturnsDeclaration + @return_params [ParametersDeclaration] +]] { + edge @return_params.lexical_scope -> @fallback.lexical_scope + + ;; Return parameters are available in the fallback function scope + 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.lexical_scope +} + +@fallback [FallbackFunctionDefinition [FallbackFunctionAttributes + item: [FallbackFunctionAttribute @modifier [ModifierInvocation]] +]] { + edge @modifier.lexical_scope -> @fallback.lexical_scope +} + +@receive [ReceiveFunctionDefinition] { + node @receive.lexical_scope +} + +@receive [ReceiveFunctionDefinition [FunctionBody @block [Block]]] { + edge @block.lexical_scope -> @receive.lexical_scope +} + +@receive [ReceiveFunctionDefinition [ReceiveFunctionAttributes + item: [ReceiveFunctionAttribute @modifier [ModifierInvocation]] +]] { + edge @modifier.lexical_scope -> @receive.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Unnamed functions (deprecated) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@unnamed_function [UnnamedFunctionDefinition] { + node @unnamed_function.lexical_scope +} + +@unnamed_function [UnnamedFunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @unnamed_function.lexical_scope + + 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.lexical_scope +} + +@unnamed_function [UnnamedFunctionDefinition + [UnnamedFunctionAttributes [UnnamedFunctionAttribute @modifier [ModifierInvocation]]] +] { + edge @modifier.lexical_scope -> @unnamed_function.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Enum definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@enum [EnumDefinition @name name: [Identifier]] { + node @enum.lexical_scope + node @enum.def + node @enum.members + + attr (@enum.def) node_definition = @name + attr (@enum.def) definiens_node = @enum + + node member + attr (member) pop_symbol = "." + + edge @enum.def -> member + edge member -> @enum.members + + ; Path to resolve the built-in type for enums (which is the same as for integer types) + node type + attr (type) pop_symbol = "@type" + node type_enum_type + attr (type_enum_type) push_symbol = "%IntTypeType" + edge @enum.def -> type + edge type -> type_enum_type + edge type_enum_type -> @enum.lexical_scope +} + +@enum [EnumDefinition + members: [EnumMembers @item [Identifier]] +] { + node def + attr (def) node_definition = @item + attr (def) definiens_node = @item + + edge @enum.members -> def +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Event definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@event [EventDefinition @name name: [Identifier]] { + node @event.lexical_scope + node @event.def + + attr (@event.def) node_definition = @name + attr (@event.def) definiens_node = @event + + node @event.params + attr (@event.params) pop_symbol = "@param_names" + edge @event.def -> @event.params +} + +@event [EventDefinition [EventParametersDeclaration [EventParameters + [EventParameter @type_name type_name: [TypeName]] +]]] { + edge @type_name.type_ref -> @event.lexical_scope +} + +@event [EventDefinition [EventParametersDeclaration [EventParameters + @param [EventParameter + @name name: [Identifier] + ] +]]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @param + + edge @event.params -> def +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Error definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@error [ErrorDefinition @name name: [Identifier]] { + node @error.lexical_scope + node @error.def + + attr (@error.def) node_definition = @name + attr (@error.def) definiens_node = @error + + node @error.params + attr (@error.params) pop_symbol = "@param_names" + edge @error.def -> @error.params + + ; Bind to built-in errorType for accessing built-in member `.selector` + node typeof + attr (typeof) push_symbol = "@typeof" + node error_type + attr (error_type) push_symbol = "%ErrorType" + edge @error.def -> typeof + edge typeof -> error_type + edge error_type -> @error.lexical_scope +} + +@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters + [ErrorParameter @type_name type_name: [TypeName]] +]]] { + edge @type_name.type_ref -> @error.lexical_scope +} + +@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters + @param [ErrorParameter + @name name: [Identifier] + ] +]]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @param + + edge @error.params -> def +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Other named definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@constant [ConstantDefinition] { + node @constant.lexical_scope + node @constant.def +} + +@constant [ConstantDefinition + @type_name type_name: [TypeName] + @name name: [Identifier] +] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @constant + + edge @constant.def -> def + + edge @type_name.type_ref -> @constant.lexical_scope +} + +@user_type [UserDefinedValueTypeDefinition @name [Identifier] @value_type [ElementaryType]] { + node @user_type.lexical_scope + node @user_type.def + + attr (@user_type.def) node_definition = @name + attr (@user_type.def) definiens_node = @user_type + + ; Provide member resolution through the built-in `%userTypeType` + ; Because the built-in is defined as a struct, we need to push an extra `@typeof` + node member_guard + attr (member_guard) pop_symbol = "." + node member + attr (member) push_symbol = "." + node typeof + attr (typeof) push_symbol = "@typeof" + node user_type_type + attr (user_type_type) push_symbol = "%UserDefinedValueType" + + edge @user_type.def -> member_guard + edge member_guard -> member + edge member -> typeof + edge typeof -> user_type_type + edge user_type_type -> @user_type.lexical_scope + + ; Hard-code built-in functions `wrap` and `unwrap` in order to be able to + ; resolve their return types + node wrap + attr (wrap) pop_symbol = "wrap" + node wrap_call + attr (wrap_call) pop_symbol = "()" + node wrap_typeof + attr (wrap_typeof) push_symbol = "@typeof" + + edge member_guard -> wrap + edge wrap -> wrap_call + edge wrap_call -> wrap_typeof + edge wrap_typeof -> @value_type.ref + edge @value_type.ref -> @user_type.lexical_scope + + node unwrap + attr (unwrap) pop_symbol = "unwrap" + node unwrap_call + attr (unwrap_call) pop_symbol = "()" + node unwrap_typeof + attr (unwrap_typeof) push_symbol = "@typeof" + node type_ref + attr (type_ref) push_symbol = (source-text @name) + + edge member_guard -> unwrap + edge unwrap -> unwrap_call + edge unwrap_call -> unwrap_typeof + edge unwrap_typeof -> type_ref + edge type_ref -> @user_type.lexical_scope +} diff --git a/crates/solidity/inputs/language/bindings/03-types.msgb b/crates/solidity/inputs/language/bindings/03-types.msgb new file mode 100644 index 0000000000..dca3acedf4 --- /dev/null +++ b/crates/solidity/inputs/language/bindings/03-types.msgb @@ -0,0 +1,404 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Identifier Paths (aka. references to custom types) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The identifier path builds two graph paths: +;; +;; - From right to left, pushing the identifiers and acting as a "reference". +;; This path begins at @id_path.push_begin and ends at @id_path.push_end. +;; +;; - From left to right, popping the identifiers (used as a definition sink in +;; using directives). This path begins at @id_path.pop_begin and ends at +;; @id_path.pop_end. +;; +;; NOTE: most of the time, and unless this identifier path is the target of a +;; using directive this second path will not be used and will form a +;; disconnected graph component. We currently have no way of determining when +;; this path is necessary, so we always construct it. +;; +;; Additionally the IdentifierPath defines another scoped variable +;; @id_path.rightmost_identifier which corresponds to the identifier in the last +;; position in the path, from left to right. This is used in the using directive +;; rules to be able to pop the name of the attached function. + +@id_path [IdentifierPath] { + ; This node connects to all parts of the path, for popping. This allows to + ; connect at any point of the path. Useful for `using` directives when the + ; target type is fully qualified but we want to resolve for the unqualified + ; name. + node @id_path.all_pop_begin +} + +@id_path [IdentifierPath @name [Identifier]] { + node @name.ref + attr (@name.ref) node_reference = @name + attr (@name.ref) parents = [@id_path.enclosing_def] + + node @name.pop + attr (@name.pop) pop_symbol = (source-text @name) + + edge @id_path.all_pop_begin -> @name.pop +} + +@id_path [IdentifierPath @name [Identifier] .] { + let @id_path.rightmost_identifier = @name + + let @id_path.push_begin = @name.ref + let @id_path.pop_end = @name.pop +} + +[IdentifierPath @left_name [Identifier] . [Period] . @right_name [Identifier]] { + node ref_member + attr (ref_member) push_symbol = "." + + edge @right_name.ref -> ref_member + edge ref_member -> @left_name.ref + + node pop_member + attr (pop_member) pop_symbol = "." + + edge @left_name.pop -> pop_member + edge pop_member -> @right_name.pop +} + +@id_path [IdentifierPath . @name [Identifier]] { + let @id_path.push_end = @name.ref + let @id_path.pop_begin = @name.pop +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Type names +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TypeName nodes should define these scoped variables: +;; +;; - @type_name.type_ref represents the node in the graph where we're ready to +;; resolve the type, and thus should generally be connected to a (lexical) +;; scope node (source node, outside edges connect *from* here). +;; +;; - @type_name.output represents the other end of the type and corresponds to a +;; state where the type has already been resolved so we can, for example +;; resolve its members (sink node, outside edges connect *to* here). +;; +;; - @type_name.pop_begin, @type_name.pop_end are used in a definition context, +;; ie. when we need to pop the type name symbol(s) from the symbol stack. +;; Additionally, @type_name.all_pop_begin links to each symbol in a typename +;; (ie. in an identifier path typename), which allows referring to a type both +;; qualified and unqualified. + +@type_name [TypeName @elementary [ElementaryType]] { + let @type_name.type_ref = @elementary.ref + let @type_name.output = @elementary.ref + let @type_name.pop_begin = @elementary.pop + let @type_name.pop_end = @elementary.pop + let @type_name.all_pop_begin = @elementary.pop +} + +@type_name [TypeName @id_path [IdentifierPath]] { + ;; For an identifier path used as a type, the left-most element is the one + ;; that connects to the parent lexical scope, because the name resolution + ;; starts at the left of the identifier. + let @type_name.type_ref = @id_path.push_end + + ;; Conversely, the complete type is found at the right-most name, and that's + ;; where users of this type should link to (eg. a variable declaration). + let @type_name.output = @id_path.push_begin + + let @type_name.pop_begin = @id_path.pop_begin + let @type_name.pop_end = @id_path.pop_end + let @type_name.all_pop_begin = @id_path.all_pop_begin +} + +@type_name [TypeName @type_variant ([ArrayTypeName] | [FunctionType])] { + let @type_name.type_ref = @type_variant.lexical_scope + let @type_name.output = @type_variant.output + let @type_name.pop_begin = @type_variant.pop_begin + let @type_name.pop_end = @type_variant.pop_end + let @type_name.all_pop_begin = @type_variant.pop_begin +} + +@type_name [TypeName @mapping [MappingType]] { + let @type_name.type_ref = @mapping.lexical_scope + let @type_name.output = @mapping.output + let @type_name.pop_begin = @mapping.pop_begin + let @type_name.pop_end = @mapping.pop_end + let @type_name.all_pop_begin = @mapping.pop_begin +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Elementary types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@elementary [ElementaryType] { + node @elementary.ref + attr (@elementary.ref) type = "push_symbol" + attr (@elementary.ref) source_node = @elementary, symbol = @elementary.symbol + + node @elementary.pop + attr (@elementary.pop) pop_symbol = @elementary.symbol + + ; These variables are a bit redundant, but necessary to easily use elementary + ; types as mapping keys + let @elementary.pop_begin = @elementary.pop + let @elementary.pop_end = @elementary.pop + let @elementary.all_pop_begin = @elementary.pop + + let @elementary.push_begin = @elementary.ref + let @elementary.push_end = @elementary.ref +} + +@elementary [ElementaryType [AddressType]] { + let @elementary.symbol = "%address" +} + +@elementary [ElementaryType [BoolKeyword]] { + let @elementary.symbol = "%bool" +} + +@elementary [ElementaryType [ByteKeyword]] { + let @elementary.symbol = "%byte" +} + +@elementary [ElementaryType @keyword [BytesKeyword]] { + let @elementary.symbol = (format "%{}" (source-text @keyword)) +} + +@elementary [ElementaryType [StringKeyword]] { + let @elementary.symbol = "%string" +} + +@elementary [ElementaryType @keyword [IntKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "int") { + let @elementary.symbol = "%int256" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + +@elementary [ElementaryType @keyword [UintKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "uint") { + let @elementary.symbol = "%uint256" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + +@elementary [ElementaryType @keyword [FixedKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "fixed") { + let @elementary.symbol = "%fixed128x18" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + +@elementary [ElementaryType @keyword [UfixedKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "ufixed") { + let @elementary.symbol = "%ufixed128x18" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Mappings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@mapping [MappingType + [MappingKey [MappingKeyType @key_type ([IdentifierPath] | [ElementaryType])]] + [MappingValue @value_type [TypeName]] +] { + node @mapping.lexical_scope + node @mapping.output + + ; Define the pushing path of the mapping type + ; ValueType <- top of the symbol stack + ; KeyType + ; %mapping <- bottom of the symbol stack + node mapping + attr (mapping) push_symbol = "%Mapping" + edge @mapping.output -> mapping + edge mapping -> @key_type.push_begin + edge @key_type.push_end -> @value_type.output + + ; Both key and value types need to be resolved + edge @value_type.type_ref -> @mapping.lexical_scope + edge @key_type.push_end -> @mapping.lexical_scope + + ; The mapping's type exposes the `[]` operator that returns the value type. + + node typeof_input + attr (typeof_input) pop_symbol = "@typeof" + edge @mapping.output -> typeof_input + + node typeof_output + attr (typeof_output) push_symbol = "@typeof" + edge typeof_output -> @value_type.output + + node index + attr (index) pop_symbol = "[]" + edge typeof_input -> index + edge index -> typeof_output + + ; Special case for mapping public state variables: they can be called + ; like a function with a key, and it's effectively the same as indexing it. + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge typeof_input -> getter_call + edge getter_call -> typeof_output + + ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only + ; This is the reverse of the pushing path above (to the `.output` node) + node pop_mapping + attr (pop_mapping) pop_symbol = "%Mapping" + + let @mapping.pop_begin = @value_type.pop_begin + edge @value_type.pop_end -> @key_type.pop_begin + edge @key_type.pop_end -> pop_mapping + let @mapping.pop_end = pop_mapping +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Arrays types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@array [ArrayTypeName] { + node @array.lexical_scope + node @array.output +} + +@array [ArrayTypeName [TypeName] index: [Expression]] { + let @array.type_symbol = "%FixedArray" +} + +@array [ArrayTypeName [OpenBracket] . [CloseBracket]] { + let @array.type_symbol = "%Array" +} + +@array [ArrayTypeName @type_name [TypeName]] { + ; Define the pushing path of the array type + ; ValueType <- top of the symbol stack + ; %array / %arrayFixed <- bottom of the symbol stack + node array + attr (array) push_symbol = @array.type_symbol + edge @array.output -> array + edge array -> @type_name.output + + ; Resolve the value type itself + edge @type_name.type_ref -> @array.lexical_scope + ; And also the "type erased" array type so we can resolve built-in members + edge array -> @array.lexical_scope + + ; Define the path to resolve index access (aka the `[]` operator) + + node typeof_input + attr (typeof_input) pop_symbol = "@typeof" + edge @array.output -> typeof_input + + node typeof_output + attr (typeof_output) push_symbol = "@typeof" + edge typeof_output -> @type_name.output + + node index + attr (index) pop_symbol = "[]" + edge typeof_input -> index + edge index -> typeof_output + + ; Special case for public state variables of type array: they can be called + ; like a function with an index, and it's effectively the same as indexing the + ; array. + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge typeof_input -> getter_call + edge getter_call -> typeof_output + + ; Define the special `.push()` built-in that returns the element type (for Solidity >= 0.6.0) + if (version-matches ">= 0.6.0") { + node built_in_member + attr (built_in_member) pop_symbol = "." + node push_built_in + attr (push_built_in) pop_symbol = "push" + node built_in_call + attr (built_in_call) pop_symbol = "()" + + edge typeof_input -> built_in_member + edge built_in_member -> push_built_in + edge push_built_in -> built_in_call + edge built_in_call -> typeof_output + } + + ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only + ; This is essentially the reverse of the second path above + node pop_array + attr (pop_array) pop_symbol = @array.type_symbol + + let @array.pop_begin = @type_name.pop_begin + edge @type_name.pop_end -> pop_array + let @array.pop_end = pop_array +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@ftype [FunctionType @attrs [FunctionTypeAttributes]] { + ; Compute the built-in type of the function + ; %functionExternal provides access to .selector and .address + var type_symbol = "%Function" + scan (source-text @attrs) { + "external" { + set type_symbol = "%ExternalFunction" + } + } + + node @ftype.lexical_scope + node @ftype.output + + ; This path pushes the function type to the symbol stack + ; TODO: add parameter and return types to distinguish between different function types + node function_type + attr (function_type) push_symbol = type_symbol + + edge @ftype.output -> function_type + edge function_type -> @ftype.lexical_scope + + ; the pop path for the using directive + node pop_function_type + attr (pop_function_type) pop_symbol = type_symbol + + let @ftype.pop_begin = pop_function_type + let @ftype.pop_end = pop_function_type +} + +@ftype [FunctionType @params [ParametersDeclaration]] { + edge @params.lexical_scope -> @ftype.lexical_scope +} + +@ftype [FunctionType [ReturnsDeclaration @return_params [ParametersDeclaration]]] { + edge @return_params.lexical_scope -> @ftype.lexical_scope +} + +@ftype [FunctionType [ReturnsDeclaration + [ParametersDeclaration [Parameters . @param [Parameter] .]] +]] { + ; Variables of a function type type can be "called" and resolve to the type of + ; the return parameter. This is only valid if the function returns a single + ; value. + node typeof + attr (typeof) pop_symbol = "@typeof" + + node call + attr (call) pop_symbol = "()" + + edge @ftype.output -> typeof + edge typeof -> call + edge call -> @param.typeof +} diff --git a/crates/solidity/inputs/language/bindings/04-statements.msgb b/crates/solidity/inputs/language/bindings/04-statements.msgb new file mode 100644 index 0000000000..7da17f3dfe --- /dev/null +++ b/crates/solidity/inputs/language/bindings/04-statements.msgb @@ -0,0 +1,354 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Blocks and generic statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@block [Block] { + node @block.lexical_scope + node @block.defs +} + +;; The first statement in a block +@block [Block [Statements . @stmt [Statement]]] { + if (version-matches ">= 0.5.0") { + edge @stmt.lexical_scope -> @block.lexical_scope + } +} + +@block [Block [Statements @stmt [Statement]]] { + ;; Hoist statement definitions for Solidity < 0.5.0 + if (version-matches "< 0.5.0") { + ;; definitions are carried over to the block + edge @block.defs -> @stmt.defs + + ;; resolution happens in the context of the block + edge @stmt.lexical_scope -> @block.lexical_scope + + ;; and the statement definitions are available block's scope + edge @block.lexical_scope -> @stmt.defs + ;; ... shadowing declarations in enclosing scopes + attr (@block.lexical_scope -> @stmt.defs) precedence = 1 + } +} + +;; Two consecutive statements +[Statements @left_stmt [Statement] . @right_stmt [Statement]] { + if (version-matches ">= 0.5.0") { + edge @right_stmt.lexical_scope -> @left_stmt.lexical_scope + } +} + +@stmt [Statement] { + node @stmt.lexical_scope + node @stmt.defs + + if (version-matches ">= 0.5.0") { + ;; For Solidity >= 0.5.0, definitions are immediately available in the + ;; statement scope. For < 0.5.0 this is also true, but resolved through the + ;; enclosing block's lexical scope. + edge @stmt.lexical_scope -> @stmt.defs + ;; Statement definitions shadow other declarations in its scope + attr (@stmt.lexical_scope -> @stmt.defs) precedence = 1 + } +} + +;; Statements of type block +@stmt [Statement @block variant: [Block]] { + edge @block.lexical_scope -> @stmt.lexical_scope + + ;; Hoist block definitions (< 0.5.0) + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @block.defs + } +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Expressions & declaration statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; In general for statements the structure is [Statement [StmtVariant]] and we +;; will define the scoped nodes .lexical_scope and (possibly) .defs in the +;; Statement CST node, skipping scoped nodes in the variant of the statement. +;; +;; For expression statements, variable and tuple declarations we define them +;; separately from the enclosing statement to be able to use them in `for` +;; initialization and condition clauses directly. Also, because we intend to +;; reuse them, all of them must have both a .lexical_scope and .defs scoped +;; nodes (even though .defs doesn't make sense for ExpressionStatement) + +@stmt [Statement @expr_stmt [ExpressionStatement]] { + edge @expr_stmt.lexical_scope -> @stmt.lexical_scope +} + +@expr_stmt [ExpressionStatement] { + node @expr_stmt.lexical_scope +} + + +;;; Variable declaration statements + +@stmt [Statement @var_decl [VariableDeclarationStatement]] { + edge @var_decl.lexical_scope -> @stmt.lexical_scope + edge @stmt.defs -> @var_decl.def +} + +@var_decl [VariableDeclarationStatement] { + node @var_decl.lexical_scope + node @var_decl.def +} + +@var_decl [VariableDeclarationStatement + [VariableDeclarationType @var_type [TypeName]] + @name name: [Identifier] +] { + attr (@var_decl.def) node_definition = @name + attr (@var_decl.def) definiens_node = @var_decl + + edge @var_type.type_ref -> @var_decl.lexical_scope + + node typeof + attr (typeof) push_symbol = "@typeof" + + edge @var_decl.def -> typeof + edge typeof -> @var_type.output +} + +@var_decl [VariableDeclarationStatement + [VariableDeclarationType [VarKeyword]] + @name name: [Identifier] +] { + attr (@var_decl.def) node_definition = @name + attr (@var_decl.def) definiens_node = @var_decl +} + +@var_decl [VariableDeclarationStatement + [VariableDeclarationType [VarKeyword]] + [VariableDeclarationValue @value [Expression]] +] { + edge @var_decl.def -> @value.output +} + + + +;;; Tuple deconstruction statements + +@stmt [Statement @tuple_decon [TupleDeconstructionStatement]] { + edge @tuple_decon.lexical_scope -> @stmt.lexical_scope + edge @stmt.defs -> @tuple_decon.defs +} + +@tuple_decon [TupleDeconstructionStatement] { + node @tuple_decon.lexical_scope + node @tuple_decon.defs +} + +@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements + [TupleDeconstructionElement + @tuple_member [TupleMember variant: [UntypedTupleMember + @name name: [Identifier]] + ] + ] +]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @tuple_member + + edge @tuple_decon.defs -> def +} + +@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements + [TupleDeconstructionElement + @tuple_member [TupleMember variant: [TypedTupleMember + @member_type type_name: [TypeName] + @name name: [Identifier]] + ] + ] +]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @tuple_member + + edge @tuple_decon.defs -> def + edge @member_type.type_ref -> @tuple_decon.lexical_scope + + node typeof + attr (typeof) push_symbol = "@typeof" + + edge def -> typeof + edge typeof -> @member_type.output +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Control statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; If conditionals + +@stmt [Statement [IfStatement @body body: [Statement]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + } +} + +@stmt [Statement [IfStatement else_branch: [ElseBranch @else_body body: [Statement]]]] { + edge @else_body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @else_body.defs + } +} + +;; For loops + +@stmt [Statement [ForStatement + initialization: [ForStatementInitialization @init_stmt [ExpressionStatement]] +]] { + edge @init_stmt.lexical_scope -> @stmt.lexical_scope +} + +@stmt [Statement [ForStatement + initialization: [ForStatementInitialization @init_stmt [VariableDeclarationStatement]] +]] { + edge @init_stmt.lexical_scope -> @stmt.lexical_scope + edge @stmt.init_defs -> @init_stmt.def +} + +@stmt [Statement [ForStatement + initialization: [ForStatementInitialization @init_stmt [TupleDeconstructionStatement]] +]] { + edge @init_stmt.lexical_scope -> @stmt.lexical_scope + edge @stmt.init_defs -> @init_stmt.defs +} + +@stmt [Statement [ForStatement + condition: [ForStatementCondition @cond_stmt [ExpressionStatement]] +]] { + edge @cond_stmt.lexical_scope -> @stmt.lexical_scope + edge @cond_stmt.lexical_scope -> @stmt.init_defs +} + +@stmt [Statement [ForStatement @iter_expr iterator: [Expression]]] { + ; for the iterator expression we need an independent scope node that can + ; connect to both the for-statement *and* the definitions in the init + ; expression + node @iter_expr.lexical_scope + edge @iter_expr.lexical_scope -> @stmt.lexical_scope + edge @iter_expr.lexical_scope -> @stmt.init_defs +} + +@stmt [Statement [ForStatement @body body: [Statement]]] { + node @stmt.init_defs + + edge @body.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @stmt.init_defs + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + edge @stmt.defs -> @stmt.init_defs + } +} + +;; While loops + +@stmt [Statement [WhileStatement @body body: [Statement]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + } +} + +;; Do-while loops + +@stmt [Statement [DoWhileStatement @body body: [Statement]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + } +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Error handling +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; Try-catch statements + +@stmt [Statement [TryStatement @body body: [Block]]] { + edge @body.lexical_scope -> @stmt.lexical_scope +} + +@stmt [Statement [TryStatement + [ReturnsDeclaration @return_params [ParametersDeclaration]] + @body body: [Block] +]] { + edge @return_params.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @return_params.defs + ;; Similar to functions, return params shadow other declarations + attr (@body.lexical_scope -> @return_params.defs) precedence = 1 +} + +@stmt [Statement [TryStatement [CatchClauses [CatchClause + @body body: [Block] +]]]] { + edge @body.lexical_scope -> @stmt.lexical_scope +} + +@stmt [Statement [TryStatement [CatchClauses [CatchClause + [CatchClauseError @catch_params parameters: [ParametersDeclaration]] + @body body: [Block] +]]]] { + edge @catch_params.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @catch_params.defs + ;; Similar to functions, catch params shadow other declarations + attr (@body.lexical_scope -> @catch_params.defs) precedence = 1 +} + +@stmt [Statement [TryStatement [CatchClauses [CatchClause + [CatchClauseError @name [Identifier]] +]]]] { + node ref + attr (ref) node_reference = @name + edge ref -> @stmt.lexical_scope +} + +;;; Revert statements + +@stmt [Statement [RevertStatement @error_ident [IdentifierPath]]] { + edge @error_ident.push_end -> @stmt.lexical_scope +} + +@stmt [Statement [RevertStatement @args [ArgumentsDeclaration]]] { + edge @args.lexical_scope -> @stmt.lexical_scope +} + +[Statement [RevertStatement + @error_ident [IdentifierPath] + @args [ArgumentsDeclaration] +]] { + edge @args.refs -> @error_ident.push_begin +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Other statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; Emit +@stmt [Statement [EmitStatement + @event_ident [IdentifierPath] + @args [ArgumentsDeclaration] +]] { + edge @event_ident.push_end -> @stmt.lexical_scope + edge @args.lexical_scope -> @stmt.lexical_scope + edge @args.refs -> @event_ident.push_begin +} + +;;; Unchecked +@stmt [Statement [UncheckedBlock @block block: [Block]]] { + edge @block.lexical_scope -> @stmt.lexical_scope +} + +;;; Assembly +@stmt [Statement [AssemblyStatement @body body: [YulBlock]]] { + edge @body.lexical_scope -> @stmt.lexical_scope +} diff --git a/crates/solidity/inputs/language/bindings/05-expressions.msgb b/crates/solidity/inputs/language/bindings/05-expressions.msgb new file mode 100644 index 0000000000..2a04b0972b --- /dev/null +++ b/crates/solidity/inputs/language/bindings/05-expressions.msgb @@ -0,0 +1,320 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Expressions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Expressions have two important scoped variables: +;; - @expr.lexical_scope should be set by the enclosing node to provide a scope +;; for name resolution +;; - @expr.output is a node provided by the expression and represents the output +;; of the expression for chaining eg. with a member access + +@expr [Expression] { + ;; this is an output scope for use in member access (and other uses) + node @expr.output +} + +;; Identifier expressions +@expr [Expression @name [Identifier]] { + node ref + attr (ref) node_reference = @name + attr (ref) parents = [@expr.enclosing_def] + + edge ref -> @expr.lexical_scope + edge @expr.output -> ref +} + +@expr [Expression @keyword ([ThisKeyword] | [SuperKeyword])] { + ; This is almost equivalent to the above rule, except it doesn't generate a reference + node keyword + attr (keyword) push_symbol = (source-text @keyword) + edge keyword -> @expr.lexical_scope + edge @expr.output -> keyword +} + +;; Member access expressions +@expr [Expression [MemberAccessExpression + @operand operand: [Expression] + @name member: [Identifier] +]] { + node @name.ref + attr (@name.ref) node_reference = @name + attr (@name.ref) parents = [@expr.enclosing_def] + + node member + attr (member) push_symbol = "." + + edge @name.ref -> member + edge member -> @operand.output + + edge @expr.output -> @name.ref + + ; Shortcut path for expressions inside contracts with using X for * directives + edge member -> @expr.star_extension +} + +;; Special case: member accesses to `super` are tagged with "super" to rank +;; virtual methods correctly +[MemberAccessExpression + operand: [Expression [SuperKeyword]] + @name member: [Identifier] +] { + attr (@name.ref) tag = "super" +} + +;; Elementary types used as expressions (eg. for type casting, or for built-ins like `string.concat`) +@expr [Expression @type [ElementaryType]] { + edge @expr.output -> @type.ref + edge @type.ref -> @expr.lexical_scope + + ; Elementary types can also be used for casting; instead of defining built-in + ; struct for each available elementary type, we define a special path here + node call + attr (call) pop_symbol = "()" + node typeof + attr (typeof) push_symbol = "@typeof" + edge @expr.output -> call + edge call -> typeof + edge typeof -> @type.ref +} + +;; Index access expressions +@expr [Expression [IndexAccessExpression + @operand operand: [Expression] +]] { + node index + attr (index) push_symbol = "[]" + + edge @expr.output -> index + edge index -> @operand.output +} + +;; Type expressions +@type_expr [Expression [TypeExpression @type [TypeName]]] { + edge @type.type_ref -> @type_expr.lexical_scope +} + +@type_expr [Expression [TypeExpression [TypeName [ElementaryType ([IntKeyword] | [UintKeyword])]]]] { + ; For integer types the type's type is fixed + node typeof + attr (typeof) push_symbol = "@typeof" + node type + attr (type) push_symbol = "%IntTypeType" + + edge @type_expr.output -> typeof + edge typeof -> type + edge type -> @type_expr.lexical_scope +} + +@type_expr [Expression [TypeExpression [TypeName @id_path [IdentifierPath]]]] { + ; For other identifiers, resolve it through a pseudo-member `%type` + node typeof + attr (typeof) push_symbol = "@typeof" + node type + attr (type) push_symbol = "@type" + + edge @type_expr.output -> typeof + edge typeof -> type + edge type -> @id_path.push_begin +} + +;; New expressions + +@new_expr [Expression [NewExpression @type [TypeName]]] { + edge @type.type_ref -> @new_expr.lexical_scope + edge @new_expr.output -> @type.output +} + + +;;; Function call expressions + +@args [ArgumentsDeclaration] { + node @args.lexical_scope + + node @args.refs + attr (@args.refs) push_symbol = "@param_names" +} + +@named_arg [NamedArgument @name [Identifier] [Colon] [Expression]] { + node @named_arg.lexical_scope + + node @named_arg.ref + attr (@named_arg.ref) node_reference = @name +} + +@args [ArgumentsDeclaration [NamedArgumentsDeclaration + [NamedArgumentGroup [NamedArguments @argument [NamedArgument]]] +]] { + edge @argument.lexical_scope -> @args.lexical_scope + edge @argument.ref -> @args.refs +} + +@funcall [Expression [FunctionCallExpression + @operand [Expression] + @args [ArgumentsDeclaration] +]] { + edge @args.lexical_scope -> @funcall.lexical_scope + + ;; Connect to the output of the function name to be able to resolve named arguments + edge @args.refs -> @operand.output + + node call + attr (call) push_symbol = "()" + + edge @funcall.output -> call + edge call -> @operand.output +} + + +;;; Call options + +@expr [Expression [CallOptionsExpression @operand [Expression] @options [CallOptions]]] { + edge @expr.output -> @operand.output + + node @options.refs + attr (@options.refs) push_symbol = "@param_names" + + node call_options + attr (call_options) push_symbol = "%CallOptions" + + edge @options.refs -> call_options + edge call_options -> @expr.lexical_scope +} + +@expr [Expression [CallOptionsExpression + @options [CallOptions @named_arg [NamedArgument]] +]] { + edge @named_arg.lexical_scope -> @expr.lexical_scope + edge @named_arg.ref -> @options.refs +} + + +;;; Payable +; These work like `address`, should they should bind to `%address` +@expr [Expression [PayableKeyword]] { + node ref + attr (ref) push_symbol = "%address" + + edge ref -> @expr.lexical_scope + edge @expr.output -> ref +} + + +;;; Tuple expressions + +; Parenthesized expressions are parsed as tuples of a single value +@expr [Expression [TupleExpression [TupleValues . [TupleValue @operand [Expression]] .]]] { + edge @expr.output -> @operand.output +} + +;;; Arithmetic, bitwise & logical operators, etc + +; Bind to the left operand only: assignment expressions +@expr [Expression [_ + @left_operand left_operand: [Expression] + ( + [Equal] + | [BarEqual] + | [PlusEqual] + | [MinusEqual] + | [CaretEqual] + | [SlashEqual] + | [PercentEqual] + | [AsteriskEqual] + | [AmpersandEqual] + | [LessThanLessThanEqual] + | [GreaterThanGreaterThanEqual] + | [GreaterThanGreaterThanGreaterThanEqual] + ) +]] { + edge @expr.output -> @left_operand.output +} + +; Unary operators postfix +@expr [Expression [_ + @operand operand: [Expression] + ([PlusPlus] | [MinusMinus]) +]] { + edge @expr.output -> @operand.output +} + +; Unary operators prefix +@expr [Expression [_ + ([PlusPlus] | [MinusMinus] | [Tilde] | [Bang] | [Minus] | [Plus]) + @operand operand: [Expression] +]] { + edge @expr.output -> @operand.output +} + +; Bind to both operands: logical and/or, arithmetic, bit-wise expressions +@expr [Expression [_ + @left_operand left_operand: [Expression] + ( + [BarBar] + | [AmpersandAmpersand] + + | [Plus] + | [Minus] + | [Asterisk] + | [Slash] + | [Percent] + | [AsteriskAsterisk] + + | [Bar] + | [Caret] + | [Ampersand] + + | [LessThanLessThan] + | [GreaterThanGreaterThan] + | [GreaterThanGreaterThanGreaterThan] + ) + @right_operand right_operand: [Expression] +]] { + edge @expr.output -> @left_operand.output + edge @expr.output -> @right_operand.output +} + +; Comparison operators bind to bool type +@expr [Expression [_ + ( + [EqualEqual] + | [BangEqual] + | [LessThan] + | [GreaterThan] + | [LessThanEqual] + | [GreaterThanEqual] + ) +]] { + node typeof + attr (typeof) push_symbol = "@typeof" + node bool + attr (bool) push_symbol = "%bool" + edge @expr.output -> typeof + edge typeof -> bool + edge bool -> @expr.lexical_scope +} + +; Ternary conditional expression binds to both branches +@expr [Expression [ConditionalExpression + @true_expression true_expression: [Expression] + @false_expression false_expression: [Expression] +]] { + edge @expr.output -> @true_expression.output + edge @expr.output -> @false_expression.output +} + + +;;; Literal Address Expressions +@expr [Expression [HexNumberExpression @hex_literal [HexLiteral]]] { + scan (source-text @hex_literal) { + "0x[0-9a-fA-F]{40}" { + ; Treat it as a valid address + node typeof + attr (typeof) push_symbol = "@typeof" + node address + attr (address) push_symbol = "%address" + edge @expr.output -> typeof + edge typeof -> address + edge address -> @expr.lexical_scope + } + } +} diff --git a/crates/solidity/inputs/language/bindings/06-yul.msgb b/crates/solidity/inputs/language/bindings/06-yul.msgb new file mode 100644 index 0000000000..77b9d544e1 --- /dev/null +++ b/crates/solidity/inputs/language/bindings/06-yul.msgb @@ -0,0 +1,332 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Yul +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; Blocks and statements + +@block [YulBlock] { + node @block.lexical_scope + ; Variables defined in this block (only used to forward the init block + ; declarations in a for statement) + node @block.variable_defs + ; Function definitions accessible from the block (ie. defined in the block, or + ; accessible in the enclosing parent block) + node @block.function_defs + + edge @block.lexical_scope -> @block.function_defs +} + +@block [YulBlock [YulStatements . @stmt [YulStatement]]] { + edge @stmt.lexical_scope -> @block.lexical_scope +} + +@block [YulBlock [YulStatements @stmt [YulStatement]]] { + edge @stmt.function_scope -> @block.function_defs + edge @block.variable_defs -> @stmt.defs +} + +[YulStatements @left_stmt [YulStatement] . @right_stmt [YulStatement]] { + edge @right_stmt.lexical_scope -> @left_stmt.lexical_scope + ; variable declaration are accessible from the next statement + edge @right_stmt.lexical_scope -> @left_stmt.defs +} + +@stmt [YulStatement] { + node @stmt.lexical_scope + node @stmt.defs + ;; Functions visible in this scope (to propagate to inner function + ;; definitions, since the lexical scope is not accessible inside a function + ;; body) + node @stmt.function_scope +} + +;;; Blocks as statements + +@stmt [YulStatement @block variant: [YulBlock]] { + edge @block.lexical_scope -> @stmt.lexical_scope + edge @block.function_defs -> @stmt.function_scope +} + +;;; Expression as statements + +@stmt [YulStatement @expr_stmt [YulExpression]] { + edge @expr_stmt.lexical_scope -> @stmt.lexical_scope +} + +;;; Variable declarations + +@stmt [YulStatement @var_decl [YulVariableDeclarationStatement]] { + edge @var_decl.lexical_scope -> @stmt.lexical_scope + edge @stmt.defs -> @var_decl.defs +} + +@var_decl [YulVariableDeclarationStatement] { + node @var_decl.lexical_scope + node @var_decl.defs +} + +@var_decl [YulVariableDeclarationStatement [YulVariableNames @name [YulIdentifier]]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @var_decl + + edge @var_decl.defs -> def +} + +@var_decl [YulVariableDeclarationStatement [YulVariableDeclarationValue + @value [YulExpression] +]] { + edge @value.lexical_scope -> @var_decl.lexical_scope +} + +;;; Variable assignments + +@stmt [YulStatement @var_assign [YulVariableAssignmentStatement]] { + edge @var_assign.lexical_scope -> @stmt.lexical_scope +} + +@var_assign [YulVariableAssignmentStatement] { + node @var_assign.lexical_scope +} + +@var_assign [YulVariableAssignmentStatement [YulPaths @path [YulPath]]] { + edge @path.lexical_scope -> @var_assign.lexical_scope +} + +@var_assign [YulVariableAssignmentStatement @expr expression: [YulExpression]] { + edge @expr.lexical_scope -> @var_assign.lexical_scope +} + +;;; Function definitions + +@block [YulBlock [YulStatements [YulStatement @fundef [YulFunctionDefinition]]]] { + ;; Function definitions are hoisted in the enclosing block + edge @block.function_defs -> @fundef.def + ;; The only definitions available in the function's lexical scope (other than + ;; parameters) are functions (ie. the body of the function doesn't have access + ;; to any outside variables) + edge @fundef.lexical_scope -> @block.function_defs + ; Exception: but outside constants *are* available, so we provide a guarded + ; access to the parent lexical scope. This guard will be popped to link to + ; available constants. + node yul_function_guard + attr (yul_function_guard) push_symbol = "@in_yul_function" + edge @fundef.lexical_scope -> yul_function_guard + edge yul_function_guard -> @block.lexical_scope +} + +;; Constants need to be available inside Yul functions. This is an exception +;; since no other external identifiers are, so the path is guarded. We create a +;; scope in the source unit, contracts and libraries, and guard it from the +;; lexical scope, so we can link constant definitions here. See the dual path in +;; the rule above. +@constant_container ([SourceUnit] | [ContractDefinition] | [LibraryDefinition]) { + node @constant_container.yul_functions_guarded_scope + attr (@constant_container.yul_functions_guarded_scope) pop_symbol = "@in_yul_function" + edge @constant_container.lexical_scope -> @constant_container.yul_functions_guarded_scope +} + +;; Make top-level constants available inside Yul functions +@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember @constant [ConstantDefinition]]]] { + edge @source_unit.yul_functions_guarded_scope -> @constant.def +} + +;; Ditto for contracts, interfaces and libraries +@contract [_ members: [_ [ContractMember + @constant [StateVariableDefinition + [StateVariableAttributes [StateVariableAttribute [ConstantKeyword]]] + ] +]]] { + edge @contract.yul_functions_guarded_scope -> @constant.def +} + +@fundef [YulFunctionDefinition + @name name: [YulIdentifier] + @body body: [YulBlock] +] { + node @fundef.lexical_scope + node @fundef.def + + node def + attr (def) node_definition = @name + attr (def) definiens_node = @fundef + + edge @fundef.def -> def + edge @body.lexical_scope -> @fundef.lexical_scope +} + +@fundef [YulFunctionDefinition [YulParametersDeclaration [YulParameters + @param [YulIdentifier] +]]] { + node def + attr (def) node_definition = @param + attr (def) definiens_node = @param + + edge @fundef.lexical_scope -> def +} + +@fundef [YulFunctionDefinition [YulReturnsDeclaration [YulVariableNames + @return_param [YulIdentifier] +]]] { + node def + attr (def) node_definition = @return_param + attr (def) definiens_node = @return_param + + edge @fundef.lexical_scope -> def +} + +;;; Stack assignment (Solidity < 0.5.0) + +@stmt [YulStatement [YulStackAssignmentStatement @name [YulIdentifier]]] { + node ref + attr (ref) node_reference = @name + + edge ref -> @stmt.lexical_scope +} + +;;; If statements + +@stmt [YulStatement [YulIfStatement + @condition condition: [YulExpression] + @body body: [YulBlock] +]] { + edge @condition.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @stmt.lexical_scope + edge @body.function_defs -> @stmt.function_scope +} + +;;; Switch statements + +@stmt [YulStatement [YulSwitchStatement + @expr expression: [YulExpression] +]] { + edge @expr.lexical_scope -> @stmt.lexical_scope +} + +@stmt [YulStatement [YulSwitchStatement [YulSwitchCases [YulSwitchCase + [_ @body body: [YulBlock]] +]]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + edge @body.function_defs -> @stmt.function_scope +} + +;;; For statements + +@stmt [YulStatement [YulForStatement + @init initialization: [YulBlock] + @cond condition: [YulExpression] + @iter iterator: [YulBlock] + @body body: [YulBlock] +]] { + edge @init.lexical_scope -> @stmt.lexical_scope + edge @cond.lexical_scope -> @stmt.lexical_scope + edge @iter.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @stmt.lexical_scope + + edge @cond.lexical_scope -> @init.variable_defs + edge @iter.lexical_scope -> @init.variable_defs + edge @body.lexical_scope -> @init.variable_defs +} + +;;; Label statements (Solidity < 0.5.0) + +@block [YulBlock [YulStatements [YulStatement @label [YulLabel @name label: [YulIdentifier]]]]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @label + + ; Labels are hoisted to the beginning of the block + edge @block.lexical_scope -> def +} + +;;; Expressions + +@expr [YulExpression] { + node @expr.lexical_scope +} + +@expr [YulExpression @path [YulPath]] { + edge @path.lexical_scope -> @expr.lexical_scope +} + +@path [YulPath] { + node @path.lexical_scope +} + +@path [YulPath . @name [YulIdentifier]] { + node ref + attr (ref) node_reference = @name + + edge ref -> @path.lexical_scope + + if (version-matches "< 0.7.0") { + ; Before Solidity 0.7.0 storage variables' `.offset` and `.slot` were + ; accessed by suffixing the name with `_offset` and `_slot` + scan (source-text @name) { + "^(.*)_(slot|offset|length)$" { + let symbol = $0 + let without_suffix = $1 + let suffix = $2 + + ; We bind the whole symbol to the built-in field for the known cases + node pop_ref + attr (pop_ref) pop_symbol = symbol + node push_suffixless + attr (push_suffixless) push_symbol = suffix + node member_of + attr (member_of) push_symbol = "." + node typeof + attr (typeof) push_symbol = "@typeof" + node yul_external + attr (yul_external) push_symbol = "%YulExternal" + + edge ref -> pop_ref + edge pop_ref -> push_suffixless + edge push_suffixless -> member_of + edge member_of -> typeof + edge typeof -> yul_external + edge yul_external -> @path.lexical_scope + } + } + } +} + +@path [YulPath [Period] @member [YulIdentifier] .] { + ; Yul variable members only apply to external variables and hence are + ; automatically bound to a special %YulExternal built-in + node ref + attr (ref) node_reference = @member + node member_of + attr (member_of) push_symbol = "." + node typeof + attr (typeof) push_symbol = "@typeof" + node yul_external + attr (yul_external) push_symbol = "%YulExternal" + + edge ref -> member_of + edge member_of -> typeof + edge typeof -> yul_external + edge yul_external -> @path.lexical_scope +} + +@expr [YulExpression @funcall [YulFunctionCallExpression]] { + edge @funcall.lexical_scope -> @expr.lexical_scope +} + +@funcall [YulFunctionCallExpression + @operand operand: [YulExpression] + @args arguments: [YulArguments] +] { + node @funcall.lexical_scope + + edge @operand.lexical_scope -> @funcall.lexical_scope + edge @args.lexical_scope -> @funcall.lexical_scope +} + +@args [YulArguments] { + node @args.lexical_scope +} + +@args [YulArguments @arg [YulExpression]] { + edge @arg.lexical_scope -> @args.lexical_scope +} diff --git a/crates/solidity/inputs/language/bindings/rules.msgb b/crates/solidity/inputs/language/bindings/rules.msgb deleted file mode 100644 index f6b54ffcf2..0000000000 --- a/crates/solidity/inputs/language/bindings/rules.msgb +++ /dev/null @@ -1,2960 +0,0 @@ -global ROOT_NODE -global FILE_PATH -global JUMP_TO_SCOPE_NODE - -attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition -attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference -attribute node_symbol = node => symbol = (source-text node), source_node = node -attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol -attribute push_symbol = symbol => type = "push_symbol", symbol = symbol -attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition -attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference - -attribute scoped_node_definition = node => type = "pop_scoped_symbol", node_symbol = node, is_definition -attribute scoped_node_reference = node => type = "push_scoped_symbol", node_symbol = node, is_reference -attribute pop_scoped_symbol = symbol => type = "pop_scoped_symbol", symbol = symbol -attribute push_scoped_symbol = symbol => type = "push_scoped_symbol", symbol = symbol - -;; Keeps a link to the enclosing contract definition to provide a parent for -;; method calls (to correctly resolve virtual methods) -inherit .enclosing_def - -inherit .parent_scope -inherit .lexical_scope - -; Used to resolve extension methods for `using for *` directives -; This is used as a minor optimization to avoid introducing new possible paths -; when there are no `using for *` directives in the contract. -inherit .star_extension - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Source unit (aka .sol file) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@source_unit [SourceUnit] { - ;; All lexical_scope nodes eventually connect to the file's root scope - node @source_unit.lexical_scope - - ;; This provides all the exported symbols from the file - node @source_unit.defs - - ;; Connect to ROOT_NODE to export our symbols - node export - edge ROOT_NODE -> export - edge export -> @source_unit.defs - - if (is-system-file FILE_PATH) { - ; If this is a system file (aka. built-ins), export everything through this - ; special symbol (which is automatically imported below) - attr (export) pop_symbol = "@@built-ins@@" - - } else { - ; This is a user file, so we want to export under the file's path symbol - attr (export) pop_symbol = FILE_PATH - - ; ... and also import the global built-ins - node built_ins - attr (built_ins) push_symbol = "@@built-ins@@" - - edge @source_unit.lexical_scope -> built_ins - edge built_ins -> ROOT_NODE - } - - let @source_unit.enclosing_def = #null - - ;; This defines a parent_scope at the source unit level (this attribute is - ;; inherited) for contracts to resolve bases (both in inheritance lists and - ;; override specifiers) - let @source_unit.parent_scope = @source_unit.lexical_scope - - ; 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. - node @source_unit.star_extension -} - -;; Top-level definitions... -@source_unit [SourceUnit [SourceUnitMembers - [SourceUnitMember @unit_member ( - [ContractDefinition] - | [LibraryDefinition] - | [InterfaceDefinition] - | [StructDefinition] - | [EnumDefinition] - | [FunctionDefinition] - | [ConstantDefinition] - | [ErrorDefinition] - | [UserDefinedValueTypeDefinition] - | [EventDefinition] - )] -]] { - edge @source_unit.lexical_scope -> @unit_member.def - edge @source_unit.defs -> @unit_member.def - - ; In the general case, the lexical scope of the definition connects directly - ; to the source unit's - edge @unit_member.lexical_scope -> @source_unit.lexical_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 -;; to export all its members and type members directly as a source unit -;; definition. -;; __SLANG_SOLIDITY_BUILT_INS_CONTRACT_NAME__ keep in sync with built-ins generation. -@source_unit [SourceUnit [SourceUnitMembers - [SourceUnitMember @contract [ContractDefinition name: ["%BuiltIns%"]]] -]] { - if (is-system-file FILE_PATH) { - edge @source_unit.defs -> @contract.instance - } -} - -@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember @using [UsingDirective]]]] { - ; TODO: this is the hook for top-level extensions, but this should connect to - ; an extensions scope that gets pushed to the scope stack, as in the case of - ; contracts/libraries (defined further down below). - edge @source_unit.lexical_scope -> @using.def -} - -@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember - @using [UsingDirective [GlobalKeyword]] -]]] { - ; global using directives are exported by this source unit - edge @source_unit.defs -> @using.def -} - -;; Import connections to the source unit -@source_unit [SourceUnit [SourceUnitMembers - [SourceUnitMember [ImportDirective - [ImportClause @import ( - [PathImport] - | [NamedImport] - | [ImportDeconstruction] - )] - ]] -]] { - node @import.defs - edge @source_unit.defs -> @import.defs - edge @source_unit.lexical_scope -> @import.defs -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Imports -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -[ImportClause - [_ - path: [StringLiteral - @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) - ] - ] -] { - ;; This node represents the imported file and the @path.import node is used by - ;; all subsequent import rules - node @path.import - - let resolved_path = (resolve-path FILE_PATH @path) - attr (@path.import) push_symbol = resolved_path - - edge @path.import -> ROOT_NODE -} - -;;; `import ` -@import [PathImport - path: [StringLiteral - @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) - ] -] { - ;; This is the "lexical" connection, which makes all symbols exported from the - ;; imported source unit available for resolution globally at this' source unit - ;; scope - edge @import.defs -> @path.import -} - -;;; `import as ` -@import [PathImport - path: [StringLiteral - @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) - ] - alias: [ImportAlias @alias [Identifier]] -] { - node def - attr (def) node_definition = @alias - attr (def) definiens_node = @import - edge @import.defs -> def - - node member - attr (member) pop_symbol = "." - edge def -> member - - ;; Lexical connection, which makes the import available as a member through - ;; the alias identifier - edge member -> @path.import -} - -;;; `import * as from ` -@import [NamedImport - alias: [ImportAlias @alias [Identifier]] - path: [StringLiteral - @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) - ] -] { - node def - attr (def) node_definition = @alias - attr (def) definiens_node = @import - edge @import.defs -> def - - node member - attr (member) pop_symbol = "." - edge def -> member - - ;; Lexical connection, which makes the import available as a member through - ;; the alias identifier - edge member -> @path.import -} - -;;; `import { [as ] ...} from ` -@import [ImportDeconstruction - symbols: [ImportDeconstructionSymbols @symbol [ImportDeconstructionSymbol]] - path: [StringLiteral - @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) - ] -] { - ;; We define these intermediate nodes for convenience only, to make the - ;; queries simpler in the two rules below - node @symbol.def - edge @import.defs -> @symbol.def - - node @symbol.import - edge @symbol.import -> @path.import -} - -@symbol [ImportDeconstructionSymbol @name name: [Identifier] .] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @symbol - attr (def) tag = "alias" ; deprioritize this definition - edge @symbol.def -> def - - node import - attr (import) node_reference = @name - edge def -> import - - edge import -> @symbol.import -} - -@symbol [ImportDeconstructionSymbol - @name name: [Identifier] - alias: [ImportAlias @alias [Identifier]] -] { - node def - attr (def) node_definition = @alias - attr (def) definiens_node = @symbol - attr (def) tag = "alias" ; deprioritize this definition - edge @symbol.def -> def - - node import - attr (import) node_reference = @name - edge def -> import - - edge import -> @symbol.import -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Common inheritance rules (apply to contracts and interfaces) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@specifier [InheritanceSpecifier [InheritanceTypes - [InheritanceType @type_name [IdentifierPath]] -]] { - ;; This should point to the enclosing contract or interface definition - let heir = @specifier.heir - - ;; Resolve base names through the parent scope of our heir (contract or - ;; interface), aka the source unit - edge @type_name.push_end -> heir.parent_scope - - ; Access instance members of the inherited contract/interface, from the - ; instance scope of the inheriting contract/interface - node instance - attr (instance) push_symbol = "@instance" - edge heir.instance -> instance - edge instance -> @type_name.push_begin - - ; Base members can also be accessed (from the instance scope) qualified with - ; the base name (eg. `Base.something`) - node member_pop - attr (member_pop) pop_symbol = "." - edge heir.instance -> @type_name.pop_begin - edge @type_name.pop_end -> member_pop - edge member_pop -> instance - - ; Base namespace-like members (ie. enums, structs, etc) are also accessible as - ; our own namespace members - node ns_member - attr (ns_member) push_symbol = "." - edge heir.ns -> ns_member - edge ns_member -> @type_name.push_begin -} - -;; The next couple of rules setup a `.parent_refs` attribute to use in the -;; resolution algorithm to perform linearisation of a contract hierarchy. - -;; NOTE: we use anchors here to prevent the query engine from returning all the -;; sublists of possible parents -@specifier [InheritanceSpecifier [InheritanceTypes . @parents [_]+ .]] { - var parent_refs = [] - for parent in @parents { - if (eq (node-type parent) "InheritanceType") { - ;; this is intentionally reversed because of how Solidity linearised the contract bases - set parent_refs = (concat [parent.ref] parent_refs) - } - } - let @specifier.parent_refs = parent_refs -} - -@parent [InheritanceType @type_name [IdentifierPath]] { - let @parent.ref = @type_name.push_begin -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Contracts -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@contract [ContractDefinition @name name: [Identifier]] { - node @contract.lexical_scope - node @contract.extensions - node @contract.def - node @contract.members - node @contract.ns - node @contract.modifiers - node @contract.instance - - 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 - - ; Instance scope can also see members and our namespace definitions - edge @contract.instance -> @contract.members - edge @contract.instance -> @contract.ns - - let @contract.enclosing_def = @contract.def - - ;; External "instance" scope access: either member access through a variable - ;; of the contract's type, or through calling (which happens on `new` - ;; invocations or casting). These should access only externally accessible - ;; members, such as functions and public variables. - node member - attr (member) pop_symbol = "." - edge member -> @contract.instance - - node type_def - attr (type_def) pop_symbol = "@typeof" - edge @contract.def -> type_def - edge type_def -> member - - node call - attr (call) pop_symbol = "()" - edge @contract.def -> call - edge call -> member - - ;; "namespace" scope access - node ns_member - attr (ns_member) pop_symbol = "." - edge @contract.def -> ns_member - edge ns_member -> @contract.ns - - ; Finally there's an @instance guarded path used by derived contracts to - ; access instance accessible members - node instance - attr (instance) pop_symbol = "@instance" - edge @contract.def -> instance - edge instance -> @contract.instance - - ; "this" keyword is available in our lexical scope and can access any - ; externally available member - node this - attr (this) pop_symbol = "this" - edge @contract.lexical_scope -> this - edge this -> member - - ;; Modifiers are available as a contract type members through a special '@modifier' guard - node modifier - attr (modifier) pop_symbol = "@modifier" - edge @contract.ns -> modifier - edge modifier -> @contract.modifiers - - ; There may be attached functions to our type. For the general case of - ; variables of our type, that's already handled via normal lexical scope - ; resolution. But for casting/`new` invocations that we resolve through the - ; `()` guard above, we need to explicitly jump to the extension scope from - ; here to attempt resolving the attached function. We cannot jump back to the - ; parent scope because that would create a cycle in the graph. - node push_typeof - 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 -> hook - - if (version-matches "< 0.5.0") { - ; For Solidity < 0.5.0 `this` also acts like an `address` - node address_ref - attr (address_ref) push_symbol = "%address" - node address_typeof - attr (address_typeof) push_symbol = "@typeof" - edge this -> address_typeof - edge address_typeof -> address_ref - edge address_ref -> @contract.lexical_scope - } - - ; This is the connection point to resolve attached functions by `using for *` - node @contract.star_extension - attr (@contract.star_extension) push_symbol = "@*" - - if (version-matches "< 0.7.0") { - ; 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.lexical_scope - } - - ; Path to resolve the built-in type for type() expressions - node type - attr (type) pop_symbol = "@type" - node type_contract_type - attr (type_contract_type) push_symbol = "%ContractTypeType" - edge @contract.def -> type - edge type -> type_contract_type - edge type_contract_type -> @contract.parent_scope - - ; The following defines the connection nodes the resolution algorithm uses - ; *only when setting a compilation context/target*. - - ; This attribute defines the sink of edges added from base contracts when - ; setting this contract as the compilation context, and should provide access - ; to anything that can be reached through `super`. The instance scope is a bit - ; too broad, but `.members` is too narrow as it doesn't allow navigation to - ; parent contracts (and from the base we need to be able to reach all - ; contracts in the hierarchy). - attr (@contract.def) export_node = @contract.instance - - ; This node will eventually connect to the contract's members being compiled - ; and grants access to definitions in that contract and all its parents - ; (recursively). It only makes sense if `super` is defined (ie. if we have - ; parents), but we define it here to be able to use it in the declaration of - ; import nodes. This is the dual of the export_node above. - node @contract.super_import - attr (@contract.super_import) pop_symbol = "." - - ; This defines the source side of edges added to base contracts when setting - ; a contract as compilation context; this allows this contract (a base) to - ; access virtual methods in any sub-contract defined in the hierarchy (both - ; with and without `super`, hence the two connection points). - attr (@contract.def) import_nodes = [@contract.lexical_scope, @contract.super_import] -} - -@contract [ContractDefinition @specifier [InheritanceSpecifier]] { - ; The `.heir` scoped variable allows the rules for `InheritanceSpecifier` - ; 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. - - ; `super_scope` is where we hook all references to our parent contracts - node @contract.super_scope - - ; Define "super" in the lexical scope - node @contract.super - attr (@contract.super) pop_symbol = "super" - edge @contract.lexical_scope -> @contract.super - - ; This connects `super` to exported scopes from all contracts in the hierarchy - ; when setting a contract compilation target (see more detailed description - ; above on the definition of the `super_import` node). - edge @contract.super -> @contract.super_import - - ; Then connect it through an `@instance` guard to the parent contracts through - ; `super_scope`. This allows "instance"-like access to members of parents - ; through `super`. - node super_instance - attr (super_instance) push_symbol = "@instance" - edge @contract.super_import -> super_instance - edge super_instance -> @contract.super_scope -} - -@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes - [InheritanceType @type_name [IdentifierPath]] -]]] { - ;; The base contract defs are directly accesible through our super scope - edge @contract.super_scope -> @type_name.push_begin -} - -; Pure definitions that cannot contain expressions -@contract [ContractDefinition [ContractMembers - [ContractMember @member ( - [EnumDefinition] - | [StructDefinition] - | [EventDefinition] - | [ErrorDefinition] - | [UserDefinedValueTypeDefinition] - )] -]] { - edge @member.lexical_scope -> @contract.lexical_scope -} - -; Definitions that can contain expressions need two scopes: -; - normal lexical scope for resolving types -; - extended scope (extended by using directives) for resolving expressions -@contract [ContractDefinition [ContractMembers - [ContractMember @member ( - [FunctionDefinition] - | [ConstructorDefinition] - | [ModifierDefinition] - | [FallbackFunctionDefinition] - | [ReceiveFunctionDefinition] - | [UnnamedFunctionDefinition] - | [StateVariableDefinition] - )] -]] { - edge @member.lexical_scope -> @contract.lexical_scope -} - -@contract [ContractDefinition [ContractMembers - [ContractMember @using [UsingDirective]] -]] { - ; Hook the using definition in the extensions scope - edge @contract.extensions -> @using.def -} - -@contract [ContractDefinition [ContractMembers - [ContractMember @member ( - [EnumDefinition] - | [StructDefinition] - | [EventDefinition] - | [ErrorDefinition] - | [UserDefinedValueTypeDefinition] - )] -]] { - ; These definition go into the "namespace" scope and are accessible externally - ; via qualified naming (eg. `Contract.MyStruct`) - edge @contract.ns -> @member.def -} - -@contract [ContractDefinition [ContractMembers - [ContractMember @state_var [StateVariableDefinition]] -]] { - ; State variables are available to derived contracts. - ; TODO: this also exposes private state variables to derived contracts, but we - ; can't easily filter them because we don't have negative assertions in our - ; query language (we would need to modify this query for anything *not* - ; containing a `PrivateKeyword` node) - edge @contract.instance -> @state_var.def -} - -;; Public state variables are also exposed as external member functions -@contract [ContractDefinition [ContractMembers - [ContractMember @state_var [StateVariableDefinition - [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] - ]] -]] { - edge @contract.members -> @state_var.def -} - -@contract [ContractDefinition [ContractMembers - [ContractMember @function [FunctionDefinition]] -]] { - ;; Contract functions are also accessible for an instance of the contract - edge @contract.members -> @function.def - - ;; This may prioritize this definition (when there are multiple options) - ;; according to the C3 linerisation ordering - attr (@function.def) tag = "c3" - attr (@function.def) parents = [@contract.def] -} - -@contract [ContractDefinition [ContractMembers - [ContractMember @function [FunctionDefinition - [FunctionAttributes [FunctionAttribute ([ExternalKeyword] | [PublicKeyword])]] - ]] -]] { - ; Public or external functions are also accessible through the contract type - ; (to retrieve their `.selector` for example) - edge @contract.ns -> @function.def -} - -@contract [ContractDefinition members: [ContractMembers - [ContractMember @modifier [ModifierDefinition]] -]] { - ; Modifiers live in their own special scope - edge @contract.modifiers -> @modifier.def - - ;; This may prioritize this definition (when there are multiple options) - ;; according to the C3 linerisation ordering - attr (@modifier.def) tag = "c3" - attr (@modifier.def) parents = [@contract.def] -} - -@contract [ContractDefinition [ContractMembers [ContractMember - [UsingDirective [UsingTarget [Asterisk]]] -]]] { - ; 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.lexical_scope -} - -; This applies to both state variables and function definitions -@override [OverrideSpecifier [OverridePathsDeclaration [OverridePaths - @base_ident [IdentifierPath] -]]] { - ;; Resolve overriden bases when listed in the function or modifiers modifiers - edge @base_ident.push_end -> @override.parent_scope -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Interfaces -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@interface [InterfaceDefinition @name name: [Identifier]] { - node @interface.lexical_scope - node @interface.def - node @interface.members - node @interface.ns - node @interface.instance - - attr (@interface.def) node_definition = @name - attr (@interface.def) definiens_node = @interface - - edge @interface.lexical_scope -> @interface.instance - - ; The extensions node is required for the inheritance rules, but not used in interfaces - let @interface.extensions = (node) - - edge @interface.instance -> @interface.members - edge @interface.instance -> @interface.ns - - ;; External "instance" like access path, to access members of a variable of - ;; the interface's type or through a casting call. - node member - attr (member) pop_symbol = "." - edge member -> @interface.instance - - node typeof - attr (typeof) pop_symbol = "@typeof" - edge @interface.def -> typeof - edge typeof -> member - - node call - attr (call) pop_symbol = "()" - edge @interface.def -> call - edge call -> member - - ; From a call we may need to resolve using the extensions scope, in case there's - ; a `using` directive on our type. This path ends up jumping to scope just to - ; handle that case. - node push_typeof - attr (push_typeof) push_symbol = "@typeof" - node push_name - attr (push_name) push_symbol = (source-text @name) - edge call -> push_typeof - edge push_typeof -> push_name - node hook - attr (hook) extension_hook - edge push_name -> hook - ; edge push_name -> JUMP_TO_SCOPE_NODE - - ;; "namespace" like access path - node ns_member - attr (ns_member) pop_symbol = "." - edge @interface.def -> ns_member - edge ns_member -> @interface.ns - - ; Finally there's guarded `@instance` path used by derived contracts to access - ; instance accessible members - node instance - attr (instance) pop_symbol = "@instance" - edge @interface.def -> instance - edge instance -> @interface.instance - - ; Path to resolve the built-in type for type() expressions - node type - attr (type) pop_symbol = "@type" - node type_interface_type - attr (type_interface_type) push_symbol = "%InterfaceTypeType" - edge @interface.def -> type - edge type -> type_interface_type - edge type_interface_type -> @interface.parent_scope -} - -@interface [InterfaceDefinition @specifier [InheritanceSpecifier]] { - let @specifier.heir = @interface - attr (@interface.def) parents = @specifier.parent_refs -} - -@interface [InterfaceDefinition [InterfaceMembers - [ContractMember @member ( - [EnumDefinition] - | [FunctionDefinition] - | [StructDefinition] - | [EventDefinition] - | [ErrorDefinition] - | [UserDefinedValueTypeDefinition] - )] -]] { - edge @member.lexical_scope -> @interface.lexical_scope - edge @interface.ns -> @member.def -} - -;; Allow references (eg. variables of the interface type) to the interface to -;; access functions -@interface [InterfaceDefinition members: [InterfaceMembers - item: [ContractMember @function variant: [FunctionDefinition]] -]] { - edge @interface.members -> @function.def -} - -[InterfaceDefinition [InterfaceMembers [ContractMember @using [UsingDirective]]]] { - ; using directives are not allowed in interfaces, but the grammar allows them - ; so we need to create an artificial node here to connect to created edges from - ; the instance nodes - let @using.lexical_scope = (node) -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Libraries -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@library [LibraryDefinition @name name: [Identifier]] { - node @library.lexical_scope - node @library.extensions - node @library.def - node @library.ns - node @library.modifiers - - 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 - edge member -> @library.ns - - ; Access to modifiers is guarded by a @modifier symbol - node modifier - attr (modifier) pop_symbol = "@modifier" - edge @library.ns -> modifier - edge modifier -> @library.modifiers - - ; Path to resolve the built-in type for type() expressions (same as contracts) - node type - attr (type) pop_symbol = "@type" - node type_library_type - attr (type_library_type) push_symbol = "%ContractTypeType" - edge @library.def -> type - edge type -> type_library_type - edge type_library_type -> @library.lexical_scope - - ; This is the connection point to resolve attached functions by `using for *` - node @library.star_extension - attr (@library.star_extension) push_symbol = "@*" -} - -@library [LibraryDefinition [LibraryMembers - [ContractMember @member ( - [EnumDefinition] - | [StructDefinition] - | [EventDefinition] - | [ErrorDefinition] - | [UserDefinedValueTypeDefinition] - )] -]] { - edge @member.lexical_scope -> @library.lexical_scope - edge @library.ns -> @member.def -} - -@library [LibraryDefinition [LibraryMembers - [ContractMember @member ( - [FunctionDefinition] - | [StateVariableDefinition [StateVariableAttributes [StateVariableAttribute [ConstantKeyword]]]] - )] -]] { - edge @member.lexical_scope -> @library.lexical_scope - edge @library.ns -> @member.def -} - -@library [LibraryDefinition [LibraryMembers - [ContractMember @modifier [ModifierDefinition]] -]] { - edge @library.modifiers -> @modifier.def - edge @modifier.lexical_scope -> @library.lexical_scope -} - -@library [LibraryDefinition [LibraryMembers - [ContractMember @using [UsingDirective]] -]] { - ; Expose the using directive from the extensions scope - edge @library.extensions -> @using.def -} - -@library [LibraryDefinition [LibraryMembers [ContractMember - [UsingDirective [UsingTarget [Asterisk]]] -]]] { - ; 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.lexical_scope -} - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Using directives -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@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 - ; target type - node @using.def - - ; This internal node connects the definition side of the clause to the target - ; for resolution, and allows handling the multiple cases of `using` syntax - ; easily - node @using.clause -} - -@using [UsingDirective [UsingClause @id_path [IdentifierPath]]] { - ; resolve the library to be used in the directive - 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 - ; need to verify that we're only using this path when resolving a function - ; access to the target type, not the target type itself - node dot_guard_pop - attr (dot_guard_pop) pop_symbol = "." - node dot_guard_push - attr (dot_guard_push) push_symbol = "." - - edge @using.clause -> dot_guard_pop - edge dot_guard_pop -> dot_guard_push - edge dot_guard_push -> @id_path.push_begin -} - -@using [UsingDirective [UsingClause [UsingDeconstruction - [UsingDeconstructionSymbols [UsingDeconstructionSymbol - @id_path [IdentifierPath] - ]] -]]] { - ; resolve the function to be used in the directive - edge @id_path.push_end -> @using.lexical_scope - - node dot - attr (dot) pop_symbol = "." - node last_identifier - attr (last_identifier) pop_symbol = (source-text @id_path.rightmost_identifier) - - edge @using.clause -> dot - edge dot -> last_identifier - edge last_identifier -> @id_path.push_begin -} - -@using [UsingDirective [UsingTarget @type_name [TypeName]]] { - ; pop the type symbols to connect to the attached function (via @using.clause) - node typeof - attr (typeof) pop_symbol = "@typeof" - node cast - attr (cast) pop_symbol = "()" - - ; We connect to all_pop_begin to be able to resolve both qualified and - ; unqualified instances of the target type - edge @using.def -> @type_name.all_pop_begin - edge @type_name.pop_end -> typeof - edge typeof -> @using.clause - edge @type_name.pop_end -> cast - edge cast -> @using.clause - - ; resolve the target type of the directive on the lexical scope - edge @type_name.type_ref -> @using.lexical_scope -} - -[ContractMember @using [UsingDirective [UsingTarget [Asterisk]]]] { - ; using X for * is only allowed inside contracts - node star - attr (star) pop_symbol = "@*" - edge @using.def -> star - edge star -> @using.clause -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Type names -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; TypeName nodes should define these scoped variables: -;; -;; - @type_name.type_ref represents the node in the graph where we're ready to -;; resolve the type, and thus should generally be connected to a (lexical) -;; scope node (source node, outside edges connect *from* here). -;; -;; - @type_name.output represents the other end of the type and corresponds to a -;; state where the type has already been resolved so we can, for example -;; resolve its members (sink node, outside edges connect *to* here). -;; -;; - @type_name.pop_begin, @type_name.pop_end are used in a definition context, -;; ie. when we need to pop the type name symbol(s) from the symbol stack. -;; Additionally, @type_name.all_pop_begin links to each symbol in a typename -;; (ie. in an identifier path typename), which allows referring to a type both -;; qualified and unqualified. - -@type_name [TypeName @elementary [ElementaryType]] { - let @type_name.type_ref = @elementary.ref - let @type_name.output = @elementary.ref - let @type_name.pop_begin = @elementary.pop - let @type_name.pop_end = @elementary.pop - let @type_name.all_pop_begin = @elementary.pop -} - -@type_name [TypeName @id_path [IdentifierPath]] { - ;; For an identifier path used as a type, the left-most element is the one - ;; that connects to the parent lexical scope, because the name resolution - ;; starts at the left of the identifier. - let @type_name.type_ref = @id_path.push_end - - ;; Conversely, the complete type is found at the right-most name, and that's - ;; where users of this type should link to (eg. a variable declaration). - let @type_name.output = @id_path.push_begin - - let @type_name.pop_begin = @id_path.pop_begin - let @type_name.pop_end = @id_path.pop_end - let @type_name.all_pop_begin = @id_path.all_pop_begin -} - -@type_name [TypeName @type_variant ([ArrayTypeName] | [FunctionType])] { - let @type_name.type_ref = @type_variant.lexical_scope - let @type_name.output = @type_variant.output - let @type_name.pop_begin = @type_variant.pop_begin - let @type_name.pop_end = @type_variant.pop_end - let @type_name.all_pop_begin = @type_variant.pop_begin -} - -@type_name [TypeName @mapping [MappingType]] { - let @type_name.type_ref = @mapping.lexical_scope - let @type_name.output = @mapping.output - let @type_name.pop_begin = @mapping.pop_begin - let @type_name.pop_end = @mapping.pop_end - let @type_name.all_pop_begin = @mapping.pop_begin -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Elementary types -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@elementary [ElementaryType] { - node @elementary.ref - attr (@elementary.ref) type = "push_symbol" - attr (@elementary.ref) source_node = @elementary, symbol = @elementary.symbol - - node @elementary.pop - attr (@elementary.pop) pop_symbol = @elementary.symbol - - ; These variables are a bit redundant, but necessary to easily use elementary - ; types as mapping keys - let @elementary.pop_begin = @elementary.pop - let @elementary.pop_end = @elementary.pop - let @elementary.all_pop_begin = @elementary.pop - - let @elementary.push_begin = @elementary.ref - let @elementary.push_end = @elementary.ref -} - -@elementary [ElementaryType [AddressType]] { - let @elementary.symbol = "%address" -} - -@elementary [ElementaryType [BoolKeyword]] { - let @elementary.symbol = "%bool" -} - -@elementary [ElementaryType [ByteKeyword]] { - let @elementary.symbol = "%byte" -} - -@elementary [ElementaryType @keyword [BytesKeyword]] { - let @elementary.symbol = (format "%{}" (source-text @keyword)) -} - -@elementary [ElementaryType [StringKeyword]] { - let @elementary.symbol = "%string" -} - -@elementary [ElementaryType @keyword [IntKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "int") { - let @elementary.symbol = "%int256" - } else { - let @elementary.symbol = (format "%{}" symbol) - } -} - -@elementary [ElementaryType @keyword [UintKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "uint") { - let @elementary.symbol = "%uint256" - } else { - let @elementary.symbol = (format "%{}" symbol) - } -} - -@elementary [ElementaryType @keyword [FixedKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "fixed") { - let @elementary.symbol = "%fixed128x18" - } else { - let @elementary.symbol = (format "%{}" symbol) - } -} - -@elementary [ElementaryType @keyword [UfixedKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "ufixed") { - let @elementary.symbol = "%ufixed128x18" - } else { - let @elementary.symbol = (format "%{}" symbol) - } -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Mappings -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@mapping [MappingType - [MappingKey [MappingKeyType @key_type ([IdentifierPath] | [ElementaryType])]] - [MappingValue @value_type [TypeName]] -] { - node @mapping.lexical_scope - node @mapping.output - - ; Define the pushing path of the mapping type - ; ValueType <- top of the symbol stack - ; KeyType - ; %mapping <- bottom of the symbol stack - node mapping - attr (mapping) push_symbol = "%Mapping" - edge @mapping.output -> mapping - edge mapping -> @key_type.push_begin - edge @key_type.push_end -> @value_type.output - - ; Both key and value types need to be resolved - edge @value_type.type_ref -> @mapping.lexical_scope - edge @key_type.push_end -> @mapping.lexical_scope - - ; The mapping's type exposes the `[]` operator that returns the value type. - - node typeof_input - attr (typeof_input) pop_symbol = "@typeof" - edge @mapping.output -> typeof_input - - node typeof_output - attr (typeof_output) push_symbol = "@typeof" - edge typeof_output -> @value_type.output - - node index - attr (index) pop_symbol = "[]" - edge typeof_input -> index - edge index -> typeof_output - - ; Special case for mapping public state variables: they can be called - ; like a function with a key, and it's effectively the same as indexing it. - node getter_call - attr (getter_call) pop_symbol = "@as_getter" - edge typeof_input -> getter_call - edge getter_call -> typeof_output - - ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only - ; This is the reverse of the pushing path above (to the `.output` node) - node pop_mapping - attr (pop_mapping) pop_symbol = "%Mapping" - - let @mapping.pop_begin = @value_type.pop_begin - edge @value_type.pop_end -> @key_type.pop_begin - edge @key_type.pop_end -> pop_mapping - let @mapping.pop_end = pop_mapping -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Arrays types -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@array [ArrayTypeName] { - node @array.lexical_scope - node @array.output -} - -@array [ArrayTypeName [TypeName] index: [Expression]] { - let @array.type_symbol = "%FixedArray" -} - -@array [ArrayTypeName [OpenBracket] . [CloseBracket]] { - let @array.type_symbol = "%Array" -} - -@array [ArrayTypeName @type_name [TypeName]] { - ; Define the pushing path of the array type - ; ValueType <- top of the symbol stack - ; %array / %arrayFixed <- bottom of the symbol stack - node array - attr (array) push_symbol = @array.type_symbol - edge @array.output -> array - edge array -> @type_name.output - - ; Resolve the value type itself - edge @type_name.type_ref -> @array.lexical_scope - ; And also the "type erased" array type so we can resolve built-in members - edge array -> @array.lexical_scope - - ; Define the path to resolve index access (aka the `[]` operator) - - node typeof_input - attr (typeof_input) pop_symbol = "@typeof" - edge @array.output -> typeof_input - - node typeof_output - attr (typeof_output) push_symbol = "@typeof" - edge typeof_output -> @type_name.output - - node index - attr (index) pop_symbol = "[]" - edge typeof_input -> index - edge index -> typeof_output - - ; Special case for public state variables of type array: they can be called - ; like a function with an index, and it's effectively the same as indexing the - ; array. - node getter_call - attr (getter_call) pop_symbol = "@as_getter" - edge typeof_input -> getter_call - edge getter_call -> typeof_output - - ; Define the special `.push()` built-in that returns the element type (for Solidity >= 0.6.0) - if (version-matches ">= 0.6.0") { - node built_in_member - attr (built_in_member) pop_symbol = "." - node push_built_in - attr (push_built_in) pop_symbol = "push" - node built_in_call - attr (built_in_call) pop_symbol = "()" - - edge typeof_input -> built_in_member - edge built_in_member -> push_built_in - edge push_built_in -> built_in_call - edge built_in_call -> typeof_output - } - - ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only - ; This is essentially the reverse of the second path above - node pop_array - attr (pop_array) pop_symbol = @array.type_symbol - - let @array.pop_begin = @type_name.pop_begin - edge @type_name.pop_end -> pop_array - let @array.pop_end = pop_array -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Function types -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@ftype [FunctionType @attrs [FunctionTypeAttributes]] { - ; Compute the built-in type of the function - ; %functionExternal provides access to .selector and .address - var type_symbol = "%Function" - scan (source-text @attrs) { - "external" { - set type_symbol = "%ExternalFunction" - } - } - - node @ftype.lexical_scope - node @ftype.output - - ; This path pushes the function type to the symbol stack - ; TODO: add parameter and return types to distinguish between different function types - node function_type - attr (function_type) push_symbol = type_symbol - - edge @ftype.output -> function_type - edge function_type -> @ftype.lexical_scope - - ; the pop path for the using directive - node pop_function_type - attr (pop_function_type) pop_symbol = type_symbol - - let @ftype.pop_begin = pop_function_type - let @ftype.pop_end = pop_function_type -} - -@ftype [FunctionType @params [ParametersDeclaration]] { - edge @params.lexical_scope -> @ftype.lexical_scope -} - -@ftype [FunctionType [ReturnsDeclaration @return_params [ParametersDeclaration]]] { - edge @return_params.lexical_scope -> @ftype.lexical_scope -} - -@ftype [FunctionType [ReturnsDeclaration - [ParametersDeclaration [Parameters . @param [Parameter] .]] -]] { - ; Variables of a function type type can be "called" and resolve to the type of - ; the return parameter. This is only valid if the function returns a single - ; value. - node typeof - attr (typeof) pop_symbol = "@typeof" - - node call - attr (call) pop_symbol = "()" - - edge @ftype.output -> typeof - edge typeof -> call - edge call -> @param.typeof -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Identifier Paths (aka. references to custom types) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; The identifier path builds two graph paths: -;; -;; - From right to left, pushing the identifiers and acting as a "reference". -;; This path begins at @id_path.push_begin and ends at @id_path.push_end. -;; -;; - From left to right, popping the identifiers (used as a definition sink in -;; using directives). This path begins at @id_path.pop_begin and ends at -;; @id_path.pop_end. -;; -;; NOTE: most of the time, and unless this identifier path is the target of a -;; using directive this second path will not be used and will form a -;; disconnected graph component. We currently have no way of determining when -;; this path is necessary, so we always construct it. -;; -;; Additionally the IdentifierPath defines another scoped variable -;; @id_path.rightmost_identifier which corresponds to the identifier in the last -;; position in the path, from left to right. This is used in the using directive -;; rules to be able to pop the name of the attached function. - -@id_path [IdentifierPath] { - ; This node connects to all parts of the path, for popping. This allows to - ; connect at any point of the path. Useful for `using` directives when the - ; target type is fully qualified but we want to resolve for the unqualified - ; name. - node @id_path.all_pop_begin -} - -@id_path [IdentifierPath @name [Identifier]] { - node @name.ref - attr (@name.ref) node_reference = @name - attr (@name.ref) parents = [@id_path.enclosing_def] - - node @name.pop - attr (@name.pop) pop_symbol = (source-text @name) - - edge @id_path.all_pop_begin -> @name.pop -} - -@id_path [IdentifierPath @name [Identifier] .] { - let @id_path.rightmost_identifier = @name - - let @id_path.push_begin = @name.ref - let @id_path.pop_end = @name.pop -} - -[IdentifierPath @left_name [Identifier] . [Period] . @right_name [Identifier]] { - node ref_member - attr (ref_member) push_symbol = "." - - edge @right_name.ref -> ref_member - edge ref_member -> @left_name.ref - - node pop_member - attr (pop_member) pop_symbol = "." - - edge @left_name.pop -> pop_member - edge pop_member -> @right_name.pop -} - -@id_path [IdentifierPath . @name [Identifier]] { - let @id_path.push_end = @name.ref - let @id_path.pop_begin = @name.pop -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Function, parameter declarations and modifiers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@param [Parameter @type_name [TypeName]] { - node @param.lexical_scope - node @param.def - - edge @type_name.type_ref -> @param.lexical_scope - - node @param.typeof - attr (@param.typeof) push_symbol = "@typeof" - edge @param.typeof -> @type_name.output -} - -@param [Parameter @name [Identifier]] { - attr (@param.def) node_definition = @name - attr (@param.def) definiens_node = @param - - edge @param.def -> @param.typeof -} - -@params [ParametersDeclaration] { - node @params.lexical_scope - node @params.defs - - ;; This scope can be used to resolve named argument calls - node @params.names - attr (@params.names) pop_symbol = "@param_names" - edge @params.names -> @params.defs -} - -@params [ParametersDeclaration [Parameters @param item: [Parameter]]] { - edge @param.lexical_scope -> @params.lexical_scope - edge @params.defs -> @param.def -} - -@function [FunctionDefinition @attrs [FunctionAttributes]] { - var type_symbol = "%Function" - scan (source-text @attrs) { - "\\b(public|external)\\b" { - set type_symbol = "%ExternalFunction" - } - } - - node @function.lexical_scope - node @function.def - - ; this path from the function definition to the scope allows attaching - ; functions to this function's type - node typeof - attr (typeof) push_symbol = "@typeof" - node type_function - attr (type_function) push_symbol = type_symbol - edge @function.def -> typeof - edge typeof -> type_function - edge type_function -> @function.lexical_scope -} - -@function [FunctionDefinition name: [FunctionName @name [Identifier]]] { - attr (@function.def) node_definition = @name - attr (@function.def) definiens_node = @function -} - -@function [FunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @function.lexical_scope - - ;; Input parameters are available in the function scope - edge @function.lexical_scope -> @params.defs - ;; ... and shadow other declarations - attr (@function.lexical_scope -> @params.defs) precedence = 1 - - ;; Connect to paramaters for named argument resolution - edge @function.def -> @params.names -} - -@function [FunctionDefinition returns: [ReturnsDeclaration - @return_params [ParametersDeclaration] -]] { - edge @return_params.lexical_scope -> @function.lexical_scope - - ;; Return parameters are available in the function scope - edge @function.lexical_scope -> @return_params.defs - ;; ... and shadow other declarations - attr (@function.lexical_scope -> @return_params.defs) precedence = 1 -} - -;; Only functions that return a single value have an actual return type -;; since tuples are not actual types in Solidity -@function [FunctionDefinition returns: [ReturnsDeclaration - [ParametersDeclaration [Parameters . @param [Parameter] .]] -]] { - node call - attr (call) pop_symbol = "()" - - edge @function.def -> call - edge call -> @param.typeof -} - -;; Connect the function body's block lexical scope to the function -@function [FunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @function.lexical_scope -} - -@function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute - @modifier [ModifierInvocation] -]]] { - edge @modifier.lexical_scope -> @function.lexical_scope -} - -@modifier [ModifierInvocation @name [IdentifierPath]] { - node @modifier.lexical_scope - - node modifier - attr (modifier) push_symbol = "@modifier" - - edge @name.push_end -> modifier - edge modifier -> @modifier.lexical_scope - - ; This allows resolving @name in the more general scope in constructors (since - ; calling a parent constructor is parsed as a modifier invocation) - let @modifier.identifier = @name.push_end -} - -@modifier [ModifierInvocation @args [ArgumentsDeclaration]] { - edge @args.lexical_scope -> @modifier.lexical_scope -} - -;;; Unnamed functions (deprecated) -@unnamed_function [UnnamedFunctionDefinition] { - node @unnamed_function.lexical_scope -} - -@unnamed_function [UnnamedFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @unnamed_function.lexical_scope - - 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.lexical_scope -} - -@unnamed_function [UnnamedFunctionDefinition - [UnnamedFunctionAttributes [UnnamedFunctionAttribute @modifier [ModifierInvocation]]] -] { - edge @modifier.lexical_scope -> @unnamed_function.lexical_scope -} - - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Constructors -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@constructor [ConstructorDefinition] { - node @constructor.lexical_scope - node @constructor.def -} - -@constructor [ConstructorDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @constructor.lexical_scope - - ;; Input parameters are available in the constructor scope - edge @constructor.lexical_scope -> @params.defs - ;; ... and shadow other declarations - attr (@constructor.lexical_scope -> @params.defs) precedence = 1 - - ;; Connect to paramaters for named argument resolution - edge @constructor.def -> @params.names -} - -;; Connect the constructor body's block lexical scope to the constructor -@constructor [ConstructorDefinition @block [Block]] { - edge @block.lexical_scope -> @constructor.lexical_scope -} - -@constructor [ConstructorDefinition [ConstructorAttributes item: [ConstructorAttribute - @modifier [ModifierInvocation] -]]] { - edge @modifier.lexical_scope -> @constructor.lexical_scope - edge @modifier.identifier -> @constructor.lexical_scope -} - -@contract [ContractDefinition [ContractMembers [ContractMember - @constructor [ConstructorDefinition] -]]] { - ;; This link allows calling a constructor with the named parameters syntax - edge @contract.def -> @constructor.def -} - -;; Solidity < 0.5.0 constructors -;; They were declared as functions of the contract's name - -@contract [ContractDefinition - @contract_name [Identifier] - [ContractMembers [ContractMember [FunctionDefinition - [FunctionName @function_name [Identifier]] - @params [ParametersDeclaration] - ]]] -] { - if (version-matches "< 0.5.0") { - if (eq (source-text @contract_name) (source-text @function_name)) { - ; Connect to paramaters for named argument resolution - edge @contract.def -> @params.names - } - } -} - -[ContractDefinition - @contract_name [Identifier] - [ContractMembers [ContractMember @function [FunctionDefinition - [FunctionName @function_name [Identifier]] - [FunctionAttributes [FunctionAttribute @modifier [ModifierInvocation]]] - ]]] -] { - if (version-matches "< 0.5.0") { - if (eq (source-text @contract_name) (source-text @function_name)) { - ; Parent constructor calls are parsed as modifier invocations - edge @modifier.identifier -> @function.lexical_scope - } - } -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Fallback and receive functions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@fallback [FallbackFunctionDefinition] { - node @fallback.lexical_scope -} - -@fallback [FallbackFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @fallback.lexical_scope - - ;; Input parameters are available in the fallback function scope - edge @fallback.lexical_scope -> @params.defs - attr (@fallback.lexical_scope -> @params.defs) precedence = 1 -} - -@fallback [FallbackFunctionDefinition returns: [ReturnsDeclaration - @return_params [ParametersDeclaration] -]] { - edge @return_params.lexical_scope -> @fallback.lexical_scope - - ;; Return parameters are available in the fallback function scope - 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.lexical_scope -} - -@fallback [FallbackFunctionDefinition [FallbackFunctionAttributes - item: [FallbackFunctionAttribute @modifier [ModifierInvocation]] -]] { - edge @modifier.lexical_scope -> @fallback.lexical_scope -} - -@receive [ReceiveFunctionDefinition] { - node @receive.lexical_scope -} - -@receive [ReceiveFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @receive.lexical_scope -} - -@receive [ReceiveFunctionDefinition [ReceiveFunctionAttributes - item: [ReceiveFunctionAttribute @modifier [ModifierInvocation]] -]] { - edge @modifier.lexical_scope -> @receive.lexical_scope -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Function modifiers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@modifier [ModifierDefinition] { - node @modifier.def - node @modifier.lexical_scope -} - -@modifier [ModifierDefinition - @name name: [Identifier] -] { - attr (@modifier.def) node_definition = @name - attr (@modifier.def) definiens_node = @modifier -} - -@modifier [ModifierDefinition - body: [FunctionBody @body [Block]] -] { - 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. - node placeholder_pop - attr (placeholder_pop) pop_symbol = "_" - node placeholder_ref - attr (placeholder_ref) push_symbol = "%placeholder" - - edge @body.lexical_scope -> placeholder_pop - edge placeholder_pop -> placeholder_ref - edge placeholder_ref -> @modifier.lexical_scope -} - -@modifier [ModifierDefinition @params [ParametersDeclaration]] { - edge @params.lexical_scope -> @modifier.lexical_scope - - ;; Input parameters are available in the modifier scope - edge @modifier.lexical_scope -> @params.defs - attr (@modifier.lexical_scope -> @params.defs) precedence = 1 -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Blocks and generic statements -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@block [Block] { - node @block.lexical_scope - node @block.defs -} - -;; The first statement in a block -@block [Block [Statements . @stmt [Statement]]] { - if (version-matches ">= 0.5.0") { - edge @stmt.lexical_scope -> @block.lexical_scope - } -} - -@block [Block [Statements @stmt [Statement]]] { - ;; Hoist statement definitions for Solidity < 0.5.0 - if (version-matches "< 0.5.0") { - ;; definitions are carried over to the block - edge @block.defs -> @stmt.defs - - ;; resolution happens in the context of the block - edge @stmt.lexical_scope -> @block.lexical_scope - - ;; and the statement definitions are available block's scope - edge @block.lexical_scope -> @stmt.defs - ;; ... shadowing declarations in enclosing scopes - attr (@block.lexical_scope -> @stmt.defs) precedence = 1 - } -} - -;; Two consecutive statements -[Statements @left_stmt [Statement] . @right_stmt [Statement]] { - if (version-matches ">= 0.5.0") { - edge @right_stmt.lexical_scope -> @left_stmt.lexical_scope - } -} - -@stmt [Statement] { - node @stmt.lexical_scope - node @stmt.defs - - if (version-matches ">= 0.5.0") { - ;; For Solidity >= 0.5.0, definitions are immediately available in the - ;; statement scope. For < 0.5.0 this is also true, but resolved through the - ;; enclosing block's lexical scope. - edge @stmt.lexical_scope -> @stmt.defs - ;; Statement definitions shadow other declarations in its scope - attr (@stmt.lexical_scope -> @stmt.defs) precedence = 1 - } -} - -;; Statements of type block -@stmt [Statement @block variant: [Block]] { - edge @block.lexical_scope -> @stmt.lexical_scope - - ;; Hoist block definitions (< 0.5.0) - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @block.defs - } -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Expressions & declaration statements -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; In general for statements the structure is [Statement [StmtVariant]] and we -;; will define the scoped nodes .lexical_scope and (possibly) .defs in the -;; Statement CST node, skipping scoped nodes in the variant of the statement. -;; -;; For expression statements, variable and tuple declarations we define them -;; separately from the enclosing statement to be able to use them in `for` -;; initialization and condition clauses directly. Also, because we intend to -;; reuse them, all of them must have both a .lexical_scope and .defs scoped -;; nodes (even though .defs doesn't make sense for ExpressionStatement) - -@stmt [Statement @expr_stmt [ExpressionStatement]] { - edge @expr_stmt.lexical_scope -> @stmt.lexical_scope -} - -@expr_stmt [ExpressionStatement] { - node @expr_stmt.lexical_scope -} - - -;;; Variable declaration statements - -@stmt [Statement @var_decl [VariableDeclarationStatement]] { - edge @var_decl.lexical_scope -> @stmt.lexical_scope - edge @stmt.defs -> @var_decl.def -} - -@var_decl [VariableDeclarationStatement] { - node @var_decl.lexical_scope - node @var_decl.def -} - -@var_decl [VariableDeclarationStatement - [VariableDeclarationType @var_type [TypeName]] - @name name: [Identifier] -] { - attr (@var_decl.def) node_definition = @name - attr (@var_decl.def) definiens_node = @var_decl - - edge @var_type.type_ref -> @var_decl.lexical_scope - - node typeof - attr (typeof) push_symbol = "@typeof" - - edge @var_decl.def -> typeof - edge typeof -> @var_type.output -} - -@var_decl [VariableDeclarationStatement - [VariableDeclarationType [VarKeyword]] - @name name: [Identifier] -] { - attr (@var_decl.def) node_definition = @name - attr (@var_decl.def) definiens_node = @var_decl -} - -@var_decl [VariableDeclarationStatement - [VariableDeclarationType [VarKeyword]] - [VariableDeclarationValue @value [Expression]] -] { - edge @var_decl.def -> @value.output -} - - - -;;; Tuple deconstruction statements - -@stmt [Statement @tuple_decon [TupleDeconstructionStatement]] { - edge @tuple_decon.lexical_scope -> @stmt.lexical_scope - edge @stmt.defs -> @tuple_decon.defs -} - -@tuple_decon [TupleDeconstructionStatement] { - node @tuple_decon.lexical_scope - node @tuple_decon.defs -} - -@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements - [TupleDeconstructionElement - @tuple_member [TupleMember variant: [UntypedTupleMember - @name name: [Identifier]] - ] - ] -]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @tuple_member - - edge @tuple_decon.defs -> def -} - -@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements - [TupleDeconstructionElement - @tuple_member [TupleMember variant: [TypedTupleMember - @member_type type_name: [TypeName] - @name name: [Identifier]] - ] - ] -]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @tuple_member - - edge @tuple_decon.defs -> def - edge @member_type.type_ref -> @tuple_decon.lexical_scope - - node typeof - attr (typeof) push_symbol = "@typeof" - - edge def -> typeof - edge typeof -> @member_type.output -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Control statements -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; If conditionals - -@stmt [Statement [IfStatement @body body: [Statement]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - } -} - -@stmt [Statement [IfStatement else_branch: [ElseBranch @else_body body: [Statement]]]] { - edge @else_body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @else_body.defs - } -} - -;; For loops - -@stmt [Statement [ForStatement - initialization: [ForStatementInitialization @init_stmt [ExpressionStatement]] -]] { - edge @init_stmt.lexical_scope -> @stmt.lexical_scope -} - -@stmt [Statement [ForStatement - initialization: [ForStatementInitialization @init_stmt [VariableDeclarationStatement]] -]] { - edge @init_stmt.lexical_scope -> @stmt.lexical_scope - edge @stmt.init_defs -> @init_stmt.def -} - -@stmt [Statement [ForStatement - initialization: [ForStatementInitialization @init_stmt [TupleDeconstructionStatement]] -]] { - edge @init_stmt.lexical_scope -> @stmt.lexical_scope - edge @stmt.init_defs -> @init_stmt.defs -} - -@stmt [Statement [ForStatement - condition: [ForStatementCondition @cond_stmt [ExpressionStatement]] -]] { - edge @cond_stmt.lexical_scope -> @stmt.lexical_scope - edge @cond_stmt.lexical_scope -> @stmt.init_defs -} - -@stmt [Statement [ForStatement @iter_expr iterator: [Expression]]] { - ; for the iterator expression we need an independent scope node that can - ; connect to both the for-statement *and* the definitions in the init - ; expression - node @iter_expr.lexical_scope - edge @iter_expr.lexical_scope -> @stmt.lexical_scope - edge @iter_expr.lexical_scope -> @stmt.init_defs -} - -@stmt [Statement [ForStatement @body body: [Statement]]] { - node @stmt.init_defs - - edge @body.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @stmt.init_defs - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - edge @stmt.defs -> @stmt.init_defs - } -} - -;; While loops - -@stmt [Statement [WhileStatement @body body: [Statement]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - } -} - -;; Do-while loops - -@stmt [Statement [DoWhileStatement @body body: [Statement]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - } -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Error handling -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;;; Try-catch statements - -@stmt [Statement [TryStatement @body body: [Block]]] { - edge @body.lexical_scope -> @stmt.lexical_scope -} - -@stmt [Statement [TryStatement - [ReturnsDeclaration @return_params [ParametersDeclaration]] - @body body: [Block] -]] { - edge @return_params.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @return_params.defs - ;; Similar to functions, return params shadow other declarations - attr (@body.lexical_scope -> @return_params.defs) precedence = 1 -} - -@stmt [Statement [TryStatement [CatchClauses [CatchClause - @body body: [Block] -]]]] { - edge @body.lexical_scope -> @stmt.lexical_scope -} - -@stmt [Statement [TryStatement [CatchClauses [CatchClause - [CatchClauseError @catch_params parameters: [ParametersDeclaration]] - @body body: [Block] -]]]] { - edge @catch_params.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @catch_params.defs - ;; Similar to functions, catch params shadow other declarations - attr (@body.lexical_scope -> @catch_params.defs) precedence = 1 -} - -@stmt [Statement [TryStatement [CatchClauses [CatchClause - [CatchClauseError @name [Identifier]] -]]]] { - node ref - attr (ref) node_reference = @name - - edge ref -> @stmt.lexical_scope -} - - -;;; Revert statements - -@stmt [Statement [RevertStatement @error_ident [IdentifierPath]]] { - edge @error_ident.push_end -> @stmt.lexical_scope -} - -@stmt [Statement [RevertStatement @args [ArgumentsDeclaration]]] { - edge @args.lexical_scope -> @stmt.lexical_scope -} - -[Statement [RevertStatement - @error_ident [IdentifierPath] - @args [ArgumentsDeclaration] -]] { - edge @args.refs -> @error_ident.push_begin -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Other statements -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;;; Emit -@stmt [Statement [EmitStatement - @event_ident [IdentifierPath] - @args [ArgumentsDeclaration] -]] { - edge @event_ident.push_end -> @stmt.lexical_scope - edge @args.lexical_scope -> @stmt.lexical_scope - edge @args.refs -> @event_ident.push_begin -} - -;;; Unchecked -@stmt [Statement [UncheckedBlock @block block: [Block]]] { - edge @block.lexical_scope -> @stmt.lexical_scope -} - -;;; Assembly -@stmt [Statement [AssemblyStatement @body body: [YulBlock]]] { - edge @body.lexical_scope -> @stmt.lexical_scope -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; State Variables -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@state_var [StateVariableDefinition] { - node @state_var.lexical_scope - node @state_var.def -} - -@state_var [StateVariableDefinition - @type_name type_name: [TypeName] - @name name: [Identifier] -] { - attr (@state_var.def) node_definition = @name - attr (@state_var.def) definiens_node = @state_var - - edge @type_name.type_ref -> @state_var.lexical_scope - - node @state_var.typeof - attr (@state_var.typeof) push_symbol = "@typeof" - - edge @state_var.def -> @state_var.typeof - edge @state_var.typeof -> @type_name.output -} - -@state_var [StateVariableDefinition - [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] -] { - ; Public state variables are used as functions when invoked from an external contract - node call - attr (call) pop_symbol = "()" - - ; In the general case using the getter can bind to the state variable's type - edge @state_var.def -> call - edge call -> @state_var.typeof - - ; Some complex types generate special getters (ie. arrays and mappings index - ; their contents, structs flatten most of their fields and return a tuple) - node getter - attr (getter) push_symbol = "@as_getter" - edge call -> getter - edge getter -> @state_var.typeof -} - -@state_var [StateVariableDefinition - [StateVariableDefinitionValue @value [Expression]] -] { - let @value.lexical_scope = @state_var.lexical_scope -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Enum definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@enum [EnumDefinition @name name: [Identifier]] { - node @enum.lexical_scope - node @enum.def - node @enum.members - - attr (@enum.def) node_definition = @name - attr (@enum.def) definiens_node = @enum - - node member - attr (member) pop_symbol = "." - - edge @enum.def -> member - edge member -> @enum.members - - ; Path to resolve the built-in type for enums (which is the same as for integer types) - node type - attr (type) pop_symbol = "@type" - node type_enum_type - attr (type_enum_type) push_symbol = "%IntTypeType" - edge @enum.def -> type - edge type -> type_enum_type - edge type_enum_type -> @enum.lexical_scope -} - -@enum [EnumDefinition - members: [EnumMembers @item [Identifier]] -] { - node def - attr (def) node_definition = @item - attr (def) definiens_node = @item - - edge @enum.members -> def -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Structure definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@struct [StructDefinition @name name: [Identifier]] { - node @struct.lexical_scope - node @struct.def - node @struct.members - - attr (@struct.def) node_definition = @name - attr (@struct.def) definiens_node = @struct - - ; Now connect normally to the struct members - node @struct.typeof - attr (@struct.typeof) pop_symbol = "@typeof" - node member - attr (member) pop_symbol = "." - edge @struct.def -> @struct.typeof - edge @struct.typeof -> member - edge member -> @struct.members - - ; Bind member names when using construction with named arguments - node param_names - attr (param_names) pop_symbol = "@param_names" - edge @struct.def -> param_names - edge param_names -> @struct.members - - ; Used as a function call (ie. casting), should bind to itself - node call - attr (call) pop_symbol = "()" - edge @struct.def -> call - edge call -> member -} - -@struct [StructDefinition [StructMembers - @member item: [StructMember @type_name [TypeName] @name name: [Identifier]] -]] { - node @member.def - attr (@member.def) node_definition = @name - attr (@member.def) definiens_node = @member - - edge @struct.members -> @member.def - - edge @type_name.type_ref -> @struct.lexical_scope - - node @member.typeof - attr (@member.typeof) push_symbol = "@typeof" - - edge @member.def -> @member.typeof - edge @member.typeof -> @type_name.output -} - -@struct [StructDefinition [StructMembers . @first_member [StructMember]]] { - ; As a public getter result, the value returned is a tuple with all our fields flattened - ; We only care about the first member for name binding, since tuples are not real types - node getter_call - attr (getter_call) pop_symbol = "@as_getter" - edge @struct.typeof -> getter_call - edge getter_call -> @first_member.typeof -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Event definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@event [EventDefinition @name name: [Identifier]] { - node @event.lexical_scope - node @event.def - - attr (@event.def) node_definition = @name - attr (@event.def) definiens_node = @event - - node @event.params - attr (@event.params) pop_symbol = "@param_names" - edge @event.def -> @event.params -} - -@event [EventDefinition [EventParametersDeclaration [EventParameters - [EventParameter @type_name type_name: [TypeName]] -]]] { - edge @type_name.type_ref -> @event.lexical_scope -} - -@event [EventDefinition [EventParametersDeclaration [EventParameters - @param [EventParameter - @name name: [Identifier] - ] -]]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @param - - edge @event.params -> def -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Error definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@error [ErrorDefinition @name name: [Identifier]] { - node @error.lexical_scope - node @error.def - - attr (@error.def) node_definition = @name - attr (@error.def) definiens_node = @error - - node @error.params - attr (@error.params) pop_symbol = "@param_names" - edge @error.def -> @error.params - - ; Bind to built-in errorType for accessing built-in member `.selector` - node typeof - attr (typeof) push_symbol = "@typeof" - node error_type - attr (error_type) push_symbol = "%ErrorType" - edge @error.def -> typeof - edge typeof -> error_type - edge error_type -> @error.lexical_scope -} - -@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters - [ErrorParameter @type_name type_name: [TypeName]] -]]] { - edge @type_name.type_ref -> @error.lexical_scope -} - -@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters - @param [ErrorParameter - @name name: [Identifier] - ] -]]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @param - - edge @error.params -> def -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Other named definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@constant [ConstantDefinition] { - node @constant.lexical_scope - node @constant.def -} - -@constant [ConstantDefinition - @type_name type_name: [TypeName] - @name name: [Identifier] -] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @constant - - edge @constant.def -> def - - edge @type_name.type_ref -> @constant.lexical_scope -} - -@user_type [UserDefinedValueTypeDefinition @name [Identifier] @value_type [ElementaryType]] { - node @user_type.lexical_scope - node @user_type.def - - attr (@user_type.def) node_definition = @name - attr (@user_type.def) definiens_node = @user_type - - ; Provide member resolution through the built-in `%userTypeType` - ; Because the built-in is defined as a struct, we need to push an extra `@typeof` - node member_guard - attr (member_guard) pop_symbol = "." - node member - attr (member) push_symbol = "." - node typeof - attr (typeof) push_symbol = "@typeof" - node user_type_type - attr (user_type_type) push_symbol = "%UserDefinedValueType" - - edge @user_type.def -> member_guard - edge member_guard -> member - edge member -> typeof - edge typeof -> user_type_type - edge user_type_type -> @user_type.lexical_scope - - ; Hard-code built-in functions `wrap` and `unwrap` in order to be able to - ; resolve their return types - node wrap - attr (wrap) pop_symbol = "wrap" - node wrap_call - attr (wrap_call) pop_symbol = "()" - node wrap_typeof - attr (wrap_typeof) push_symbol = "@typeof" - - edge member_guard -> wrap - edge wrap -> wrap_call - edge wrap_call -> wrap_typeof - edge wrap_typeof -> @value_type.ref - edge @value_type.ref -> @user_type.lexical_scope - - node unwrap - attr (unwrap) pop_symbol = "unwrap" - node unwrap_call - attr (unwrap_call) pop_symbol = "()" - node unwrap_typeof - attr (unwrap_typeof) push_symbol = "@typeof" - node type_ref - attr (type_ref) push_symbol = (source-text @name) - - edge member_guard -> unwrap - edge unwrap -> unwrap_call - edge unwrap_call -> unwrap_typeof - edge unwrap_typeof -> type_ref - edge type_ref -> @user_type.lexical_scope -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Expressions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; Expressions have two important scoped variables: -;; - @expr.lexical_scope should be set by the enclosing node to provide a scope -;; for name resolution -;; - @expr.output is a node provided by the expression and represents the output -;; of the expression for chaining eg. with a member access - -@expr [Expression] { - ;; this is an output scope for use in member access (and other uses) - node @expr.output -} - -;; Identifier expressions -@expr [Expression @name [Identifier]] { - node ref - attr (ref) node_reference = @name - attr (ref) parents = [@expr.enclosing_def] - - edge ref -> @expr.lexical_scope - edge @expr.output -> ref -} - -@expr [Expression @keyword ([ThisKeyword] | [SuperKeyword])] { - ; This is almost equivalent to the above rule, except it doesn't generate a reference - node keyword - attr (keyword) push_symbol = (source-text @keyword) - edge keyword -> @expr.lexical_scope - edge @expr.output -> keyword -} - -;; Member access expressions -@expr [Expression [MemberAccessExpression - @operand operand: [Expression] - @name member: [Identifier] -]] { - node @name.ref - attr (@name.ref) node_reference = @name - attr (@name.ref) parents = [@expr.enclosing_def] - - node member - attr (member) push_symbol = "." - - edge @name.ref -> member - edge member -> @operand.output - - edge @expr.output -> @name.ref - - ; Shortcut path for expressions inside contracts with using X for * directives - edge member -> @expr.star_extension -} - -;; Special case: member accesses to `super` are tagged with "super" to rank -;; virtual methods correctly -[MemberAccessExpression - operand: [Expression [SuperKeyword]] - @name member: [Identifier] -] { - attr (@name.ref) tag = "super" -} - -;; Elementary types used as expressions (eg. for type casting, or for built-ins like `string.concat`) -@expr [Expression @type [ElementaryType]] { - edge @expr.output -> @type.ref - edge @type.ref -> @expr.lexical_scope - - ; Elementary types can also be used for casting; instead of defining built-in - ; struct for each available elementary type, we define a special path here - node call - attr (call) pop_symbol = "()" - node typeof - attr (typeof) push_symbol = "@typeof" - edge @expr.output -> call - edge call -> typeof - edge typeof -> @type.ref -} - -;; Index access expressions -@expr [Expression [IndexAccessExpression - @operand operand: [Expression] -]] { - node index - attr (index) push_symbol = "[]" - - edge @expr.output -> index - edge index -> @operand.output -} - -;; Type expressions -@type_expr [Expression [TypeExpression @type [TypeName]]] { - edge @type.type_ref -> @type_expr.lexical_scope -} - -@type_expr [Expression [TypeExpression [TypeName [ElementaryType ([IntKeyword] | [UintKeyword])]]]] { - ; For integer types the type's type is fixed - node typeof - attr (typeof) push_symbol = "@typeof" - node type - attr (type) push_symbol = "%IntTypeType" - - edge @type_expr.output -> typeof - edge typeof -> type - edge type -> @type_expr.lexical_scope -} - -@type_expr [Expression [TypeExpression [TypeName @id_path [IdentifierPath]]]] { - ; For other identifiers, resolve it through a pseudo-member `%type` - node typeof - attr (typeof) push_symbol = "@typeof" - node type - attr (type) push_symbol = "@type" - - edge @type_expr.output -> typeof - edge typeof -> type - edge type -> @id_path.push_begin -} - -;; New expressions - -@new_expr [Expression [NewExpression @type [TypeName]]] { - edge @type.type_ref -> @new_expr.lexical_scope - edge @new_expr.output -> @type.output -} - - -;;; Function call expressions - -@args [ArgumentsDeclaration] { - node @args.lexical_scope - - node @args.refs - attr (@args.refs) push_symbol = "@param_names" -} - -@named_arg [NamedArgument @name [Identifier] [Colon] [Expression]] { - node @named_arg.lexical_scope - - node @named_arg.ref - attr (@named_arg.ref) node_reference = @name -} - -@args [ArgumentsDeclaration [NamedArgumentsDeclaration - [NamedArgumentGroup [NamedArguments @argument [NamedArgument]]] -]] { - edge @argument.lexical_scope -> @args.lexical_scope - edge @argument.ref -> @args.refs -} - -@funcall [Expression [FunctionCallExpression - @operand [Expression] - @args [ArgumentsDeclaration] -]] { - edge @args.lexical_scope -> @funcall.lexical_scope - - ;; Connect to the output of the function name to be able to resolve named arguments - edge @args.refs -> @operand.output - - node call - attr (call) push_symbol = "()" - - edge @funcall.output -> call - edge call -> @operand.output -} - - -;;; Call options - -@expr [Expression [CallOptionsExpression @operand [Expression] @options [CallOptions]]] { - edge @expr.output -> @operand.output - - node @options.refs - attr (@options.refs) push_symbol = "@param_names" - - node call_options - attr (call_options) push_symbol = "%CallOptions" - - edge @options.refs -> call_options - edge call_options -> @expr.lexical_scope -} - -@expr [Expression [CallOptionsExpression - @options [CallOptions @named_arg [NamedArgument]] -]] { - edge @named_arg.lexical_scope -> @expr.lexical_scope - edge @named_arg.ref -> @options.refs -} - - -;;; Payable -; These work like `address`, should they should bind to `%address` -@expr [Expression [PayableKeyword]] { - node ref - attr (ref) push_symbol = "%address" - - edge ref -> @expr.lexical_scope - edge @expr.output -> ref -} - - -;;; Tuple expressions - -; Parenthesized expressions are parsed as tuples of a single value -@expr [Expression [TupleExpression [TupleValues . [TupleValue @operand [Expression]] .]]] { - edge @expr.output -> @operand.output -} - -;;; Arithmetic, bitwise & logical operators, etc - -; Bind to the left operand only: assignment expressions -@expr [Expression [_ - @left_operand left_operand: [Expression] - ( - [Equal] - | [BarEqual] - | [PlusEqual] - | [MinusEqual] - | [CaretEqual] - | [SlashEqual] - | [PercentEqual] - | [AsteriskEqual] - | [AmpersandEqual] - | [LessThanLessThanEqual] - | [GreaterThanGreaterThanEqual] - | [GreaterThanGreaterThanGreaterThanEqual] - ) -]] { - edge @expr.output -> @left_operand.output -} - -; Unary operators postfix -@expr [Expression [_ - @operand operand: [Expression] - ([PlusPlus] | [MinusMinus]) -]] { - edge @expr.output -> @operand.output -} - -; Unary operators prefix -@expr [Expression [_ - ([PlusPlus] | [MinusMinus] | [Tilde] | [Bang] | [Minus] | [Plus]) - @operand operand: [Expression] -]] { - edge @expr.output -> @operand.output -} - -; Bind to both operands: logical and/or, arithmetic, bit-wise expressions -@expr [Expression [_ - @left_operand left_operand: [Expression] - ( - [BarBar] - | [AmpersandAmpersand] - - | [Plus] - | [Minus] - | [Asterisk] - | [Slash] - | [Percent] - | [AsteriskAsterisk] - - | [Bar] - | [Caret] - | [Ampersand] - - | [LessThanLessThan] - | [GreaterThanGreaterThan] - | [GreaterThanGreaterThanGreaterThan] - ) - @right_operand right_operand: [Expression] -]] { - edge @expr.output -> @left_operand.output - edge @expr.output -> @right_operand.output -} - -; Comparison operators bind to bool type -@expr [Expression [_ - ( - [EqualEqual] - | [BangEqual] - | [LessThan] - | [GreaterThan] - | [LessThanEqual] - | [GreaterThanEqual] - ) -]] { - node typeof - attr (typeof) push_symbol = "@typeof" - node bool - attr (bool) push_symbol = "%bool" - edge @expr.output -> typeof - edge typeof -> bool - edge bool -> @expr.lexical_scope -} - -; Ternary conditional expression binds to both branches -@expr [Expression [ConditionalExpression - @true_expression true_expression: [Expression] - @false_expression false_expression: [Expression] -]] { - edge @expr.output -> @true_expression.output - edge @expr.output -> @false_expression.output -} - - -;;; Literal Address Expressions -@expr [Expression [HexNumberExpression @hex_literal [HexLiteral]]] { - scan (source-text @hex_literal) { - "0x[0-9a-fA-F]{40}" { - ; Treat it as a valid address - node typeof - attr (typeof) push_symbol = "@typeof" - node address - attr (address) push_symbol = "%address" - edge @expr.output -> typeof - edge typeof -> address - edge address -> @expr.lexical_scope - } - } -} - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Yul -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;;; Blocks and statements - -@block [YulBlock] { - node @block.lexical_scope - ; Variables defined in this block (only used to forward the init block - ; declarations in a for statement) - node @block.variable_defs - ; Function definitions accessible from the block (ie. defined in the block, or - ; accessible in the enclosing parent block) - node @block.function_defs - - edge @block.lexical_scope -> @block.function_defs -} - -@block [YulBlock [YulStatements . @stmt [YulStatement]]] { - edge @stmt.lexical_scope -> @block.lexical_scope -} - -@block [YulBlock [YulStatements @stmt [YulStatement]]] { - edge @stmt.function_scope -> @block.function_defs - edge @block.variable_defs -> @stmt.defs -} - -[YulStatements @left_stmt [YulStatement] . @right_stmt [YulStatement]] { - edge @right_stmt.lexical_scope -> @left_stmt.lexical_scope - ; variable declaration are accessible from the next statement - edge @right_stmt.lexical_scope -> @left_stmt.defs -} - -@stmt [YulStatement] { - node @stmt.lexical_scope - node @stmt.defs - ;; Functions visible in this scope (to propagate to inner function - ;; definitions, since the lexical scope is not accessible inside a function - ;; body) - node @stmt.function_scope -} - -;;; Blocks as statements - -@stmt [YulStatement @block variant: [YulBlock]] { - edge @block.lexical_scope -> @stmt.lexical_scope - edge @block.function_defs -> @stmt.function_scope -} - -;;; Expression as statements - -@stmt [YulStatement @expr_stmt [YulExpression]] { - edge @expr_stmt.lexical_scope -> @stmt.lexical_scope -} - -;;; Variable declarations - -@stmt [YulStatement @var_decl [YulVariableDeclarationStatement]] { - edge @var_decl.lexical_scope -> @stmt.lexical_scope - edge @stmt.defs -> @var_decl.defs -} - -@var_decl [YulVariableDeclarationStatement] { - node @var_decl.lexical_scope - node @var_decl.defs -} - -@var_decl [YulVariableDeclarationStatement [YulVariableNames @name [YulIdentifier]]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @var_decl - - edge @var_decl.defs -> def -} - -@var_decl [YulVariableDeclarationStatement [YulVariableDeclarationValue - @value [YulExpression] -]] { - edge @value.lexical_scope -> @var_decl.lexical_scope -} - -;;; Variable assignments - -@stmt [YulStatement @var_assign [YulVariableAssignmentStatement]] { - edge @var_assign.lexical_scope -> @stmt.lexical_scope -} - -@var_assign [YulVariableAssignmentStatement] { - node @var_assign.lexical_scope -} - -@var_assign [YulVariableAssignmentStatement [YulPaths @path [YulPath]]] { - edge @path.lexical_scope -> @var_assign.lexical_scope -} - -@var_assign [YulVariableAssignmentStatement @expr expression: [YulExpression]] { - edge @expr.lexical_scope -> @var_assign.lexical_scope -} - -;;; Function definitions - -@block [YulBlock [YulStatements [YulStatement @fundef [YulFunctionDefinition]]]] { - ;; Function definitions are hoisted in the enclosing block - edge @block.function_defs -> @fundef.def - ;; The only definitions available in the function's lexical scope (other than - ;; parameters) are functions (ie. the body of the function doesn't have access - ;; to any outside variables) - edge @fundef.lexical_scope -> @block.function_defs - ; Exception: but outside constants *are* available, so we provide a guarded - ; access to the parent lexical scope. This guard will be popped to link to - ; available constants. - node yul_function_guard - attr (yul_function_guard) push_symbol = "@in_yul_function" - edge @fundef.lexical_scope -> yul_function_guard - edge yul_function_guard -> @block.lexical_scope -} - -;; Constants need to be available inside Yul functions. This is an exception -;; since no other external identifiers are, so the path is guarded. We create a -;; scope in the source unit, contracts and libraries, and guard it from the -;; lexical scope, so we can link constant definitions here. See the dual path in -;; the rule above. -@constant_container ([SourceUnit] | [ContractDefinition] | [LibraryDefinition]) { - node @constant_container.yul_functions_guarded_scope - attr (@constant_container.yul_functions_guarded_scope) pop_symbol = "@in_yul_function" - edge @constant_container.lexical_scope -> @constant_container.yul_functions_guarded_scope -} - -;; Make top-level constants available inside Yul functions -@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember @constant [ConstantDefinition]]]] { - edge @source_unit.yul_functions_guarded_scope -> @constant.def -} - -;; Ditto for contracts, interfaces and libraries -@contract [_ members: [_ [ContractMember - @constant [StateVariableDefinition - [StateVariableAttributes [StateVariableAttribute [ConstantKeyword]]] - ] -]]] { - edge @contract.yul_functions_guarded_scope -> @constant.def -} - -@fundef [YulFunctionDefinition - @name name: [YulIdentifier] - @body body: [YulBlock] -] { - node @fundef.lexical_scope - node @fundef.def - - node def - attr (def) node_definition = @name - attr (def) definiens_node = @fundef - - edge @fundef.def -> def - edge @body.lexical_scope -> @fundef.lexical_scope -} - -@fundef [YulFunctionDefinition [YulParametersDeclaration [YulParameters - @param [YulIdentifier] -]]] { - node def - attr (def) node_definition = @param - attr (def) definiens_node = @param - - edge @fundef.lexical_scope -> def -} - -@fundef [YulFunctionDefinition [YulReturnsDeclaration [YulVariableNames - @return_param [YulIdentifier] -]]] { - node def - attr (def) node_definition = @return_param - attr (def) definiens_node = @return_param - - edge @fundef.lexical_scope -> def -} - -;;; Stack assignment (Solidity < 0.5.0) - -@stmt [YulStatement [YulStackAssignmentStatement @name [YulIdentifier]]] { - node ref - attr (ref) node_reference = @name - - edge ref -> @stmt.lexical_scope -} - -;;; If statements - -@stmt [YulStatement [YulIfStatement - @condition condition: [YulExpression] - @body body: [YulBlock] -]] { - edge @condition.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @stmt.lexical_scope - edge @body.function_defs -> @stmt.function_scope -} - -;;; Switch statements - -@stmt [YulStatement [YulSwitchStatement - @expr expression: [YulExpression] -]] { - edge @expr.lexical_scope -> @stmt.lexical_scope -} - -@stmt [YulStatement [YulSwitchStatement [YulSwitchCases [YulSwitchCase - [_ @body body: [YulBlock]] -]]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - edge @body.function_defs -> @stmt.function_scope -} - -;;; For statements - -@stmt [YulStatement [YulForStatement - @init initialization: [YulBlock] - @cond condition: [YulExpression] - @iter iterator: [YulBlock] - @body body: [YulBlock] -]] { - edge @init.lexical_scope -> @stmt.lexical_scope - edge @cond.lexical_scope -> @stmt.lexical_scope - edge @iter.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @stmt.lexical_scope - - edge @cond.lexical_scope -> @init.variable_defs - edge @iter.lexical_scope -> @init.variable_defs - edge @body.lexical_scope -> @init.variable_defs -} - -;;; Label statements (Solidity < 0.5.0) - -@block [YulBlock [YulStatements [YulStatement @label [YulLabel @name label: [YulIdentifier]]]]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @label - - ; Labels are hoisted to the beginning of the block - edge @block.lexical_scope -> def -} - -;;; Expressions - -@expr [YulExpression] { - node @expr.lexical_scope -} - -@expr [YulExpression @path [YulPath]] { - edge @path.lexical_scope -> @expr.lexical_scope -} - -@path [YulPath] { - node @path.lexical_scope -} - -@path [YulPath . @name [YulIdentifier]] { - node ref - attr (ref) node_reference = @name - - edge ref -> @path.lexical_scope - - if (version-matches "< 0.7.0") { - ; Before Solidity 0.7.0 storage variables' `.offset` and `.slot` were - ; accessed by suffixing the name with `_offset` and `_slot` - scan (source-text @name) { - "^(.*)_(slot|offset|length)$" { - let symbol = $0 - let without_suffix = $1 - let suffix = $2 - - ; We bind the whole symbol to the built-in field for the known cases - node pop_ref - attr (pop_ref) pop_symbol = symbol - node push_suffixless - attr (push_suffixless) push_symbol = suffix - node member_of - attr (member_of) push_symbol = "." - node typeof - attr (typeof) push_symbol = "@typeof" - node yul_external - attr (yul_external) push_symbol = "%YulExternal" - - edge ref -> pop_ref - edge pop_ref -> push_suffixless - edge push_suffixless -> member_of - edge member_of -> typeof - edge typeof -> yul_external - edge yul_external -> @path.lexical_scope - } - } - } -} - -@path [YulPath [Period] @member [YulIdentifier] .] { - ; Yul variable members only apply to external variables and hence are - ; automatically bound to a special %YulExternal built-in - node ref - attr (ref) node_reference = @member - node member_of - attr (member_of) push_symbol = "." - node typeof - attr (typeof) push_symbol = "@typeof" - node yul_external - attr (yul_external) push_symbol = "%YulExternal" - - edge ref -> member_of - edge member_of -> typeof - edge typeof -> yul_external - edge yul_external -> @path.lexical_scope -} - -@expr [YulExpression @funcall [YulFunctionCallExpression]] { - edge @funcall.lexical_scope -> @expr.lexical_scope -} - -@funcall [YulFunctionCallExpression - @operand operand: [YulExpression] - @args arguments: [YulArguments] -] { - node @funcall.lexical_scope - - edge @operand.lexical_scope -> @funcall.lexical_scope - edge @args.lexical_scope -> @funcall.lexical_scope -} - -@args [YulArguments] { - node @args.lexical_scope -} - -@args [YulArguments @arg [YulExpression]] { - edge @arg.lexical_scope -> @args.lexical_scope -} diff --git a/crates/solidity/inputs/language/bindings/user-rules.parts b/crates/solidity/inputs/language/bindings/user-rules.parts index 631b2dabb8..4d02716c71 100644 --- a/crates/solidity/inputs/language/bindings/user-rules.parts +++ b/crates/solidity/inputs/language/bindings/user-rules.parts @@ -1 +1,7 @@ -rules.msgb +00-top-level.msgb +01-definitions.msgb +02-other-definitions.msgb +03-types.msgb +04-statements.msgb +05-expressions.msgb +06-yul.msgb 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 28d75105e5..781a3f9e0e 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 @@ -163,10 +163,8 @@ inherit .star_extension ;; This node represents the imported file and the @path.import node is used by ;; all subsequent import rules node @path.import - let resolved_path = (resolve-path FILE_PATH @path) attr (@path.import) push_symbol = resolved_path - edge @path.import -> ROOT_NODE } @@ -272,63 +270,6 @@ inherit .star_extension } -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Common inheritance rules (apply to contracts and interfaces) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@specifier [InheritanceSpecifier [InheritanceTypes - [InheritanceType @type_name [IdentifierPath]] -]] { - ;; This should point to the enclosing contract or interface definition - let heir = @specifier.heir - - ;; Resolve base names through the parent scope of our heir (contract or - ;; interface), aka the source unit - edge @type_name.push_end -> heir.parent_scope - - ; Access instance members of the inherited contract/interface, from the - ; instance scope of the inheriting contract/interface - node instance - attr (instance) push_symbol = "@instance" - edge heir.instance -> instance - edge instance -> @type_name.push_begin - - ; Base members can also be accessed (from the instance scope) qualified with - ; the base name (eg. `Base.something`) - node member_pop - attr (member_pop) pop_symbol = "." - edge heir.instance -> @type_name.pop_begin - edge @type_name.pop_end -> member_pop - edge member_pop -> instance - - ; Base namespace-like members (ie. enums, structs, etc) are also accessible as - ; our own namespace members - node ns_member - attr (ns_member) push_symbol = "." - edge heir.ns -> ns_member - edge ns_member -> @type_name.push_begin -} - -;; The next couple of rules setup a `.parent_refs` attribute to use in the -;; resolution algorithm to perform linearisation of a contract hierarchy. - -;; NOTE: we use anchors here to prevent the query engine from returning all the -;; sublists of possible parents -@specifier [InheritanceSpecifier [InheritanceTypes . @parents [_]+ .]] { - var parent_refs = [] - for parent in @parents { - if (eq (node-type parent) "InheritanceType") { - ;; this is intentionally reversed because of how Solidity linearised the contract bases - set parent_refs = (concat [parent.ref] parent_refs) - } - } - let @specifier.parent_refs = parent_refs -} - -@parent [InheritanceType @type_name [IdentifierPath]] { - let @parent.ref = @type_name.push_begin -} - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Contracts @@ -747,6 +688,64 @@ inherit .star_extension } +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Common inheritance rules (apply to contracts and interfaces) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@specifier [InheritanceSpecifier [InheritanceTypes + [InheritanceType @type_name [IdentifierPath]] +]] { + ;; This should point to the enclosing contract or interface definition + let heir = @specifier.heir + + ;; Resolve base names through the parent scope of our heir (contract or + ;; interface), aka the source unit + edge @type_name.push_end -> heir.parent_scope + + ; Access instance members of the inherited contract/interface, from the + ; instance scope of the inheriting contract/interface + node instance + attr (instance) push_symbol = "@instance" + edge heir.instance -> instance + edge instance -> @type_name.push_begin + + ; Base members can also be accessed (from the instance scope) qualified with + ; the base name (eg. `Base.something`) + node member_pop + attr (member_pop) pop_symbol = "." + edge heir.instance -> @type_name.pop_begin + edge @type_name.pop_end -> member_pop + edge member_pop -> instance + + ; Base namespace-like members (ie. enums, structs, etc) are also accessible as + ; our own namespace members + node ns_member + attr (ns_member) push_symbol = "." + edge heir.ns -> ns_member + edge ns_member -> @type_name.push_begin +} + +;; The next couple of rules setup a `.parent_refs` attribute to use in the +;; resolution algorithm to perform linearisation of a contract hierarchy. + +;; NOTE: we use anchors here to prevent the query engine from returning all the +;; sublists of possible parents +@specifier [InheritanceSpecifier [InheritanceTypes . @parents [_]+ .]] { + var parent_refs = [] + for parent in @parents { + if (eq (node-type parent) "InheritanceType") { + ;; this is intentionally reversed because of how Solidity linearised the contract bases + set parent_refs = (concat [parent.ref] parent_refs) + } + } + let @specifier.parent_refs = parent_refs +} + +@parent [InheritanceType @type_name [IdentifierPath]] { + let @parent.ref = @type_name.push_begin +} + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Libraries ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -837,1477 +836,1476 @@ inherit .star_extension edge @library.star_extension -> @library.lexical_scope } + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Using directives +;;; Function, parameter declarations and modifiers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@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 - ; target type - node @using.def +@param [Parameter @type_name [TypeName]] { + node @param.lexical_scope + node @param.def - ; This internal node connects the definition side of the clause to the target - ; for resolution, and allows handling the multiple cases of `using` syntax - ; easily - node @using.clause -} + edge @type_name.type_ref -> @param.lexical_scope -@using [UsingDirective [UsingClause @id_path [IdentifierPath]]] { - ; resolve the library to be used in the directive - edge @id_path.push_end -> @using.lexical_scope + node @param.typeof + attr (@param.typeof) push_symbol = "@typeof" + edge @param.typeof -> @type_name.output +} - ; 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 - ; need to verify that we're only using this path when resolving a function - ; access to the target type, not the target type itself - node dot_guard_pop - attr (dot_guard_pop) pop_symbol = "." - node dot_guard_push - attr (dot_guard_push) push_symbol = "." +@param [Parameter @name [Identifier]] { + attr (@param.def) node_definition = @name + attr (@param.def) definiens_node = @param - edge @using.clause -> dot_guard_pop - edge dot_guard_pop -> dot_guard_push - edge dot_guard_push -> @id_path.push_begin + edge @param.def -> @param.typeof } -@using [UsingDirective [UsingClause [UsingDeconstruction - [UsingDeconstructionSymbols [UsingDeconstructionSymbol - @id_path [IdentifierPath] - ]] -]]] { - ; resolve the function to be used in the directive - edge @id_path.push_end -> @using.lexical_scope +@params [ParametersDeclaration] { + node @params.lexical_scope + node @params.defs - node dot - attr (dot) pop_symbol = "." - node last_identifier - attr (last_identifier) pop_symbol = (source-text @id_path.rightmost_identifier) + ;; This scope can be used to resolve named argument calls + node @params.names + attr (@params.names) pop_symbol = "@param_names" + edge @params.names -> @params.defs +} - edge @using.clause -> dot - edge dot -> last_identifier - edge last_identifier -> @id_path.push_begin +@params [ParametersDeclaration [Parameters @param item: [Parameter]]] { + edge @param.lexical_scope -> @params.lexical_scope + edge @params.defs -> @param.def } -@using [UsingDirective [UsingTarget @type_name [TypeName]]] { - ; pop the type symbols to connect to the attached function (via @using.clause) - node typeof - attr (typeof) pop_symbol = "@typeof" - node cast - attr (cast) pop_symbol = "()" +@function [FunctionDefinition @attrs [FunctionAttributes]] { + var type_symbol = "%Function" + scan (source-text @attrs) { + "\\b(public|external)\\b" { + set type_symbol = "%ExternalFunction" + } + } - ; We connect to all_pop_begin to be able to resolve both qualified and - ; unqualified instances of the target type - edge @using.def -> @type_name.all_pop_begin - edge @type_name.pop_end -> typeof - edge typeof -> @using.clause - edge @type_name.pop_end -> cast - edge cast -> @using.clause + node @function.lexical_scope + node @function.def - ; resolve the target type of the directive on the lexical scope - edge @type_name.type_ref -> @using.lexical_scope + ; this path from the function definition to the scope allows attaching + ; functions to this function's type + node typeof + attr (typeof) push_symbol = "@typeof" + node type_function + attr (type_function) push_symbol = type_symbol + edge @function.def -> typeof + edge typeof -> type_function + edge type_function -> @function.lexical_scope } -[ContractMember @using [UsingDirective [UsingTarget [Asterisk]]]] { - ; using X for * is only allowed inside contracts - node star - attr (star) pop_symbol = "@*" - edge @using.def -> star - edge star -> @using.clause +@function [FunctionDefinition name: [FunctionName @name [Identifier]]] { + attr (@function.def) node_definition = @name + attr (@function.def) definiens_node = @function } +@function [FunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @function.lexical_scope -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Type names -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Input parameters are available in the function scope + edge @function.lexical_scope -> @params.defs + ;; ... and shadow other declarations + attr (@function.lexical_scope -> @params.defs) precedence = 1 -;; TypeName nodes should define these scoped variables: -;; -;; - @type_name.type_ref represents the node in the graph where we're ready to -;; resolve the type, and thus should generally be connected to a (lexical) -;; scope node (source node, outside edges connect *from* here). -;; -;; - @type_name.output represents the other end of the type and corresponds to a -;; state where the type has already been resolved so we can, for example -;; resolve its members (sink node, outside edges connect *to* here). -;; -;; - @type_name.pop_begin, @type_name.pop_end are used in a definition context, -;; ie. when we need to pop the type name symbol(s) from the symbol stack. -;; Additionally, @type_name.all_pop_begin links to each symbol in a typename -;; (ie. in an identifier path typename), which allows referring to a type both -;; qualified and unqualified. + ;; Connect to paramaters for named argument resolution + edge @function.def -> @params.names +} -@type_name [TypeName @elementary [ElementaryType]] { - let @type_name.type_ref = @elementary.ref - let @type_name.output = @elementary.ref - let @type_name.pop_begin = @elementary.pop - let @type_name.pop_end = @elementary.pop - let @type_name.all_pop_begin = @elementary.pop -} +@function [FunctionDefinition returns: [ReturnsDeclaration + @return_params [ParametersDeclaration] +]] { + edge @return_params.lexical_scope -> @function.lexical_scope -@type_name [TypeName @id_path [IdentifierPath]] { - ;; For an identifier path used as a type, the left-most element is the one - ;; that connects to the parent lexical scope, because the name resolution - ;; starts at the left of the identifier. - let @type_name.type_ref = @id_path.push_end + ;; Return parameters are available in the function scope + edge @function.lexical_scope -> @return_params.defs + ;; ... and shadow other declarations + attr (@function.lexical_scope -> @return_params.defs) precedence = 1 +} - ;; Conversely, the complete type is found at the right-most name, and that's - ;; where users of this type should link to (eg. a variable declaration). - let @type_name.output = @id_path.push_begin +;; Only functions that return a single value have an actual return type +;; since tuples are not actual types in Solidity +@function [FunctionDefinition returns: [ReturnsDeclaration + [ParametersDeclaration [Parameters . @param [Parameter] .]] +]] { + node call + attr (call) pop_symbol = "()" - let @type_name.pop_begin = @id_path.pop_begin - let @type_name.pop_end = @id_path.pop_end - let @type_name.all_pop_begin = @id_path.all_pop_begin + edge @function.def -> call + edge call -> @param.typeof } -@type_name [TypeName @type_variant ([ArrayTypeName] | [FunctionType])] { - let @type_name.type_ref = @type_variant.lexical_scope - let @type_name.output = @type_variant.output - let @type_name.pop_begin = @type_variant.pop_begin - let @type_name.pop_end = @type_variant.pop_end - let @type_name.all_pop_begin = @type_variant.pop_begin +;; Connect the function body's block lexical scope to the function +@function [FunctionDefinition [FunctionBody @block [Block]]] { + edge @block.lexical_scope -> @function.lexical_scope } -@type_name [TypeName @mapping [MappingType]] { - let @type_name.type_ref = @mapping.lexical_scope - let @type_name.output = @mapping.output - let @type_name.pop_begin = @mapping.pop_begin - let @type_name.pop_end = @mapping.pop_end - let @type_name.all_pop_begin = @mapping.pop_begin +@function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute + @modifier [ModifierInvocation] +]]] { + edge @modifier.lexical_scope -> @function.lexical_scope } +@modifier [ModifierInvocation @name [IdentifierPath]] { + node @modifier.lexical_scope -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Elementary types -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@elementary [ElementaryType] { - node @elementary.ref - attr (@elementary.ref) type = "push_symbol" - attr (@elementary.ref) source_node = @elementary, symbol = @elementary.symbol - - node @elementary.pop - attr (@elementary.pop) pop_symbol = @elementary.symbol + node modifier + attr (modifier) push_symbol = "@modifier" - ; These variables are a bit redundant, but necessary to easily use elementary - ; types as mapping keys - let @elementary.pop_begin = @elementary.pop - let @elementary.pop_end = @elementary.pop - let @elementary.all_pop_begin = @elementary.pop + edge @name.push_end -> modifier + edge modifier -> @modifier.lexical_scope - let @elementary.push_begin = @elementary.ref - let @elementary.push_end = @elementary.ref + ; This allows resolving @name in the more general scope in constructors (since + ; calling a parent constructor is parsed as a modifier invocation) + let @modifier.identifier = @name.push_end } -@elementary [ElementaryType [AddressType]] { - let @elementary.symbol = "%address" +@modifier [ModifierInvocation @args [ArgumentsDeclaration]] { + edge @args.lexical_scope -> @modifier.lexical_scope } -@elementary [ElementaryType [BoolKeyword]] { - let @elementary.symbol = "%bool" -} -@elementary [ElementaryType [ByteKeyword]] { - let @elementary.symbol = "%byte" -} +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; State Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@elementary [ElementaryType @keyword [BytesKeyword]] { - let @elementary.symbol = (format "%{}" (source-text @keyword)) +@state_var [StateVariableDefinition] { + node @state_var.lexical_scope + node @state_var.def } -@elementary [ElementaryType [StringKeyword]] { - let @elementary.symbol = "%string" -} +@state_var [StateVariableDefinition + @type_name type_name: [TypeName] + @name name: [Identifier] +] { + attr (@state_var.def) node_definition = @name + attr (@state_var.def) definiens_node = @state_var -@elementary [ElementaryType @keyword [IntKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "int") { - let @elementary.symbol = "%int256" - } else { - let @elementary.symbol = (format "%{}" symbol) - } -} + edge @type_name.type_ref -> @state_var.lexical_scope -@elementary [ElementaryType @keyword [UintKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "uint") { - let @elementary.symbol = "%uint256" - } else { - let @elementary.symbol = (format "%{}" symbol) - } + node @state_var.typeof + attr (@state_var.typeof) push_symbol = "@typeof" + + edge @state_var.def -> @state_var.typeof + edge @state_var.typeof -> @type_name.output } -@elementary [ElementaryType @keyword [FixedKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "fixed") { - let @elementary.symbol = "%fixed128x18" - } else { - let @elementary.symbol = (format "%{}" symbol) - } +@state_var [StateVariableDefinition + [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] +] { + ; Public state variables are used as functions when invoked from an external contract + node call + attr (call) pop_symbol = "()" + + ; In the general case using the getter can bind to the state variable's type + edge @state_var.def -> call + edge call -> @state_var.typeof + + ; Some complex types generate special getters (ie. arrays and mappings index + ; their contents, structs flatten most of their fields and return a tuple) + node getter + attr (getter) push_symbol = "@as_getter" + edge call -> getter + edge getter -> @state_var.typeof } -@elementary [ElementaryType @keyword [UfixedKeyword]] { - let symbol = (source-text @keyword) - if (eq symbol "ufixed") { - let @elementary.symbol = "%ufixed128x18" - } else { - let @elementary.symbol = (format "%{}" symbol) - } +@state_var [StateVariableDefinition + [StateVariableDefinitionValue @value [Expression]] +] { + let @value.lexical_scope = @state_var.lexical_scope } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Mappings +;;; Structure definitions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@mapping [MappingType - [MappingKey [MappingKeyType @key_type ([IdentifierPath] | [ElementaryType])]] - [MappingValue @value_type [TypeName]] -] { - node @mapping.lexical_scope - node @mapping.output +@struct [StructDefinition @name name: [Identifier]] { + node @struct.lexical_scope + node @struct.def + node @struct.members - ; Define the pushing path of the mapping type - ; ValueType <- top of the symbol stack - ; KeyType - ; %mapping <- bottom of the symbol stack - node mapping - attr (mapping) push_symbol = "%Mapping" - edge @mapping.output -> mapping - edge mapping -> @key_type.push_begin - edge @key_type.push_end -> @value_type.output + attr (@struct.def) node_definition = @name + attr (@struct.def) definiens_node = @struct - ; Both key and value types need to be resolved - edge @value_type.type_ref -> @mapping.lexical_scope - edge @key_type.push_end -> @mapping.lexical_scope + ; Now connect normally to the struct members + node @struct.typeof + attr (@struct.typeof) pop_symbol = "@typeof" + node member + attr (member) pop_symbol = "." + edge @struct.def -> @struct.typeof + edge @struct.typeof -> member + edge member -> @struct.members - ; The mapping's type exposes the `[]` operator that returns the value type. + ; Bind member names when using construction with named arguments + node param_names + attr (param_names) pop_symbol = "@param_names" + edge @struct.def -> param_names + edge param_names -> @struct.members - node typeof_input - attr (typeof_input) pop_symbol = "@typeof" - edge @mapping.output -> typeof_input + ; Used as a function call (ie. casting), should bind to itself + node call + attr (call) pop_symbol = "()" + edge @struct.def -> call + edge call -> member +} - node typeof_output - attr (typeof_output) push_symbol = "@typeof" - edge typeof_output -> @value_type.output +@struct [StructDefinition [StructMembers + @member item: [StructMember @type_name [TypeName] @name name: [Identifier]] +]] { + node @member.def + attr (@member.def) node_definition = @name + attr (@member.def) definiens_node = @member - node index - attr (index) pop_symbol = "[]" - edge typeof_input -> index - edge index -> typeof_output + edge @struct.members -> @member.def - ; Special case for mapping public state variables: they can be called - ; like a function with a key, and it's effectively the same as indexing it. - node getter_call - attr (getter_call) pop_symbol = "@as_getter" - edge typeof_input -> getter_call - edge getter_call -> typeof_output + edge @type_name.type_ref -> @struct.lexical_scope - ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only - ; This is the reverse of the pushing path above (to the `.output` node) - node pop_mapping - attr (pop_mapping) pop_symbol = "%Mapping" + node @member.typeof + attr (@member.typeof) push_symbol = "@typeof" - let @mapping.pop_begin = @value_type.pop_begin - edge @value_type.pop_end -> @key_type.pop_begin - edge @key_type.pop_end -> pop_mapping - let @mapping.pop_end = pop_mapping + edge @member.def -> @member.typeof + edge @member.typeof -> @type_name.output } +@struct [StructDefinition [StructMembers . @first_member [StructMember]]] { + ; As a public getter result, the value returned is a tuple with all our fields flattened + ; We only care about the first member for name binding, since tuples are not real types + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge @struct.typeof -> getter_call + edge getter_call -> @first_member.typeof +} ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Arrays types +;;; Using directives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@array [ArrayTypeName] { - node @array.lexical_scope - node @array.output -} - -@array [ArrayTypeName [TypeName] index: [Expression]] { - let @array.type_symbol = "%FixedArray" -} +@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 + ; target type + node @using.def -@array [ArrayTypeName [OpenBracket] . [CloseBracket]] { - let @array.type_symbol = "%Array" + ; This internal node connects the definition side of the clause to the target + ; for resolution, and allows handling the multiple cases of `using` syntax + ; easily + node @using.clause } -@array [ArrayTypeName @type_name [TypeName]] { - ; Define the pushing path of the array type - ; ValueType <- top of the symbol stack - ; %array / %arrayFixed <- bottom of the symbol stack - node array - attr (array) push_symbol = @array.type_symbol - edge @array.output -> array - edge array -> @type_name.output - - ; Resolve the value type itself - edge @type_name.type_ref -> @array.lexical_scope - ; And also the "type erased" array type so we can resolve built-in members - edge array -> @array.lexical_scope +@using [UsingDirective [UsingClause @id_path [IdentifierPath]]] { + ; resolve the library to be used in the directive + edge @id_path.push_end -> @using.lexical_scope - ; Define the path to resolve index access (aka the `[]` operator) + ; 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 + ; need to verify that we're only using this path when resolving a function + ; access to the target type, not the target type itself + node dot_guard_pop + attr (dot_guard_pop) pop_symbol = "." + node dot_guard_push + attr (dot_guard_push) push_symbol = "." - node typeof_input - attr (typeof_input) pop_symbol = "@typeof" - edge @array.output -> typeof_input + edge @using.clause -> dot_guard_pop + edge dot_guard_pop -> dot_guard_push + edge dot_guard_push -> @id_path.push_begin +} - node typeof_output - attr (typeof_output) push_symbol = "@typeof" - edge typeof_output -> @type_name.output +@using [UsingDirective [UsingClause [UsingDeconstruction + [UsingDeconstructionSymbols [UsingDeconstructionSymbol + @id_path [IdentifierPath] + ]] +]]] { + ; resolve the function to be used in the directive + edge @id_path.push_end -> @using.lexical_scope - node index - attr (index) pop_symbol = "[]" - edge typeof_input -> index - edge index -> typeof_output + node dot + attr (dot) pop_symbol = "." + node last_identifier + attr (last_identifier) pop_symbol = (source-text @id_path.rightmost_identifier) - ; Special case for public state variables of type array: they can be called - ; like a function with an index, and it's effectively the same as indexing the - ; array. - node getter_call - attr (getter_call) pop_symbol = "@as_getter" - edge typeof_input -> getter_call - edge getter_call -> typeof_output + edge @using.clause -> dot + edge dot -> last_identifier + edge last_identifier -> @id_path.push_begin +} - ; Define the special `.push()` built-in that returns the element type (for Solidity >= 0.6.0) - if (version-matches ">= 0.6.0") { - node built_in_member - attr (built_in_member) pop_symbol = "." - node push_built_in - attr (push_built_in) pop_symbol = "push" - node built_in_call - attr (built_in_call) pop_symbol = "()" +@using [UsingDirective [UsingTarget @type_name [TypeName]]] { + ; pop the type symbols to connect to the attached function (via @using.clause) + node typeof + attr (typeof) pop_symbol = "@typeof" + node cast + attr (cast) pop_symbol = "()" - edge typeof_input -> built_in_member - edge built_in_member -> push_built_in - edge push_built_in -> built_in_call - edge built_in_call -> typeof_output - } + ; We connect to all_pop_begin to be able to resolve both qualified and + ; unqualified instances of the target type + edge @using.def -> @type_name.all_pop_begin + edge @type_name.pop_end -> typeof + edge typeof -> @using.clause + edge @type_name.pop_end -> cast + edge cast -> @using.clause - ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only - ; This is essentially the reverse of the second path above - node pop_array - attr (pop_array) pop_symbol = @array.type_symbol + ; resolve the target type of the directive on the lexical scope + edge @type_name.type_ref -> @using.lexical_scope +} - let @array.pop_begin = @type_name.pop_begin - edge @type_name.pop_end -> pop_array - let @array.pop_end = pop_array +[ContractMember @using [UsingDirective [UsingTarget [Asterisk]]]] { + ; using X for * is only allowed inside contracts + node star + attr (star) pop_symbol = "@*" + edge @using.def -> star + edge star -> @using.clause } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Function types +;;; Constructors ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@ftype [FunctionType @attrs [FunctionTypeAttributes]] { - ; Compute the built-in type of the function - ; %functionExternal provides access to .selector and .address - var type_symbol = "%Function" - scan (source-text @attrs) { - "external" { - set type_symbol = "%ExternalFunction" - } - } - - node @ftype.lexical_scope - node @ftype.output +@constructor [ConstructorDefinition] { + node @constructor.lexical_scope + node @constructor.def +} - ; This path pushes the function type to the symbol stack - ; TODO: add parameter and return types to distinguish between different function types - node function_type - attr (function_type) push_symbol = type_symbol +@constructor [ConstructorDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @constructor.lexical_scope - edge @ftype.output -> function_type - edge function_type -> @ftype.lexical_scope + ;; Input parameters are available in the constructor scope + edge @constructor.lexical_scope -> @params.defs + ;; ... and shadow other declarations + attr (@constructor.lexical_scope -> @params.defs) precedence = 1 - ; the pop path for the using directive - node pop_function_type - attr (pop_function_type) pop_symbol = type_symbol + ;; Connect to paramaters for named argument resolution + edge @constructor.def -> @params.names +} - let @ftype.pop_begin = pop_function_type - let @ftype.pop_end = pop_function_type +;; Connect the constructor body's block lexical scope to the constructor +@constructor [ConstructorDefinition @block [Block]] { + edge @block.lexical_scope -> @constructor.lexical_scope } -@ftype [FunctionType @params [ParametersDeclaration]] { - edge @params.lexical_scope -> @ftype.lexical_scope +@constructor [ConstructorDefinition [ConstructorAttributes item: [ConstructorAttribute + @modifier [ModifierInvocation] +]]] { + edge @modifier.lexical_scope -> @constructor.lexical_scope + edge @modifier.identifier -> @constructor.lexical_scope } -@ftype [FunctionType [ReturnsDeclaration @return_params [ParametersDeclaration]]] { - edge @return_params.lexical_scope -> @ftype.lexical_scope +@contract [ContractDefinition [ContractMembers [ContractMember + @constructor [ConstructorDefinition] +]]] { + ;; This link allows calling a constructor with the named parameters syntax + edge @contract.def -> @constructor.def } -@ftype [FunctionType [ReturnsDeclaration - [ParametersDeclaration [Parameters . @param [Parameter] .]] -]] { - ; Variables of a function type type can be "called" and resolve to the type of - ; the return parameter. This is only valid if the function returns a single - ; value. - node typeof - attr (typeof) pop_symbol = "@typeof" +;; Solidity < 0.5.0 constructors +;; They were declared as functions of the contract's name - node call - attr (call) pop_symbol = "()" +@contract [ContractDefinition + @contract_name [Identifier] + [ContractMembers [ContractMember [FunctionDefinition + [FunctionName @function_name [Identifier]] + @params [ParametersDeclaration] + ]]] +] { + if (version-matches "< 0.5.0") { + if (eq (source-text @contract_name) (source-text @function_name)) { + ; Connect to paramaters for named argument resolution + edge @contract.def -> @params.names + } + } +} - edge @ftype.output -> typeof - edge typeof -> call - edge call -> @param.typeof +[ContractDefinition + @contract_name [Identifier] + [ContractMembers [ContractMember @function [FunctionDefinition + [FunctionName @function_name [Identifier]] + [FunctionAttributes [FunctionAttribute @modifier [ModifierInvocation]]] + ]]] +] { + if (version-matches "< 0.5.0") { + if (eq (source-text @contract_name) (source-text @function_name)) { + ; Parent constructor calls are parsed as modifier invocations + edge @modifier.identifier -> @function.lexical_scope + } + } } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Identifier Paths (aka. references to custom types) +;;; Function modifiers ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; The identifier path builds two graph paths: -;; -;; - From right to left, pushing the identifiers and acting as a "reference". -;; This path begins at @id_path.push_begin and ends at @id_path.push_end. -;; -;; - From left to right, popping the identifiers (used as a definition sink in -;; using directives). This path begins at @id_path.pop_begin and ends at -;; @id_path.pop_end. -;; -;; NOTE: most of the time, and unless this identifier path is the target of a -;; using directive this second path will not be used and will form a -;; disconnected graph component. We currently have no way of determining when -;; this path is necessary, so we always construct it. -;; -;; Additionally the IdentifierPath defines another scoped variable -;; @id_path.rightmost_identifier which corresponds to the identifier in the last -;; position in the path, from left to right. This is used in the using directive -;; rules to be able to pop the name of the attached function. +@modifier [ModifierDefinition] { + node @modifier.def + node @modifier.lexical_scope +} -@id_path [IdentifierPath] { - ; This node connects to all parts of the path, for popping. This allows to - ; connect at any point of the path. Useful for `using` directives when the - ; target type is fully qualified but we want to resolve for the unqualified - ; name. - node @id_path.all_pop_begin +@modifier [ModifierDefinition + @name name: [Identifier] +] { + attr (@modifier.def) node_definition = @name + attr (@modifier.def) definiens_node = @modifier } -@id_path [IdentifierPath @name [Identifier]] { - node @name.ref - attr (@name.ref) node_reference = @name - attr (@name.ref) parents = [@id_path.enclosing_def] +@modifier [ModifierDefinition + body: [FunctionBody @body [Block]] +] { + edge @body.lexical_scope -> @modifier.lexical_scope - node @name.pop - attr (@name.pop) pop_symbol = (source-text @name) + ; Special case: bind the place holder statement `_` to the built-in + ; `%placeholder`. This only happens in the body of a modifier. + node placeholder_pop + attr (placeholder_pop) pop_symbol = "_" + node placeholder_ref + attr (placeholder_ref) push_symbol = "%placeholder" - edge @id_path.all_pop_begin -> @name.pop + edge @body.lexical_scope -> placeholder_pop + edge placeholder_pop -> placeholder_ref + edge placeholder_ref -> @modifier.lexical_scope } -@id_path [IdentifierPath @name [Identifier] .] { - let @id_path.rightmost_identifier = @name +@modifier [ModifierDefinition @params [ParametersDeclaration]] { + edge @params.lexical_scope -> @modifier.lexical_scope - let @id_path.push_begin = @name.ref - let @id_path.pop_end = @name.pop + ;; Input parameters are available in the modifier scope + edge @modifier.lexical_scope -> @params.defs + attr (@modifier.lexical_scope -> @params.defs) precedence = 1 } -[IdentifierPath @left_name [Identifier] . [Period] . @right_name [Identifier]] { - node ref_member - attr (ref_member) push_symbol = "." - edge @right_name.ref -> ref_member - edge ref_member -> @left_name.ref +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Fallback and receive functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - node pop_member - attr (pop_member) pop_symbol = "." +@fallback [FallbackFunctionDefinition] { + node @fallback.lexical_scope +} - edge @left_name.pop -> pop_member - edge pop_member -> @right_name.pop +@fallback [FallbackFunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @fallback.lexical_scope + + ;; Input parameters are available in the fallback function scope + edge @fallback.lexical_scope -> @params.defs + attr (@fallback.lexical_scope -> @params.defs) precedence = 1 } -@id_path [IdentifierPath . @name [Identifier]] { - let @id_path.push_end = @name.ref - let @id_path.pop_begin = @name.pop +@fallback [FallbackFunctionDefinition returns: [ReturnsDeclaration + @return_params [ParametersDeclaration] +]] { + edge @return_params.lexical_scope -> @fallback.lexical_scope + + ;; Return parameters are available in the fallback function scope + 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.lexical_scope +} -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Function, parameter declarations and modifiers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +@fallback [FallbackFunctionDefinition [FallbackFunctionAttributes + item: [FallbackFunctionAttribute @modifier [ModifierInvocation]] +]] { + edge @modifier.lexical_scope -> @fallback.lexical_scope +} -@param [Parameter @type_name [TypeName]] { - node @param.lexical_scope - node @param.def +@receive [ReceiveFunctionDefinition] { + node @receive.lexical_scope +} - edge @type_name.type_ref -> @param.lexical_scope +@receive [ReceiveFunctionDefinition [FunctionBody @block [Block]]] { + edge @block.lexical_scope -> @receive.lexical_scope +} - node @param.typeof - attr (@param.typeof) push_symbol = "@typeof" - edge @param.typeof -> @type_name.output +@receive [ReceiveFunctionDefinition [ReceiveFunctionAttributes + item: [ReceiveFunctionAttribute @modifier [ModifierInvocation]] +]] { + edge @modifier.lexical_scope -> @receive.lexical_scope } -@param [Parameter @name [Identifier]] { - attr (@param.def) node_definition = @name - attr (@param.def) definiens_node = @param - edge @param.def -> @param.typeof +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Unnamed functions (deprecated) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@unnamed_function [UnnamedFunctionDefinition] { + node @unnamed_function.lexical_scope } -@params [ParametersDeclaration] { - node @params.lexical_scope - node @params.defs +@unnamed_function [UnnamedFunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @unnamed_function.lexical_scope - ;; This scope can be used to resolve named argument calls - node @params.names - attr (@params.names) pop_symbol = "@param_names" - edge @params.names -> @params.defs + edge @unnamed_function.lexical_scope -> @params.defs + attr (@unnamed_function.lexical_scope -> @params.defs) precedence = 1 } -@params [ParametersDeclaration [Parameters @param item: [Parameter]]] { - edge @param.lexical_scope -> @params.lexical_scope - edge @params.defs -> @param.def +@unnamed_function [UnnamedFunctionDefinition [FunctionBody @block [Block]]] { + edge @block.lexical_scope -> @unnamed_function.lexical_scope } -@function [FunctionDefinition @attrs [FunctionAttributes]] { - var type_symbol = "%Function" - scan (source-text @attrs) { - "\\b(public|external)\\b" { - set type_symbol = "%ExternalFunction" - } - } +@unnamed_function [UnnamedFunctionDefinition + [UnnamedFunctionAttributes [UnnamedFunctionAttribute @modifier [ModifierInvocation]]] +] { + edge @modifier.lexical_scope -> @unnamed_function.lexical_scope +} - node @function.lexical_scope - node @function.def - ; this path from the function definition to the scope allows attaching - ; functions to this function's type - node typeof - attr (typeof) push_symbol = "@typeof" - node type_function - attr (type_function) push_symbol = type_symbol - edge @function.def -> typeof - edge typeof -> type_function - edge type_function -> @function.lexical_scope +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Enum definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@enum [EnumDefinition @name name: [Identifier]] { + node @enum.lexical_scope + node @enum.def + node @enum.members + + attr (@enum.def) node_definition = @name + attr (@enum.def) definiens_node = @enum + + node member + attr (member) pop_symbol = "." + + edge @enum.def -> member + edge member -> @enum.members + + ; Path to resolve the built-in type for enums (which is the same as for integer types) + node type + attr (type) pop_symbol = "@type" + node type_enum_type + attr (type_enum_type) push_symbol = "%IntTypeType" + edge @enum.def -> type + edge type -> type_enum_type + edge type_enum_type -> @enum.lexical_scope } -@function [FunctionDefinition name: [FunctionName @name [Identifier]]] { - attr (@function.def) node_definition = @name - attr (@function.def) definiens_node = @function +@enum [EnumDefinition + members: [EnumMembers @item [Identifier]] +] { + node def + attr (def) node_definition = @item + attr (def) definiens_node = @item + + edge @enum.members -> def } -@function [FunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @function.lexical_scope - ;; Input parameters are available in the function scope - edge @function.lexical_scope -> @params.defs - ;; ... and shadow other declarations - attr (@function.lexical_scope -> @params.defs) precedence = 1 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Event definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; Connect to paramaters for named argument resolution - edge @function.def -> @params.names +@event [EventDefinition @name name: [Identifier]] { + node @event.lexical_scope + node @event.def + + attr (@event.def) node_definition = @name + attr (@event.def) definiens_node = @event + + node @event.params + attr (@event.params) pop_symbol = "@param_names" + edge @event.def -> @event.params } -@function [FunctionDefinition returns: [ReturnsDeclaration - @return_params [ParametersDeclaration] -]] { - edge @return_params.lexical_scope -> @function.lexical_scope +@event [EventDefinition [EventParametersDeclaration [EventParameters + [EventParameter @type_name type_name: [TypeName]] +]]] { + edge @type_name.type_ref -> @event.lexical_scope +} - ;; Return parameters are available in the function scope - edge @function.lexical_scope -> @return_params.defs - ;; ... and shadow other declarations - attr (@function.lexical_scope -> @return_params.defs) precedence = 1 +@event [EventDefinition [EventParametersDeclaration [EventParameters + @param [EventParameter + @name name: [Identifier] + ] +]]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @param + + edge @event.params -> def } -;; Only functions that return a single value have an actual return type -;; since tuples are not actual types in Solidity -@function [FunctionDefinition returns: [ReturnsDeclaration - [ParametersDeclaration [Parameters . @param [Parameter] .]] -]] { - node call - attr (call) pop_symbol = "()" - edge @function.def -> call - edge call -> @param.typeof +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Error definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@error [ErrorDefinition @name name: [Identifier]] { + node @error.lexical_scope + node @error.def + + attr (@error.def) node_definition = @name + attr (@error.def) definiens_node = @error + + node @error.params + attr (@error.params) pop_symbol = "@param_names" + edge @error.def -> @error.params + + ; Bind to built-in errorType for accessing built-in member `.selector` + node typeof + attr (typeof) push_symbol = "@typeof" + node error_type + attr (error_type) push_symbol = "%ErrorType" + edge @error.def -> typeof + edge typeof -> error_type + edge error_type -> @error.lexical_scope } -;; Connect the function body's block lexical scope to the function -@function [FunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @function.lexical_scope +@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters + [ErrorParameter @type_name type_name: [TypeName]] +]]] { + edge @type_name.type_ref -> @error.lexical_scope } -@function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute - @modifier [ModifierInvocation] +@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters + @param [ErrorParameter + @name name: [Identifier] + ] ]]] { - edge @modifier.lexical_scope -> @function.lexical_scope -} + node def + attr (def) node_definition = @name + attr (def) definiens_node = @param -@modifier [ModifierInvocation @name [IdentifierPath]] { - node @modifier.lexical_scope + edge @error.params -> def +} - node modifier - attr (modifier) push_symbol = "@modifier" - edge @name.push_end -> modifier - edge modifier -> @modifier.lexical_scope +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Other named definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ; This allows resolving @name in the more general scope in constructors (since - ; calling a parent constructor is parsed as a modifier invocation) - let @modifier.identifier = @name.push_end +@constant [ConstantDefinition] { + node @constant.lexical_scope + node @constant.def } -@modifier [ModifierInvocation @args [ArgumentsDeclaration]] { - edge @args.lexical_scope -> @modifier.lexical_scope -} +@constant [ConstantDefinition + @type_name type_name: [TypeName] + @name name: [Identifier] +] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @constant -;;; Unnamed functions (deprecated) -@unnamed_function [UnnamedFunctionDefinition] { - node @unnamed_function.lexical_scope + edge @constant.def -> def + + edge @type_name.type_ref -> @constant.lexical_scope } -@unnamed_function [UnnamedFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @unnamed_function.lexical_scope +@user_type [UserDefinedValueTypeDefinition @name [Identifier] @value_type [ElementaryType]] { + node @user_type.lexical_scope + node @user_type.def - edge @unnamed_function.lexical_scope -> @params.defs - attr (@unnamed_function.lexical_scope -> @params.defs) precedence = 1 -} + attr (@user_type.def) node_definition = @name + attr (@user_type.def) definiens_node = @user_type -@unnamed_function [UnnamedFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @unnamed_function.lexical_scope -} + ; Provide member resolution through the built-in `%userTypeType` + ; Because the built-in is defined as a struct, we need to push an extra `@typeof` + node member_guard + attr (member_guard) pop_symbol = "." + node member + attr (member) push_symbol = "." + node typeof + attr (typeof) push_symbol = "@typeof" + node user_type_type + attr (user_type_type) push_symbol = "%UserDefinedValueType" -@unnamed_function [UnnamedFunctionDefinition - [UnnamedFunctionAttributes [UnnamedFunctionAttribute @modifier [ModifierInvocation]]] -] { - edge @modifier.lexical_scope -> @unnamed_function.lexical_scope -} + edge @user_type.def -> member_guard + edge member_guard -> member + edge member -> typeof + edge typeof -> user_type_type + edge user_type_type -> @user_type.lexical_scope + + ; Hard-code built-in functions `wrap` and `unwrap` in order to be able to + ; resolve their return types + node wrap + attr (wrap) pop_symbol = "wrap" + node wrap_call + attr (wrap_call) pop_symbol = "()" + node wrap_typeof + attr (wrap_typeof) push_symbol = "@typeof" + + edge member_guard -> wrap + edge wrap -> wrap_call + edge wrap_call -> wrap_typeof + edge wrap_typeof -> @value_type.ref + edge @value_type.ref -> @user_type.lexical_scope + node unwrap + attr (unwrap) pop_symbol = "unwrap" + node unwrap_call + attr (unwrap_call) pop_symbol = "()" + node unwrap_typeof + attr (unwrap_typeof) push_symbol = "@typeof" + node type_ref + attr (type_ref) push_symbol = (source-text @name) + edge member_guard -> unwrap + edge unwrap -> unwrap_call + edge unwrap_call -> unwrap_typeof + edge unwrap_typeof -> type_ref + edge type_ref -> @user_type.lexical_scope +} ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Constructors +;;; Identifier Paths (aka. references to custom types) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@constructor [ConstructorDefinition] { - node @constructor.lexical_scope - node @constructor.def +;; The identifier path builds two graph paths: +;; +;; - From right to left, pushing the identifiers and acting as a "reference". +;; This path begins at @id_path.push_begin and ends at @id_path.push_end. +;; +;; - From left to right, popping the identifiers (used as a definition sink in +;; using directives). This path begins at @id_path.pop_begin and ends at +;; @id_path.pop_end. +;; +;; NOTE: most of the time, and unless this identifier path is the target of a +;; using directive this second path will not be used and will form a +;; disconnected graph component. We currently have no way of determining when +;; this path is necessary, so we always construct it. +;; +;; Additionally the IdentifierPath defines another scoped variable +;; @id_path.rightmost_identifier which corresponds to the identifier in the last +;; position in the path, from left to right. This is used in the using directive +;; rules to be able to pop the name of the attached function. + +@id_path [IdentifierPath] { + ; This node connects to all parts of the path, for popping. This allows to + ; connect at any point of the path. Useful for `using` directives when the + ; target type is fully qualified but we want to resolve for the unqualified + ; name. + node @id_path.all_pop_begin } -@constructor [ConstructorDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @constructor.lexical_scope +@id_path [IdentifierPath @name [Identifier]] { + node @name.ref + attr (@name.ref) node_reference = @name + attr (@name.ref) parents = [@id_path.enclosing_def] - ;; Input parameters are available in the constructor scope - edge @constructor.lexical_scope -> @params.defs - ;; ... and shadow other declarations - attr (@constructor.lexical_scope -> @params.defs) precedence = 1 + node @name.pop + attr (@name.pop) pop_symbol = (source-text @name) - ;; Connect to paramaters for named argument resolution - edge @constructor.def -> @params.names + edge @id_path.all_pop_begin -> @name.pop } -;; Connect the constructor body's block lexical scope to the constructor -@constructor [ConstructorDefinition @block [Block]] { - edge @block.lexical_scope -> @constructor.lexical_scope -} +@id_path [IdentifierPath @name [Identifier] .] { + let @id_path.rightmost_identifier = @name -@constructor [ConstructorDefinition [ConstructorAttributes item: [ConstructorAttribute - @modifier [ModifierInvocation] -]]] { - edge @modifier.lexical_scope -> @constructor.lexical_scope - edge @modifier.identifier -> @constructor.lexical_scope + let @id_path.push_begin = @name.ref + let @id_path.pop_end = @name.pop } -@contract [ContractDefinition [ContractMembers [ContractMember - @constructor [ConstructorDefinition] -]]] { - ;; This link allows calling a constructor with the named parameters syntax - edge @contract.def -> @constructor.def -} +[IdentifierPath @left_name [Identifier] . [Period] . @right_name [Identifier]] { + node ref_member + attr (ref_member) push_symbol = "." -;; Solidity < 0.5.0 constructors -;; They were declared as functions of the contract's name + edge @right_name.ref -> ref_member + edge ref_member -> @left_name.ref -@contract [ContractDefinition - @contract_name [Identifier] - [ContractMembers [ContractMember [FunctionDefinition - [FunctionName @function_name [Identifier]] - @params [ParametersDeclaration] - ]]] -] { - if (version-matches "< 0.5.0") { - if (eq (source-text @contract_name) (source-text @function_name)) { - ; Connect to paramaters for named argument resolution - edge @contract.def -> @params.names - } - } + node pop_member + attr (pop_member) pop_symbol = "." + + edge @left_name.pop -> pop_member + edge pop_member -> @right_name.pop } -[ContractDefinition - @contract_name [Identifier] - [ContractMembers [ContractMember @function [FunctionDefinition - [FunctionName @function_name [Identifier]] - [FunctionAttributes [FunctionAttribute @modifier [ModifierInvocation]]] - ]]] -] { - if (version-matches "< 0.5.0") { - if (eq (source-text @contract_name) (source-text @function_name)) { - ; Parent constructor calls are parsed as modifier invocations - edge @modifier.identifier -> @function.lexical_scope - } - } +@id_path [IdentifierPath . @name [Identifier]] { + let @id_path.push_end = @name.ref + let @id_path.pop_begin = @name.pop } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Fallback and receive functions +;;; Type names ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@fallback [FallbackFunctionDefinition] { - node @fallback.lexical_scope -} - -@fallback [FallbackFunctionDefinition @params parameters: [ParametersDeclaration]] { - edge @params.lexical_scope -> @fallback.lexical_scope - - ;; Input parameters are available in the fallback function scope - edge @fallback.lexical_scope -> @params.defs - attr (@fallback.lexical_scope -> @params.defs) precedence = 1 -} - -@fallback [FallbackFunctionDefinition returns: [ReturnsDeclaration - @return_params [ParametersDeclaration] -]] { - edge @return_params.lexical_scope -> @fallback.lexical_scope +;; TypeName nodes should define these scoped variables: +;; +;; - @type_name.type_ref represents the node in the graph where we're ready to +;; resolve the type, and thus should generally be connected to a (lexical) +;; scope node (source node, outside edges connect *from* here). +;; +;; - @type_name.output represents the other end of the type and corresponds to a +;; state where the type has already been resolved so we can, for example +;; resolve its members (sink node, outside edges connect *to* here). +;; +;; - @type_name.pop_begin, @type_name.pop_end are used in a definition context, +;; ie. when we need to pop the type name symbol(s) from the symbol stack. +;; Additionally, @type_name.all_pop_begin links to each symbol in a typename +;; (ie. in an identifier path typename), which allows referring to a type both +;; qualified and unqualified. - ;; Return parameters are available in the fallback function scope - edge @fallback.lexical_scope -> @return_params.defs - attr (@fallback.lexical_scope -> @return_params.defs) precedence = 1 +@type_name [TypeName @elementary [ElementaryType]] { + let @type_name.type_ref = @elementary.ref + let @type_name.output = @elementary.ref + let @type_name.pop_begin = @elementary.pop + let @type_name.pop_end = @elementary.pop + let @type_name.all_pop_begin = @elementary.pop } -@fallback [FallbackFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @fallback.lexical_scope -} +@type_name [TypeName @id_path [IdentifierPath]] { + ;; For an identifier path used as a type, the left-most element is the one + ;; that connects to the parent lexical scope, because the name resolution + ;; starts at the left of the identifier. + let @type_name.type_ref = @id_path.push_end -@fallback [FallbackFunctionDefinition [FallbackFunctionAttributes - item: [FallbackFunctionAttribute @modifier [ModifierInvocation]] -]] { - edge @modifier.lexical_scope -> @fallback.lexical_scope -} + ;; Conversely, the complete type is found at the right-most name, and that's + ;; where users of this type should link to (eg. a variable declaration). + let @type_name.output = @id_path.push_begin -@receive [ReceiveFunctionDefinition] { - node @receive.lexical_scope + let @type_name.pop_begin = @id_path.pop_begin + let @type_name.pop_end = @id_path.pop_end + let @type_name.all_pop_begin = @id_path.all_pop_begin } -@receive [ReceiveFunctionDefinition [FunctionBody @block [Block]]] { - edge @block.lexical_scope -> @receive.lexical_scope +@type_name [TypeName @type_variant ([ArrayTypeName] | [FunctionType])] { + let @type_name.type_ref = @type_variant.lexical_scope + let @type_name.output = @type_variant.output + let @type_name.pop_begin = @type_variant.pop_begin + let @type_name.pop_end = @type_variant.pop_end + let @type_name.all_pop_begin = @type_variant.pop_begin } -@receive [ReceiveFunctionDefinition [ReceiveFunctionAttributes - item: [ReceiveFunctionAttribute @modifier [ModifierInvocation]] -]] { - edge @modifier.lexical_scope -> @receive.lexical_scope +@type_name [TypeName @mapping [MappingType]] { + let @type_name.type_ref = @mapping.lexical_scope + let @type_name.output = @mapping.output + let @type_name.pop_begin = @mapping.pop_begin + let @type_name.pop_end = @mapping.pop_end + let @type_name.all_pop_begin = @mapping.pop_begin } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Function modifiers +;;; Elementary types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@modifier [ModifierDefinition] { - node @modifier.def - node @modifier.lexical_scope -} - -@modifier [ModifierDefinition - @name name: [Identifier] -] { - attr (@modifier.def) node_definition = @name - attr (@modifier.def) definiens_node = @modifier -} +@elementary [ElementaryType] { + node @elementary.ref + attr (@elementary.ref) type = "push_symbol" + attr (@elementary.ref) source_node = @elementary, symbol = @elementary.symbol -@modifier [ModifierDefinition - body: [FunctionBody @body [Block]] -] { - edge @body.lexical_scope -> @modifier.lexical_scope + node @elementary.pop + attr (@elementary.pop) pop_symbol = @elementary.symbol - ; Special case: bind the place holder statement `_` to the built-in - ; `%placeholder`. This only happens in the body of a modifier. - node placeholder_pop - attr (placeholder_pop) pop_symbol = "_" - node placeholder_ref - attr (placeholder_ref) push_symbol = "%placeholder" + ; These variables are a bit redundant, but necessary to easily use elementary + ; types as mapping keys + let @elementary.pop_begin = @elementary.pop + let @elementary.pop_end = @elementary.pop + let @elementary.all_pop_begin = @elementary.pop - edge @body.lexical_scope -> placeholder_pop - edge placeholder_pop -> placeholder_ref - edge placeholder_ref -> @modifier.lexical_scope + let @elementary.push_begin = @elementary.ref + let @elementary.push_end = @elementary.ref } -@modifier [ModifierDefinition @params [ParametersDeclaration]] { - edge @params.lexical_scope -> @modifier.lexical_scope - - ;; Input parameters are available in the modifier scope - edge @modifier.lexical_scope -> @params.defs - attr (@modifier.lexical_scope -> @params.defs) precedence = 1 +@elementary [ElementaryType [AddressType]] { + let @elementary.symbol = "%address" } - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Blocks and generic statements -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@block [Block] { - node @block.lexical_scope - node @block.defs +@elementary [ElementaryType [BoolKeyword]] { + let @elementary.symbol = "%bool" } -;; The first statement in a block -@block [Block [Statements . @stmt [Statement]]] { - if (version-matches ">= 0.5.0") { - edge @stmt.lexical_scope -> @block.lexical_scope - } +@elementary [ElementaryType [ByteKeyword]] { + let @elementary.symbol = "%byte" } -@block [Block [Statements @stmt [Statement]]] { - ;; Hoist statement definitions for Solidity < 0.5.0 - if (version-matches "< 0.5.0") { - ;; definitions are carried over to the block - edge @block.defs -> @stmt.defs +@elementary [ElementaryType @keyword [BytesKeyword]] { + let @elementary.symbol = (format "%{}" (source-text @keyword)) +} - ;; resolution happens in the context of the block - edge @stmt.lexical_scope -> @block.lexical_scope +@elementary [ElementaryType [StringKeyword]] { + let @elementary.symbol = "%string" +} - ;; and the statement definitions are available block's scope - edge @block.lexical_scope -> @stmt.defs - ;; ... shadowing declarations in enclosing scopes - attr (@block.lexical_scope -> @stmt.defs) precedence = 1 +@elementary [ElementaryType @keyword [IntKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "int") { + let @elementary.symbol = "%int256" + } else { + let @elementary.symbol = (format "%{}" symbol) } } -;; Two consecutive statements -[Statements @left_stmt [Statement] . @right_stmt [Statement]] { - if (version-matches ">= 0.5.0") { - edge @right_stmt.lexical_scope -> @left_stmt.lexical_scope +@elementary [ElementaryType @keyword [UintKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "uint") { + let @elementary.symbol = "%uint256" + } else { + let @elementary.symbol = (format "%{}" symbol) } } -@stmt [Statement] { - node @stmt.lexical_scope - node @stmt.defs - - if (version-matches ">= 0.5.0") { - ;; For Solidity >= 0.5.0, definitions are immediately available in the - ;; statement scope. For < 0.5.0 this is also true, but resolved through the - ;; enclosing block's lexical scope. - edge @stmt.lexical_scope -> @stmt.defs - ;; Statement definitions shadow other declarations in its scope - attr (@stmt.lexical_scope -> @stmt.defs) precedence = 1 +@elementary [ElementaryType @keyword [FixedKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "fixed") { + let @elementary.symbol = "%fixed128x18" + } else { + let @elementary.symbol = (format "%{}" symbol) } } -;; Statements of type block -@stmt [Statement @block variant: [Block]] { - edge @block.lexical_scope -> @stmt.lexical_scope - - ;; Hoist block definitions (< 0.5.0) - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @block.defs +@elementary [ElementaryType @keyword [UfixedKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "ufixed") { + let @elementary.symbol = "%ufixed128x18" + } else { + let @elementary.symbol = (format "%{}" symbol) } } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Expressions & declaration statements +;;; Mappings ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; In general for statements the structure is [Statement [StmtVariant]] and we -;; will define the scoped nodes .lexical_scope and (possibly) .defs in the -;; Statement CST node, skipping scoped nodes in the variant of the statement. -;; -;; For expression statements, variable and tuple declarations we define them -;; separately from the enclosing statement to be able to use them in `for` -;; initialization and condition clauses directly. Also, because we intend to -;; reuse them, all of them must have both a .lexical_scope and .defs scoped -;; nodes (even though .defs doesn't make sense for ExpressionStatement) +@mapping [MappingType + [MappingKey [MappingKeyType @key_type ([IdentifierPath] | [ElementaryType])]] + [MappingValue @value_type [TypeName]] +] { + node @mapping.lexical_scope + node @mapping.output -@stmt [Statement @expr_stmt [ExpressionStatement]] { - edge @expr_stmt.lexical_scope -> @stmt.lexical_scope -} + ; Define the pushing path of the mapping type + ; ValueType <- top of the symbol stack + ; KeyType + ; %mapping <- bottom of the symbol stack + node mapping + attr (mapping) push_symbol = "%Mapping" + edge @mapping.output -> mapping + edge mapping -> @key_type.push_begin + edge @key_type.push_end -> @value_type.output -@expr_stmt [ExpressionStatement] { - node @expr_stmt.lexical_scope -} + ; Both key and value types need to be resolved + edge @value_type.type_ref -> @mapping.lexical_scope + edge @key_type.push_end -> @mapping.lexical_scope + ; The mapping's type exposes the `[]` operator that returns the value type. -;;; Variable declaration statements + node typeof_input + attr (typeof_input) pop_symbol = "@typeof" + edge @mapping.output -> typeof_input -@stmt [Statement @var_decl [VariableDeclarationStatement]] { - edge @var_decl.lexical_scope -> @stmt.lexical_scope - edge @stmt.defs -> @var_decl.def -} + node typeof_output + attr (typeof_output) push_symbol = "@typeof" + edge typeof_output -> @value_type.output -@var_decl [VariableDeclarationStatement] { - node @var_decl.lexical_scope - node @var_decl.def -} + node index + attr (index) pop_symbol = "[]" + edge typeof_input -> index + edge index -> typeof_output -@var_decl [VariableDeclarationStatement - [VariableDeclarationType @var_type [TypeName]] - @name name: [Identifier] -] { - attr (@var_decl.def) node_definition = @name - attr (@var_decl.def) definiens_node = @var_decl + ; Special case for mapping public state variables: they can be called + ; like a function with a key, and it's effectively the same as indexing it. + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge typeof_input -> getter_call + edge getter_call -> typeof_output - edge @var_type.type_ref -> @var_decl.lexical_scope + ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only + ; This is the reverse of the pushing path above (to the `.output` node) + node pop_mapping + attr (pop_mapping) pop_symbol = "%Mapping" + + let @mapping.pop_begin = @value_type.pop_begin + edge @value_type.pop_end -> @key_type.pop_begin + edge @key_type.pop_end -> pop_mapping + let @mapping.pop_end = pop_mapping +} - node typeof - attr (typeof) push_symbol = "@typeof" - edge @var_decl.def -> typeof - edge typeof -> @var_type.output +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Arrays types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@array [ArrayTypeName] { + node @array.lexical_scope + node @array.output } -@var_decl [VariableDeclarationStatement - [VariableDeclarationType [VarKeyword]] - @name name: [Identifier] -] { - attr (@var_decl.def) node_definition = @name - attr (@var_decl.def) definiens_node = @var_decl +@array [ArrayTypeName [TypeName] index: [Expression]] { + let @array.type_symbol = "%FixedArray" } -@var_decl [VariableDeclarationStatement - [VariableDeclarationType [VarKeyword]] - [VariableDeclarationValue @value [Expression]] -] { - edge @var_decl.def -> @value.output +@array [ArrayTypeName [OpenBracket] . [CloseBracket]] { + let @array.type_symbol = "%Array" } +@array [ArrayTypeName @type_name [TypeName]] { + ; Define the pushing path of the array type + ; ValueType <- top of the symbol stack + ; %array / %arrayFixed <- bottom of the symbol stack + node array + attr (array) push_symbol = @array.type_symbol + edge @array.output -> array + edge array -> @type_name.output + ; Resolve the value type itself + edge @type_name.type_ref -> @array.lexical_scope + ; And also the "type erased" array type so we can resolve built-in members + edge array -> @array.lexical_scope -;;; Tuple deconstruction statements + ; Define the path to resolve index access (aka the `[]` operator) -@stmt [Statement @tuple_decon [TupleDeconstructionStatement]] { - edge @tuple_decon.lexical_scope -> @stmt.lexical_scope - edge @stmt.defs -> @tuple_decon.defs -} + node typeof_input + attr (typeof_input) pop_symbol = "@typeof" + edge @array.output -> typeof_input -@tuple_decon [TupleDeconstructionStatement] { - node @tuple_decon.lexical_scope - node @tuple_decon.defs -} + node typeof_output + attr (typeof_output) push_symbol = "@typeof" + edge typeof_output -> @type_name.output -@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements - [TupleDeconstructionElement - @tuple_member [TupleMember variant: [UntypedTupleMember - @name name: [Identifier]] - ] - ] -]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @tuple_member + node index + attr (index) pop_symbol = "[]" + edge typeof_input -> index + edge index -> typeof_output - edge @tuple_decon.defs -> def -} + ; Special case for public state variables of type array: they can be called + ; like a function with an index, and it's effectively the same as indexing the + ; array. + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge typeof_input -> getter_call + edge getter_call -> typeof_output -@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements - [TupleDeconstructionElement - @tuple_member [TupleMember variant: [TypedTupleMember - @member_type type_name: [TypeName] - @name name: [Identifier]] - ] - ] -]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @tuple_member + ; Define the special `.push()` built-in that returns the element type (for Solidity >= 0.6.0) + if (version-matches ">= 0.6.0") { + node built_in_member + attr (built_in_member) pop_symbol = "." + node push_built_in + attr (push_built_in) pop_symbol = "push" + node built_in_call + attr (built_in_call) pop_symbol = "()" - edge @tuple_decon.defs -> def - edge @member_type.type_ref -> @tuple_decon.lexical_scope + edge typeof_input -> built_in_member + edge built_in_member -> push_built_in + edge push_built_in -> built_in_call + edge built_in_call -> typeof_output + } - node typeof - attr (typeof) push_symbol = "@typeof" + ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only + ; This is essentially the reverse of the second path above + node pop_array + attr (pop_array) pop_symbol = @array.type_symbol - edge def -> typeof - edge typeof -> @member_type.output + let @array.pop_begin = @type_name.pop_begin + edge @type_name.pop_end -> pop_array + let @array.pop_end = pop_array } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Control statements +;;; Function types ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; If conditionals - -@stmt [Statement [IfStatement @body body: [Statement]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - } -} - -@stmt [Statement [IfStatement else_branch: [ElseBranch @else_body body: [Statement]]]] { - edge @else_body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @else_body.defs +@ftype [FunctionType @attrs [FunctionTypeAttributes]] { + ; Compute the built-in type of the function + ; %functionExternal provides access to .selector and .address + var type_symbol = "%Function" + scan (source-text @attrs) { + "external" { + set type_symbol = "%ExternalFunction" + } } -} -;; For loops + node @ftype.lexical_scope + node @ftype.output -@stmt [Statement [ForStatement - initialization: [ForStatementInitialization @init_stmt [ExpressionStatement]] -]] { - edge @init_stmt.lexical_scope -> @stmt.lexical_scope -} + ; This path pushes the function type to the symbol stack + ; TODO: add parameter and return types to distinguish between different function types + node function_type + attr (function_type) push_symbol = type_symbol -@stmt [Statement [ForStatement - initialization: [ForStatementInitialization @init_stmt [VariableDeclarationStatement]] -]] { - edge @init_stmt.lexical_scope -> @stmt.lexical_scope - edge @stmt.init_defs -> @init_stmt.def -} + edge @ftype.output -> function_type + edge function_type -> @ftype.lexical_scope -@stmt [Statement [ForStatement - initialization: [ForStatementInitialization @init_stmt [TupleDeconstructionStatement]] -]] { - edge @init_stmt.lexical_scope -> @stmt.lexical_scope - edge @stmt.init_defs -> @init_stmt.defs -} + ; the pop path for the using directive + node pop_function_type + attr (pop_function_type) pop_symbol = type_symbol -@stmt [Statement [ForStatement - condition: [ForStatementCondition @cond_stmt [ExpressionStatement]] -]] { - edge @cond_stmt.lexical_scope -> @stmt.lexical_scope - edge @cond_stmt.lexical_scope -> @stmt.init_defs + let @ftype.pop_begin = pop_function_type + let @ftype.pop_end = pop_function_type } -@stmt [Statement [ForStatement @iter_expr iterator: [Expression]]] { - ; for the iterator expression we need an independent scope node that can - ; connect to both the for-statement *and* the definitions in the init - ; expression - node @iter_expr.lexical_scope - edge @iter_expr.lexical_scope -> @stmt.lexical_scope - edge @iter_expr.lexical_scope -> @stmt.init_defs +@ftype [FunctionType @params [ParametersDeclaration]] { + edge @params.lexical_scope -> @ftype.lexical_scope } -@stmt [Statement [ForStatement @body body: [Statement]]] { - node @stmt.init_defs - - edge @body.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @stmt.init_defs - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - edge @stmt.defs -> @stmt.init_defs - } +@ftype [FunctionType [ReturnsDeclaration @return_params [ParametersDeclaration]]] { + edge @return_params.lexical_scope -> @ftype.lexical_scope } -;; While loops - -@stmt [Statement [WhileStatement @body body: [Statement]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - } -} +@ftype [FunctionType [ReturnsDeclaration + [ParametersDeclaration [Parameters . @param [Parameter] .]] +]] { + ; Variables of a function type type can be "called" and resolve to the type of + ; the return parameter. This is only valid if the function returns a single + ; value. + node typeof + attr (typeof) pop_symbol = "@typeof" -;; Do-while loops + node call + attr (call) pop_symbol = "()" -@stmt [Statement [DoWhileStatement @body body: [Statement]]] { - edge @body.lexical_scope -> @stmt.lexical_scope - if (version-matches "< 0.5.0") { - edge @stmt.defs -> @body.defs - } + edge @ftype.output -> typeof + edge typeof -> call + edge call -> @param.typeof } - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Error handling +;;; Blocks and generic statements ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Try-catch statements - -@stmt [Statement [TryStatement @body body: [Block]]] { - edge @body.lexical_scope -> @stmt.lexical_scope -} - -@stmt [Statement [TryStatement - [ReturnsDeclaration @return_params [ParametersDeclaration]] - @body body: [Block] -]] { - edge @return_params.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @return_params.defs - ;; Similar to functions, return params shadow other declarations - attr (@body.lexical_scope -> @return_params.defs) precedence = 1 -} - -@stmt [Statement [TryStatement [CatchClauses [CatchClause - @body body: [Block] -]]]] { - edge @body.lexical_scope -> @stmt.lexical_scope +@block [Block] { + node @block.lexical_scope + node @block.defs } -@stmt [Statement [TryStatement [CatchClauses [CatchClause - [CatchClauseError @catch_params parameters: [ParametersDeclaration]] - @body body: [Block] -]]]] { - edge @catch_params.lexical_scope -> @stmt.lexical_scope - edge @body.lexical_scope -> @catch_params.defs - ;; Similar to functions, catch params shadow other declarations - attr (@body.lexical_scope -> @catch_params.defs) precedence = 1 +;; The first statement in a block +@block [Block [Statements . @stmt [Statement]]] { + if (version-matches ">= 0.5.0") { + edge @stmt.lexical_scope -> @block.lexical_scope + } } -@stmt [Statement [TryStatement [CatchClauses [CatchClause - [CatchClauseError @name [Identifier]] -]]]] { - node ref - attr (ref) node_reference = @name +@block [Block [Statements @stmt [Statement]]] { + ;; Hoist statement definitions for Solidity < 0.5.0 + if (version-matches "< 0.5.0") { + ;; definitions are carried over to the block + edge @block.defs -> @stmt.defs - edge ref -> @stmt.lexical_scope + ;; resolution happens in the context of the block + edge @stmt.lexical_scope -> @block.lexical_scope + + ;; and the statement definitions are available block's scope + edge @block.lexical_scope -> @stmt.defs + ;; ... shadowing declarations in enclosing scopes + attr (@block.lexical_scope -> @stmt.defs) precedence = 1 + } } +;; Two consecutive statements +[Statements @left_stmt [Statement] . @right_stmt [Statement]] { + if (version-matches ">= 0.5.0") { + edge @right_stmt.lexical_scope -> @left_stmt.lexical_scope + } +} -;;; Revert statements +@stmt [Statement] { + node @stmt.lexical_scope + node @stmt.defs -@stmt [Statement [RevertStatement @error_ident [IdentifierPath]]] { - edge @error_ident.push_end -> @stmt.lexical_scope + if (version-matches ">= 0.5.0") { + ;; For Solidity >= 0.5.0, definitions are immediately available in the + ;; statement scope. For < 0.5.0 this is also true, but resolved through the + ;; enclosing block's lexical scope. + edge @stmt.lexical_scope -> @stmt.defs + ;; Statement definitions shadow other declarations in its scope + attr (@stmt.lexical_scope -> @stmt.defs) precedence = 1 + } } -@stmt [Statement [RevertStatement @args [ArgumentsDeclaration]]] { - edge @args.lexical_scope -> @stmt.lexical_scope -} +;; Statements of type block +@stmt [Statement @block variant: [Block]] { + edge @block.lexical_scope -> @stmt.lexical_scope -[Statement [RevertStatement - @error_ident [IdentifierPath] - @args [ArgumentsDeclaration] -]] { - edge @args.refs -> @error_ident.push_begin + ;; Hoist block definitions (< 0.5.0) + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @block.defs + } } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Other statements +;;; Expressions & declaration statements ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Emit -@stmt [Statement [EmitStatement - @event_ident [IdentifierPath] - @args [ArgumentsDeclaration] -]] { - edge @event_ident.push_end -> @stmt.lexical_scope - edge @args.lexical_scope -> @stmt.lexical_scope - edge @args.refs -> @event_ident.push_begin -} +;; In general for statements the structure is [Statement [StmtVariant]] and we +;; will define the scoped nodes .lexical_scope and (possibly) .defs in the +;; Statement CST node, skipping scoped nodes in the variant of the statement. +;; +;; For expression statements, variable and tuple declarations we define them +;; separately from the enclosing statement to be able to use them in `for` +;; initialization and condition clauses directly. Also, because we intend to +;; reuse them, all of them must have both a .lexical_scope and .defs scoped +;; nodes (even though .defs doesn't make sense for ExpressionStatement) -;;; Unchecked -@stmt [Statement [UncheckedBlock @block block: [Block]]] { - edge @block.lexical_scope -> @stmt.lexical_scope +@stmt [Statement @expr_stmt [ExpressionStatement]] { + edge @expr_stmt.lexical_scope -> @stmt.lexical_scope } -;;; Assembly -@stmt [Statement [AssemblyStatement @body body: [YulBlock]]] { - edge @body.lexical_scope -> @stmt.lexical_scope +@expr_stmt [ExpressionStatement] { + node @expr_stmt.lexical_scope } -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; State Variables -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Variable declaration statements -@state_var [StateVariableDefinition] { - node @state_var.lexical_scope - node @state_var.def +@stmt [Statement @var_decl [VariableDeclarationStatement]] { + edge @var_decl.lexical_scope -> @stmt.lexical_scope + edge @stmt.defs -> @var_decl.def } -@state_var [StateVariableDefinition - @type_name type_name: [TypeName] +@var_decl [VariableDeclarationStatement] { + node @var_decl.lexical_scope + node @var_decl.def +} + +@var_decl [VariableDeclarationStatement + [VariableDeclarationType @var_type [TypeName]] @name name: [Identifier] ] { - attr (@state_var.def) node_definition = @name - attr (@state_var.def) definiens_node = @state_var + attr (@var_decl.def) node_definition = @name + attr (@var_decl.def) definiens_node = @var_decl - edge @type_name.type_ref -> @state_var.lexical_scope + edge @var_type.type_ref -> @var_decl.lexical_scope - node @state_var.typeof - attr (@state_var.typeof) push_symbol = "@typeof" + node typeof + attr (typeof) push_symbol = "@typeof" - edge @state_var.def -> @state_var.typeof - edge @state_var.typeof -> @type_name.output + edge @var_decl.def -> typeof + edge typeof -> @var_type.output } -@state_var [StateVariableDefinition - [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] +@var_decl [VariableDeclarationStatement + [VariableDeclarationType [VarKeyword]] + @name name: [Identifier] ] { - ; Public state variables are used as functions when invoked from an external contract - node call - attr (call) pop_symbol = "()" - - ; In the general case using the getter can bind to the state variable's type - edge @state_var.def -> call - edge call -> @state_var.typeof - - ; Some complex types generate special getters (ie. arrays and mappings index - ; their contents, structs flatten most of their fields and return a tuple) - node getter - attr (getter) push_symbol = "@as_getter" - edge call -> getter - edge getter -> @state_var.typeof + attr (@var_decl.def) node_definition = @name + attr (@var_decl.def) definiens_node = @var_decl } -@state_var [StateVariableDefinition - [StateVariableDefinitionValue @value [Expression]] +@var_decl [VariableDeclarationStatement + [VariableDeclarationType [VarKeyword]] + [VariableDeclarationValue @value [Expression]] ] { - let @value.lexical_scope = @state_var.lexical_scope + edge @var_decl.def -> @value.output } -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Enum definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@enum [EnumDefinition @name name: [Identifier]] { - node @enum.lexical_scope - node @enum.def - node @enum.members - - attr (@enum.def) node_definition = @name - attr (@enum.def) definiens_node = @enum - - node member - attr (member) pop_symbol = "." - edge @enum.def -> member - edge member -> @enum.members +;;; Tuple deconstruction statements - ; Path to resolve the built-in type for enums (which is the same as for integer types) - node type - attr (type) pop_symbol = "@type" - node type_enum_type - attr (type_enum_type) push_symbol = "%IntTypeType" - edge @enum.def -> type - edge type -> type_enum_type - edge type_enum_type -> @enum.lexical_scope +@stmt [Statement @tuple_decon [TupleDeconstructionStatement]] { + edge @tuple_decon.lexical_scope -> @stmt.lexical_scope + edge @stmt.defs -> @tuple_decon.defs } -@enum [EnumDefinition - members: [EnumMembers @item [Identifier]] -] { - node def - attr (def) node_definition = @item - attr (def) definiens_node = @item - - edge @enum.members -> def +@tuple_decon [TupleDeconstructionStatement] { + node @tuple_decon.lexical_scope + node @tuple_decon.defs } +@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements + [TupleDeconstructionElement + @tuple_member [TupleMember variant: [UntypedTupleMember + @name name: [Identifier]] + ] + ] +]] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @tuple_member -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Structure definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -@struct [StructDefinition @name name: [Identifier]] { - node @struct.lexical_scope - node @struct.def - node @struct.members - - attr (@struct.def) node_definition = @name - attr (@struct.def) definiens_node = @struct - - ; Now connect normally to the struct members - node @struct.typeof - attr (@struct.typeof) pop_symbol = "@typeof" - node member - attr (member) pop_symbol = "." - edge @struct.def -> @struct.typeof - edge @struct.typeof -> member - edge member -> @struct.members - - ; Bind member names when using construction with named arguments - node param_names - attr (param_names) pop_symbol = "@param_names" - edge @struct.def -> param_names - edge param_names -> @struct.members - - ; Used as a function call (ie. casting), should bind to itself - node call - attr (call) pop_symbol = "()" - edge @struct.def -> call - edge call -> member + edge @tuple_decon.defs -> def } -@struct [StructDefinition [StructMembers - @member item: [StructMember @type_name [TypeName] @name name: [Identifier]] +@tuple_decon [TupleDeconstructionStatement [TupleDeconstructionElements + [TupleDeconstructionElement + @tuple_member [TupleMember variant: [TypedTupleMember + @member_type type_name: [TypeName] + @name name: [Identifier]] + ] + ] ]] { - node @member.def - attr (@member.def) node_definition = @name - attr (@member.def) definiens_node = @member - - edge @struct.members -> @member.def - - edge @type_name.type_ref -> @struct.lexical_scope + node def + attr (def) node_definition = @name + attr (def) definiens_node = @tuple_member - node @member.typeof - attr (@member.typeof) push_symbol = "@typeof" + edge @tuple_decon.defs -> def + edge @member_type.type_ref -> @tuple_decon.lexical_scope - edge @member.def -> @member.typeof - edge @member.typeof -> @type_name.output -} + node typeof + attr (typeof) push_symbol = "@typeof" -@struct [StructDefinition [StructMembers . @first_member [StructMember]]] { - ; As a public getter result, the value returned is a tuple with all our fields flattened - ; We only care about the first member for name binding, since tuples are not real types - node getter_call - attr (getter_call) pop_symbol = "@as_getter" - edge @struct.typeof -> getter_call - edge getter_call -> @first_member.typeof + edge def -> typeof + edge typeof -> @member_type.output } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Event definitions +;;; Control statements ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@event [EventDefinition @name name: [Identifier]] { - node @event.lexical_scope - node @event.def - - attr (@event.def) node_definition = @name - attr (@event.def) definiens_node = @event +;; If conditionals - node @event.params - attr (@event.params) pop_symbol = "@param_names" - edge @event.def -> @event.params +@stmt [Statement [IfStatement @body body: [Statement]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + } } -@event [EventDefinition [EventParametersDeclaration [EventParameters - [EventParameter @type_name type_name: [TypeName]] -]]] { - edge @type_name.type_ref -> @event.lexical_scope +@stmt [Statement [IfStatement else_branch: [ElseBranch @else_body body: [Statement]]]] { + edge @else_body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @else_body.defs + } } -@event [EventDefinition [EventParametersDeclaration [EventParameters - @param [EventParameter - @name name: [Identifier] - ] -]]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @param +;; For loops - edge @event.params -> def +@stmt [Statement [ForStatement + initialization: [ForStatementInitialization @init_stmt [ExpressionStatement]] +]] { + edge @init_stmt.lexical_scope -> @stmt.lexical_scope } +@stmt [Statement [ForStatement + initialization: [ForStatementInitialization @init_stmt [VariableDeclarationStatement]] +]] { + edge @init_stmt.lexical_scope -> @stmt.lexical_scope + edge @stmt.init_defs -> @init_stmt.def +} -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Error definitions -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +@stmt [Statement [ForStatement + initialization: [ForStatementInitialization @init_stmt [TupleDeconstructionStatement]] +]] { + edge @init_stmt.lexical_scope -> @stmt.lexical_scope + edge @stmt.init_defs -> @init_stmt.defs +} -@error [ErrorDefinition @name name: [Identifier]] { - node @error.lexical_scope - node @error.def +@stmt [Statement [ForStatement + condition: [ForStatementCondition @cond_stmt [ExpressionStatement]] +]] { + edge @cond_stmt.lexical_scope -> @stmt.lexical_scope + edge @cond_stmt.lexical_scope -> @stmt.init_defs +} - attr (@error.def) node_definition = @name - attr (@error.def) definiens_node = @error +@stmt [Statement [ForStatement @iter_expr iterator: [Expression]]] { + ; for the iterator expression we need an independent scope node that can + ; connect to both the for-statement *and* the definitions in the init + ; expression + node @iter_expr.lexical_scope + edge @iter_expr.lexical_scope -> @stmt.lexical_scope + edge @iter_expr.lexical_scope -> @stmt.init_defs +} - node @error.params - attr (@error.params) pop_symbol = "@param_names" - edge @error.def -> @error.params +@stmt [Statement [ForStatement @body body: [Statement]]] { + node @stmt.init_defs - ; Bind to built-in errorType for accessing built-in member `.selector` - node typeof - attr (typeof) push_symbol = "@typeof" - node error_type - attr (error_type) push_symbol = "%ErrorType" - edge @error.def -> typeof - edge typeof -> error_type - edge error_type -> @error.lexical_scope + edge @body.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @stmt.init_defs + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + edge @stmt.defs -> @stmt.init_defs + } } -@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters - [ErrorParameter @type_name type_name: [TypeName]] -]]] { - edge @type_name.type_ref -> @error.lexical_scope +;; While loops + +@stmt [Statement [WhileStatement @body body: [Statement]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + } } -@error [ErrorDefinition [ErrorParametersDeclaration [ErrorParameters - @param [ErrorParameter - @name name: [Identifier] - ] -]]] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @param +;; Do-while loops - edge @error.params -> def +@stmt [Statement [DoWhileStatement @body body: [Statement]]] { + edge @body.lexical_scope -> @stmt.lexical_scope + if (version-matches "< 0.5.0") { + edge @stmt.defs -> @body.defs + } } ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;; Other named definitions +;;; Error handling ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -@constant [ConstantDefinition] { - node @constant.lexical_scope - node @constant.def +;;; Try-catch statements + +@stmt [Statement [TryStatement @body body: [Block]]] { + edge @body.lexical_scope -> @stmt.lexical_scope } -@constant [ConstantDefinition - @type_name type_name: [TypeName] - @name name: [Identifier] -] { - node def - attr (def) node_definition = @name - attr (def) definiens_node = @constant +@stmt [Statement [TryStatement + [ReturnsDeclaration @return_params [ParametersDeclaration]] + @body body: [Block] +]] { + edge @return_params.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @return_params.defs + ;; Similar to functions, return params shadow other declarations + attr (@body.lexical_scope -> @return_params.defs) precedence = 1 +} - edge @constant.def -> def +@stmt [Statement [TryStatement [CatchClauses [CatchClause + @body body: [Block] +]]]] { + edge @body.lexical_scope -> @stmt.lexical_scope +} - edge @type_name.type_ref -> @constant.lexical_scope +@stmt [Statement [TryStatement [CatchClauses [CatchClause + [CatchClauseError @catch_params parameters: [ParametersDeclaration]] + @body body: [Block] +]]]] { + edge @catch_params.lexical_scope -> @stmt.lexical_scope + edge @body.lexical_scope -> @catch_params.defs + ;; Similar to functions, catch params shadow other declarations + attr (@body.lexical_scope -> @catch_params.defs) precedence = 1 } -@user_type [UserDefinedValueTypeDefinition @name [Identifier] @value_type [ElementaryType]] { - node @user_type.lexical_scope - node @user_type.def +@stmt [Statement [TryStatement [CatchClauses [CatchClause + [CatchClauseError @name [Identifier]] +]]]] { + node ref + attr (ref) node_reference = @name + edge ref -> @stmt.lexical_scope +} - attr (@user_type.def) node_definition = @name - attr (@user_type.def) definiens_node = @user_type +;;; Revert statements - ; Provide member resolution through the built-in `%userTypeType` - ; Because the built-in is defined as a struct, we need to push an extra `@typeof` - node member_guard - attr (member_guard) pop_symbol = "." - node member - attr (member) push_symbol = "." - node typeof - attr (typeof) push_symbol = "@typeof" - node user_type_type - attr (user_type_type) push_symbol = "%UserDefinedValueType" +@stmt [Statement [RevertStatement @error_ident [IdentifierPath]]] { + edge @error_ident.push_end -> @stmt.lexical_scope +} - edge @user_type.def -> member_guard - edge member_guard -> member - edge member -> typeof - edge typeof -> user_type_type - edge user_type_type -> @user_type.lexical_scope +@stmt [Statement [RevertStatement @args [ArgumentsDeclaration]]] { + edge @args.lexical_scope -> @stmt.lexical_scope +} - ; Hard-code built-in functions `wrap` and `unwrap` in order to be able to - ; resolve their return types - node wrap - attr (wrap) pop_symbol = "wrap" - node wrap_call - attr (wrap_call) pop_symbol = "()" - node wrap_typeof - attr (wrap_typeof) push_symbol = "@typeof" +[Statement [RevertStatement + @error_ident [IdentifierPath] + @args [ArgumentsDeclaration] +]] { + edge @args.refs -> @error_ident.push_begin +} - edge member_guard -> wrap - edge wrap -> wrap_call - edge wrap_call -> wrap_typeof - edge wrap_typeof -> @value_type.ref - edge @value_type.ref -> @user_type.lexical_scope - node unwrap - attr (unwrap) pop_symbol = "unwrap" - node unwrap_call - attr (unwrap_call) pop_symbol = "()" - node unwrap_typeof - attr (unwrap_typeof) push_symbol = "@typeof" - node type_ref - attr (type_ref) push_symbol = (source-text @name) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Other statements +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - edge member_guard -> unwrap - edge unwrap -> unwrap_call - edge unwrap_call -> unwrap_typeof - edge unwrap_typeof -> type_ref - edge type_ref -> @user_type.lexical_scope +;;; Emit +@stmt [Statement [EmitStatement + @event_ident [IdentifierPath] + @args [ArgumentsDeclaration] +]] { + edge @event_ident.push_end -> @stmt.lexical_scope + edge @args.lexical_scope -> @stmt.lexical_scope + edge @args.refs -> @event_ident.push_begin +} + +;;; Unchecked +@stmt [Statement [UncheckedBlock @block block: [Block]]] { + edge @block.lexical_scope -> @stmt.lexical_scope } +;;; Assembly +@stmt [Statement [AssemblyStatement @body body: [YulBlock]]] { + edge @body.lexical_scope -> @stmt.lexical_scope +} ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Expressions @@ -2630,7 +2628,6 @@ inherit .star_extension } } - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Yul ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 976a6c8a766346e7f700b2757be5d451277cd78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Fri, 29 Nov 2024 14:12:43 -0500 Subject: [PATCH 4/6] Add and use a different binding rules set for system files (built-ins) --- .../runtime/bindings/binding_rules.rs.jinja2 | 10 +- .../bindings/generated/binding_rules.rs | 8 +- .../cargo/crate/src/runtime/bindings/mod.rs | 12 +- .../runtime/generator/src/bindings/mod.rs | 14 +- .../bindings/generated/public_api.txt | 2 +- crates/metaslang/bindings/src/lib.rs | 54 +- .../language/bindings/system-rules.parts | 3 + .../cargo/crate/generated/public_api.txt | 3 +- .../bindings/generated/binding_rules.rs | 1488 ++++++++++++++++- .../cargo/crate/src/generated/bindings/mod.rs | 12 +- .../outputs/cargo/tests/src/binding_rules.rs | 25 +- .../language/bindings/system-rules.parts | 0 .../bindings/generated/binding_rules.rs | 8 +- .../cargo/crate/src/generated/bindings/mod.rs | 12 +- 14 files changed, 1611 insertions(+), 40 deletions(-) create mode 100644 crates/solidity/inputs/language/bindings/system-rules.parts create mode 100644 crates/testlang/inputs/language/bindings/system-rules.parts diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/binding_rules.rs.jinja2 b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/binding_rules.rs.jinja2 index a169d26a03..2d2182c6f3 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/binding_rules.rs.jinja2 +++ b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/binding_rules.rs.jinja2 @@ -1,5 +1,9 @@ #[allow(clippy::needless_raw_string_hashes)] -#[allow(dead_code)] // TODO(#982): use to create the graph -pub const BINDING_RULES_SOURCE: &str = r#####" - {{ model.bindings.binding_rules_source }} +pub const USER_BINDING_RULES_SOURCE: &str = r#####" + {{ model.bindings.user_binding_rules_source }} "#####; + +#[allow(clippy::needless_raw_string_hashes)] +pub const SYSTEM_BINDING_RULES_SOURCE: &str = r#####" + {{ model.bindings.system_binding_rules_source }} +"#####; \ No newline at end of file diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/generated/binding_rules.rs b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/generated/binding_rules.rs index 16e24e7bb4..1f70f53791 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/generated/binding_rules.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/generated/binding_rules.rs @@ -1,7 +1,11 @@ // This file is generated automatically by infrastructure scripts. Please don't edit by hand. #[allow(clippy::needless_raw_string_hashes)] -#[allow(dead_code)] // TODO(#982): use to create the graph -pub const BINDING_RULES_SOURCE: &str = r#####" +pub const USER_BINDING_RULES_SOURCE: &str = r#####" + +"#####; + +#[allow(clippy::needless_raw_string_hashes)] +pub const SYSTEM_BINDING_RULES_SOURCE: &str = r#####" "#####; diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs index 4766b80e6c..5aa7ed4638 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs @@ -32,7 +32,8 @@ pub fn create_with_resolver( ) -> Result { let mut binding_graph = BindingGraph::create( version.clone(), - binding_rules::BINDING_RULES_SOURCE, + binding_rules::USER_BINDING_RULES_SOURCE, + binding_rules::SYSTEM_BINDING_RULES_SOURCE, resolver, ); @@ -42,6 +43,11 @@ pub fn create_with_resolver( } #[cfg(feature = "__private_testing_utils")] -pub fn get_binding_rules() -> &'static str { - binding_rules::BINDING_RULES_SOURCE +pub fn get_user_binding_rules() -> &'static str { + binding_rules::USER_BINDING_RULES_SOURCE +} + +#[cfg(feature = "__private_testing_utils")] +pub fn get_system_binding_rules() -> &'static str { + binding_rules::SYSTEM_BINDING_RULES_SOURCE } diff --git a/crates/codegen/runtime/generator/src/bindings/mod.rs b/crates/codegen/runtime/generator/src/bindings/mod.rs index ed3b4c454f..4cdf5806da 100644 --- a/crates/codegen/runtime/generator/src/bindings/mod.rs +++ b/crates/codegen/runtime/generator/src/bindings/mod.rs @@ -1,4 +1,5 @@ -use std::{collections::BTreeSet, path::Path}; +use std::collections::BTreeSet; +use std::path::Path; use anyhow::Result; use codegen_language_definition::model; @@ -8,7 +9,8 @@ use serde::Serialize; #[derive(Default, Serialize)] pub struct BindingsModel { - binding_rules_source: String, + user_binding_rules_source: String, + system_binding_rules_source: String, built_ins_versions: BTreeSet, file_extension: String, } @@ -16,12 +18,14 @@ pub struct BindingsModel { impl BindingsModel { pub fn from_language(language: &model::Language) -> Result { let binding_rules_dir = &language.binding_rules_dir; - let binding_rules_source = build_rules(binding_rules_dir, "user-rules.parts")?; + let user_binding_rules_source = assemble_rules(binding_rules_dir, "user-rules.parts")?; + let system_binding_rules_source = assemble_rules(binding_rules_dir, "system-rules.parts")?; let built_ins_versions = language.collect_built_ins_versions(); let file_extension = language.file_extension.clone().unwrap_or_default(); Ok(Self { - binding_rules_source, + user_binding_rules_source, + system_binding_rules_source, built_ins_versions, file_extension, }) @@ -29,7 +33,7 @@ impl BindingsModel { } // Builds a rules file by concatenating the file parts listed in the given `parts_file` -fn build_rules(rules_dir: &Path, parts_file: &str) -> Result { +fn assemble_rules(rules_dir: &Path, parts_file: &str) -> Result { // We use `CodegenFileSystem` here to ensure the rules are rebuilt if the rules file changes let mut fs = CodegenFileSystem::new(rules_dir)?; let parts_contents = fs.read_file(rules_dir.join(parts_file))?; diff --git a/crates/metaslang/bindings/generated/public_api.txt b/crates/metaslang/bindings/generated/public_api.txt index e0fe3e3212..71189c5d19 100644 --- a/crates/metaslang/bindings/generated/public_api.txt +++ b/crates/metaslang/bindings/generated/public_api.txt @@ -29,7 +29,7 @@ pub fn metaslang_bindings::BindingGraph::add_user_file(&mut self, file_path: pub fn metaslang_bindings::BindingGraph::add_user_file_returning_graph(&mut self, file_path: &str, tree_cursor: metaslang_cst::cursor::Cursor) -> metaslang_graph_builder::graph::Graph pub fn metaslang_bindings::BindingGraph::all_definitions(&self) -> impl core::iter::traits::iterator::Iterator> + '_ pub fn metaslang_bindings::BindingGraph::all_references(&self) -> impl core::iter::traits::iterator::Iterator> + '_ -pub fn metaslang_bindings::BindingGraph::create(version: semver::Version, binding_rules: &str, path_resolver: alloc::rc::Rc>) -> Self +pub fn metaslang_bindings::BindingGraph::create(version: semver::Version, user_binding_rules: &str, system_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::reference_at(&self, cursor: &metaslang_cst::cursor::Cursor) -> core::option::Option> diff --git a/crates/metaslang/bindings/src/lib.rs b/crates/metaslang/bindings/src/lib.rs index e830f97004..c792a4595a 100644 --- a/crates/metaslang/bindings/src/lib.rs +++ b/crates/metaslang/bindings/src/lib.rs @@ -48,7 +48,8 @@ pub(crate) struct ReferenceBindingInfo { } pub struct BindingGraph { - graph_builder_file: File, + user_graph_builder_file: File, + system_graph_builder_file: File, functions: Functions, stack_graph: StackGraph, cursors: HashMap>, @@ -123,16 +124,20 @@ pub trait PathResolver { impl BindingGraph { pub fn create( version: Version, - binding_rules: &str, + user_binding_rules: &str, + system_binding_rules: &str, path_resolver: Rc>, ) -> Self { - let graph_builder_file = - File::from_str(binding_rules).expect("Bindings stack graph builder parse error"); + let user_graph_builder_file = + File::from_str(user_binding_rules).expect("Bindings stack graph builder parse error"); + let system_graph_builder_file = + File::from_str(system_binding_rules).expect("Bindings stack graph builder parse error"); let stack_graph = StackGraph::new(); let functions = builder::default_functions(version, path_resolver); Self { - graph_builder_file, + user_graph_builder_file, + system_graph_builder_file, functions, stack_graph, cursors: HashMap::new(), @@ -148,13 +153,13 @@ impl BindingGraph { pub fn add_system_file(&mut self, file_path: &str, tree_cursor: Cursor) { let file_kind = FileDescriptor::System(file_path.into()); let file = self.stack_graph.get_or_create_file(&file_kind.as_string()); - _ = self.add_file_internal(file, tree_cursor); + _ = self.add_system_file_internal(file, tree_cursor); } pub fn add_user_file(&mut self, file_path: &str, tree_cursor: Cursor) { let file_kind = FileDescriptor::User(file_path.into()); let file = self.stack_graph.get_or_create_file(&file_kind.as_string()); - _ = self.add_file_internal(file, tree_cursor); + _ = self.add_user_file_internal(file, tree_cursor); } #[cfg(feature = "__private_testing_utils")] @@ -165,22 +170,49 @@ impl BindingGraph { ) -> metaslang_graph_builder::graph::Graph { let file_kind = FileDescriptor::User(file_path.into()); let file = self.stack_graph.get_or_create_file(&file_kind.as_string()); - let result = self.add_file_internal(file, tree_cursor); + let result = self.add_user_file_internal(file, tree_cursor); result.graph } - fn add_file_internal(&mut self, file: FileHandle, tree_cursor: Cursor) -> BuildResult { + fn add_system_file_internal( + &mut self, + file: FileHandle, + tree_cursor: Cursor, + ) -> BuildResult { let builder = Builder::new( - &self.graph_builder_file, + &self.system_graph_builder_file, &self.functions, &mut self.stack_graph, file, tree_cursor, ); - let mut result = builder + let result = builder .build(&builder::NoCancellation) .expect("Internal error while building bindings"); + self.add_graph_internal(result) + } + + fn add_user_file_internal( + &mut self, + file: FileHandle, + tree_cursor: Cursor, + ) -> BuildResult { + let builder = Builder::new( + &self.user_graph_builder_file, + &self.functions, + &mut self.stack_graph, + file, + tree_cursor, + ); + let result = builder + .build(&builder::NoCancellation) + .expect("Internal error while building bindings"); + + self.add_graph_internal(result) + } + + fn add_graph_internal(&mut self, mut result: BuildResult) -> BuildResult { for (handle, cursor) in result.cursors.drain() { let cursor_id = cursor.node().id(); if self.stack_graph[handle].is_definition() { diff --git a/crates/solidity/inputs/language/bindings/system-rules.parts b/crates/solidity/inputs/language/bindings/system-rules.parts new file mode 100644 index 0000000000..d56cb10bef --- /dev/null +++ b/crates/solidity/inputs/language/bindings/system-rules.parts @@ -0,0 +1,3 @@ +00-top-level.msgb +01-definitions.msgb +03-types.msgb diff --git a/crates/solidity/outputs/cargo/crate/generated/public_api.txt b/crates/solidity/outputs/cargo/crate/generated/public_api.txt index 48a9440e5c..38de95cc05 100644 --- a/crates/solidity/outputs/cargo/crate/generated/public_api.txt +++ b/crates/solidity/outputs/cargo/crate/generated/public_api.txt @@ -17,7 +17,8 @@ pub fn slang_solidity::bindings::BindingGraphInitializationError::fmt(&self, f: impl core::fmt::Display for slang_solidity::bindings::BindingGraphInitializationError pub fn slang_solidity::bindings::BindingGraphInitializationError::fmt(&self, __formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result pub fn slang_solidity::bindings::create_with_resolver(version: semver::Version, resolver: alloc::rc::Rc>) -> core::result::Result -pub fn slang_solidity::bindings::get_binding_rules() -> &'static str +pub fn slang_solidity::bindings::get_system_binding_rules() -> &'static str +pub fn slang_solidity::bindings::get_user_binding_rules() -> &'static str pub type slang_solidity::bindings::BindingGraph = metaslang_bindings::BindingGraph pub type slang_solidity::bindings::BindingLocation = metaslang_bindings::location::BindingLocation pub type slang_solidity::bindings::Definition<'a> = metaslang_bindings::Definition<'a, slang_solidity::cst::KindTypes> 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 781a3f9e0e..142217d37a 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 @@ -1,8 +1,7 @@ // This file is generated automatically by infrastructure scripts. Please don't edit by hand. #[allow(clippy::needless_raw_string_hashes)] -#[allow(dead_code)] // TODO(#982): use to create the graph -pub const BINDING_RULES_SOURCE: &str = r#####" +pub const USER_BINDING_RULES_SOURCE: &str = r#####" global ROOT_NODE global FILE_PATH global JUMP_TO_SCOPE_NODE @@ -2962,3 +2961,1488 @@ inherit .star_extension } "#####; + +#[allow(clippy::needless_raw_string_hashes)] +pub const SYSTEM_BINDING_RULES_SOURCE: &str = r#####" + global ROOT_NODE +global FILE_PATH +global JUMP_TO_SCOPE_NODE + +attribute node_definition = node => type = "pop_symbol", node_symbol = node, is_definition +attribute node_reference = node => type = "push_symbol", node_symbol = node, is_reference +attribute node_symbol = node => symbol = (source-text node), source_node = node +attribute pop_symbol = symbol => type = "pop_symbol", symbol = symbol +attribute push_symbol = symbol => type = "push_symbol", symbol = symbol +attribute symbol_definition = symbol => type = "pop_symbol", symbol = symbol, is_definition +attribute symbol_reference = symbol => type = "push_symbol", symbol = symbol, is_reference + +attribute scoped_node_definition = node => type = "pop_scoped_symbol", node_symbol = node, is_definition +attribute scoped_node_reference = node => type = "push_scoped_symbol", node_symbol = node, is_reference +attribute pop_scoped_symbol = symbol => type = "pop_scoped_symbol", symbol = symbol +attribute push_scoped_symbol = symbol => type = "push_scoped_symbol", symbol = symbol + +;; Keeps a link to the enclosing contract definition to provide a parent for +;; method calls (to correctly resolve virtual methods) +inherit .enclosing_def + +inherit .parent_scope +inherit .lexical_scope + +; Used to resolve extension methods for `using for *` directives +; This is used as a minor optimization to avoid introducing new possible paths +; when there are no `using for *` directives in the contract. +inherit .star_extension + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Source unit (aka .sol file) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@source_unit [SourceUnit] { + ;; All lexical_scope nodes eventually connect to the file's root scope + node @source_unit.lexical_scope + + ;; This provides all the exported symbols from the file + node @source_unit.defs + + ;; Connect to ROOT_NODE to export our symbols + node export + edge ROOT_NODE -> export + edge export -> @source_unit.defs + + if (is-system-file FILE_PATH) { + ; If this is a system file (aka. built-ins), export everything through this + ; special symbol (which is automatically imported below) + attr (export) pop_symbol = "@@built-ins@@" + + } else { + ; This is a user file, so we want to export under the file's path symbol + attr (export) pop_symbol = FILE_PATH + + ; ... and also import the global built-ins + node built_ins + attr (built_ins) push_symbol = "@@built-ins@@" + + edge @source_unit.lexical_scope -> built_ins + edge built_ins -> ROOT_NODE + } + + let @source_unit.enclosing_def = #null + + ;; This defines a parent_scope at the source unit level (this attribute is + ;; inherited) for contracts to resolve bases (both in inheritance lists and + ;; override specifiers) + let @source_unit.parent_scope = @source_unit.lexical_scope + + ; 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. + node @source_unit.star_extension +} + +;; Top-level definitions... +@source_unit [SourceUnit [SourceUnitMembers + [SourceUnitMember @unit_member ( + [ContractDefinition] + | [LibraryDefinition] + | [InterfaceDefinition] + | [StructDefinition] + | [EnumDefinition] + | [FunctionDefinition] + | [ConstantDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + | [EventDefinition] + )] +]] { + edge @source_unit.lexical_scope -> @unit_member.def + edge @source_unit.defs -> @unit_member.def + + ; In the general case, the lexical scope of the definition connects directly + ; to the source unit's + edge @unit_member.lexical_scope -> @source_unit.lexical_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 +;; to export all its members and type members directly as a source unit +;; definition. +;; __SLANG_SOLIDITY_BUILT_INS_CONTRACT_NAME__ keep in sync with built-ins generation. +@source_unit [SourceUnit [SourceUnitMembers + [SourceUnitMember @contract [ContractDefinition name: ["%BuiltIns%"]]] +]] { + if (is-system-file FILE_PATH) { + edge @source_unit.defs -> @contract.instance + } +} + +@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember @using [UsingDirective]]]] { + ; TODO: this is the hook for top-level extensions, but this should connect to + ; an extensions scope that gets pushed to the scope stack, as in the case of + ; contracts/libraries (defined further down below). + edge @source_unit.lexical_scope -> @using.def +} + +@source_unit [SourceUnit [SourceUnitMembers [SourceUnitMember + @using [UsingDirective [GlobalKeyword]] +]]] { + ; global using directives are exported by this source unit + edge @source_unit.defs -> @using.def +} + +;; Import connections to the source unit +@source_unit [SourceUnit [SourceUnitMembers + [SourceUnitMember [ImportDirective + [ImportClause @import ( + [PathImport] + | [NamedImport] + | [ImportDeconstruction] + )] + ]] +]] { + node @import.defs + edge @source_unit.defs -> @import.defs + edge @source_unit.lexical_scope -> @import.defs +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Imports +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +[ImportClause + [_ + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] + ] +] { + ;; This node represents the imported file and the @path.import node is used by + ;; all subsequent import rules + node @path.import + let resolved_path = (resolve-path FILE_PATH @path) + attr (@path.import) push_symbol = resolved_path + edge @path.import -> ROOT_NODE +} + +;;; `import ` +@import [PathImport + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] +] { + ;; This is the "lexical" connection, which makes all symbols exported from the + ;; imported source unit available for resolution globally at this' source unit + ;; scope + edge @import.defs -> @path.import +} + +;;; `import as ` +@import [PathImport + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] + alias: [ImportAlias @alias [Identifier]] +] { + node def + attr (def) node_definition = @alias + attr (def) definiens_node = @import + edge @import.defs -> def + + node member + attr (member) pop_symbol = "." + edge def -> member + + ;; Lexical connection, which makes the import available as a member through + ;; the alias identifier + edge member -> @path.import +} + +;;; `import * as from ` +@import [NamedImport + alias: [ImportAlias @alias [Identifier]] + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] +] { + node def + attr (def) node_definition = @alias + attr (def) definiens_node = @import + edge @import.defs -> def + + node member + attr (member) pop_symbol = "." + edge def -> member + + ;; Lexical connection, which makes the import available as a member through + ;; the alias identifier + edge member -> @path.import +} + +;;; `import { [as ] ...} from ` +@import [ImportDeconstruction + symbols: [ImportDeconstructionSymbols @symbol [ImportDeconstructionSymbol]] + path: [StringLiteral + @path ([DoubleQuotedStringLiteral] | [SingleQuotedStringLiteral]) + ] +] { + ;; We define these intermediate nodes for convenience only, to make the + ;; queries simpler in the two rules below + node @symbol.def + edge @import.defs -> @symbol.def + + node @symbol.import + edge @symbol.import -> @path.import +} + +@symbol [ImportDeconstructionSymbol @name name: [Identifier] .] { + node def + attr (def) node_definition = @name + attr (def) definiens_node = @symbol + attr (def) tag = "alias" ; deprioritize this definition + edge @symbol.def -> def + + node import + attr (import) node_reference = @name + edge def -> import + + edge import -> @symbol.import +} + +@symbol [ImportDeconstructionSymbol + @name name: [Identifier] + alias: [ImportAlias @alias [Identifier]] +] { + node def + attr (def) node_definition = @alias + attr (def) definiens_node = @symbol + attr (def) tag = "alias" ; deprioritize this definition + edge @symbol.def -> def + + node import + attr (import) node_reference = @name + edge def -> import + + edge import -> @symbol.import +} + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Contracts +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@contract [ContractDefinition @name name: [Identifier]] { + node @contract.lexical_scope + node @contract.extensions + node @contract.def + node @contract.members + node @contract.ns + node @contract.modifiers + node @contract.instance + + 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 + + ; Instance scope can also see members and our namespace definitions + edge @contract.instance -> @contract.members + edge @contract.instance -> @contract.ns + + let @contract.enclosing_def = @contract.def + + ;; External "instance" scope access: either member access through a variable + ;; of the contract's type, or through calling (which happens on `new` + ;; invocations or casting). These should access only externally accessible + ;; members, such as functions and public variables. + node member + attr (member) pop_symbol = "." + edge member -> @contract.instance + + node type_def + attr (type_def) pop_symbol = "@typeof" + edge @contract.def -> type_def + edge type_def -> member + + node call + attr (call) pop_symbol = "()" + edge @contract.def -> call + edge call -> member + + ;; "namespace" scope access + node ns_member + attr (ns_member) pop_symbol = "." + edge @contract.def -> ns_member + edge ns_member -> @contract.ns + + ; Finally there's an @instance guarded path used by derived contracts to + ; access instance accessible members + node instance + attr (instance) pop_symbol = "@instance" + edge @contract.def -> instance + edge instance -> @contract.instance + + ; "this" keyword is available in our lexical scope and can access any + ; externally available member + node this + attr (this) pop_symbol = "this" + edge @contract.lexical_scope -> this + edge this -> member + + ;; Modifiers are available as a contract type members through a special '@modifier' guard + node modifier + attr (modifier) pop_symbol = "@modifier" + edge @contract.ns -> modifier + edge modifier -> @contract.modifiers + + ; There may be attached functions to our type. For the general case of + ; variables of our type, that's already handled via normal lexical scope + ; resolution. But for casting/`new` invocations that we resolve through the + ; `()` guard above, we need to explicitly jump to the extension scope from + ; here to attempt resolving the attached function. We cannot jump back to the + ; parent scope because that would create a cycle in the graph. + node push_typeof + 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 -> hook + + if (version-matches "< 0.5.0") { + ; For Solidity < 0.5.0 `this` also acts like an `address` + node address_ref + attr (address_ref) push_symbol = "%address" + node address_typeof + attr (address_typeof) push_symbol = "@typeof" + edge this -> address_typeof + edge address_typeof -> address_ref + edge address_ref -> @contract.lexical_scope + } + + ; This is the connection point to resolve attached functions by `using for *` + node @contract.star_extension + attr (@contract.star_extension) push_symbol = "@*" + + if (version-matches "< 0.7.0") { + ; 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.lexical_scope + } + + ; Path to resolve the built-in type for type() expressions + node type + attr (type) pop_symbol = "@type" + node type_contract_type + attr (type_contract_type) push_symbol = "%ContractTypeType" + edge @contract.def -> type + edge type -> type_contract_type + edge type_contract_type -> @contract.parent_scope + + ; The following defines the connection nodes the resolution algorithm uses + ; *only when setting a compilation context/target*. + + ; This attribute defines the sink of edges added from base contracts when + ; setting this contract as the compilation context, and should provide access + ; to anything that can be reached through `super`. The instance scope is a bit + ; too broad, but `.members` is too narrow as it doesn't allow navigation to + ; parent contracts (and from the base we need to be able to reach all + ; contracts in the hierarchy). + attr (@contract.def) export_node = @contract.instance + + ; This node will eventually connect to the contract's members being compiled + ; and grants access to definitions in that contract and all its parents + ; (recursively). It only makes sense if `super` is defined (ie. if we have + ; parents), but we define it here to be able to use it in the declaration of + ; import nodes. This is the dual of the export_node above. + node @contract.super_import + attr (@contract.super_import) pop_symbol = "." + + ; This defines the source side of edges added to base contracts when setting + ; a contract as compilation context; this allows this contract (a base) to + ; access virtual methods in any sub-contract defined in the hierarchy (both + ; with and without `super`, hence the two connection points). + attr (@contract.def) import_nodes = [@contract.lexical_scope, @contract.super_import] +} + +@contract [ContractDefinition @specifier [InheritanceSpecifier]] { + ; The `.heir` scoped variable allows the rules for `InheritanceSpecifier` + ; 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. + + ; `super_scope` is where we hook all references to our parent contracts + node @contract.super_scope + + ; Define "super" in the lexical scope + node @contract.super + attr (@contract.super) pop_symbol = "super" + edge @contract.lexical_scope -> @contract.super + + ; This connects `super` to exported scopes from all contracts in the hierarchy + ; when setting a contract compilation target (see more detailed description + ; above on the definition of the `super_import` node). + edge @contract.super -> @contract.super_import + + ; Then connect it through an `@instance` guard to the parent contracts through + ; `super_scope`. This allows "instance"-like access to members of parents + ; through `super`. + node super_instance + attr (super_instance) push_symbol = "@instance" + edge @contract.super_import -> super_instance + edge super_instance -> @contract.super_scope +} + +@contract [ContractDefinition [InheritanceSpecifier [InheritanceTypes + [InheritanceType @type_name [IdentifierPath]] +]]] { + ;; The base contract defs are directly accesible through our super scope + edge @contract.super_scope -> @type_name.push_begin +} + +; Pure definitions that cannot contain expressions +@contract [ContractDefinition [ContractMembers + [ContractMember @member ( + [EnumDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + edge @member.lexical_scope -> @contract.lexical_scope +} + +; Definitions that can contain expressions need two scopes: +; - normal lexical scope for resolving types +; - extended scope (extended by using directives) for resolving expressions +@contract [ContractDefinition [ContractMembers + [ContractMember @member ( + [FunctionDefinition] + | [ConstructorDefinition] + | [ModifierDefinition] + | [FallbackFunctionDefinition] + | [ReceiveFunctionDefinition] + | [UnnamedFunctionDefinition] + | [StateVariableDefinition] + )] +]] { + edge @member.lexical_scope -> @contract.lexical_scope +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @using [UsingDirective]] +]] { + ; Hook the using definition in the extensions scope + edge @contract.extensions -> @using.def +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @member ( + [EnumDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + ; These definition go into the "namespace" scope and are accessible externally + ; via qualified naming (eg. `Contract.MyStruct`) + edge @contract.ns -> @member.def +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @state_var [StateVariableDefinition]] +]] { + ; State variables are available to derived contracts. + ; TODO: this also exposes private state variables to derived contracts, but we + ; can't easily filter them because we don't have negative assertions in our + ; query language (we would need to modify this query for anything *not* + ; containing a `PrivateKeyword` node) + edge @contract.instance -> @state_var.def +} + +;; Public state variables are also exposed as external member functions +@contract [ContractDefinition [ContractMembers + [ContractMember @state_var [StateVariableDefinition + [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] + ]] +]] { + edge @contract.members -> @state_var.def +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @function [FunctionDefinition]] +]] { + ;; Contract functions are also accessible for an instance of the contract + edge @contract.members -> @function.def + + ;; This may prioritize this definition (when there are multiple options) + ;; according to the C3 linerisation ordering + attr (@function.def) tag = "c3" + attr (@function.def) parents = [@contract.def] +} + +@contract [ContractDefinition [ContractMembers + [ContractMember @function [FunctionDefinition + [FunctionAttributes [FunctionAttribute ([ExternalKeyword] | [PublicKeyword])]] + ]] +]] { + ; Public or external functions are also accessible through the contract type + ; (to retrieve their `.selector` for example) + edge @contract.ns -> @function.def +} + +@contract [ContractDefinition members: [ContractMembers + [ContractMember @modifier [ModifierDefinition]] +]] { + ; Modifiers live in their own special scope + edge @contract.modifiers -> @modifier.def + + ;; This may prioritize this definition (when there are multiple options) + ;; according to the C3 linerisation ordering + attr (@modifier.def) tag = "c3" + attr (@modifier.def) parents = [@contract.def] +} + +@contract [ContractDefinition [ContractMembers [ContractMember + [UsingDirective [UsingTarget [Asterisk]]] +]]] { + ; 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.lexical_scope +} + +; This applies to both state variables and function definitions +@override [OverrideSpecifier [OverridePathsDeclaration [OverridePaths + @base_ident [IdentifierPath] +]]] { + ;; Resolve overriden bases when listed in the function or modifiers modifiers + edge @base_ident.push_end -> @override.parent_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Interfaces +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@interface [InterfaceDefinition @name name: [Identifier]] { + node @interface.lexical_scope + node @interface.def + node @interface.members + node @interface.ns + node @interface.instance + + attr (@interface.def) node_definition = @name + attr (@interface.def) definiens_node = @interface + + edge @interface.lexical_scope -> @interface.instance + + ; The extensions node is required for the inheritance rules, but not used in interfaces + let @interface.extensions = (node) + + edge @interface.instance -> @interface.members + edge @interface.instance -> @interface.ns + + ;; External "instance" like access path, to access members of a variable of + ;; the interface's type or through a casting call. + node member + attr (member) pop_symbol = "." + edge member -> @interface.instance + + node typeof + attr (typeof) pop_symbol = "@typeof" + edge @interface.def -> typeof + edge typeof -> member + + node call + attr (call) pop_symbol = "()" + edge @interface.def -> call + edge call -> member + + ; From a call we may need to resolve using the extensions scope, in case there's + ; a `using` directive on our type. This path ends up jumping to scope just to + ; handle that case. + node push_typeof + attr (push_typeof) push_symbol = "@typeof" + node push_name + attr (push_name) push_symbol = (source-text @name) + edge call -> push_typeof + edge push_typeof -> push_name + node hook + attr (hook) extension_hook + edge push_name -> hook + ; edge push_name -> JUMP_TO_SCOPE_NODE + + ;; "namespace" like access path + node ns_member + attr (ns_member) pop_symbol = "." + edge @interface.def -> ns_member + edge ns_member -> @interface.ns + + ; Finally there's guarded `@instance` path used by derived contracts to access + ; instance accessible members + node instance + attr (instance) pop_symbol = "@instance" + edge @interface.def -> instance + edge instance -> @interface.instance + + ; Path to resolve the built-in type for type() expressions + node type + attr (type) pop_symbol = "@type" + node type_interface_type + attr (type_interface_type) push_symbol = "%InterfaceTypeType" + edge @interface.def -> type + edge type -> type_interface_type + edge type_interface_type -> @interface.parent_scope +} + +@interface [InterfaceDefinition @specifier [InheritanceSpecifier]] { + let @specifier.heir = @interface + attr (@interface.def) parents = @specifier.parent_refs +} + +@interface [InterfaceDefinition [InterfaceMembers + [ContractMember @member ( + [EnumDefinition] + | [FunctionDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + edge @member.lexical_scope -> @interface.lexical_scope + edge @interface.ns -> @member.def +} + +;; Allow references (eg. variables of the interface type) to the interface to +;; access functions +@interface [InterfaceDefinition members: [InterfaceMembers + item: [ContractMember @function variant: [FunctionDefinition]] +]] { + edge @interface.members -> @function.def +} + +[InterfaceDefinition [InterfaceMembers [ContractMember @using [UsingDirective]]]] { + ; using directives are not allowed in interfaces, but the grammar allows them + ; so we need to create an artificial node here to connect to created edges from + ; the instance nodes + let @using.lexical_scope = (node) +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Common inheritance rules (apply to contracts and interfaces) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@specifier [InheritanceSpecifier [InheritanceTypes + [InheritanceType @type_name [IdentifierPath]] +]] { + ;; This should point to the enclosing contract or interface definition + let heir = @specifier.heir + + ;; Resolve base names through the parent scope of our heir (contract or + ;; interface), aka the source unit + edge @type_name.push_end -> heir.parent_scope + + ; Access instance members of the inherited contract/interface, from the + ; instance scope of the inheriting contract/interface + node instance + attr (instance) push_symbol = "@instance" + edge heir.instance -> instance + edge instance -> @type_name.push_begin + + ; Base members can also be accessed (from the instance scope) qualified with + ; the base name (eg. `Base.something`) + node member_pop + attr (member_pop) pop_symbol = "." + edge heir.instance -> @type_name.pop_begin + edge @type_name.pop_end -> member_pop + edge member_pop -> instance + + ; Base namespace-like members (ie. enums, structs, etc) are also accessible as + ; our own namespace members + node ns_member + attr (ns_member) push_symbol = "." + edge heir.ns -> ns_member + edge ns_member -> @type_name.push_begin +} + +;; The next couple of rules setup a `.parent_refs` attribute to use in the +;; resolution algorithm to perform linearisation of a contract hierarchy. + +;; NOTE: we use anchors here to prevent the query engine from returning all the +;; sublists of possible parents +@specifier [InheritanceSpecifier [InheritanceTypes . @parents [_]+ .]] { + var parent_refs = [] + for parent in @parents { + if (eq (node-type parent) "InheritanceType") { + ;; this is intentionally reversed because of how Solidity linearised the contract bases + set parent_refs = (concat [parent.ref] parent_refs) + } + } + let @specifier.parent_refs = parent_refs +} + +@parent [InheritanceType @type_name [IdentifierPath]] { + let @parent.ref = @type_name.push_begin +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Libraries +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@library [LibraryDefinition @name name: [Identifier]] { + node @library.lexical_scope + node @library.extensions + node @library.def + node @library.ns + node @library.modifiers + + 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 + edge member -> @library.ns + + ; Access to modifiers is guarded by a @modifier symbol + node modifier + attr (modifier) pop_symbol = "@modifier" + edge @library.ns -> modifier + edge modifier -> @library.modifiers + + ; Path to resolve the built-in type for type() expressions (same as contracts) + node type + attr (type) pop_symbol = "@type" + node type_library_type + attr (type_library_type) push_symbol = "%ContractTypeType" + edge @library.def -> type + edge type -> type_library_type + edge type_library_type -> @library.lexical_scope + + ; This is the connection point to resolve attached functions by `using for *` + node @library.star_extension + attr (@library.star_extension) push_symbol = "@*" +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @member ( + [EnumDefinition] + | [StructDefinition] + | [EventDefinition] + | [ErrorDefinition] + | [UserDefinedValueTypeDefinition] + )] +]] { + edge @member.lexical_scope -> @library.lexical_scope + edge @library.ns -> @member.def +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @member ( + [FunctionDefinition] + | [StateVariableDefinition [StateVariableAttributes [StateVariableAttribute [ConstantKeyword]]]] + )] +]] { + edge @member.lexical_scope -> @library.lexical_scope + edge @library.ns -> @member.def +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @modifier [ModifierDefinition]] +]] { + edge @library.modifiers -> @modifier.def + edge @modifier.lexical_scope -> @library.lexical_scope +} + +@library [LibraryDefinition [LibraryMembers + [ContractMember @using [UsingDirective]] +]] { + ; Expose the using directive from the extensions scope + edge @library.extensions -> @using.def +} + +@library [LibraryDefinition [LibraryMembers [ContractMember + [UsingDirective [UsingTarget [Asterisk]]] +]]] { + ; 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.lexical_scope +} + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function, parameter declarations and modifiers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@param [Parameter @type_name [TypeName]] { + node @param.lexical_scope + node @param.def + + edge @type_name.type_ref -> @param.lexical_scope + + node @param.typeof + attr (@param.typeof) push_symbol = "@typeof" + edge @param.typeof -> @type_name.output +} + +@param [Parameter @name [Identifier]] { + attr (@param.def) node_definition = @name + attr (@param.def) definiens_node = @param + + edge @param.def -> @param.typeof +} + +@params [ParametersDeclaration] { + node @params.lexical_scope + node @params.defs + + ;; This scope can be used to resolve named argument calls + node @params.names + attr (@params.names) pop_symbol = "@param_names" + edge @params.names -> @params.defs +} + +@params [ParametersDeclaration [Parameters @param item: [Parameter]]] { + edge @param.lexical_scope -> @params.lexical_scope + edge @params.defs -> @param.def +} + +@function [FunctionDefinition @attrs [FunctionAttributes]] { + var type_symbol = "%Function" + scan (source-text @attrs) { + "\\b(public|external)\\b" { + set type_symbol = "%ExternalFunction" + } + } + + node @function.lexical_scope + node @function.def + + ; this path from the function definition to the scope allows attaching + ; functions to this function's type + node typeof + attr (typeof) push_symbol = "@typeof" + node type_function + attr (type_function) push_symbol = type_symbol + edge @function.def -> typeof + edge typeof -> type_function + edge type_function -> @function.lexical_scope +} + +@function [FunctionDefinition name: [FunctionName @name [Identifier]]] { + attr (@function.def) node_definition = @name + attr (@function.def) definiens_node = @function +} + +@function [FunctionDefinition @params parameters: [ParametersDeclaration]] { + edge @params.lexical_scope -> @function.lexical_scope + + ;; Input parameters are available in the function scope + edge @function.lexical_scope -> @params.defs + ;; ... and shadow other declarations + attr (@function.lexical_scope -> @params.defs) precedence = 1 + + ;; Connect to paramaters for named argument resolution + edge @function.def -> @params.names +} + +@function [FunctionDefinition returns: [ReturnsDeclaration + @return_params [ParametersDeclaration] +]] { + edge @return_params.lexical_scope -> @function.lexical_scope + + ;; Return parameters are available in the function scope + edge @function.lexical_scope -> @return_params.defs + ;; ... and shadow other declarations + attr (@function.lexical_scope -> @return_params.defs) precedence = 1 +} + +;; Only functions that return a single value have an actual return type +;; since tuples are not actual types in Solidity +@function [FunctionDefinition returns: [ReturnsDeclaration + [ParametersDeclaration [Parameters . @param [Parameter] .]] +]] { + node call + attr (call) pop_symbol = "()" + + edge @function.def -> call + edge call -> @param.typeof +} + +;; Connect the function body's block lexical scope to the function +@function [FunctionDefinition [FunctionBody @block [Block]]] { + edge @block.lexical_scope -> @function.lexical_scope +} + +@function [FunctionDefinition [FunctionAttributes item: [FunctionAttribute + @modifier [ModifierInvocation] +]]] { + edge @modifier.lexical_scope -> @function.lexical_scope +} + +@modifier [ModifierInvocation @name [IdentifierPath]] { + node @modifier.lexical_scope + + node modifier + attr (modifier) push_symbol = "@modifier" + + edge @name.push_end -> modifier + edge modifier -> @modifier.lexical_scope + + ; This allows resolving @name in the more general scope in constructors (since + ; calling a parent constructor is parsed as a modifier invocation) + let @modifier.identifier = @name.push_end +} + +@modifier [ModifierInvocation @args [ArgumentsDeclaration]] { + edge @args.lexical_scope -> @modifier.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; State Variables +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@state_var [StateVariableDefinition] { + node @state_var.lexical_scope + node @state_var.def +} + +@state_var [StateVariableDefinition + @type_name type_name: [TypeName] + @name name: [Identifier] +] { + attr (@state_var.def) node_definition = @name + attr (@state_var.def) definiens_node = @state_var + + edge @type_name.type_ref -> @state_var.lexical_scope + + node @state_var.typeof + attr (@state_var.typeof) push_symbol = "@typeof" + + edge @state_var.def -> @state_var.typeof + edge @state_var.typeof -> @type_name.output +} + +@state_var [StateVariableDefinition + [StateVariableAttributes [StateVariableAttribute [PublicKeyword]]] +] { + ; Public state variables are used as functions when invoked from an external contract + node call + attr (call) pop_symbol = "()" + + ; In the general case using the getter can bind to the state variable's type + edge @state_var.def -> call + edge call -> @state_var.typeof + + ; Some complex types generate special getters (ie. arrays and mappings index + ; their contents, structs flatten most of their fields and return a tuple) + node getter + attr (getter) push_symbol = "@as_getter" + edge call -> getter + edge getter -> @state_var.typeof +} + +@state_var [StateVariableDefinition + [StateVariableDefinitionValue @value [Expression]] +] { + let @value.lexical_scope = @state_var.lexical_scope +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Structure definitions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@struct [StructDefinition @name name: [Identifier]] { + node @struct.lexical_scope + node @struct.def + node @struct.members + + attr (@struct.def) node_definition = @name + attr (@struct.def) definiens_node = @struct + + ; Now connect normally to the struct members + node @struct.typeof + attr (@struct.typeof) pop_symbol = "@typeof" + node member + attr (member) pop_symbol = "." + edge @struct.def -> @struct.typeof + edge @struct.typeof -> member + edge member -> @struct.members + + ; Bind member names when using construction with named arguments + node param_names + attr (param_names) pop_symbol = "@param_names" + edge @struct.def -> param_names + edge param_names -> @struct.members + + ; Used as a function call (ie. casting), should bind to itself + node call + attr (call) pop_symbol = "()" + edge @struct.def -> call + edge call -> member +} + +@struct [StructDefinition [StructMembers + @member item: [StructMember @type_name [TypeName] @name name: [Identifier]] +]] { + node @member.def + attr (@member.def) node_definition = @name + attr (@member.def) definiens_node = @member + + edge @struct.members -> @member.def + + edge @type_name.type_ref -> @struct.lexical_scope + + node @member.typeof + attr (@member.typeof) push_symbol = "@typeof" + + edge @member.def -> @member.typeof + edge @member.typeof -> @type_name.output +} + +@struct [StructDefinition [StructMembers . @first_member [StructMember]]] { + ; As a public getter result, the value returned is a tuple with all our fields flattened + ; We only care about the first member for name binding, since tuples are not real types + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge @struct.typeof -> getter_call + edge getter_call -> @first_member.typeof +} + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Identifier Paths (aka. references to custom types) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; The identifier path builds two graph paths: +;; +;; - From right to left, pushing the identifiers and acting as a "reference". +;; This path begins at @id_path.push_begin and ends at @id_path.push_end. +;; +;; - From left to right, popping the identifiers (used as a definition sink in +;; using directives). This path begins at @id_path.pop_begin and ends at +;; @id_path.pop_end. +;; +;; NOTE: most of the time, and unless this identifier path is the target of a +;; using directive this second path will not be used and will form a +;; disconnected graph component. We currently have no way of determining when +;; this path is necessary, so we always construct it. +;; +;; Additionally the IdentifierPath defines another scoped variable +;; @id_path.rightmost_identifier which corresponds to the identifier in the last +;; position in the path, from left to right. This is used in the using directive +;; rules to be able to pop the name of the attached function. + +@id_path [IdentifierPath] { + ; This node connects to all parts of the path, for popping. This allows to + ; connect at any point of the path. Useful for `using` directives when the + ; target type is fully qualified but we want to resolve for the unqualified + ; name. + node @id_path.all_pop_begin +} + +@id_path [IdentifierPath @name [Identifier]] { + node @name.ref + attr (@name.ref) node_reference = @name + attr (@name.ref) parents = [@id_path.enclosing_def] + + node @name.pop + attr (@name.pop) pop_symbol = (source-text @name) + + edge @id_path.all_pop_begin -> @name.pop +} + +@id_path [IdentifierPath @name [Identifier] .] { + let @id_path.rightmost_identifier = @name + + let @id_path.push_begin = @name.ref + let @id_path.pop_end = @name.pop +} + +[IdentifierPath @left_name [Identifier] . [Period] . @right_name [Identifier]] { + node ref_member + attr (ref_member) push_symbol = "." + + edge @right_name.ref -> ref_member + edge ref_member -> @left_name.ref + + node pop_member + attr (pop_member) pop_symbol = "." + + edge @left_name.pop -> pop_member + edge pop_member -> @right_name.pop +} + +@id_path [IdentifierPath . @name [Identifier]] { + let @id_path.push_end = @name.ref + let @id_path.pop_begin = @name.pop +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Type names +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TypeName nodes should define these scoped variables: +;; +;; - @type_name.type_ref represents the node in the graph where we're ready to +;; resolve the type, and thus should generally be connected to a (lexical) +;; scope node (source node, outside edges connect *from* here). +;; +;; - @type_name.output represents the other end of the type and corresponds to a +;; state where the type has already been resolved so we can, for example +;; resolve its members (sink node, outside edges connect *to* here). +;; +;; - @type_name.pop_begin, @type_name.pop_end are used in a definition context, +;; ie. when we need to pop the type name symbol(s) from the symbol stack. +;; Additionally, @type_name.all_pop_begin links to each symbol in a typename +;; (ie. in an identifier path typename), which allows referring to a type both +;; qualified and unqualified. + +@type_name [TypeName @elementary [ElementaryType]] { + let @type_name.type_ref = @elementary.ref + let @type_name.output = @elementary.ref + let @type_name.pop_begin = @elementary.pop + let @type_name.pop_end = @elementary.pop + let @type_name.all_pop_begin = @elementary.pop +} + +@type_name [TypeName @id_path [IdentifierPath]] { + ;; For an identifier path used as a type, the left-most element is the one + ;; that connects to the parent lexical scope, because the name resolution + ;; starts at the left of the identifier. + let @type_name.type_ref = @id_path.push_end + + ;; Conversely, the complete type is found at the right-most name, and that's + ;; where users of this type should link to (eg. a variable declaration). + let @type_name.output = @id_path.push_begin + + let @type_name.pop_begin = @id_path.pop_begin + let @type_name.pop_end = @id_path.pop_end + let @type_name.all_pop_begin = @id_path.all_pop_begin +} + +@type_name [TypeName @type_variant ([ArrayTypeName] | [FunctionType])] { + let @type_name.type_ref = @type_variant.lexical_scope + let @type_name.output = @type_variant.output + let @type_name.pop_begin = @type_variant.pop_begin + let @type_name.pop_end = @type_variant.pop_end + let @type_name.all_pop_begin = @type_variant.pop_begin +} + +@type_name [TypeName @mapping [MappingType]] { + let @type_name.type_ref = @mapping.lexical_scope + let @type_name.output = @mapping.output + let @type_name.pop_begin = @mapping.pop_begin + let @type_name.pop_end = @mapping.pop_end + let @type_name.all_pop_begin = @mapping.pop_begin +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Elementary types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@elementary [ElementaryType] { + node @elementary.ref + attr (@elementary.ref) type = "push_symbol" + attr (@elementary.ref) source_node = @elementary, symbol = @elementary.symbol + + node @elementary.pop + attr (@elementary.pop) pop_symbol = @elementary.symbol + + ; These variables are a bit redundant, but necessary to easily use elementary + ; types as mapping keys + let @elementary.pop_begin = @elementary.pop + let @elementary.pop_end = @elementary.pop + let @elementary.all_pop_begin = @elementary.pop + + let @elementary.push_begin = @elementary.ref + let @elementary.push_end = @elementary.ref +} + +@elementary [ElementaryType [AddressType]] { + let @elementary.symbol = "%address" +} + +@elementary [ElementaryType [BoolKeyword]] { + let @elementary.symbol = "%bool" +} + +@elementary [ElementaryType [ByteKeyword]] { + let @elementary.symbol = "%byte" +} + +@elementary [ElementaryType @keyword [BytesKeyword]] { + let @elementary.symbol = (format "%{}" (source-text @keyword)) +} + +@elementary [ElementaryType [StringKeyword]] { + let @elementary.symbol = "%string" +} + +@elementary [ElementaryType @keyword [IntKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "int") { + let @elementary.symbol = "%int256" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + +@elementary [ElementaryType @keyword [UintKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "uint") { + let @elementary.symbol = "%uint256" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + +@elementary [ElementaryType @keyword [FixedKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "fixed") { + let @elementary.symbol = "%fixed128x18" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + +@elementary [ElementaryType @keyword [UfixedKeyword]] { + let symbol = (source-text @keyword) + if (eq symbol "ufixed") { + let @elementary.symbol = "%ufixed128x18" + } else { + let @elementary.symbol = (format "%{}" symbol) + } +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Mappings +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@mapping [MappingType + [MappingKey [MappingKeyType @key_type ([IdentifierPath] | [ElementaryType])]] + [MappingValue @value_type [TypeName]] +] { + node @mapping.lexical_scope + node @mapping.output + + ; Define the pushing path of the mapping type + ; ValueType <- top of the symbol stack + ; KeyType + ; %mapping <- bottom of the symbol stack + node mapping + attr (mapping) push_symbol = "%Mapping" + edge @mapping.output -> mapping + edge mapping -> @key_type.push_begin + edge @key_type.push_end -> @value_type.output + + ; Both key and value types need to be resolved + edge @value_type.type_ref -> @mapping.lexical_scope + edge @key_type.push_end -> @mapping.lexical_scope + + ; The mapping's type exposes the `[]` operator that returns the value type. + + node typeof_input + attr (typeof_input) pop_symbol = "@typeof" + edge @mapping.output -> typeof_input + + node typeof_output + attr (typeof_output) push_symbol = "@typeof" + edge typeof_output -> @value_type.output + + node index + attr (index) pop_symbol = "[]" + edge typeof_input -> index + edge index -> typeof_output + + ; Special case for mapping public state variables: they can be called + ; like a function with a key, and it's effectively the same as indexing it. + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge typeof_input -> getter_call + edge getter_call -> typeof_output + + ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only + ; This is the reverse of the pushing path above (to the `.output` node) + node pop_mapping + attr (pop_mapping) pop_symbol = "%Mapping" + + let @mapping.pop_begin = @value_type.pop_begin + edge @value_type.pop_end -> @key_type.pop_begin + edge @key_type.pop_end -> pop_mapping + let @mapping.pop_end = pop_mapping +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Arrays types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@array [ArrayTypeName] { + node @array.lexical_scope + node @array.output +} + +@array [ArrayTypeName [TypeName] index: [Expression]] { + let @array.type_symbol = "%FixedArray" +} + +@array [ArrayTypeName [OpenBracket] . [CloseBracket]] { + let @array.type_symbol = "%Array" +} + +@array [ArrayTypeName @type_name [TypeName]] { + ; Define the pushing path of the array type + ; ValueType <- top of the symbol stack + ; %array / %arrayFixed <- bottom of the symbol stack + node array + attr (array) push_symbol = @array.type_symbol + edge @array.output -> array + edge array -> @type_name.output + + ; Resolve the value type itself + edge @type_name.type_ref -> @array.lexical_scope + ; And also the "type erased" array type so we can resolve built-in members + edge array -> @array.lexical_scope + + ; Define the path to resolve index access (aka the `[]` operator) + + node typeof_input + attr (typeof_input) pop_symbol = "@typeof" + edge @array.output -> typeof_input + + node typeof_output + attr (typeof_output) push_symbol = "@typeof" + edge typeof_output -> @type_name.output + + node index + attr (index) pop_symbol = "[]" + edge typeof_input -> index + edge index -> typeof_output + + ; Special case for public state variables of type array: they can be called + ; like a function with an index, and it's effectively the same as indexing the + ; array. + node getter_call + attr (getter_call) pop_symbol = "@as_getter" + edge typeof_input -> getter_call + edge getter_call -> typeof_output + + ; Define the special `.push()` built-in that returns the element type (for Solidity >= 0.6.0) + if (version-matches ">= 0.6.0") { + node built_in_member + attr (built_in_member) pop_symbol = "." + node push_built_in + attr (push_built_in) pop_symbol = "push" + node built_in_call + attr (built_in_call) pop_symbol = "()" + + edge typeof_input -> built_in_member + edge built_in_member -> push_built_in + edge push_built_in -> built_in_call + edge built_in_call -> typeof_output + } + + ; Now we define the "definition" route (aka. the pop route), to use in `using` directives only + ; This is essentially the reverse of the second path above + node pop_array + attr (pop_array) pop_symbol = @array.type_symbol + + let @array.pop_begin = @type_name.pop_begin + edge @type_name.pop_end -> pop_array + let @array.pop_end = pop_array +} + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Function types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +@ftype [FunctionType @attrs [FunctionTypeAttributes]] { + ; Compute the built-in type of the function + ; %functionExternal provides access to .selector and .address + var type_symbol = "%Function" + scan (source-text @attrs) { + "external" { + set type_symbol = "%ExternalFunction" + } + } + + node @ftype.lexical_scope + node @ftype.output + + ; This path pushes the function type to the symbol stack + ; TODO: add parameter and return types to distinguish between different function types + node function_type + attr (function_type) push_symbol = type_symbol + + edge @ftype.output -> function_type + edge function_type -> @ftype.lexical_scope + + ; the pop path for the using directive + node pop_function_type + attr (pop_function_type) pop_symbol = type_symbol + + let @ftype.pop_begin = pop_function_type + let @ftype.pop_end = pop_function_type +} + +@ftype [FunctionType @params [ParametersDeclaration]] { + edge @params.lexical_scope -> @ftype.lexical_scope +} + +@ftype [FunctionType [ReturnsDeclaration @return_params [ParametersDeclaration]]] { + edge @return_params.lexical_scope -> @ftype.lexical_scope +} + +@ftype [FunctionType [ReturnsDeclaration + [ParametersDeclaration [Parameters . @param [Parameter] .]] +]] { + ; Variables of a function type type can be "called" and resolve to the type of + ; the return parameter. This is only valid if the function returns a single + ; value. + node typeof + attr (typeof) pop_symbol = "@typeof" + + node call + attr (call) pop_symbol = "()" + + edge @ftype.output -> typeof + edge typeof -> call + edge call -> @param.typeof +} + +"#####; diff --git a/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs b/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs index 7803930461..965983ba21 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs @@ -34,7 +34,8 @@ pub fn create_with_resolver( ) -> Result { let mut binding_graph = BindingGraph::create( version.clone(), - binding_rules::BINDING_RULES_SOURCE, + binding_rules::USER_BINDING_RULES_SOURCE, + binding_rules::SYSTEM_BINDING_RULES_SOURCE, resolver, ); @@ -44,6 +45,11 @@ pub fn create_with_resolver( } #[cfg(feature = "__private_testing_utils")] -pub fn get_binding_rules() -> &'static str { - binding_rules::BINDING_RULES_SOURCE +pub fn get_user_binding_rules() -> &'static str { + binding_rules::USER_BINDING_RULES_SOURCE +} + +#[cfg(feature = "__private_testing_utils")] +pub fn get_system_binding_rules() -> &'static str { + binding_rules::SYSTEM_BINDING_RULES_SOURCE } diff --git a/crates/solidity/outputs/cargo/tests/src/binding_rules.rs b/crates/solidity/outputs/cargo/tests/src/binding_rules.rs index e8bbc239ab..b9ce8a19a1 100644 --- a/crates/solidity/outputs/cargo/tests/src/binding_rules.rs +++ b/crates/solidity/outputs/cargo/tests/src/binding_rules.rs @@ -5,17 +5,34 @@ use slang_solidity::bindings; use slang_solidity::cst::KindTypes; #[test] -fn test_binding_rules_parse_successfully() { - let binding_rules = bindings::get_binding_rules(); +fn test_user_binding_rules_parse_successfully() { + let binding_rules = bindings::get_user_binding_rules(); let graph_builder = File::::from_str(binding_rules); assert!( graph_builder.is_ok(), - "Parsing binding rules failed:\n{}", + "Parsing user binding rules failed:\n{}", graph_builder .err() .map(|err| err - .display_pretty(&PathBuf::from("rules.msgb"), binding_rules) + .display_pretty(&PathBuf::from("user-rules.msgb"), binding_rules) + .to_string()) + .unwrap_or_default() + ); +} + +#[test] +fn test_system_binding_rules_parse_successfully() { + let binding_rules = bindings::get_system_binding_rules(); + let graph_builder = File::::from_str(binding_rules); + + assert!( + graph_builder.is_ok(), + "Parsing system binding rules failed:\n{}", + graph_builder + .err() + .map(|err| err + .display_pretty(&PathBuf::from("system-rules.msgb"), binding_rules) .to_string()) .unwrap_or_default() ); diff --git a/crates/testlang/inputs/language/bindings/system-rules.parts b/crates/testlang/inputs/language/bindings/system-rules.parts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs b/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs index 16e24e7bb4..1f70f53791 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/bindings/generated/binding_rules.rs @@ -1,7 +1,11 @@ // This file is generated automatically by infrastructure scripts. Please don't edit by hand. #[allow(clippy::needless_raw_string_hashes)] -#[allow(dead_code)] // TODO(#982): use to create the graph -pub const BINDING_RULES_SOURCE: &str = r#####" +pub const USER_BINDING_RULES_SOURCE: &str = r#####" + +"#####; + +#[allow(clippy::needless_raw_string_hashes)] +pub const SYSTEM_BINDING_RULES_SOURCE: &str = r#####" "#####; diff --git a/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs b/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs index 7803930461..965983ba21 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs @@ -34,7 +34,8 @@ pub fn create_with_resolver( ) -> Result { let mut binding_graph = BindingGraph::create( version.clone(), - binding_rules::BINDING_RULES_SOURCE, + binding_rules::USER_BINDING_RULES_SOURCE, + binding_rules::SYSTEM_BINDING_RULES_SOURCE, resolver, ); @@ -44,6 +45,11 @@ pub fn create_with_resolver( } #[cfg(feature = "__private_testing_utils")] -pub fn get_binding_rules() -> &'static str { - binding_rules::BINDING_RULES_SOURCE +pub fn get_user_binding_rules() -> &'static str { + binding_rules::USER_BINDING_RULES_SOURCE +} + +#[cfg(feature = "__private_testing_utils")] +pub fn get_system_binding_rules() -> &'static str { + binding_rules::SYSTEM_BINDING_RULES_SOURCE } From 177c7f09d7fad61c38dc7759260c1a83b310f1d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Fri, 29 Nov 2024 14:52:23 -0500 Subject: [PATCH 5/6] Use a fixed Solidity parser version to parse built-ins file --- .../cargo/crate/src/extensions/bindings/mod.rs | 2 +- .../cargo/crate/src/runtime/bindings/mod.rs | 3 ++- .../cargo/crate/src/extensions/bindings/mod.rs | 16 +++++++++++----- .../cargo/crate/src/generated/bindings/mod.rs | 3 ++- .../cargo/crate/src/extensions/bindings/mod.rs | 2 +- .../cargo/crate/src/generated/bindings/mod.rs | 3 ++- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/crates/codegen/runtime/cargo/crate/src/extensions/bindings/mod.rs b/crates/codegen/runtime/cargo/crate/src/extensions/bindings/mod.rs index eee8b7880f..92cf63b2e4 100644 --- a/crates/codegen/runtime/cargo/crate/src/extensions/bindings/mod.rs +++ b/crates/codegen/runtime/cargo/crate/src/extensions/bindings/mod.rs @@ -6,7 +6,7 @@ use crate::parser::ParserInitializationError; #[allow(clippy::needless_pass_by_value)] pub fn add_built_ins( _binding_graph: &mut BindingGraph, - _version: Version, + _version: &Version, ) -> Result<(), ParserInitializationError> { unreachable!("Built-ins are Solidity-specific") } diff --git a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs index 5aa7ed4638..d788c030ae 100644 --- a/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs +++ b/crates/codegen/runtime/cargo/crate/src/runtime/bindings/mod.rs @@ -26,6 +26,7 @@ pub enum BindingGraphInitializationError { ParserInitialization(#[from] ParserInitializationError), } +#[allow(clippy::needless_pass_by_value)] pub fn create_with_resolver( version: Version, resolver: Rc>, @@ -37,7 +38,7 @@ pub fn create_with_resolver( resolver, ); - crate::extensions::bindings::add_built_ins(&mut binding_graph, version)?; + crate::extensions::bindings::add_built_ins(&mut binding_graph, &version)?; Ok(binding_graph) } diff --git a/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs b/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs index b3ec4aa052..d2bdb47843 100644 --- a/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs +++ b/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs @@ -8,15 +8,21 @@ use crate::bindings::BindingGraph; use crate::cst::{Edge, Node, NonterminalNode, TerminalKind, TerminalNode}; use crate::parser::{Parser, ParserInitializationError}; +static BUILT_INS_PARSER_VERSION: Version = Version::new(0, 8, 28); + pub fn add_built_ins( binding_graph: &mut BindingGraph, - version: Version, + version: &Version, ) -> Result<(), ParserInitializationError> { - let source = get_built_ins_contents(&version); - let parser = Parser::create(version)?; - let parse_output = parser.parse(Parser::ROOT_KIND, source); + let parser = Parser::create(BUILT_INS_PARSER_VERSION.clone())?; + let built_ins_parse_output = parser.parse(Parser::ROOT_KIND, get_built_ins_contents(version)); + assert!( + built_ins_parse_output.is_valid(), + "built-ins parse without errors" + ); - let built_ins_cursor = transform(parse_output.tree()).cursor_with_offset(TextIndex::ZERO); + let built_ins_cursor = transform(built_ins_parse_output.tree()) + .cursor_with_offset(TextIndex::ZERO); binding_graph.add_system_file("built_ins.sol", built_ins_cursor); Ok(()) diff --git a/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs b/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs index 965983ba21..d1a8401d8d 100644 --- a/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs +++ b/crates/solidity/outputs/cargo/crate/src/generated/bindings/mod.rs @@ -28,6 +28,7 @@ pub enum BindingGraphInitializationError { ParserInitialization(#[from] ParserInitializationError), } +#[allow(clippy::needless_pass_by_value)] pub fn create_with_resolver( version: Version, resolver: Rc>, @@ -39,7 +40,7 @@ pub fn create_with_resolver( resolver, ); - crate::extensions::bindings::add_built_ins(&mut binding_graph, version)?; + crate::extensions::bindings::add_built_ins(&mut binding_graph, &version)?; Ok(binding_graph) } diff --git a/crates/testlang/outputs/cargo/crate/src/extensions/bindings/mod.rs b/crates/testlang/outputs/cargo/crate/src/extensions/bindings/mod.rs index eee8b7880f..92cf63b2e4 100644 --- a/crates/testlang/outputs/cargo/crate/src/extensions/bindings/mod.rs +++ b/crates/testlang/outputs/cargo/crate/src/extensions/bindings/mod.rs @@ -6,7 +6,7 @@ use crate::parser::ParserInitializationError; #[allow(clippy::needless_pass_by_value)] pub fn add_built_ins( _binding_graph: &mut BindingGraph, - _version: Version, + _version: &Version, ) -> Result<(), ParserInitializationError> { unreachable!("Built-ins are Solidity-specific") } diff --git a/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs b/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs index 965983ba21..d1a8401d8d 100644 --- a/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs +++ b/crates/testlang/outputs/cargo/crate/src/generated/bindings/mod.rs @@ -28,6 +28,7 @@ pub enum BindingGraphInitializationError { ParserInitialization(#[from] ParserInitializationError), } +#[allow(clippy::needless_pass_by_value)] pub fn create_with_resolver( version: Version, resolver: Rc>, @@ -39,7 +40,7 @@ pub fn create_with_resolver( resolver, ); - crate::extensions::bindings::add_built_ins(&mut binding_graph, version)?; + crate::extensions::bindings::add_built_ins(&mut binding_graph, &version)?; Ok(binding_graph) } From b72d7b453755f30f3fcc86d107d00d19d63e2fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Gir=C3=A1ldez?= Date: Tue, 24 Dec 2024 13:53:54 -0500 Subject: [PATCH 6/6] Fix formatting --- .../outputs/cargo/crate/src/extensions/bindings/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs b/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs index d2bdb47843..cd50f5f6c3 100644 --- a/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs +++ b/crates/solidity/outputs/cargo/crate/src/extensions/bindings/mod.rs @@ -21,8 +21,8 @@ pub fn add_built_ins( "built-ins parse without errors" ); - let built_ins_cursor = transform(built_ins_parse_output.tree()) - .cursor_with_offset(TextIndex::ZERO); + let built_ins_cursor = + transform(built_ins_parse_output.tree()).cursor_with_offset(TextIndex::ZERO); binding_graph.add_system_file("built_ins.sol", built_ins_cursor); Ok(())