From 8bda5186d0caa082959c6ea7c59ff0adbecb5aaf Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Sun, 16 Aug 2026 19:29:00 +0400 Subject: [PATCH 1/4] feat(slang): modules --- Cargo.lock | 14 +- Cargo.toml | 2 +- solx-mlir/tests/lit/enum_variant.sol | 12 ++ .../lit/free_function_operator_reached.sol | 27 ++++ solx-mlir/tests/lit/free_function_pointer.sol | 25 ++++ ...dowing.sol => free_function_shadowing.sol} | 0 .../tests/lit/free_function_using_for.sol | 22 +++ solx-mlir/tests/lit/lit.cfg.py | 11 +- solx-mlir/tests/lit/module_members.sol | 44 ++++++ solx-mlir/tests/lit/module_values.sol | 120 ++++++++++++++++ solx-mlir/tests/lit/names_discarded.sol | 45 ++++++ .../tests/lit/namespace_function_pointer.sol | 8 ++ .../tests/lit/qualifier/library_external.sol | 16 +++ solx-mlir/tests/lit/qualifier/module.sol | 28 ++++ solx-mlir/tests/lit/qualifier/type_name.sol | 80 +++++++++++ .../contract/function/expression/call/mod.rs | 45 ++++-- .../contract/function/expression/member.rs | 13 +- .../src/contract/function/expression/mod.rs | 33 ++++- .../qualifier_library_external/library.sol | 9 ++ .../qualifier_library_external/main.sol | 34 +++++ .../qualifier_library_external/test.json | 19 +++ .../complex/qualifier_module/main.sol | 56 ++++++++ .../complex/qualifier_module/module.sol | 11 ++ .../complex/qualifier_module/nested.sol | 5 + .../complex/qualifier_module/test.json | 29 ++++ .../complex/qualifier_type_name/main.sol | 131 ++++++++++++++++++ .../complex/qualifier_type_name/module.sol | 31 +++++ .../complex/qualifier_type_name/test.json | 65 +++++++++ 28 files changed, 905 insertions(+), 30 deletions(-) create mode 100644 solx-mlir/tests/lit/free_function_operator_reached.sol create mode 100644 solx-mlir/tests/lit/free_function_pointer.sol rename solx-mlir/tests/lit/{function_shadowing.sol => free_function_shadowing.sol} (100%) create mode 100644 solx-mlir/tests/lit/free_function_using_for.sol create mode 100644 solx-mlir/tests/lit/module_members.sol create mode 100644 solx-mlir/tests/lit/module_values.sol create mode 100644 solx-mlir/tests/lit/names_discarded.sol create mode 100644 solx-mlir/tests/lit/qualifier/library_external.sol create mode 100644 solx-mlir/tests/lit/qualifier/module.sol create mode 100644 solx-mlir/tests/lit/qualifier/type_name.sol create mode 100644 tests/solidity/complex/qualifier_library_external/library.sol create mode 100644 tests/solidity/complex/qualifier_library_external/main.sol create mode 100644 tests/solidity/complex/qualifier_library_external/test.json create mode 100644 tests/solidity/complex/qualifier_module/main.sol create mode 100644 tests/solidity/complex/qualifier_module/module.sol create mode 100644 tests/solidity/complex/qualifier_module/nested.sol create mode 100644 tests/solidity/complex/qualifier_module/test.json create mode 100644 tests/solidity/complex/qualifier_type_name/main.sol create mode 100644 tests/solidity/complex/qualifier_type_name/module.sol create mode 100644 tests/solidity/complex/qualifier_type_name/test.json diff --git a/Cargo.lock b/Cargo.lock index d5c20bb1a..475eb9d26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4567,7 +4567,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slang_solidity_v2" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" dependencies = [ "slang_solidity_v2_ast", "slang_solidity_v2_common", @@ -4580,7 +4580,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_ast" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" dependencies = [ "itertools 0.15.0", "num-bigint", @@ -4597,7 +4597,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_common" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" dependencies = [ "fxhash", "indexmap 2.14.0", @@ -4611,12 +4611,12 @@ dependencies = [ [[package]] name = "slang_solidity_v2_cst" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" [[package]] name = "slang_solidity_v2_ir" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" dependencies = [ "slang_solidity_v2_common", "slang_solidity_v2_cst", @@ -4625,7 +4625,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_parser" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" dependencies = [ "lalrpop", "lalrpop-util", @@ -4639,7 +4639,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_semantic" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" +source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" dependencies = [ "num-bigint", "num-integer", diff --git a/Cargo.toml b/Cargo.toml index a9cd8d62d..245db1ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,4 +94,4 @@ features = [ [workspace.dependencies.slang_solidity_v2] git = "https://github.com/NomicFoundation/slang.git" # TODO: pin to a release tag instead of a revision. -rev = "8948967bda8badbc225339b4b97764d13688f2c6" +rev = "5dfc6a34b68d90f05396cee098d159c25d8ae626" diff --git a/solx-mlir/tests/lit/enum_variant.sol b/solx-mlir/tests/lit/enum_variant.sol index b6df4c504..21904bf46 100644 --- a/solx-mlir/tests/lit/enum_variant.sol +++ b/solx-mlir/tests/lit/enum_variant.sol @@ -9,6 +9,10 @@ // CHECK: sol.constant 2 : ui256 // CHECK: sol.enum_cast %{{.*}} : ui256 to !sol.enum<2> +// CHECK: sol.func @{{.*interface_variant.*}}-> !sol.enum<2> +// CHECK: sol.constant 1 : ui256 +// CHECK: sol.enum_cast %{{.*}} : ui256 to !sol.enum<2> + // CHECK: sol.func @{{.*type_min.*}}-> !sol.enum<2> // CHECK: sol.constant 0 : ui256 // CHECK: sol.enum_cast %{{.*}} : ui256 to !sol.enum<2> @@ -17,6 +21,10 @@ // CHECK: sol.constant 2 : ui256 // CHECK: sol.enum_cast %{{.*}} : ui256 to !sol.enum<2> +interface I { + enum E { First, Second, Third } +} + contract C { enum E { First, Second, Third } @@ -28,6 +36,10 @@ contract C { return C.E.Third; } + function interface_variant() public pure returns (I.E) { + return I.E.Second; + } + function type_min() public pure returns (E) { return type(E).min; } diff --git a/solx-mlir/tests/lit/free_function_operator_reached.sol b/solx-mlir/tests/lit/free_function_operator_reached.sol new file mode 100644 index 000000000..6fc7cc234 --- /dev/null +++ b/solx-mlir/tests/lit/free_function_operator_reached.sol @@ -0,0 +1,27 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s + +// Free functions reached only via a user-defined operator binding: solc's print-init calls +// @add yet never lowers it or its helper, so this is solx-only. + +// CHECK: sol.func @{{.*}}f{{.*}}(%{{.*}}: si32, %{{.*}}: si32) -> si32 +// CHECK: sol.call @"add(T,T)_[[ADD:[0-9]+]]"(%{{.*}}, %{{.*}}) : (si32, si32) -> si32 +// CHECK: sol.func @"add(T,T)_[[ADD]]"(%{{.*}}: si32, %{{.*}}: si32) -> si32 +// CHECK: sol.call @"helper(T)_[[HELPER:[0-9]+]]"(%{{.*}}) : (si32) -> si32 +// CHECK: sol.func @"helper(T)_[[HELPER]]"(%{{.*}}: si32) -> si32 + +type T is int32; +using {add as +} for T global; + +function helper(T x) pure returns (T) { + return x; +} + +function add(T x, T y) pure returns (T) { + return helper(x); +} + +contract C { + function f(T x, T y) public pure returns (T) { + return x + y; + } +} diff --git a/solx-mlir/tests/lit/free_function_pointer.sol b/solx-mlir/tests/lit/free_function_pointer.sol new file mode 100644 index 000000000..0d4fdf1d5 --- /dev/null +++ b/solx-mlir/tests/lit/free_function_pointer.sol @@ -0,0 +1,25 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s +// RUN: solc --mlir-action=print-init %s 2>/dev/null | FileCheck %s + +// CHECK: sol.func @{{.*invoke.*}}(%{{.*}}: !sol.func_ref<(ui256) -> ui256>, %{{.*}}: ui256) -> ui256 +// CHECK: sol.func @{{.*run.*}}(%{{.*}}: ui256) -> ui256 attributes {{.*}}selector = -1538984471 : i32 +// CHECK: %[[F:.*]] = sol.func_constant @{{.*increment.*}} : !sol.func_ref<(ui256) -> ui256> +// CHECK: %[[R:.*]] = sol.call @{{.*invoke.*}}(%[[F]], %{{.*}}) : (!sol.func_ref<(ui256) -> ui256>, ui256) -> ui256 +// CHECK: sol.return %[[R]] : ui256 + +function increment(uint256 a) pure returns (uint256) { + return a + 1; +} + +contract C { + function invoke( + function(uint256) pure returns (uint256) f, + uint256 x + ) internal pure returns (uint256) { + return f(x); + } + + function run(uint256 x) public pure returns (uint256) { + return invoke(increment, x); + } +} diff --git a/solx-mlir/tests/lit/function_shadowing.sol b/solx-mlir/tests/lit/free_function_shadowing.sol similarity index 100% rename from solx-mlir/tests/lit/function_shadowing.sol rename to solx-mlir/tests/lit/free_function_shadowing.sol diff --git a/solx-mlir/tests/lit/free_function_using_for.sol b/solx-mlir/tests/lit/free_function_using_for.sol new file mode 100644 index 000000000..d7a79b3ca --- /dev/null +++ b/solx-mlir/tests/lit/free_function_using_for.sol @@ -0,0 +1,22 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s + +// solc's print-init drops the receiver from a using-for free-function call, calling +// `@double() : ()`, while legacy forwards it, so this is solx-only. + +// CHECK: sol.func @{{.*}}f{{.*}}(%{{.*}}: ui256) -> ui256 +// CHECK: %[[R:.*]] = sol.call @"double(uint256)_[[D:[0-9]+]]"(%{{.*}}) : (ui256) -> ui256 +// CHECK: sol.return %[[R]] : ui256 +// CHECK: sol.func @"double(uint256)_[[D]]"(%{{.*}}: ui256) -> ui256 +// CHECK: sol.cmul + +function double(uint256 a) pure returns (uint256) { + return a * 2; +} + +contract C { + using {double} for uint256; + + function f(uint256 x) public pure returns (uint256) { + return x.double(); + } +} diff --git a/solx-mlir/tests/lit/lit.cfg.py b/solx-mlir/tests/lit/lit.cfg.py index e0b814881..9492bfe0e 100644 --- a/solx-mlir/tests/lit/lit.cfg.py +++ b/solx-mlir/tests/lit/lit.cfg.py @@ -6,7 +6,7 @@ config.suffixes = [".sol"] config_dir = os.path.dirname(os.path.abspath(__file__)) -solx_root = os.path.join(config_dir, "..", "..", "..") +solx_root = os.path.normpath(os.path.join(config_dir, "..", "..", "..")) solx_bin_dir = os.path.join(solx_root, "target", os.environ.get("SOLX_LIT_TARGET", ""), "debug") solc_bin_dir = os.path.join(solx_root, "solx-solidity", "build", "solc") @@ -20,6 +20,15 @@ config.substitutions.append( ("%for_loop", os.path.join(solx_root, "tests", "solidity", "simple", "loop", "for").replace("\\", "/")) ) +config.substitutions.append( + ("%qualifier_module", os.path.join(solx_root, "tests", "solidity", "complex", "qualifier_module").replace("\\", "/")) +) +config.substitutions.append( + ("%qualifier_type_name", os.path.join(solx_root, "tests", "solidity", "complex", "qualifier_type_name").replace("\\", "/")) +) +config.substitutions.append( + ("%qualifier_library_external", os.path.join(solx_root, "tests", "solidity", "complex", "qualifier_library_external").replace("\\", "/")) +) config.test_source_root = config_dir config.test_exec_root = os.path.join(config_dir, "Output") diff --git a/solx-mlir/tests/lit/module_members.sol b/solx-mlir/tests/lit/module_members.sol new file mode 100644 index 000000000..ceb3b8121 --- /dev/null +++ b/solx-mlir/tests/lit/module_members.sol @@ -0,0 +1,44 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s + +// solc's print-init substitutes `sol.timestamp` for module member accesses and crashes on a +// renamed self-import, so this is solx-only. + +// CHECK: sol.func @{{.*chained.*}}() -> ui256 attributes {{.*}}selector = 1955792327 : i32 +// CHECK: %[[CK:.*]] = sol.constant 11 : ui8 +// CHECK: %[[CV:.*]] = sol.cast %[[CK]] : ui8 to ui256 +// CHECK: sol.return %[[CV]] : ui256 + +// CHECK: sol.func @{{.*renamed.*}}() -> ui256 attributes {{.*}}selector = -1753621920 : i32 +// CHECK: %[[RA:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: %[[RC:.*]] = sol.call @{{.*freeTriple.*}}(%[[RA]]) : (ui256) -> ui256 +// CHECK: sol.constant 11 : ui8 +// CHECK: sol.return + +// CHECK: sol.func @{{.*starred.*}}() -> ui256 attributes {{.*}}selector = -1638182610 : i32 +// CHECK: %[[SK:.*]] = sol.constant 11 : ui8 +// CHECK: %[[SV:.*]] = sol.cast %[[SK]] : ui8 to ui256 +// CHECK: sol.return %[[SV]] : ui256 + +import "./module_members.sol" as M; +import {FREE_K as RENAMED_K, freeTriple} from "./module_members.sol"; +import * as S from "./module_members.sol"; + +uint256 constant FREE_K = 11; + +function freeTriple(uint256 x) pure returns (uint256) { + return x * 3; +} + +contract ModuleMembers { + function chained() public pure returns (uint256) { + return M.M.M.FREE_K; + } + + function renamed() public pure returns (uint256) { + return RENAMED_K + freeTriple(2); + } + + function starred() public pure returns (uint256) { + return S.FREE_K; + } +} diff --git a/solx-mlir/tests/lit/module_values.sol b/solx-mlir/tests/lit/module_values.sol new file mode 100644 index 000000000..03227014a --- /dev/null +++ b/solx-mlir/tests/lit/module_values.sol @@ -0,0 +1,120 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s + +// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. + +// CHECK: sol.func @{{.*parenthesized.*}}() -> ui256 attributes {{.*}}selector = -923061170 : i32 +// CHECK: %[[PK:.*]] = sol.constant 11 : ui8 +// CHECK: %[[PV:.*]] = sol.cast %[[PK]] : ui8 to ui256 +// CHECK: sol.return %[[PV]] : ui256 + +// CHECK: sol.func @{{.*conditional.*}}() -> ui256 attributes {{.*}}selector = -1619768731 : i32 +// CHECK: %[[CP:.*]] = sol.addr_of @{{.*flag.*}} : !sol.ptr +// CHECK: sol.store %true, %[[CP]] : i1, !sol.ptr +// CHECK: sol.constant 11 : ui8 +// CHECK: sol.return + +// CHECK: sol.func @{{.*chained.*}}() -> ui256 attributes {{.*}}selector = 1955792327 : i32 +// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr +// CHECK: sol.constant 11 : ui8 +// CHECK: sol.return + +// CHECK: sol.func @{{.*called.*}}() -> ui256 attributes {{.*}}selector = 1358542541 : i32 +// CHECK: %[[AV:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr +// CHECK: %[[AF:.*]] = sol.func_constant @{{.*freeHalf.*}} : !sol.func_ref<(ui256) -> ui256> +// CHECK: %[[RV:.*]] = sol.icall %[[AF]](%[[AV]]) : !sol.func_ref<(ui256) -> ui256>, (ui256) -> ui256 +// CHECK: sol.return %[[RV]] : ui256 + +// CHECK: sol.func @{{.*tried.*}}() -> ui256 attributes {{.*}}selector = -553464963 : i32 +// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr +// CHECK: %[[LA:.*]] = sol.lib_addr "{{[^"]*}}Halver" : !sol.address +// CHECK: sol.ext_call "{{.*half.*}}"(%{{.*}}) at %[[LA]] gas %{{.*}}try_call{{.*}} : !sol.address, (ui256) -> (i1, ui256) + +// CHECK: sol.func @{{.*designated.*}}() -> ui256 attributes {{.*}}selector = -1019040149 : i32 +// CHECK: %[[DV:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr +// CHECK: %[[DF:.*]] = sol.func_constant @{{.*freeHalf.*}} : !sol.func_ref<(ui256) -> ui256> +// CHECK: %[[DR:.*]] = sol.icall %[[DF]](%[[DV]]) : !sol.func_ref<(ui256) -> ui256>, (ui256) -> ui256 +// CHECK: sol.return %[[DR]] : ui256 + +// CHECK: sol.func @{{.*qualified.*}}() -> ui256 attributes {{.*}}selector = -228858638 : i32 +// CHECK: %[[QV:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: %[[QR:.*]] = sol.call @{{.*halve.*}}(%[[QV]]) : (ui256) -> ui256 +// CHECK: sol.return %[[QR]] : ui256 + +// CHECK: sol.func @{{.*wrapped.*}}() -> ui256 attributes {{.*}}selector = 1357319496 : i32 +// CHECK: %[[WV:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: sol.return %[[WV]] : ui256 + +// CHECK: sol.func @{{.*stray.*}}() -> i1 attributes {{.*}}selector = -903043920 : i32 +// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr +// CHECK: %[[SP:.*]] = sol.addr_of @{{.*flag.*}} : !sol.ptr +// CHECK: %[[SV:.*]] = sol.load %[[SP]] : !sol.ptr, i1 +// CHECK: sol.return %[[SV]] : i1 + +import "./module_values.sol" as M; + +uint256 constant FREE_K = 11; + +type Cost is uint256; + +function freeHalf(uint256 x) pure returns (uint256) { + return x / 2; +} + +library Halver { + function half(uint256 x) public pure returns (uint256) { + return x / 2; + } +} + +contract ModuleValues { + bool flag; + + function parenthesized() public pure returns (uint256) { + return (M).FREE_K; + } + + function conditional() public returns (uint256) { + return ((flag = true) ? M : M).FREE_K; + } + + function chained() public returns (uint256) { + return ((flag = true) ? M : M).M.FREE_K; + } + + function called() public returns (uint256) { + return ((flag = true) ? M : M).freeHalf(8); + } + + function tried() public returns (uint256) { + try ((flag = true) ? M : M).Halver.half(4) returns (uint256 v) { + return v; + } catch { + return 0; + } + } + + function designated() public returns (uint256) { + return (((flag = true) ? M : M).freeHalf)(8); + } + + function halve(uint256 x) internal pure returns (uint256) { + return x / 2; + } + + function qualified() public returns (uint256) { + return ((flag = true) ? M : M).ModuleValues.halve(6); + } + + function wrapped() public returns (uint256) { + return Cost.unwrap(((flag = true) ? M : M).Cost.wrap(6)); + } + + function stray() public returns (bool) { + M; + M.ModuleValues; + ((flag = true) ? M : M).ModuleValues; + return flag; + } +} diff --git a/solx-mlir/tests/lit/names_discarded.sol b/solx-mlir/tests/lit/names_discarded.sol new file mode 100644 index 000000000..5b738b2af --- /dev/null +++ b/solx-mlir/tests/lit/names_discarded.sol @@ -0,0 +1,45 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s + +// solc's print-init crashes on a discarded struct, error, or event name, so this is solx-only. + +// CHECK: sol.func @{{.*structs.*}}() +// CHECK-NEXT: sol.return + +// CHECK: sol.func @{{.*errors.*}}() +// CHECK-NEXT: sol.return + +// CHECK: sol.func @{{.*events.*}}() +// CHECK-NEXT: sol.return + +struct Pair { + uint256 first; +} + +error Missing(uint256 code); + +event Logged(uint256 code); + +contract C { + struct Inner { + uint256 value; + } + + error Absent(uint256 code); + + event Traced(uint256 code); + + function structs() public pure { + Pair; + Inner; + } + + function errors() public pure { + Missing; + Absent; + } + + function events() public pure { + Logged; + Traced; + } +} diff --git a/solx-mlir/tests/lit/namespace_function_pointer.sol b/solx-mlir/tests/lit/namespace_function_pointer.sol index cff53bb13..7bb4e20c3 100644 --- a/solx-mlir/tests/lit/namespace_function_pointer.sol +++ b/solx-mlir/tests/lit/namespace_function_pointer.sol @@ -9,6 +9,9 @@ // CHECK: sol.func @{{.*run.*}} // CHECK: sol.func_constant @{{.*g.*}} : !sol.func_ref<() -> ui256> // CHECK: sol.icall %{{[0-9]+}}() : !sol.func_ref<() -> ui256>, () -> ui256 +// CHECK: sol.func @{{.*parenthesized.*}} +// CHECK: sol.func_constant @{{.*g.*}} : !sol.func_ref<() -> ui256> +// CHECK: sol.icall %{{[0-9]+}}() : !sol.func_ref<() -> ui256>, () -> ui256 // CHECK: sol.func @{{.*qualified_library.*}} // CHECK: sol.func_constant @{{.*pick.*}} : !sol.func_ref<() -> ui256> // CHECK: sol.icall %{{[0-9]+}}() : !sol.func_ref<() -> ui256>, () -> ui256 @@ -30,6 +33,11 @@ contract C { return functionPointer(); } + function parenthesized() public returns (uint256) { + function () internal returns (uint256) functionPointer = (C).g; + return functionPointer(); + } + function qualified_library() public pure returns (uint256) { function () internal pure returns (uint256) functionPointer = Lib.pick; return functionPointer(); diff --git a/solx-mlir/tests/lit/qualifier/library_external.sol b/solx-mlir/tests/lit/qualifier/library_external.sol new file mode 100644 index 000000000..04b365fdb --- /dev/null +++ b/solx-mlir/tests/lit/qualifier/library_external.sol @@ -0,0 +1,16 @@ +// RUN: solx --emit-mlir=sol %qualifier_library_external/main.sol %qualifier_library_external/library.sol | FileCheck %s + +// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. + +// CHECK: sol.func @{{.*externalCall.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.lib_addr +// CHECK: sol.call @{{.*eight.*}} +// CHECK: sol.ext_call + +// CHECK: sol.func @{{.*tried.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.lib_addr +// CHECK: sol.call @{{.*eight.*}} +// CHECK: sol.ext_call "{{.*half.*}}"({{.*}}try_call +// CHECK: sol.try diff --git a/solx-mlir/tests/lit/qualifier/module.sol b/solx-mlir/tests/lit/qualifier/module.sol new file mode 100644 index 000000000..0e7023303 --- /dev/null +++ b/solx-mlir/tests/lit/qualifier/module.sol @@ -0,0 +1,28 @@ +// RUN: solx --emit-mlir=sol %qualifier_module/main.sol %qualifier_module/module.sol %qualifier_module/nested.sol | FileCheck %s + +// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. + +// CHECK: sol.func @{{.*constantMember.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*freeCall.*}} +// CHECK: sol.call @{{.*eight.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.icall + +// CHECK: sol.func @{{.*designator.*}} +// CHECK: sol.call @{{.*eight.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.icall + +// CHECK: sol.func @{{.*chain.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*aliased.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*stray.*}} +// CHECK: sol.call @{{.*mark.*}} diff --git a/solx-mlir/tests/lit/qualifier/type_name.sol b/solx-mlir/tests/lit/qualifier/type_name.sol new file mode 100644 index 000000000..5f3700538 --- /dev/null +++ b/solx-mlir/tests/lit/qualifier/type_name.sol @@ -0,0 +1,80 @@ +// RUN: solx --emit-mlir=sol %qualifier_type_name/main.sol %qualifier_type_name/module.sol | FileCheck %s + +// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. + +// CHECK: sol.func @{{.*enumMember.*}} +// CHECK: sol.call @{{.*mark.*}} +// CHECK: sol.enum_cast + +// CHECK: sol.func @{{.*interfaceEnum.*}} +// CHECK-NOT: sol.call +// CHECK: sol.enum_cast + +// CHECK: sol.func @{{.*stateRead.*}} +// CHECK-NOT: sol.call +// CHECK: sol.return + +// CHECK: sol.func @{{.*stateWrite.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.call @{{.*nine.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return + +// CHECK: sol.func @{{.*stateCompound.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.call @{{.*nine.*}} +// CHECK: sol.cadd +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return + +// CHECK: sol.func @{{.*stateDelete.*}} +// CHECK-NOT: sol.call +// CHECK: sol.return + +// CHECK: sol.func @{{.*fieldRead.*}} +// CHECK-NOT: sol.call +// CHECK: sol.return + +// CHECK: sol.func @{{.*fieldWrite.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.call @{{.*nine.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return + +// CHECK: sol.func @{{.*constantMember.*}} +// CHECK-NOT: sol.call +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*immutableMember.*}} +// CHECK-NOT: sol.call +// CHECK: sol.load_immutable + +// CHECK: sol.func @{{.*internalCall.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.call @{{.*seven.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return + +// CHECK: sol.func @{{.*libraryCall.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.call @{{.*eight.*}} +// CHECK: sol.call @{{.*half.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return + +// CHECK: sol.func @{{.*libraryConstant.*}} +// CHECK-NOT: sol.call +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*wrap.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.call @{{.*nine.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return + +// CHECK: sol.func @{{.*construction.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.malloc +// CHECK: sol.call @{{.*nine.*}} +// CHECK-NOT: sol.call @{{.*mark.*}} +// CHECK: sol.return diff --git a/solx-slang/src/contract/function/expression/call/mod.rs b/solx-slang/src/contract/function/expression/call/mod.rs index 1f337c8bb..a6d145dc2 100644 --- a/solx-slang/src/contract/function/expression/call/mod.rs +++ b/solx-slang/src/contract/function/expression/call/mod.rs @@ -83,6 +83,7 @@ impl Call { scope: &mut FunctionScope<'_, '_, 'context>, ) -> Vec> { let (callee, options) = Self::callee(node); + Self::qualifier_effect(&callee, scope); let kind = Self::from_call(node, callee); let arguments = kind.arguments(node); match kind { @@ -161,6 +162,7 @@ impl Call { scope: &mut FunctionScope<'_, '_, 'context>, ) -> (Value<'context>, Vec>) { let (callee, options) = Self::callee(node); + Self::qualifier_effect(&callee, scope); let kind = Self::from_call(node, callee); let arguments = kind.arguments(node); match kind { @@ -232,6 +234,21 @@ impl Call { } } + /// Evaluates an externally dispatched library callee's qualifier for effect before the + /// arguments. + fn qualifier_effect(callee: &Expression, scope: &mut FunctionScope) { + if let Expression::MemberAccessExpression(access) = callee + && let Some(Definition::Function(function)) = access.member().resolve_to_definition() + && matches!( + FunctionScope::resolved_definition(&access.operand()), + Some(Definition::Library(_)) + ) + && function.compute_selector().is_some() + { + scope.expression_effect(&access.operand()); + } + } + /// Classifies `call`'s callee into the single kind that emits it. A type conversion is probed /// before the callee's shape, its callee may be an elementary type or `payable` keyword as well /// as a named type, and its one-argument arity is part of the classification, per the variant's @@ -288,21 +305,21 @@ impl Call { function_type, ); } - if matches!( - FunctionScope::resolved_definition(&access.operand()), - Some(Definition::Contract(_) | Definition::Import(_)) - ) { - if let Some(Definition::Function(function_definition)) = + if let Some(Definition::Contract(_)) = + FunctionScope::resolved_definition(&access.operand()) + && let Some(Definition::Function(function_definition)) = access.member().resolve_to_definition() - { - return Self::Function(function_definition); - } - if let Some(Type::Function(function_type)) = access.get_type() { - return Self::FunctionPointer( - Expression::MemberAccessExpression(access), - function_type, - ); - } + { + return Self::Function(function_definition); + } + if let Some(Type::UserMetaType(meta)) = access.operand().get_type() + && let Definition::Contract(_) | Definition::Import(_) = meta.definition() + && let Some(Type::Function(function_type)) = access.get_type() + { + return Self::FunctionPointer( + Expression::MemberAccessExpression(access), + function_type, + ); } if let Some(Definition::Function(function_definition)) = access.member().resolve_to_definition() diff --git a/solx-slang/src/contract/function/expression/member.rs b/solx-slang/src/contract/function/expression/member.rs index 1f7288938..82d75e007 100644 --- a/solx-slang/src/contract/function/expression/member.rs +++ b/solx-slang/src/contract/function/expression/member.rs @@ -36,6 +36,7 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' } if let Some(Definition::EnumMember(member)) = node.member().resolve_to_definition() { + self.expression_effect(&operand); let Some(Definition::Enum(enum_definition)) = member.enclosing_definition() else { unreachable!("an enum member is declared by an enum"); }; @@ -67,6 +68,9 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' ); } + if Self::is_module_typed(&operand) { + self.expression_effect(&operand); + } if Self::is_namespace_member(&operand, &node.member()) { return self.identifier(&node.member()); } @@ -220,10 +224,9 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' self.expression(operand).external_function_selector(self) } - /// Whether the member access qualifies a namespace (`C.x`, `L.f`, `M.Lib.K` — the operand may - /// chain aliases to any depth) and so resolves through its member alone: the namespace itself - /// denotes no value, and a selector-bearing library function is dispatched as an external - /// callee instead. + /// Whether the member access qualifies a namespace and so resolves through its member: a + /// contract, a library, an alias chain of any depth, or a module denoted by type alone. A + /// selector-bearing library function is dispatched as an external callee instead. fn is_namespace_member(operand: &Expression, member: &Identifier) -> bool { match Self::resolved_definition(operand) { Some(Definition::Contract(_) | Definition::Import(_)) => true, @@ -232,7 +235,7 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' Some(Definition::Function(function)) => function.compute_selector().is_none(), _ => false, }, - _ => false, + _ => Self::is_module_typed(operand), } } diff --git a/solx-slang/src/contract/function/expression/mod.rs b/solx-slang/src/contract/function/expression/mod.rs index bf49ed535..105d2fc8c 100644 --- a/solx-slang/src/contract/function/expression/mod.rs +++ b/solx-slang/src/contract/function/expression/mod.rs @@ -168,14 +168,30 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' if matches!( inner.resolve_to_definition(), Some( - Definition::Contract(_) | Definition::Interface(_) | Definition::Library(_) + Definition::Contract(_) + | Definition::Interface(_) + | Definition::Library(_) + | Definition::Import(_) + | Definition::Struct(_) + | Definition::Enum(_) + | Definition::Error(_) + | Definition::Event(_) + | Definition::UserDefinedValueType(_) ) ) => {} + Expression::MemberAccessExpression(inner) + if Self::is_module_typed(&inner.operand()) => + { + self.expression_effect(&inner.operand()); + } Expression::MemberAccessExpression(inner) if matches!( Self::resolved_definition(&inner.operand()), Some( - Definition::Contract(_) | Definition::Interface(_) | Definition::Library(_) + Definition::Contract(_) + | Definition::Interface(_) + | Definition::Library(_) + | Definition::Import(_) ) ) => {} _ => { @@ -254,7 +270,20 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' match expression { Expression::Identifier(identifier) => identifier.resolve_to_definition(), Expression::MemberAccessExpression(access) => access.member().resolve_to_definition(), + Expression::TupleExpression(inner) if inner.items().len() == 1 => { + Self::resolved_definition(&inner.items().iter().next()?.expression()?) + } _ => None, } } + + /// Whether a parenthesized or conditional form over import aliases denotes a module by its + /// type, which the resolution walk cannot name: never materialized, it evaluates for effect + /// and its members resolve by name. + pub fn is_module_typed(expression: &Expression) -> bool { + match expression.get_type() { + Some(Type::UserMetaType(meta)) => matches!(meta.definition(), Definition::Import(_)), + _ => false, + } + } } diff --git a/tests/solidity/complex/qualifier_library_external/library.sol b/tests/solidity/complex/qualifier_library_external/library.sol new file mode 100644 index 000000000..cc2e56005 --- /dev/null +++ b/tests/solidity/complex/qualifier_library_external/library.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +library Halver { + function half(uint256 x) public pure returns (uint256) { + return x / 2; + } +} diff --git a/tests/solidity/complex/qualifier_library_external/main.sol b/tests/solidity/complex/qualifier_library_external/main.sol new file mode 100644 index 000000000..b0614a0cf --- /dev/null +++ b/tests/solidity/complex/qualifier_library_external/main.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./library.sol" as M; + +contract Test { + uint256 sequence; + + function mark(uint256 digit) internal returns (bool) { + sequence = sequence * 10 + digit; + return true; + } + + function eight() internal returns (uint256) { + mark(2); + return 8; + } + + function externalCall() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Halver.half(eight()); + return (value, sequence); + } + + function tried() public returns (uint256, uint256) { + sequence = 0; + try (mark(1) ? M : M).Halver.half(eight()) returns (uint256 value) { + return (value, sequence); + } catch { + return (0, sequence); + } + } +} diff --git a/tests/solidity/complex/qualifier_library_external/test.json b/tests/solidity/complex/qualifier_library_external/test.json new file mode 100644 index 000000000..c2167f2d2 --- /dev/null +++ b/tests/solidity/complex/qualifier_library_external/test.json @@ -0,0 +1,19 @@ +{ "modes": [ "E" ], "cases": [ { + "name": "external_call", + "inputs": [ { "instance": "Test", "method": "externalCall", "calldata": [] } ], + "expected": [ "4", "12" ] +}, { + "name": "tried", + "inputs": [ { "instance": "Test", "method": "tried", "calldata": [] } ], + "expected": [ "4", "12" ] +} ], + "contracts": { + "Test": "main.sol:Test", + "Library": "library.sol:Halver" + }, + "libraries": { + "library.sol": { + "Halver": "Library" + } + } +} diff --git a/tests/solidity/complex/qualifier_module/main.sol b/tests/solidity/complex/qualifier_module/main.sol new file mode 100644 index 000000000..47aba2783 --- /dev/null +++ b/tests/solidity/complex/qualifier_module/main.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./module.sol" as M; +import "./module.sol" as Second; + +contract Test { + uint256 sequence; + + function mark(uint256 digit) internal returns (bool) { + sequence = sequence * 10 + digit; + return true; + } + + function eight() internal returns (uint256) { + mark(2); + return 8; + } + + function constantMember() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).FREE_K; + return (value, sequence); + } + + function freeCall() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).half(eight()); + return (value, sequence); + } + + function designator() public returns (uint256, uint256) { + sequence = 0; + uint256 value = ((mark(1) ? M : M).half)(eight()); + return (value, sequence); + } + + function chain() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Nested.FREE_K; + return (value, sequence); + } + + function aliased() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : Second).FREE_K; + return (value, sequence); + } + + function stray() public returns (uint256) { + sequence = 0; + (mark(1) ? M : M).Test; + return sequence; + } +} diff --git a/tests/solidity/complex/qualifier_module/module.sol b/tests/solidity/complex/qualifier_module/module.sol new file mode 100644 index 000000000..137982b3c --- /dev/null +++ b/tests/solidity/complex/qualifier_module/module.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./main.sol"; +import "./nested.sol"; +import "./nested.sol" as Nested; + +function half(uint256 x) pure returns (uint256) { + return x / 2; +} diff --git a/tests/solidity/complex/qualifier_module/nested.sol b/tests/solidity/complex/qualifier_module/nested.sol new file mode 100644 index 000000000..840225c9e --- /dev/null +++ b/tests/solidity/complex/qualifier_module/nested.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +uint256 constant FREE_K = 7; diff --git a/tests/solidity/complex/qualifier_module/test.json b/tests/solidity/complex/qualifier_module/test.json new file mode 100644 index 000000000..4c6afbb26 --- /dev/null +++ b/tests/solidity/complex/qualifier_module/test.json @@ -0,0 +1,29 @@ +{ "modes": [ "E" ], "cases": [ { + "name": "constant_member", + "inputs": [ { "instance": "Test", "method": "constantMember", "calldata": [] } ], + "expected": [ "7", "1" ] +}, { + "name": "free_call", + "inputs": [ { "instance": "Test", "method": "freeCall", "calldata": [] } ], + "expected": [ "4", "21" ] +}, { + "name": "designator", + "inputs": [ { "instance": "Test", "method": "designator", "calldata": [] } ], + "expected": [ "4", "21" ] +}, { + "name": "chain", + "inputs": [ { "instance": "Test", "method": "chain", "calldata": [] } ], + "expected": [ "7", "1" ] +}, { + "name": "aliased", + "inputs": [ { "instance": "Test", "method": "aliased", "calldata": [] } ], + "expected": [ "7", "1" ] +}, { + "name": "stray", + "inputs": [ { "instance": "Test", "method": "stray", "calldata": [] } ], + "expected": [ "1" ] +} ], + "contracts": { + "Test": "main.sol:Test" + } +} diff --git a/tests/solidity/complex/qualifier_type_name/main.sol b/tests/solidity/complex/qualifier_type_name/main.sol new file mode 100644 index 000000000..1faa2c6c0 --- /dev/null +++ b/tests/solidity/complex/qualifier_type_name/main.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./module.sol" as M; + +contract Test { + struct Inner { + uint256 value; + } + + uint256 constant SEVEN = 7; + uint256 immutable given = 9; + uint256 stored; + Inner inner; + uint256 sequence; + + function mark(uint256 digit) internal returns (bool) { + sequence = sequence * 10 + digit; + return true; + } + + function seven() internal returns (uint256) { + mark(2); + return 7; + } + + function eight() internal returns (uint256) { + mark(2); + return 8; + } + + function nine() internal returns (uint256) { + mark(2); + return 9; + } + + function enumMember() public returns (uint256, uint256) { + sequence = 0; + M.Tier tier = (mark(1) ? M : M).Tier.High; + return (uint256(tier), sequence); + } + + function interfaceEnum() public returns (uint256, uint256) { + sequence = 0; + M.Surface.Level level = (mark(1) ? M : M).Surface.Level.High; + return (uint256(level), sequence); + } + + function stateRead() public returns (uint256, uint256) { + stored = 9; + sequence = 0; + uint256 value = (mark(1) ? M : M).Test.stored; + return (value, sequence); + } + + function stateWrite() public returns (uint256, uint256) { + sequence = 0; + (mark(1) ? M : M).Test.stored = nine(); + return (stored, sequence); + } + + function stateCompound() public returns (uint256, uint256) { + stored = 9; + sequence = 0; + (mark(1) ? M : M).Test.stored += nine(); + return (stored, sequence); + } + + function stateDelete() public returns (uint256, uint256) { + stored = 9; + sequence = 0; + delete (mark(1) ? M : M).Test.stored; + return (stored, sequence); + } + + function fieldRead() public returns (uint256, uint256) { + inner.value = 9; + sequence = 0; + uint256 value = (mark(1) ? M : M).Test.inner.value; + return (value, sequence); + } + + function fieldWrite() public returns (uint256, uint256) { + sequence = 0; + (mark(1) ? M : M).Test.inner.value = nine(); + return (inner.value, sequence); + } + + function constantMember() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Test.SEVEN; + return (value, sequence); + } + + function immutableMember() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Test.given; + return (value, sequence); + } + + function internalCall() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Test.seven(); + return (value, sequence); + } + + function libraryCall() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Halver.half(eight()); + return (value, sequence); + } + + function libraryConstant() public returns (uint256, uint256) { + sequence = 0; + uint256 value = (mark(1) ? M : M).Halver.SEVEN; + return (value, sequence); + } + + function wrap() public returns (uint256, uint256) { + sequence = 0; + uint256 value = M.Cost.unwrap((mark(1) ? M : M).Cost.wrap(nine())); + return (value, sequence); + } + + function construction() public returns (uint256, uint256) { + sequence = 0; + M.Pair memory pair = (mark(1) ? M : M).Pair(nine()); + return (pair.first, sequence); + } +} diff --git a/tests/solidity/complex/qualifier_type_name/module.sol b/tests/solidity/complex/qualifier_type_name/module.sol new file mode 100644 index 000000000..0d116c389 --- /dev/null +++ b/tests/solidity/complex/qualifier_type_name/module.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./main.sol"; + +enum Tier { + Low, + High +} + +interface Surface { + enum Level { + Low, + High + } +} + +type Cost is uint256; + +struct Pair { + uint256 first; +} + +library Halver { + uint256 internal constant SEVEN = 7; + + function half(uint256 x) internal pure returns (uint256) { + return x / 2; + } +} diff --git a/tests/solidity/complex/qualifier_type_name/test.json b/tests/solidity/complex/qualifier_type_name/test.json new file mode 100644 index 000000000..b530b34d2 --- /dev/null +++ b/tests/solidity/complex/qualifier_type_name/test.json @@ -0,0 +1,65 @@ +{ "modes": [ "E" ], "cases": [ { + "name": "enum_member", + "inputs": [ { "instance": "Test", "method": "enumMember", "calldata": [] } ], + "expected": [ "1", "1" ] +}, { + "name": "interface_enum", + "inputs": [ { "instance": "Test", "method": "interfaceEnum", "calldata": [] } ], + "expected": [ "1", "0" ] +}, { + "name": "state_read", + "inputs": [ { "instance": "Test", "method": "stateRead", "calldata": [] } ], + "expected": [ "9", "0" ] +}, { + "name": "state_write", + "inputs": [ { "instance": "Test", "method": "stateWrite", "calldata": [] } ], + "expected": [ "9", "2" ] +}, { + "name": "state_compound", + "inputs": [ { "instance": "Test", "method": "stateCompound", "calldata": [] } ], + "expected": [ "18", "2" ] +}, { + "name": "state_delete", + "inputs": [ { "instance": "Test", "method": "stateDelete", "calldata": [] } ], + "expected": [ "0", "0" ] +}, { + "name": "field_read", + "inputs": [ { "instance": "Test", "method": "fieldRead", "calldata": [] } ], + "expected": [ "9", "0" ] +}, { + "name": "field_write", + "inputs": [ { "instance": "Test", "method": "fieldWrite", "calldata": [] } ], + "expected": [ "9", "2" ] +}, { + "name": "constant_member", + "inputs": [ { "instance": "Test", "method": "constantMember", "calldata": [] } ], + "expected": [ "7", "0" ] +}, { + "name": "immutable_member", + "inputs": [ { "instance": "Test", "method": "immutableMember", "calldata": [] } ], + "expected": [ "9", "0" ] +}, { + "name": "internal_call", + "inputs": [ { "instance": "Test", "method": "internalCall", "calldata": [] } ], + "expected": [ "7", "2" ] +}, { + "name": "library_call", + "inputs": [ { "instance": "Test", "method": "libraryCall", "calldata": [] } ], + "expected": [ "4", "2" ] +}, { + "name": "library_constant", + "inputs": [ { "instance": "Test", "method": "libraryConstant", "calldata": [] } ], + "expected": [ "7", "0" ] +}, { + "name": "wrap", + "inputs": [ { "instance": "Test", "method": "wrap", "calldata": [] } ], + "expected": [ "9", "2" ] +}, { + "name": "construction", + "inputs": [ { "instance": "Test", "method": "construction", "calldata": [] } ], + "expected": [ "9", "2" ] +} ], + "contracts": { + "Test": "main.sol:Test" + } +} From bc4c7f8dae9c54f944a40f15c26c91f650fd8448 Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Thu, 20 Aug 2026 20:32:39 +0400 Subject: [PATCH 2/4] refactor(slang): hold the library call qualifier in its classification --- .../contract/function/expression/call/mod.rs | 47 +++++++------------ 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/solx-slang/src/contract/function/expression/call/mod.rs b/solx-slang/src/contract/function/expression/call/mod.rs index a6d145dc2..e75236ddd 100644 --- a/solx-slang/src/contract/function/expression/call/mod.rs +++ b/solx-slang/src/contract/function/expression/call/mod.rs @@ -55,7 +55,7 @@ pub enum Call { External(MemberAccessExpression, ExternalCallee, FunctionType), /// A qualified call to an externally visible library function (`L.f(x)`), dispatched by /// `DELEGATECALL` at the linked library address on the library selector. - Library(FunctionDefinition, u32), + Library(Expression, FunctionDefinition, u32), /// An attached call to an externally visible library function (`x.f(y)`): the receiver leads /// the argument list of the same library dispatch. AttachedLibrary(Expression, FunctionDefinition, u32), @@ -83,7 +83,6 @@ impl Call { scope: &mut FunctionScope<'_, '_, 'context>, ) -> Vec> { let (callee, options) = Self::callee(node); - Self::qualifier_effect(&callee, scope); let kind = Self::from_call(node, callee); let arguments = kind.arguments(node); match kind { @@ -116,7 +115,8 @@ impl Call { ); values } - Self::Library(function_definition, selector) => { + Self::Library(qualifier, function_definition, selector) => { + scope.expression_effect(&qualifier); let (_status, values) = Self::library( &function_definition, selector, @@ -162,7 +162,6 @@ impl Call { scope: &mut FunctionScope<'_, '_, 'context>, ) -> (Value<'context>, Vec>) { let (callee, options) = Self::callee(node); - Self::qualifier_effect(&callee, scope); let kind = Self::from_call(node, callee); let arguments = kind.arguments(node); match kind { @@ -193,14 +192,17 @@ impl Call { true, scope, ), - Self::Library(function_definition, selector) => Self::library( - &function_definition, - selector, - &arguments, - options.as_ref(), - true, - scope, - ), + Self::Library(qualifier, function_definition, selector) => { + scope.expression_effect(&qualifier); + Self::library( + &function_definition, + selector, + &arguments, + options.as_ref(), + true, + scope, + ) + } Self::AttachedLibrary(receiver, function_definition, selector) => { Self::attached_library( receiver, @@ -234,21 +236,6 @@ impl Call { } } - /// Evaluates an externally dispatched library callee's qualifier for effect before the - /// arguments. - fn qualifier_effect(callee: &Expression, scope: &mut FunctionScope) { - if let Expression::MemberAccessExpression(access) = callee - && let Some(Definition::Function(function)) = access.member().resolve_to_definition() - && matches!( - FunctionScope::resolved_definition(&access.operand()), - Some(Definition::Library(_)) - ) - && function.compute_selector().is_some() - { - scope.expression_effect(&access.operand()); - } - } - /// Classifies `call`'s callee into the single kind that emits it. A type conversion is probed /// before the callee's shape, its callee may be an elementary type or `payable` keyword as well /// as a named type, and its one-argument arity is part of the classification, per the variant's @@ -330,7 +317,9 @@ impl Call { ) { (Some(Definition::Library(_)), _) => { return match function_definition.compute_selector() { - Some(selector) => Self::Library(function_definition, selector), + Some(selector) => { + Self::Library(access.operand(), function_definition, selector) + } None => Self::Function(function_definition), }; } @@ -410,7 +399,7 @@ impl Call { Self::Creation(_, Some(function_definition)) | Self::External(_, ExternalCallee::Function(function_definition, _), _) | Self::Function(function_definition) - | Self::Library(function_definition, _) => FunctionScope::named_arguments( + | Self::Library(_, function_definition, _) => FunctionScope::named_arguments( &named, function_definition .parameters() From 92a2c63af12d1eaf2fdefd40d7588462f963a1fb Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Thu, 20 Aug 2026 21:45:00 +0400 Subject: [PATCH 3/4] feat(slang): dispatch namespace-qualified calls directly --- solx-mlir/tests/lit/module_members.sol | 36 ++++++++-- solx-mlir/tests/lit/module_values.sol | 9 ++- solx-mlir/tests/lit/qualifier/module.sol | 16 ++++- .../contract/function/expression/call/mod.rs | 69 +++++++++++-------- .../complex/qualifier_module/main.sol | 13 ++++ .../complex/qualifier_module/module.sol | 1 + .../complex/qualifier_module/test.json | 12 ++++ 7 files changed, 112 insertions(+), 44 deletions(-) diff --git a/solx-mlir/tests/lit/module_members.sol b/solx-mlir/tests/lit/module_members.sol index ceb3b8121..dd487bce9 100644 --- a/solx-mlir/tests/lit/module_members.sol +++ b/solx-mlir/tests/lit/module_members.sol @@ -1,7 +1,5 @@ // RUN: solx --emit-mlir=sol %s | FileCheck %s - -// solc's print-init substitutes `sol.timestamp` for module member accesses and crashes on a -// renamed self-import, so this is solx-only. +// RUN: solc --mlir-action=print-init %s 2>/dev/null | FileCheck %s // CHECK: sol.func @{{.*chained.*}}() -> ui256 attributes {{.*}}selector = 1955792327 : i32 // CHECK: %[[CK:.*]] = sol.constant 11 : ui8 @@ -11,14 +9,28 @@ // CHECK: sol.func @{{.*renamed.*}}() -> ui256 attributes {{.*}}selector = -1753621920 : i32 // CHECK: %[[RA:.*]] = sol.cast %{{.*}} : ui8 to ui256 // CHECK: %[[RC:.*]] = sol.call @{{.*freeTriple.*}}(%[[RA]]) : (ui256) -> ui256 -// CHECK: sol.constant 11 : ui8 -// CHECK: sol.return +// CHECK: sol.return %[[RC]] : ui256 // CHECK: sol.func @{{.*starred.*}}() -> ui256 attributes {{.*}}selector = -1638182610 : i32 // CHECK: %[[SK:.*]] = sol.constant 11 : ui8 // CHECK: %[[SV:.*]] = sol.cast %[[SK]] : ui8 to ui256 // CHECK: sol.return %[[SV]] : ui256 +// CHECK: sol.func @{{.*plainCall.*}}() -> ui256 attributes {{.*}}selector = 1887173101 : i32 +// CHECK: %[[PA:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: %[[PC:.*]] = sol.call @{{.*freeTriple.*}}(%[[PA]]) : (ui256) -> ui256 +// CHECK: sol.return %[[PC]] : ui256 + +// CHECK: sol.func @{{.*chainedCall.*}}() -> ui256 attributes {{.*}}selector = -1246699396 : i32 +// CHECK: %[[CA:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: %[[CC:.*]] = sol.call @{{.*freeTriple.*}}(%[[CA]]) : (ui256) -> ui256 +// CHECK: sol.return %[[CC]] : ui256 + +// CHECK: sol.func @{{.*starredCall.*}}() -> ui256 attributes {{.*}}selector = -548888477 : i32 +// CHECK: %[[SA:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: %[[SC:.*]] = sol.call @{{.*freeTriple.*}}(%[[SA]]) : (ui256) -> ui256 +// CHECK: sol.return %[[SC]] : ui256 + import "./module_members.sol" as M; import {FREE_K as RENAMED_K, freeTriple} from "./module_members.sol"; import * as S from "./module_members.sol"; @@ -35,10 +47,22 @@ contract ModuleMembers { } function renamed() public pure returns (uint256) { - return RENAMED_K + freeTriple(2); + return freeTriple(RENAMED_K); } function starred() public pure returns (uint256) { return S.FREE_K; } + + function plainCall() public pure returns (uint256) { + return M.freeTriple(4); + } + + function chainedCall() public pure returns (uint256) { + return M.M.freeTriple(4); + } + + function starredCall() public pure returns (uint256) { + return S.freeTriple(4); + } } diff --git a/solx-mlir/tests/lit/module_values.sol b/solx-mlir/tests/lit/module_values.sol index 03227014a..d1ff10834 100644 --- a/solx-mlir/tests/lit/module_values.sol +++ b/solx-mlir/tests/lit/module_values.sol @@ -1,6 +1,7 @@ // RUN: solx --emit-mlir=sol %s | FileCheck %s -// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. +// solc's print-init drops a computed module qualifier's effect, which legacy keeps, so this is +// solx-only. // CHECK: sol.func @{{.*parenthesized.*}}() -> ui256 attributes {{.*}}selector = -923061170 : i32 // CHECK: %[[PK:.*]] = sol.constant 11 : ui8 @@ -21,8 +22,7 @@ // CHECK: sol.func @{{.*called.*}}() -> ui256 attributes {{.*}}selector = 1358542541 : i32 // CHECK: %[[AV:.*]] = sol.cast %{{.*}} : ui8 to ui256 // CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: %[[AF:.*]] = sol.func_constant @{{.*freeHalf.*}} : !sol.func_ref<(ui256) -> ui256> -// CHECK: %[[RV:.*]] = sol.icall %[[AF]](%[[AV]]) : !sol.func_ref<(ui256) -> ui256>, (ui256) -> ui256 +// CHECK: %[[RV:.*]] = sol.call @{{.*freeHalf.*}}(%[[AV]]) : (ui256) -> ui256 // CHECK: sol.return %[[RV]] : ui256 // CHECK: sol.func @{{.*tried.*}}() -> ui256 attributes {{.*}}selector = -553464963 : i32 @@ -33,8 +33,7 @@ // CHECK: sol.func @{{.*designated.*}}() -> ui256 attributes {{.*}}selector = -1019040149 : i32 // CHECK: %[[DV:.*]] = sol.cast %{{.*}} : ui8 to ui256 // CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: %[[DF:.*]] = sol.func_constant @{{.*freeHalf.*}} : !sol.func_ref<(ui256) -> ui256> -// CHECK: %[[DR:.*]] = sol.icall %[[DF]](%[[DV]]) : !sol.func_ref<(ui256) -> ui256>, (ui256) -> ui256 +// CHECK: %[[DR:.*]] = sol.call @{{.*freeHalf.*}}(%[[DV]]) : (ui256) -> ui256 // CHECK: sol.return %[[DR]] : ui256 // CHECK: sol.func @{{.*qualified.*}}() -> ui256 attributes {{.*}}selector = -228858638 : i32 diff --git a/solx-mlir/tests/lit/qualifier/module.sol b/solx-mlir/tests/lit/qualifier/module.sol index 0e7023303..deb4e0bc5 100644 --- a/solx-mlir/tests/lit/qualifier/module.sol +++ b/solx-mlir/tests/lit/qualifier/module.sol @@ -1,6 +1,7 @@ // RUN: solx --emit-mlir=sol %qualifier_module/main.sol %qualifier_module/module.sol %qualifier_module/nested.sol | FileCheck %s -// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. +// solc's print-init drops a computed module qualifier's effect, which legacy keeps, so this is +// solx-only. // CHECK: sol.func @{{.*constantMember.*}} // CHECK: sol.call @{{.*mark.*}} @@ -9,12 +10,12 @@ // CHECK: sol.func @{{.*freeCall.*}} // CHECK: sol.call @{{.*eight.*}} // CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.icall +// CHECK: sol.call @{{.*half.*}} // CHECK: sol.func @{{.*designator.*}} // CHECK: sol.call @{{.*eight.*}} // CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.icall +// CHECK: sol.call @{{.*half.*}} // CHECK: sol.func @{{.*chain.*}} // CHECK: sol.call @{{.*mark.*}} @@ -26,3 +27,12 @@ // CHECK: sol.func @{{.*stray.*}} // CHECK: sol.call @{{.*mark.*}} + +// CHECK: sol.func @{{.*plain.*}} +// CHECK: sol.call @{{.*half.*}} + +// CHECK: sol.func @{{.*chained.*}} +// CHECK: sol.call @{{.*half.*}} + +// CHECK: sol.func @{{.*starred.*}} +// CHECK: sol.call @{{.*half.*}} diff --git a/solx-slang/src/contract/function/expression/call/mod.rs b/solx-slang/src/contract/function/expression/call/mod.rs index e75236ddd..2761121d1 100644 --- a/solx-slang/src/contract/function/expression/call/mod.rs +++ b/solx-slang/src/contract/function/expression/call/mod.rs @@ -65,6 +65,9 @@ pub enum Call { Member(MemberAccessExpression), /// A direct call to a named function. Function(FunctionDefinition), + /// A direct call to a function named through a module denoted by type alone; the module is + /// evaluated for effect. + Module(Expression, FunctionDefinition), /// An attached call to a selectorless library or free function: the receiver leads the /// argument list of an internal `sol.call`. Attached(Expression, FunctionDefinition), @@ -142,11 +145,16 @@ impl Call { Self::Member(access) => { Self::member(&access, node, &arguments, options.as_ref(), scope) } - Self::Function(function_definition) => scope.call(&function_definition, &arguments), + Self::Function(function_definition) => { + scope.call(&function_definition, &arguments, None) + } + Self::Module(qualifier, function_definition) => { + scope.call(&function_definition, &arguments, Some(&qualifier)) + } Self::Attached(receiver, function_definition) => { let operands: Vec = std::iter::once(receiver).chain(arguments).collect(); - scope.call(&function_definition, &operands) + scope.call(&function_definition, &operands, None) } Self::FunctionPointer(callee, function_type) => { Self::function_pointer(&callee, &function_type, &arguments, options.as_ref(), scope) @@ -230,6 +238,7 @@ impl Call { | Self::TypeConversion | Self::Builtin(_) | Self::Function(_) + | Self::Module(..) | Self::Attached(..) => { unreachable!("a guarded call dispatches externally or creates a contract") } @@ -282,32 +291,6 @@ impl Call { unimplemented!("unsupported callee '{}'", identifier.name()) } Expression::MemberAccessExpression(access) => { - if matches!( - access.member().resolve_to_definition(), - Some(Definition::StructMember(_)) - ) && let Some(Type::Function(function_type)) = access.get_type() - { - return Self::FunctionPointer( - Expression::MemberAccessExpression(access), - function_type, - ); - } - if let Some(Definition::Contract(_)) = - FunctionScope::resolved_definition(&access.operand()) - && let Some(Definition::Function(function_definition)) = - access.member().resolve_to_definition() - { - return Self::Function(function_definition); - } - if let Some(Type::UserMetaType(meta)) = access.operand().get_type() - && let Definition::Contract(_) | Definition::Import(_) = meta.definition() - && let Some(Type::Function(function_type)) = access.get_type() - { - return Self::FunctionPointer( - Expression::MemberAccessExpression(access), - function_type, - ); - } if let Some(Definition::Function(function_definition)) = access.member().resolve_to_definition() { @@ -315,6 +298,9 @@ impl Call { FunctionScope::resolved_definition(&access.operand()), function_definition.enclosing_definition(), ) { + (Some(Definition::Contract(_) | Definition::Import(_)), _) => { + return Self::Function(function_definition); + } (Some(Definition::Library(_)), _) => { return match function_definition.compute_selector() { Some(selector) => { @@ -333,12 +319,29 @@ impl Call { None => Self::Attached(access.operand(), function_definition), }; } + (_, None) if FunctionScope::is_module_typed(&access.operand()) => { + return Self::Module(access.operand(), function_definition); + } (_, None) => { return Self::Attached(access.operand(), function_definition); } _ => {} } } + if let Some(Type::Function(function_type)) = access.get_type() + && (matches!( + access.member().resolve_to_definition(), + Some(Definition::StructMember(_)) + ) || matches!( + FunctionScope::resolved_definition(&access.operand()), + Some(Definition::Contract(_)) + )) + { + return Self::FunctionPointer( + Expression::MemberAccessExpression(access), + function_type, + ); + } if let Some(definition) = access.member().resolve_to_definition() && let Some(Type::Function(function_type)) = call.operand().get_type() && let Some(callee) = ExternalCallee::from_definition(definition) @@ -399,6 +402,7 @@ impl Call { Self::Creation(_, Some(function_definition)) | Self::External(_, ExternalCallee::Function(function_definition, _), _) | Self::Function(function_definition) + | Self::Module(_, function_definition) | Self::Library(_, function_definition, _) => FunctionScope::named_arguments( &named, function_definition @@ -1174,18 +1178,20 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' function_definition: &FunctionDefinition, operands: &[Expression], ) -> Value<'context> { - self.call(function_definition, operands) + self.call(function_definition, operands, None) .into_iter() .next() .expect("a user-defined operator's function returns one value") } /// Defines the callee in this module if absent and converts each argument to its declared - /// parameter type before `sol.call`. + /// parameter type before `sol.call`; a qualifier evaluates for effect after the arguments, + /// matching legacy's order. fn call( &mut self, function_definition: &FunctionDefinition, arguments: &[Expression], + qualifier: Option<&Expression>, ) -> Vec> { self.contract.function_definition(function_definition); let signature = self @@ -1193,6 +1199,9 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' .source_unit .function_signature(function_definition); let converted = self.converted_arguments(arguments, &signature.function_type.parameters); + if let Some(qualifier) = qualifier { + self.expression_effect(qualifier); + } Function::call(&signature, &converted, self) } } diff --git a/tests/solidity/complex/qualifier_module/main.sol b/tests/solidity/complex/qualifier_module/main.sol index 47aba2783..90d26ba60 100644 --- a/tests/solidity/complex/qualifier_module/main.sol +++ b/tests/solidity/complex/qualifier_module/main.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.0; import "./module.sol" as M; import "./module.sol" as Second; +import * as Star from "./module.sol"; contract Test { uint256 sequence; @@ -53,4 +54,16 @@ contract Test { (mark(1) ? M : M).Test; return sequence; } + + function plain() public pure returns (uint256) { + return M.half(8); + } + + function chained() public pure returns (uint256) { + return M.Mod.half(8); + } + + function starred() public pure returns (uint256) { + return Star.half(8); + } } diff --git a/tests/solidity/complex/qualifier_module/module.sol b/tests/solidity/complex/qualifier_module/module.sol index 137982b3c..5e6a8c3e0 100644 --- a/tests/solidity/complex/qualifier_module/module.sol +++ b/tests/solidity/complex/qualifier_module/module.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.0; import "./main.sol"; +import "./module.sol" as Mod; import "./nested.sol"; import "./nested.sol" as Nested; diff --git a/tests/solidity/complex/qualifier_module/test.json b/tests/solidity/complex/qualifier_module/test.json index 4c6afbb26..d6fe587cd 100644 --- a/tests/solidity/complex/qualifier_module/test.json +++ b/tests/solidity/complex/qualifier_module/test.json @@ -22,6 +22,18 @@ "name": "stray", "inputs": [ { "instance": "Test", "method": "stray", "calldata": [] } ], "expected": [ "1" ] +}, { + "name": "plain", + "inputs": [ { "instance": "Test", "method": "plain", "calldata": [] } ], + "expected": [ "4" ] +}, { + "name": "chained", + "inputs": [ { "instance": "Test", "method": "chained", "calldata": [] } ], + "expected": [ "4" ] +}, { + "name": "starred", + "inputs": [ { "instance": "Test", "method": "starred", "calldata": [] } ], + "expected": [ "4" ] } ], "contracts": { "Test": "main.sol:Test" From 700cd82b623dad02cce5f994572bff0cf485df5f Mon Sep 17 00:00:00 2001 From: Oleksandr Zarudnyi Date: Fri, 21 Aug 2026 00:55:08 +0400 Subject: [PATCH 4/4] refactor(slang): drop conditional module qualifiers --- Cargo.lock | 33 ++--- Cargo.toml | 2 +- solidity.yaml | 3 +- solx-mlir/tests/lit/module_members.sol | 33 +++++ solx-mlir/tests/lit/module_values.sol | 119 ------------------ .../tests/lit/qualifier/library_external.sol | 9 +- solx-mlir/tests/lit/qualifier/module.sol | 32 +---- solx-mlir/tests/lit/qualifier/type_name.sol | 53 ++------ .../contract/function/expression/call/mod.rs | 56 +++------ .../contract/function/expression/member.rs | 9 +- .../src/contract/function/expression/mod.rs | 15 --- .../qualifier_library_external/main.sol | 27 +--- .../qualifier_library_external/test.json | 4 +- .../complex/qualifier_module/main.sol | 53 +------- .../complex/qualifier_module/test.json | 28 +---- .../complex/qualifier_type_name/main.sol | 114 ++++++----------- .../complex/qualifier_type_name/test.json | 30 ++--- 17 files changed, 158 insertions(+), 462 deletions(-) delete mode 100644 solx-mlir/tests/lit/module_values.sol diff --git a/Cargo.lock b/Cargo.lock index 475eb9d26..c8a155b2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,7 +1208,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1723,7 +1723,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3035,7 +3035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -3417,7 +3417,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4029,7 +4029,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4086,7 +4086,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4567,7 +4567,7 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slang_solidity_v2" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" dependencies = [ "slang_solidity_v2_ast", "slang_solidity_v2_common", @@ -4580,7 +4580,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_ast" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" dependencies = [ "itertools 0.15.0", "num-bigint", @@ -4597,7 +4597,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_common" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" dependencies = [ "fxhash", "indexmap 2.14.0", @@ -4611,12 +4611,12 @@ dependencies = [ [[package]] name = "slang_solidity_v2_cst" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" [[package]] name = "slang_solidity_v2_ir" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" dependencies = [ "slang_solidity_v2_common", "slang_solidity_v2_cst", @@ -4625,7 +4625,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_parser" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" dependencies = [ "lalrpop", "lalrpop-util", @@ -4639,7 +4639,7 @@ dependencies = [ [[package]] name = "slang_solidity_v2_semantic" version = "1.3.8" -source = "git+https://github.com/NomicFoundation/slang.git?rev=5dfc6a34b68d90f05396cee098d159c25d8ae626#5dfc6a34b68d90f05396cee098d159c25d8ae626" +source = "git+https://github.com/NomicFoundation/slang.git?rev=8948967bda8badbc225339b4b97764d13688f2c6#8948967bda8badbc225339b4b97764d13688f2c6" dependencies = [ "num-bigint", "num-integer", @@ -5000,6 +5000,7 @@ dependencies = [ "cfg-if", "libc", "psm", + "windows-sys 0.52.0", "windows-sys 0.59.0", ] @@ -5134,7 +5135,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5143,7 +5144,7 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -5774,7 +5775,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 245db1ebc..a9cd8d62d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,4 +94,4 @@ features = [ [workspace.dependencies.slang_solidity_v2] git = "https://github.com/NomicFoundation/slang.git" # TODO: pin to a release tag instead of a revision. -rev = "5dfc6a34b68d90f05396cee098d159c25d8ae626" +rev = "8948967bda8badbc225339b4b97764d13688f2c6" diff --git a/solidity.yaml b/solidity.yaml index a51757433..426b05f09 100644 --- a/solidity.yaml +++ b/solidity.yaml @@ -2696,7 +2696,8 @@ entries: version: '>=0.4.12' module_from_ternary_expression.sol: hash: '0xc1560929c43117f8d19e6ab15ecf4cdb' - enabled: true + enabled: false + comment: Module conditionals are rejected by Slang version: '>=0.6.11' tuple_from_ternary_expression.sol: hash: '0xeb7fb8021a1150d98f082fbd659dec97' diff --git a/solx-mlir/tests/lit/module_members.sol b/solx-mlir/tests/lit/module_members.sol index dd487bce9..904c1a1dd 100644 --- a/solx-mlir/tests/lit/module_members.sol +++ b/solx-mlir/tests/lit/module_members.sol @@ -31,12 +31,29 @@ // CHECK: %[[SC:.*]] = sol.call @{{.*freeTriple.*}}(%[[SA]]) : (ui256) -> ui256 // CHECK: sol.return %[[SC]] : ui256 +// CHECK: sol.func @{{.*parenthesized.*}}() -> ui256 attributes {{.*}}selector = -923061170 : i32 +// CHECK: %[[NK:.*]] = sol.constant 11 : ui8 +// CHECK: %[[NV:.*]] = sol.cast %[[NK]] : ui8 to ui256 +// CHECK: sol.return %[[NV]] : ui256 + +// CHECK: sol.func @{{.*qualified.*}}() -> ui256 attributes {{.*}}selector = -228858638 : i32 +// CHECK: %[[QA:.*]] = sol.cast %{{.*}} : ui8 to ui256 +// CHECK: %[[QC:.*]] = sol.call @{{.*halve.*}}(%[[QA]]) : (ui256) -> ui256 +// CHECK: sol.return %[[QC]] : ui256 + +// CHECK: sol.func @{{.*wrapped.*}}() -> ui256 attributes {{.*}}selector = 1357319496 : i32 +// CHECK: %[[WK:.*]] = sol.constant 6 : ui8 +// CHECK: %[[WV:.*]] = sol.cast %[[WK]] : ui8 to ui256 +// CHECK: sol.return %[[WV]] : ui256 + import "./module_members.sol" as M; import {FREE_K as RENAMED_K, freeTriple} from "./module_members.sol"; import * as S from "./module_members.sol"; uint256 constant FREE_K = 11; +type Cost is uint256; + function freeTriple(uint256 x) pure returns (uint256) { return x * 3; } @@ -65,4 +82,20 @@ contract ModuleMembers { function starredCall() public pure returns (uint256) { return S.freeTriple(4); } + + function parenthesized() public pure returns (uint256) { + return (M).FREE_K; + } + + function qualified() public pure returns (uint256) { + return M.ModuleMembers.halve(6); + } + + function wrapped() public pure returns (uint256) { + return Cost.unwrap(M.Cost.wrap(6)); + } + + function halve(uint256 x) internal pure returns (uint256) { + return x / 2; + } } diff --git a/solx-mlir/tests/lit/module_values.sol b/solx-mlir/tests/lit/module_values.sol deleted file mode 100644 index d1ff10834..000000000 --- a/solx-mlir/tests/lit/module_values.sol +++ /dev/null @@ -1,119 +0,0 @@ -// RUN: solx --emit-mlir=sol %s | FileCheck %s - -// solc's print-init drops a computed module qualifier's effect, which legacy keeps, so this is -// solx-only. - -// CHECK: sol.func @{{.*parenthesized.*}}() -> ui256 attributes {{.*}}selector = -923061170 : i32 -// CHECK: %[[PK:.*]] = sol.constant 11 : ui8 -// CHECK: %[[PV:.*]] = sol.cast %[[PK]] : ui8 to ui256 -// CHECK: sol.return %[[PV]] : ui256 - -// CHECK: sol.func @{{.*conditional.*}}() -> ui256 attributes {{.*}}selector = -1619768731 : i32 -// CHECK: %[[CP:.*]] = sol.addr_of @{{.*flag.*}} : !sol.ptr -// CHECK: sol.store %true, %[[CP]] : i1, !sol.ptr -// CHECK: sol.constant 11 : ui8 -// CHECK: sol.return - -// CHECK: sol.func @{{.*chained.*}}() -> ui256 attributes {{.*}}selector = 1955792327 : i32 -// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: sol.constant 11 : ui8 -// CHECK: sol.return - -// CHECK: sol.func @{{.*called.*}}() -> ui256 attributes {{.*}}selector = 1358542541 : i32 -// CHECK: %[[AV:.*]] = sol.cast %{{.*}} : ui8 to ui256 -// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: %[[RV:.*]] = sol.call @{{.*freeHalf.*}}(%[[AV]]) : (ui256) -> ui256 -// CHECK: sol.return %[[RV]] : ui256 - -// CHECK: sol.func @{{.*tried.*}}() -> ui256 attributes {{.*}}selector = -553464963 : i32 -// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: %[[LA:.*]] = sol.lib_addr "{{[^"]*}}Halver" : !sol.address -// CHECK: sol.ext_call "{{.*half.*}}"(%{{.*}}) at %[[LA]] gas %{{.*}}try_call{{.*}} : !sol.address, (ui256) -> (i1, ui256) - -// CHECK: sol.func @{{.*designated.*}}() -> ui256 attributes {{.*}}selector = -1019040149 : i32 -// CHECK: %[[DV:.*]] = sol.cast %{{.*}} : ui8 to ui256 -// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: %[[DR:.*]] = sol.call @{{.*freeHalf.*}}(%[[DV]]) : (ui256) -> ui256 -// CHECK: sol.return %[[DR]] : ui256 - -// CHECK: sol.func @{{.*qualified.*}}() -> ui256 attributes {{.*}}selector = -228858638 : i32 -// CHECK: %[[QV:.*]] = sol.cast %{{.*}} : ui8 to ui256 -// CHECK: %[[QR:.*]] = sol.call @{{.*halve.*}}(%[[QV]]) : (ui256) -> ui256 -// CHECK: sol.return %[[QR]] : ui256 - -// CHECK: sol.func @{{.*wrapped.*}}() -> ui256 attributes {{.*}}selector = 1357319496 : i32 -// CHECK: %[[WV:.*]] = sol.cast %{{.*}} : ui8 to ui256 -// CHECK: sol.return %[[WV]] : ui256 - -// CHECK: sol.func @{{.*stray.*}}() -> i1 attributes {{.*}}selector = -903043920 : i32 -// CHECK: sol.store %true, %{{.*}} : i1, !sol.ptr -// CHECK: %[[SP:.*]] = sol.addr_of @{{.*flag.*}} : !sol.ptr -// CHECK: %[[SV:.*]] = sol.load %[[SP]] : !sol.ptr, i1 -// CHECK: sol.return %[[SV]] : i1 - -import "./module_values.sol" as M; - -uint256 constant FREE_K = 11; - -type Cost is uint256; - -function freeHalf(uint256 x) pure returns (uint256) { - return x / 2; -} - -library Halver { - function half(uint256 x) public pure returns (uint256) { - return x / 2; - } -} - -contract ModuleValues { - bool flag; - - function parenthesized() public pure returns (uint256) { - return (M).FREE_K; - } - - function conditional() public returns (uint256) { - return ((flag = true) ? M : M).FREE_K; - } - - function chained() public returns (uint256) { - return ((flag = true) ? M : M).M.FREE_K; - } - - function called() public returns (uint256) { - return ((flag = true) ? M : M).freeHalf(8); - } - - function tried() public returns (uint256) { - try ((flag = true) ? M : M).Halver.half(4) returns (uint256 v) { - return v; - } catch { - return 0; - } - } - - function designated() public returns (uint256) { - return (((flag = true) ? M : M).freeHalf)(8); - } - - function halve(uint256 x) internal pure returns (uint256) { - return x / 2; - } - - function qualified() public returns (uint256) { - return ((flag = true) ? M : M).ModuleValues.halve(6); - } - - function wrapped() public returns (uint256) { - return Cost.unwrap(((flag = true) ? M : M).Cost.wrap(6)); - } - - function stray() public returns (bool) { - M; - M.ModuleValues; - ((flag = true) ? M : M).ModuleValues; - return flag; - } -} diff --git a/solx-mlir/tests/lit/qualifier/library_external.sol b/solx-mlir/tests/lit/qualifier/library_external.sol index 04b365fdb..3e38b5feb 100644 --- a/solx-mlir/tests/lit/qualifier/library_external.sol +++ b/solx-mlir/tests/lit/qualifier/library_external.sol @@ -1,16 +1,11 @@ // RUN: solx --emit-mlir=sol %qualifier_library_external/main.sol %qualifier_library_external/library.sol | FileCheck %s - -// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. +// RUN: solc --mlir-action=print-init %qualifier_library_external/main.sol %qualifier_library_external/library.sol 2>/dev/null | FileCheck %s // CHECK: sol.func @{{.*externalCall.*}} -// CHECK: sol.call @{{.*mark.*}} // CHECK: sol.lib_addr -// CHECK: sol.call @{{.*eight.*}} -// CHECK: sol.ext_call +// CHECK: sol.ext_call "{{.*half.*}}"({{.*}}library_call // CHECK: sol.func @{{.*tried.*}} -// CHECK: sol.call @{{.*mark.*}} // CHECK: sol.lib_addr -// CHECK: sol.call @{{.*eight.*}} // CHECK: sol.ext_call "{{.*half.*}}"({{.*}}try_call // CHECK: sol.try diff --git a/solx-mlir/tests/lit/qualifier/module.sol b/solx-mlir/tests/lit/qualifier/module.sol index deb4e0bc5..c6d58db99 100644 --- a/solx-mlir/tests/lit/qualifier/module.sol +++ b/solx-mlir/tests/lit/qualifier/module.sol @@ -1,32 +1,5 @@ // RUN: solx --emit-mlir=sol %qualifier_module/main.sol %qualifier_module/module.sol %qualifier_module/nested.sol | FileCheck %s - -// solc's print-init drops a computed module qualifier's effect, which legacy keeps, so this is -// solx-only. - -// CHECK: sol.func @{{.*constantMember.*}} -// CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.constant 7 - -// CHECK: sol.func @{{.*freeCall.*}} -// CHECK: sol.call @{{.*eight.*}} -// CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*half.*}} - -// CHECK: sol.func @{{.*designator.*}} -// CHECK: sol.call @{{.*eight.*}} -// CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*half.*}} - -// CHECK: sol.func @{{.*chain.*}} -// CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.constant 7 - -// CHECK: sol.func @{{.*aliased.*}} -// CHECK: sol.call @{{.*mark.*}} -// CHECK: sol.constant 7 - -// CHECK: sol.func @{{.*stray.*}} -// CHECK: sol.call @{{.*mark.*}} +// RUN: solc --mlir-action=print-init %qualifier_module/main.sol %qualifier_module/module.sol %qualifier_module/nested.sol 2>/dev/null | FileCheck %s // CHECK: sol.func @{{.*plain.*}} // CHECK: sol.call @{{.*half.*}} @@ -36,3 +9,6 @@ // CHECK: sol.func @{{.*starred.*}} // CHECK: sol.call @{{.*half.*}} + +// CHECK: sol.func @{{.*chain.*}} +// CHECK: sol.constant 7 diff --git a/solx-mlir/tests/lit/qualifier/type_name.sol b/solx-mlir/tests/lit/qualifier/type_name.sol index 5f3700538..9a9e8dbca 100644 --- a/solx-mlir/tests/lit/qualifier/type_name.sol +++ b/solx-mlir/tests/lit/qualifier/type_name.sol @@ -1,80 +1,53 @@ // RUN: solx --emit-mlir=sol %qualifier_type_name/main.sol %qualifier_type_name/module.sol | FileCheck %s - -// solc's print-init substitutes `sol.timestamp` for module member accesses, so this is solx-only. +// RUN: solc --mlir-action=print-init %qualifier_type_name/main.sol %qualifier_type_name/module.sol 2>/dev/null | FileCheck %s // CHECK: sol.func @{{.*enumMember.*}} -// CHECK: sol.call @{{.*mark.*}} // CHECK: sol.enum_cast // CHECK: sol.func @{{.*interfaceEnum.*}} -// CHECK-NOT: sol.call // CHECK: sol.enum_cast // CHECK: sol.func @{{.*stateRead.*}} -// CHECK-NOT: sol.call -// CHECK: sol.return +// CHECK: sol.store %{{.*}} : ui256, !sol.ptr +// CHECK: sol.load // CHECK: sol.func @{{.*stateWrite.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*nine.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return +// CHECK: sol.store %{{.*}} : ui256, !sol.ptr +// CHECK: sol.load // CHECK: sol.func @{{.*stateCompound.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*nine.*}} // CHECK: sol.cadd -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return +// CHECK: sol.store // CHECK: sol.func @{{.*stateDelete.*}} -// CHECK-NOT: sol.call -// CHECK: sol.return +// CHECK: sol.store %c0{{.*}} : ui256, !sol.ptr +// CHECK: sol.load // CHECK: sol.func @{{.*fieldRead.*}} -// CHECK-NOT: sol.call -// CHECK: sol.return +// CHECK: sol.gep +// CHECK: sol.load // CHECK: sol.func @{{.*fieldWrite.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*nine.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return +// CHECK: sol.gep +// CHECK: sol.store // CHECK: sol.func @{{.*constantMember.*}} -// CHECK-NOT: sol.call // CHECK: sol.constant 7 // CHECK: sol.func @{{.*immutableMember.*}} -// CHECK-NOT: sol.call // CHECK: sol.load_immutable // CHECK: sol.func @{{.*internalCall.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} // CHECK: sol.call @{{.*seven.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return // CHECK: sol.func @{{.*libraryCall.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*eight.*}} // CHECK: sol.call @{{.*half.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return // CHECK: sol.func @{{.*libraryConstant.*}} -// CHECK-NOT: sol.call // CHECK: sol.constant 7 // CHECK: sol.func @{{.*wrap.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.call @{{.*nine.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return +// CHECK: sol.constant 9 // CHECK: sol.func @{{.*construction.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} // CHECK: sol.malloc -// CHECK: sol.call @{{.*nine.*}} -// CHECK-NOT: sol.call @{{.*mark.*}} -// CHECK: sol.return diff --git a/solx-slang/src/contract/function/expression/call/mod.rs b/solx-slang/src/contract/function/expression/call/mod.rs index 2761121d1..f01dcece5 100644 --- a/solx-slang/src/contract/function/expression/call/mod.rs +++ b/solx-slang/src/contract/function/expression/call/mod.rs @@ -55,7 +55,7 @@ pub enum Call { External(MemberAccessExpression, ExternalCallee, FunctionType), /// A qualified call to an externally visible library function (`L.f(x)`), dispatched by /// `DELEGATECALL` at the linked library address on the library selector. - Library(Expression, FunctionDefinition, u32), + Library(FunctionDefinition, u32), /// An attached call to an externally visible library function (`x.f(y)`): the receiver leads /// the argument list of the same library dispatch. AttachedLibrary(Expression, FunctionDefinition, u32), @@ -65,9 +65,6 @@ pub enum Call { Member(MemberAccessExpression), /// A direct call to a named function. Function(FunctionDefinition), - /// A direct call to a function named through a module denoted by type alone; the module is - /// evaluated for effect. - Module(Expression, FunctionDefinition), /// An attached call to a selectorless library or free function: the receiver leads the /// argument list of an internal `sol.call`. Attached(Expression, FunctionDefinition), @@ -118,8 +115,7 @@ impl Call { ); values } - Self::Library(qualifier, function_definition, selector) => { - scope.expression_effect(&qualifier); + Self::Library(function_definition, selector) => { let (_status, values) = Self::library( &function_definition, selector, @@ -145,16 +141,11 @@ impl Call { Self::Member(access) => { Self::member(&access, node, &arguments, options.as_ref(), scope) } - Self::Function(function_definition) => { - scope.call(&function_definition, &arguments, None) - } - Self::Module(qualifier, function_definition) => { - scope.call(&function_definition, &arguments, Some(&qualifier)) - } + Self::Function(function_definition) => scope.call(&function_definition, &arguments), Self::Attached(receiver, function_definition) => { let operands: Vec = std::iter::once(receiver).chain(arguments).collect(); - scope.call(&function_definition, &operands, None) + scope.call(&function_definition, &operands) } Self::FunctionPointer(callee, function_type) => { Self::function_pointer(&callee, &function_type, &arguments, options.as_ref(), scope) @@ -200,17 +191,14 @@ impl Call { true, scope, ), - Self::Library(qualifier, function_definition, selector) => { - scope.expression_effect(&qualifier); - Self::library( - &function_definition, - selector, - &arguments, - options.as_ref(), - true, - scope, - ) - } + Self::Library(function_definition, selector) => Self::library( + &function_definition, + selector, + &arguments, + options.as_ref(), + true, + scope, + ), Self::AttachedLibrary(receiver, function_definition, selector) => { Self::attached_library( receiver, @@ -238,7 +226,6 @@ impl Call { | Self::TypeConversion | Self::Builtin(_) | Self::Function(_) - | Self::Module(..) | Self::Attached(..) => { unreachable!("a guarded call dispatches externally or creates a contract") } @@ -303,9 +290,7 @@ impl Call { } (Some(Definition::Library(_)), _) => { return match function_definition.compute_selector() { - Some(selector) => { - Self::Library(access.operand(), function_definition, selector) - } + Some(selector) => Self::Library(function_definition, selector), None => Self::Function(function_definition), }; } @@ -319,9 +304,6 @@ impl Call { None => Self::Attached(access.operand(), function_definition), }; } - (_, None) if FunctionScope::is_module_typed(&access.operand()) => { - return Self::Module(access.operand(), function_definition); - } (_, None) => { return Self::Attached(access.operand(), function_definition); } @@ -402,8 +384,7 @@ impl Call { Self::Creation(_, Some(function_definition)) | Self::External(_, ExternalCallee::Function(function_definition, _), _) | Self::Function(function_definition) - | Self::Module(_, function_definition) - | Self::Library(_, function_definition, _) => FunctionScope::named_arguments( + | Self::Library(function_definition, _) => FunctionScope::named_arguments( &named, function_definition .parameters() @@ -1178,20 +1159,18 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' function_definition: &FunctionDefinition, operands: &[Expression], ) -> Value<'context> { - self.call(function_definition, operands, None) + self.call(function_definition, operands) .into_iter() .next() .expect("a user-defined operator's function returns one value") } /// Defines the callee in this module if absent and converts each argument to its declared - /// parameter type before `sol.call`; a qualifier evaluates for effect after the arguments, - /// matching legacy's order. + /// parameter type before `sol.call`. fn call( &mut self, function_definition: &FunctionDefinition, arguments: &[Expression], - qualifier: Option<&Expression>, ) -> Vec> { self.contract.function_definition(function_definition); let signature = self @@ -1199,9 +1178,6 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' .source_unit .function_signature(function_definition); let converted = self.converted_arguments(arguments, &signature.function_type.parameters); - if let Some(qualifier) = qualifier { - self.expression_effect(qualifier); - } Function::call(&signature, &converted, self) } } diff --git a/solx-slang/src/contract/function/expression/member.rs b/solx-slang/src/contract/function/expression/member.rs index 82d75e007..529522c92 100644 --- a/solx-slang/src/contract/function/expression/member.rs +++ b/solx-slang/src/contract/function/expression/member.rs @@ -68,9 +68,6 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' ); } - if Self::is_module_typed(&operand) { - self.expression_effect(&operand); - } if Self::is_namespace_member(&operand, &node.member()) { return self.identifier(&node.member()); } @@ -225,8 +222,8 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' } /// Whether the member access qualifies a namespace and so resolves through its member: a - /// contract, a library, an alias chain of any depth, or a module denoted by type alone. A - /// selector-bearing library function is dispatched as an external callee instead. + /// contract, a library, or an alias chain of any depth. A selector-bearing library function + /// is dispatched as an external callee instead. fn is_namespace_member(operand: &Expression, member: &Identifier) -> bool { match Self::resolved_definition(operand) { Some(Definition::Contract(_) | Definition::Import(_)) => true, @@ -235,7 +232,7 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' Some(Definition::Function(function)) => function.compute_selector().is_none(), _ => false, }, - _ => Self::is_module_typed(operand), + _ => false, } } diff --git a/solx-slang/src/contract/function/expression/mod.rs b/solx-slang/src/contract/function/expression/mod.rs index 105d2fc8c..2f5156f8e 100644 --- a/solx-slang/src/contract/function/expression/mod.rs +++ b/solx-slang/src/contract/function/expression/mod.rs @@ -179,11 +179,6 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' | Definition::UserDefinedValueType(_) ) ) => {} - Expression::MemberAccessExpression(inner) - if Self::is_module_typed(&inner.operand()) => - { - self.expression_effect(&inner.operand()); - } Expression::MemberAccessExpression(inner) if matches!( Self::resolved_definition(&inner.operand()), @@ -276,14 +271,4 @@ impl<'contract, 'source_unit, 'context> FunctionScope<'contract, 'source_unit, ' _ => None, } } - - /// Whether a parenthesized or conditional form over import aliases denotes a module by its - /// type, which the resolution walk cannot name: never materialized, it evaluates for effect - /// and its members resolve by name. - pub fn is_module_typed(expression: &Expression) -> bool { - match expression.get_type() { - Some(Type::UserMetaType(meta)) => matches!(meta.definition(), Definition::Import(_)), - _ => false, - } - } } diff --git a/tests/solidity/complex/qualifier_library_external/main.sol b/tests/solidity/complex/qualifier_library_external/main.sol index b0614a0cf..5782c598d 100644 --- a/tests/solidity/complex/qualifier_library_external/main.sol +++ b/tests/solidity/complex/qualifier_library_external/main.sol @@ -5,30 +5,15 @@ pragma solidity >=0.8.0; import "./library.sol" as M; contract Test { - uint256 sequence; - - function mark(uint256 digit) internal returns (bool) { - sequence = sequence * 10 + digit; - return true; - } - - function eight() internal returns (uint256) { - mark(2); - return 8; - } - - function externalCall() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Halver.half(eight()); - return (value, sequence); + function externalCall() public pure returns (uint256) { + return M.Halver.half(8); } - function tried() public returns (uint256, uint256) { - sequence = 0; - try (mark(1) ? M : M).Halver.half(eight()) returns (uint256 value) { - return (value, sequence); + function tried() public returns (uint256) { + try M.Halver.half(8) returns (uint256 value) { + return value; } catch { - return (0, sequence); + return 0; } } } diff --git a/tests/solidity/complex/qualifier_library_external/test.json b/tests/solidity/complex/qualifier_library_external/test.json index c2167f2d2..8f8767133 100644 --- a/tests/solidity/complex/qualifier_library_external/test.json +++ b/tests/solidity/complex/qualifier_library_external/test.json @@ -1,11 +1,11 @@ { "modes": [ "E" ], "cases": [ { "name": "external_call", "inputs": [ { "instance": "Test", "method": "externalCall", "calldata": [] } ], - "expected": [ "4", "12" ] + "expected": [ "4" ] }, { "name": "tried", "inputs": [ { "instance": "Test", "method": "tried", "calldata": [] } ], - "expected": [ "4", "12" ] + "expected": [ "4" ] } ], "contracts": { "Test": "main.sol:Test", diff --git a/tests/solidity/complex/qualifier_module/main.sol b/tests/solidity/complex/qualifier_module/main.sol index 90d26ba60..dc4829c16 100644 --- a/tests/solidity/complex/qualifier_module/main.sol +++ b/tests/solidity/complex/qualifier_module/main.sol @@ -3,58 +3,9 @@ pragma solidity >=0.8.0; import "./module.sol" as M; -import "./module.sol" as Second; import * as Star from "./module.sol"; contract Test { - uint256 sequence; - - function mark(uint256 digit) internal returns (bool) { - sequence = sequence * 10 + digit; - return true; - } - - function eight() internal returns (uint256) { - mark(2); - return 8; - } - - function constantMember() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).FREE_K; - return (value, sequence); - } - - function freeCall() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).half(eight()); - return (value, sequence); - } - - function designator() public returns (uint256, uint256) { - sequence = 0; - uint256 value = ((mark(1) ? M : M).half)(eight()); - return (value, sequence); - } - - function chain() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Nested.FREE_K; - return (value, sequence); - } - - function aliased() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : Second).FREE_K; - return (value, sequence); - } - - function stray() public returns (uint256) { - sequence = 0; - (mark(1) ? M : M).Test; - return sequence; - } - function plain() public pure returns (uint256) { return M.half(8); } @@ -66,4 +17,8 @@ contract Test { function starred() public pure returns (uint256) { return Star.half(8); } + + function chain() public pure returns (uint256) { + return M.Nested.FREE_K; + } } diff --git a/tests/solidity/complex/qualifier_module/test.json b/tests/solidity/complex/qualifier_module/test.json index d6fe587cd..7f5ba6944 100644 --- a/tests/solidity/complex/qualifier_module/test.json +++ b/tests/solidity/complex/qualifier_module/test.json @@ -1,28 +1,4 @@ { "modes": [ "E" ], "cases": [ { - "name": "constant_member", - "inputs": [ { "instance": "Test", "method": "constantMember", "calldata": [] } ], - "expected": [ "7", "1" ] -}, { - "name": "free_call", - "inputs": [ { "instance": "Test", "method": "freeCall", "calldata": [] } ], - "expected": [ "4", "21" ] -}, { - "name": "designator", - "inputs": [ { "instance": "Test", "method": "designator", "calldata": [] } ], - "expected": [ "4", "21" ] -}, { - "name": "chain", - "inputs": [ { "instance": "Test", "method": "chain", "calldata": [] } ], - "expected": [ "7", "1" ] -}, { - "name": "aliased", - "inputs": [ { "instance": "Test", "method": "aliased", "calldata": [] } ], - "expected": [ "7", "1" ] -}, { - "name": "stray", - "inputs": [ { "instance": "Test", "method": "stray", "calldata": [] } ], - "expected": [ "1" ] -}, { "name": "plain", "inputs": [ { "instance": "Test", "method": "plain", "calldata": [] } ], "expected": [ "4" ] @@ -34,6 +10,10 @@ "name": "starred", "inputs": [ { "instance": "Test", "method": "starred", "calldata": [] } ], "expected": [ "4" ] +}, { + "name": "chain", + "inputs": [ { "instance": "Test", "method": "chain", "calldata": [] } ], + "expected": [ "7" ] } ], "contracts": { "Test": "main.sol:Test" diff --git a/tests/solidity/complex/qualifier_type_name/main.sol b/tests/solidity/complex/qualifier_type_name/main.sol index 1faa2c6c0..e5964e191 100644 --- a/tests/solidity/complex/qualifier_type_name/main.sol +++ b/tests/solidity/complex/qualifier_type_name/main.sol @@ -13,119 +13,77 @@ contract Test { uint256 immutable given = 9; uint256 stored; Inner inner; - uint256 sequence; - function mark(uint256 digit) internal returns (bool) { - sequence = sequence * 10 + digit; - return true; - } - - function seven() internal returns (uint256) { - mark(2); + function seven() internal pure returns (uint256) { return 7; } - function eight() internal returns (uint256) { - mark(2); - return 8; - } - - function nine() internal returns (uint256) { - mark(2); - return 9; - } - - function enumMember() public returns (uint256, uint256) { - sequence = 0; - M.Tier tier = (mark(1) ? M : M).Tier.High; - return (uint256(tier), sequence); + function enumMember() public pure returns (uint256) { + return uint256(M.Tier.High); } - function interfaceEnum() public returns (uint256, uint256) { - sequence = 0; - M.Surface.Level level = (mark(1) ? M : M).Surface.Level.High; - return (uint256(level), sequence); + function interfaceEnum() public pure returns (uint256) { + return uint256(M.Surface.Level.High); } - function stateRead() public returns (uint256, uint256) { + function stateRead() public returns (uint256) { stored = 9; - sequence = 0; - uint256 value = (mark(1) ? M : M).Test.stored; - return (value, sequence); + return M.Test.stored; } - function stateWrite() public returns (uint256, uint256) { - sequence = 0; - (mark(1) ? M : M).Test.stored = nine(); - return (stored, sequence); + function stateWrite() public returns (uint256) { + M.Test.stored = 9; + return stored; } - function stateCompound() public returns (uint256, uint256) { + function stateCompound() public returns (uint256) { stored = 9; - sequence = 0; - (mark(1) ? M : M).Test.stored += nine(); - return (stored, sequence); + M.Test.stored += 9; + return stored; } - function stateDelete() public returns (uint256, uint256) { + function stateDelete() public returns (uint256) { stored = 9; - sequence = 0; - delete (mark(1) ? M : M).Test.stored; - return (stored, sequence); + delete M.Test.stored; + return stored; } - function fieldRead() public returns (uint256, uint256) { + function fieldRead() public returns (uint256) { inner.value = 9; - sequence = 0; - uint256 value = (mark(1) ? M : M).Test.inner.value; - return (value, sequence); + return M.Test.inner.value; } - function fieldWrite() public returns (uint256, uint256) { - sequence = 0; - (mark(1) ? M : M).Test.inner.value = nine(); - return (inner.value, sequence); + function fieldWrite() public returns (uint256) { + M.Test.inner.value = 9; + return inner.value; } - function constantMember() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Test.SEVEN; - return (value, sequence); + function constantMember() public pure returns (uint256) { + return M.Test.SEVEN; } - function immutableMember() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Test.given; - return (value, sequence); + function immutableMember() public view returns (uint256) { + return M.Test.given; } - function internalCall() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Test.seven(); - return (value, sequence); + function internalCall() public pure returns (uint256) { + return M.Test.seven(); } - function libraryCall() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Halver.half(eight()); - return (value, sequence); + function libraryCall() public pure returns (uint256) { + return M.Halver.half(8); } - function libraryConstant() public returns (uint256, uint256) { - sequence = 0; - uint256 value = (mark(1) ? M : M).Halver.SEVEN; - return (value, sequence); + function libraryConstant() public pure returns (uint256) { + return M.Halver.SEVEN; } - function wrap() public returns (uint256, uint256) { - sequence = 0; - uint256 value = M.Cost.unwrap((mark(1) ? M : M).Cost.wrap(nine())); - return (value, sequence); + function wrap() public pure returns (uint256) { + return M.Cost.unwrap(M.Cost.wrap(9)); } - function construction() public returns (uint256, uint256) { - sequence = 0; - M.Pair memory pair = (mark(1) ? M : M).Pair(nine()); - return (pair.first, sequence); + function construction() public pure returns (uint256) { + M.Pair memory pair = M.Pair(9); + return pair.first; } } diff --git a/tests/solidity/complex/qualifier_type_name/test.json b/tests/solidity/complex/qualifier_type_name/test.json index b530b34d2..f4ff95092 100644 --- a/tests/solidity/complex/qualifier_type_name/test.json +++ b/tests/solidity/complex/qualifier_type_name/test.json @@ -1,63 +1,63 @@ { "modes": [ "E" ], "cases": [ { "name": "enum_member", "inputs": [ { "instance": "Test", "method": "enumMember", "calldata": [] } ], - "expected": [ "1", "1" ] + "expected": [ "1" ] }, { "name": "interface_enum", "inputs": [ { "instance": "Test", "method": "interfaceEnum", "calldata": [] } ], - "expected": [ "1", "0" ] + "expected": [ "1" ] }, { "name": "state_read", "inputs": [ { "instance": "Test", "method": "stateRead", "calldata": [] } ], - "expected": [ "9", "0" ] + "expected": [ "9" ] }, { "name": "state_write", "inputs": [ { "instance": "Test", "method": "stateWrite", "calldata": [] } ], - "expected": [ "9", "2" ] + "expected": [ "9" ] }, { "name": "state_compound", "inputs": [ { "instance": "Test", "method": "stateCompound", "calldata": [] } ], - "expected": [ "18", "2" ] + "expected": [ "18" ] }, { "name": "state_delete", "inputs": [ { "instance": "Test", "method": "stateDelete", "calldata": [] } ], - "expected": [ "0", "0" ] + "expected": [ "0" ] }, { "name": "field_read", "inputs": [ { "instance": "Test", "method": "fieldRead", "calldata": [] } ], - "expected": [ "9", "0" ] + "expected": [ "9" ] }, { "name": "field_write", "inputs": [ { "instance": "Test", "method": "fieldWrite", "calldata": [] } ], - "expected": [ "9", "2" ] + "expected": [ "9" ] }, { "name": "constant_member", "inputs": [ { "instance": "Test", "method": "constantMember", "calldata": [] } ], - "expected": [ "7", "0" ] + "expected": [ "7" ] }, { "name": "immutable_member", "inputs": [ { "instance": "Test", "method": "immutableMember", "calldata": [] } ], - "expected": [ "9", "0" ] + "expected": [ "9" ] }, { "name": "internal_call", "inputs": [ { "instance": "Test", "method": "internalCall", "calldata": [] } ], - "expected": [ "7", "2" ] + "expected": [ "7" ] }, { "name": "library_call", "inputs": [ { "instance": "Test", "method": "libraryCall", "calldata": [] } ], - "expected": [ "4", "2" ] + "expected": [ "4" ] }, { "name": "library_constant", "inputs": [ { "instance": "Test", "method": "libraryConstant", "calldata": [] } ], - "expected": [ "7", "0" ] + "expected": [ "7" ] }, { "name": "wrap", "inputs": [ { "instance": "Test", "method": "wrap", "calldata": [] } ], - "expected": [ "9", "2" ] + "expected": [ "9" ] }, { "name": "construction", "inputs": [ { "instance": "Test", "method": "construction", "calldata": [] } ], - "expected": [ "9", "2" ] + "expected": [ "9" ] } ], "contracts": { "Test": "main.sol:Test"