diff --git a/Cargo.lock b/Cargo.lock index d5c20bb1a..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]] @@ -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/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/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..904c1a1dd --- /dev/null +++ b/solx-mlir/tests/lit/module_members.sol @@ -0,0 +1,101 @@ +// RUN: solx --emit-mlir=sol %s | FileCheck %s +// 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 +// 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.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 + +// 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; +} + +contract ModuleMembers { + function chained() public pure returns (uint256) { + return M.M.M.FREE_K; + } + + function renamed() public pure returns (uint256) { + 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); + } + + 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/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..3e38b5feb --- /dev/null +++ b/solx-mlir/tests/lit/qualifier/library_external.sol @@ -0,0 +1,11 @@ +// RUN: solx --emit-mlir=sol %qualifier_library_external/main.sol %qualifier_library_external/library.sol | FileCheck %s +// 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.lib_addr +// CHECK: sol.ext_call "{{.*half.*}}"({{.*}}library_call + +// CHECK: sol.func @{{.*tried.*}} +// CHECK: sol.lib_addr +// 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..c6d58db99 --- /dev/null +++ b/solx-mlir/tests/lit/qualifier/module.sol @@ -0,0 +1,14 @@ +// RUN: solx --emit-mlir=sol %qualifier_module/main.sol %qualifier_module/module.sol %qualifier_module/nested.sol | FileCheck %s +// 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.*}} + +// CHECK: sol.func @{{.*chained.*}} +// CHECK: sol.call @{{.*half.*}} + +// 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 new file mode 100644 index 000000000..9a9e8dbca --- /dev/null +++ b/solx-mlir/tests/lit/qualifier/type_name.sol @@ -0,0 +1,53 @@ +// RUN: solx --emit-mlir=sol %qualifier_type_name/main.sol %qualifier_type_name/module.sol | FileCheck %s +// 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.enum_cast + +// CHECK: sol.func @{{.*interfaceEnum.*}} +// CHECK: sol.enum_cast + +// CHECK: sol.func @{{.*stateRead.*}} +// CHECK: sol.store %{{.*}} : ui256, !sol.ptr +// CHECK: sol.load + +// CHECK: sol.func @{{.*stateWrite.*}} +// CHECK: sol.store %{{.*}} : ui256, !sol.ptr +// CHECK: sol.load + +// CHECK: sol.func @{{.*stateCompound.*}} +// CHECK: sol.cadd +// CHECK: sol.store + +// CHECK: sol.func @{{.*stateDelete.*}} +// CHECK: sol.store %c0{{.*}} : ui256, !sol.ptr +// CHECK: sol.load + +// CHECK: sol.func @{{.*fieldRead.*}} +// CHECK: sol.gep +// CHECK: sol.load + +// CHECK: sol.func @{{.*fieldWrite.*}} +// CHECK: sol.gep +// CHECK: sol.store + +// CHECK: sol.func @{{.*constantMember.*}} +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*immutableMember.*}} +// CHECK: sol.load_immutable + +// CHECK: sol.func @{{.*internalCall.*}} +// CHECK: sol.call @{{.*seven.*}} + +// CHECK: sol.func @{{.*libraryCall.*}} +// CHECK: sol.call @{{.*half.*}} + +// CHECK: sol.func @{{.*libraryConstant.*}} +// CHECK: sol.constant 7 + +// CHECK: sol.func @{{.*wrap.*}} +// CHECK: sol.constant 9 + +// CHECK: sol.func @{{.*construction.*}} +// CHECK: sol.malloc diff --git a/solx-slang/src/contract/function/expression/call/mod.rs b/solx-slang/src/contract/function/expression/call/mod.rs index 1f337c8bb..f01dcece5 100644 --- a/solx-slang/src/contract/function/expression/call/mod.rs +++ b/solx-slang/src/contract/function/expression/call/mod.rs @@ -278,32 +278,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 matches!( - FunctionScope::resolved_definition(&access.operand()), - Some(Definition::Contract(_) | Definition::Import(_)) - ) { - if 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, - ); - } - } if let Some(Definition::Function(function_definition)) = access.member().resolve_to_definition() { @@ -311,6 +285,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) => Self::Library(function_definition, selector), @@ -333,6 +310,20 @@ impl Call { _ => {} } } + 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) diff --git a/solx-slang/src/contract/function/expression/member.rs b/solx-slang/src/contract/function/expression/member.rs index 1f7288938..529522c92 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"); }; @@ -220,10 +221,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, 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, diff --git a/solx-slang/src/contract/function/expression/mod.rs b/solx-slang/src/contract/function/expression/mod.rs index bf49ed535..2f5156f8e 100644 --- a/solx-slang/src/contract/function/expression/mod.rs +++ b/solx-slang/src/contract/function/expression/mod.rs @@ -168,14 +168,25 @@ 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 matches!( Self::resolved_definition(&inner.operand()), Some( - Definition::Contract(_) | Definition::Interface(_) | Definition::Library(_) + Definition::Contract(_) + | Definition::Interface(_) + | Definition::Library(_) + | Definition::Import(_) ) ) => {} _ => { @@ -254,6 +265,9 @@ 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, } } 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..5782c598d --- /dev/null +++ b/tests/solidity/complex/qualifier_library_external/main.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./library.sol" as M; + +contract Test { + function externalCall() public pure returns (uint256) { + return M.Halver.half(8); + } + + function tried() public returns (uint256) { + try M.Halver.half(8) returns (uint256 value) { + return value; + } catch { + return 0; + } + } +} 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..8f8767133 --- /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" ] +}, { + "name": "tried", + "inputs": [ { "instance": "Test", "method": "tried", "calldata": [] } ], + "expected": [ "4" ] +} ], + "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..dc4829c16 --- /dev/null +++ b/tests/solidity/complex/qualifier_module/main.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./module.sol" as M; +import * as Star from "./module.sol"; + +contract Test { + 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); + } + + function chain() public pure returns (uint256) { + return M.Nested.FREE_K; + } +} diff --git a/tests/solidity/complex/qualifier_module/module.sol b/tests/solidity/complex/qualifier_module/module.sol new file mode 100644 index 000000000..5e6a8c3e0 --- /dev/null +++ b/tests/solidity/complex/qualifier_module/module.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.0; + +import "./main.sol"; +import "./module.sol" as Mod; +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..7f5ba6944 --- /dev/null +++ b/tests/solidity/complex/qualifier_module/test.json @@ -0,0 +1,21 @@ +{ "modes": [ "E" ], "cases": [ { + "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" ] +}, { + "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 new file mode 100644 index 000000000..e5964e191 --- /dev/null +++ b/tests/solidity/complex/qualifier_type_name/main.sol @@ -0,0 +1,89 @@ +// 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; + + function seven() internal pure returns (uint256) { + return 7; + } + + function enumMember() public pure returns (uint256) { + return uint256(M.Tier.High); + } + + function interfaceEnum() public pure returns (uint256) { + return uint256(M.Surface.Level.High); + } + + function stateRead() public returns (uint256) { + stored = 9; + return M.Test.stored; + } + + function stateWrite() public returns (uint256) { + M.Test.stored = 9; + return stored; + } + + function stateCompound() public returns (uint256) { + stored = 9; + M.Test.stored += 9; + return stored; + } + + function stateDelete() public returns (uint256) { + stored = 9; + delete M.Test.stored; + return stored; + } + + function fieldRead() public returns (uint256) { + inner.value = 9; + return M.Test.inner.value; + } + + function fieldWrite() public returns (uint256) { + M.Test.inner.value = 9; + return inner.value; + } + + function constantMember() public pure returns (uint256) { + return M.Test.SEVEN; + } + + function immutableMember() public view returns (uint256) { + return M.Test.given; + } + + function internalCall() public pure returns (uint256) { + return M.Test.seven(); + } + + function libraryCall() public pure returns (uint256) { + return M.Halver.half(8); + } + + function libraryConstant() public pure returns (uint256) { + return M.Halver.SEVEN; + } + + function wrap() public pure returns (uint256) { + return M.Cost.unwrap(M.Cost.wrap(9)); + } + + 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/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..f4ff95092 --- /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" ] +}, { + "name": "interface_enum", + "inputs": [ { "instance": "Test", "method": "interfaceEnum", "calldata": [] } ], + "expected": [ "1" ] +}, { + "name": "state_read", + "inputs": [ { "instance": "Test", "method": "stateRead", "calldata": [] } ], + "expected": [ "9" ] +}, { + "name": "state_write", + "inputs": [ { "instance": "Test", "method": "stateWrite", "calldata": [] } ], + "expected": [ "9" ] +}, { + "name": "state_compound", + "inputs": [ { "instance": "Test", "method": "stateCompound", "calldata": [] } ], + "expected": [ "18" ] +}, { + "name": "state_delete", + "inputs": [ { "instance": "Test", "method": "stateDelete", "calldata": [] } ], + "expected": [ "0" ] +}, { + "name": "field_read", + "inputs": [ { "instance": "Test", "method": "fieldRead", "calldata": [] } ], + "expected": [ "9" ] +}, { + "name": "field_write", + "inputs": [ { "instance": "Test", "method": "fieldWrite", "calldata": [] } ], + "expected": [ "9" ] +}, { + "name": "constant_member", + "inputs": [ { "instance": "Test", "method": "constantMember", "calldata": [] } ], + "expected": [ "7" ] +}, { + "name": "immutable_member", + "inputs": [ { "instance": "Test", "method": "immutableMember", "calldata": [] } ], + "expected": [ "9" ] +}, { + "name": "internal_call", + "inputs": [ { "instance": "Test", "method": "internalCall", "calldata": [] } ], + "expected": [ "7" ] +}, { + "name": "library_call", + "inputs": [ { "instance": "Test", "method": "libraryCall", "calldata": [] } ], + "expected": [ "4" ] +}, { + "name": "library_constant", + "inputs": [ { "instance": "Test", "method": "libraryConstant", "calldata": [] } ], + "expected": [ "7" ] +}, { + "name": "wrap", + "inputs": [ { "instance": "Test", "method": "wrap", "calldata": [] } ], + "expected": [ "9" ] +}, { + "name": "construction", + "inputs": [ { "instance": "Test", "method": "construction", "calldata": [] } ], + "expected": [ "9" ] +} ], + "contracts": { + "Test": "main.sol:Test" + } +}