From 3ca765152c8da33a9d5f41acce8ebf67354fbbb9 Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Mon, 4 Jul 2022 17:06:53 +0100 Subject: [PATCH] Check for calling of associated functions as method calls. This adds a new error and checking for calls of associated functions using method call syntax. This means for code like the following: ``` struct Bar {} impl Bar { fn associated() {} } fn main() -> u64 { let bar = Bar {}; bar.associated(); 0 } ``` we now emit an error: ``` error --> main.sw:11:3 | 9 | 10 | let bar = Bar {}; 11 | bar.associated(); | ^^^^^^^^^^^^^^^^ Cannot call associated function "associated" as method call. Use associated function syntax instead. 12 | 0 13 | } | ``` This brings us closer to Rust behaviour, which also checks for this and only allows functions taking a self to be called with method call syntax. It adds a few more tests for this and also updates some standard library trait methods to take self so they work with the new check. --- sway-core/src/error.rs | 6 ++++++ .../function/function_parameter.rs | 4 ++++ .../typed_expression/method_application.rs | 19 +++++++++++++++++++ sway-lib-std/src/address.sw | 9 +++++---- sway-lib-std/src/contract_id.sw | 9 +++++---- sway-lib-std/src/u128.sw | 9 +++++---- sway-lib-std/src/u256.sw | 11 ++++++----- sway-lib-std/src/vm/evm/evm_address.sw | 9 +++++---- .../associated_fn_as_method_call/Forc.lock | 4 ++++ .../associated_fn_as_method_call/Forc.toml | 6 ++++++ .../json_abi_oracle.json | 1 + .../associated_fn_as_method_call/src/main.sw | 13 +++++++++++++ .../associated_fn_as_method_call/test.toml | 4 ++++ .../Forc.lock | 4 ++++ .../Forc.toml | 6 ++++++ .../json_abi_oracle.json | 1 + .../src/main.sw | 13 +++++++++++++ .../test.toml | 4 ++++ 18 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/test.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/test.toml diff --git a/sway-core/src/error.rs b/sway-core/src/error.rs index c34128a8d54..f01820f067b 100644 --- a/sway-core/src/error.rs +++ b/sway-core/src/error.rs @@ -514,6 +514,11 @@ pub enum CompileError { variable_name: Ident, span: Span, }, + #[error( + "Cannot call associated function \"{fn_name}\" as a method. Use associated function \ + syntax instead." + )] + AssociatedFunctionCalledAsMethod { fn_name: Ident, span: Span }, #[error( "Generic type \"{name}\" is not in scope. Perhaps you meant to specify type parameters in \ the function signature? For example: \n`fn \ @@ -1048,6 +1053,7 @@ impl Spanned for CompileError { ReassignmentToNonVariable { span, .. } => span.clone(), AssignmentToNonMutable { name } => name.span(), MethodRequiresMutableSelf { span, .. } => span.clone(), + AssociatedFunctionCalledAsMethod { span, .. } => span.clone(), TypeParameterNotInTypeScope { span, .. } => span.clone(), MultipleImmediates(span) => span.clone(), MismatchedTypeInTrait { span, .. } => span.clone(), diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/function/function_parameter.rs b/sway-core/src/semantic_analysis/ast_node/declaration/function/function_parameter.rs index 7d917d0552e..ccc3eb9325e 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/function/function_parameter.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/function/function_parameter.rs @@ -36,6 +36,10 @@ impl CopyTypes for TypedFunctionParameter { } impl TypedFunctionParameter { + pub fn is_self(&self) -> bool { + self.name.as_str() == "self" + } + pub(crate) fn type_check( mut ctx: TypeCheckContext, parameter: FunctionParameter, diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 7172125c6f3..c3b9a7c13e1 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -157,6 +157,25 @@ pub(crate) fn type_check_method_application( } }; + // If this function is being called with method call syntax, a.b(c), + // then make sure the first parameter is self, else issue an error. + if !method.is_contract_call { + if let MethodName::FromModule { ref method_name } = method_name { + let is_first_param_self = method + .parameters + .get(0) + .map(|f| f.is_self()) + .unwrap_or_default(); + if !is_first_param_self { + errors.push(CompileError::AssociatedFunctionCalledAsMethod { + fn_name: method_name.clone(), + span, + }); + return err(warnings, errors); + } + } + } + // Validate mutability of self. Check that the variable that the method is called on is mutable // _if_ the method requires mutable self. if let ( diff --git a/sway-lib-std/src/address.sw b/sway-lib-std/src/address.sw index 0eedbb3b90f..a822a8badd2 100644 --- a/sway-lib-std/src/address.sw +++ b/sway-lib-std/src/address.sw @@ -17,10 +17,7 @@ impl core::ops::Eq for Address { pub trait From { fn from(b: b256) -> Self; -} { - fn into(addr: Address) -> b256 { - addr.value - } + fn into(self) -> b256; } /// Functions for casting between the b256 and Address types. @@ -30,4 +27,8 @@ impl From for Address { value: bits, } } + + fn into(self) -> b256 { + self.value + } } diff --git a/sway-lib-std/src/contract_id.sw b/sway-lib-std/src/contract_id.sw index 5eedc46edc7..22c226e2502 100644 --- a/sway-lib-std/src/contract_id.sw +++ b/sway-lib-std/src/contract_id.sw @@ -18,10 +18,7 @@ impl core::ops::Eq for ContractId { // TODO: make this a generic trait. tracked here: https://github.com/FuelLabs/sway-lib-std/issues/58 pub trait From { fn from(b: b256) -> Self; -} { - fn into(id: ContractId) -> b256 { - id.value - } + fn into(self) -> b256; } /// Functions for casting between the b256 and ContractId types. @@ -31,4 +28,8 @@ impl From for ContractId { value: bits, } } + + fn into(self) -> b256 { + self.value + } } diff --git a/sway-lib-std/src/u128.sw b/sway-lib-std/src/u128.sw index 82a7ce2dfaf..3f257743992 100644 --- a/sway-lib-std/src/u128.sw +++ b/sway-lib-std/src/u128.sw @@ -19,10 +19,7 @@ pub enum U128Error { pub trait From { /// Function for creating U128 from its u64 components. pub fn from(upper: u64, lower: u64) -> Self; -} { - fn into(val: U128) -> (u64, u64) { - (val.upper, val.lower) - } + fn into(self) -> (u64, u64); } impl From for U128 { @@ -31,6 +28,10 @@ impl From for U128 { upper, lower, } } + + fn into(self) -> (u64, u64) { + (self.upper, self.lower) + } } impl core::ops::Eq for U128 { diff --git a/sway-lib-std/src/u256.sw b/sway-lib-std/src/u256.sw index 5d9547c3deb..3cd9a9598e2 100644 --- a/sway-lib-std/src/u256.sw +++ b/sway-lib-std/src/u256.sw @@ -19,11 +19,7 @@ pub enum U256Error { pub trait From { /// Function for creating a U256 from its u64 components. pub fn from(a: u64, b: u64, c: u64, d: u64) -> Self; -} { - /// Function for extracting 4 u64s from a U256. - fn into(val: U256) -> (u64, u64, u64, u64) { - (val.a, val.b, val.c, val.d) - } + fn into(self) -> (u64, u64, u64, u64); } impl From for U256 { @@ -32,6 +28,11 @@ impl From for U256 { a, b, c, d, } } + + /// Function for extracting 4 u64s from a U256. + fn into(self) -> (u64, u64, u64, u64) { + (self.a, self.b, self.c, self.d) + } } impl core::ops::Eq for U256 { diff --git a/sway-lib-std/src/vm/evm/evm_address.sw b/sway-lib-std/src/vm/evm/evm_address.sw index b93fe3f3c9b..9861d86e9bc 100644 --- a/sway-lib-std/src/vm/evm/evm_address.sw +++ b/sway-lib-std/src/vm/evm/evm_address.sw @@ -18,10 +18,7 @@ impl core::ops::Eq for EvmAddress { pub trait From { fn from(b: b256) -> Self; -} { - fn into(addr: EvmAddress) -> b256 { - addr.value - } + fn into(self) -> b256; } /// Functions for casting between the b256 and Address types. @@ -36,4 +33,8 @@ impl From for EvmAddress { value: bits, } } + + fn into(self) -> b256 { + self.value + } } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.lock new file mode 100644 index 00000000000..0d5f690c19c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.lock @@ -0,0 +1,4 @@ +[[package]] +name = 'associated_fn_as_method_call' +source = 'root' +dependencies = [] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.toml new file mode 100644 index 00000000000..68bf2cc0bf4 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "associated_fn_as_method_call" +entry = "main.sw" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/json_abi_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/json_abi_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/src/main.sw new file mode 100644 index 00000000000..f522f7925f8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/src/main.sw @@ -0,0 +1,13 @@ +script; + +struct Bar {} + +impl Bar { + fn associated() {} +} + +fn main() -> u64 { + let bar = Bar {}; + bar.associated(); + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/test.toml new file mode 100644 index 00000000000..dc0ff50e15d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_as_method_call/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: bar.associated() +# nextln: $()Cannot call associated function "associated" as a method. Use associated function syntax instead. diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.lock new file mode 100644 index 00000000000..39a9e37aa7c --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.lock @@ -0,0 +1,4 @@ +[[package]] +name = 'associated_fn_params_as_method_call' +source = 'root' +dependencies = [] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.toml new file mode 100644 index 00000000000..03aa5b941cf --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/Forc.toml @@ -0,0 +1,6 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "associated_fn_params_as_method_call" +entry = "main.sw" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/json_abi_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/json_abi_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/src/main.sw new file mode 100644 index 00000000000..f522f7925f8 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/src/main.sw @@ -0,0 +1,13 @@ +script; + +struct Bar {} + +impl Bar { + fn associated() {} +} + +fn main() -> u64 { + let bar = Bar {}; + bar.associated(); + 0 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/test.toml new file mode 100644 index 00000000000..dc0ff50e15d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/associated_fn_params_as_method_call/test.toml @@ -0,0 +1,4 @@ +category = "fail" + +# check: bar.associated() +# nextln: $()Cannot call associated function "associated" as a method. Use associated function syntax instead.