From 98cfd71e972bc3749a3e69b9343b6c2bc2de0a7b Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sun, 7 Jun 2026 17:10:46 -0300 Subject: [PATCH 1/3] Report missing self parameter with a dedicated error Using `self` as a value in a method without a `self` parameter resolved to the injected `Self` type parameter, producing the misleading "used as a variable, but it is actually a generic type parameter" message. Detect that case and emit SelfParameterNotAvailable, which points at the missing `self` parameter instead. Closes #7645 --- .../ast_node/expression/typed_expression.rs | 9 +++++++++ sway-error/src/error.rs | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index d0081fba437..09461c86d88 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -732,6 +732,15 @@ impl ty::TyExpression { span, } } + // `self` and `Self` are inserted into the namespace as a generic type parameter + // (`GenericTypeForFunctionScope`). When `self` is used as a value but the enclosing + // function has no `self` parameter, resolution falls through to that type parameter. + // Reporting it as "actually a generic type parameter" is misleading, so emit a + // dedicated error pointing at the missing `self` parameter instead. + Some(ty::TyDecl::GenericTypeForFunctionScope(_)) if name.as_str() == "self" => { + let err = handler.emit_err(CompileError::SelfParameterNotAvailable { span }); + ty::TyExpression::error(err, name.span(), engines) + } Some(a) => { let err = handler.emit_err(CompileError::NotAVariable { name: name.clone(), diff --git a/sway-error/src/error.rs b/sway-error/src/error.rs index 2fdb25dfcc4..f8183e893e2 100644 --- a/sway-error/src/error.rs +++ b/sway-error/src/error.rs @@ -83,6 +83,10 @@ pub enum CompileError { what_it_is: &'static str, span: Span, }, + #[error( + "\"self\" is not available here because the enclosing function does not have a \"self\" parameter." + )] + SelfParameterNotAvailable { span: Span }, #[error("{feature} is currently not implemented.")] Unimplemented { /// The description of the unimplemented feature, @@ -1261,6 +1265,7 @@ impl Spanned for CompileError { ModuleDepGraphCyclicReference { .. } => Span::dummy(), UnknownVariable { span, .. } => span.clone(), NotAVariable { span, .. } => span.clone(), + SelfParameterNotAvailable { span, .. } => span.clone(), Unimplemented { span, .. } => span.clone(), TypeError(err) => err.span(), ParseError { span, .. } => span.clone(), From 92efa2e6344fbb92dc54c9db7f246cc09145db67 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sun, 7 Jun 2026 17:10:46 -0300 Subject: [PATCH 2/3] Add test for self used without a self parameter --- .../self_used_without_self_parameter/Forc.lock | 3 +++ .../self_used_without_self_parameter/Forc.toml | 6 ++++++ .../self_used_without_self_parameter/src/main.sw | 14 ++++++++++++++ .../self_used_without_self_parameter/test.toml | 7 +++++++ 4 files changed, 30 insertions(+) create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/test.toml diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.lock new file mode 100644 index 00000000000..8492fd22f24 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.lock @@ -0,0 +1,3 @@ +[[package]] +name = 'self_used_without_self_parameter' +source = 'member' diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.toml new file mode 100644 index 00000000000..82631962e21 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/Forc.toml @@ -0,0 +1,6 @@ +[project] +name = "self_used_without_self_parameter" +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +implicit-std = false diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw new file mode 100644 index 00000000000..3ad216e2e52 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw @@ -0,0 +1,14 @@ +library; + +struct S {} + +impl S { + fn use_self() { + let _ = self.x(); + let _ = self; + } + + fn x(self) -> u64 { + 0 + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/test.toml b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/test.toml new file mode 100644 index 00000000000..fa5a9d67c5d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/test.toml @@ -0,0 +1,7 @@ +category = "fail" + +# check: $()let _ = self.x(); +# nextln: $()"self" is not available here because the enclosing function does not have a "self" parameter. + +# check: $()let _ = self; +# nextln: $()"self" is not available here because the enclosing function does not have a "self" parameter. From 44836c24256e9b0f3a00c7f8da0855fc05f987f1 Mon Sep 17 00:00:00 2001 From: Dnreikronos Date: Sun, 7 Jun 2026 17:54:42 -0300 Subject: [PATCH 3/3] @ Split self-without-self test into separate functions The filecheck `check`/`nextln` pairs were binding to the wrong error. With both `self.x()` and bare `self` in one function, the bare `self` line showed up as trailing context inside the first error block, so the second `check: let _ = self;` matched there and its `nextln` for the error message failed (the next line was `}`, not the message). Putting each use in its own function keeps the two error context windows from overlapping, so each check/nextln pair binds to its own error. @ --- .../should_fail/self_used_without_self_parameter/src/main.sw | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw index 3ad216e2e52..b7752d34441 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_fail/self_used_without_self_parameter/src/main.sw @@ -3,8 +3,11 @@ library; struct S {} impl S { - fn use_self() { + fn use_self_method() { let _ = self.x(); + } + + fn use_self_value() { let _ = self; }