Skip to content

let self in method with receiver incorrectly complains about macro hygiene #143134

@sodiboo

Description

@sodiboo

Code

trait T {
  fn f(self);
}
impl T for () {
  fn f(self) {
      let self = ();
  }
}

Current output

error[E0424]: expected unit struct, unit variant or constant, found local variable `self`
 --> src/lib.rs:6:11
  |
5 | /   fn f(self) {
6 | |       let self = ();
  | |           ^^^^ `self` value is a keyword and may not be bound to variables or shadowed
7 | |   }
  | |___- this function has a `self` parameter, but a macro invocation can only access identifiers it receives from parameters

Desired output

error[E0424]: expected unit struct, unit variant or constant, found local variable `self`
 --> src/lib.rs:6:11
  |
5 |   fn f(self) {
6 |       let self = ();
  |           ^^^^ `self` value is a keyword and may not be bound to variables or shadowed
7 |   }
  |
  |
  help: remove this `let` keyword if you meant to assign to `self`
7 |       let self = ();
  |       ^^^

Rationale and extra context

This code uses absolutely no macros, yet the diagnostic complains about macro hygiene! This seems to have been introduced while fixing #39057

I'm not really sure what's the best diagnostic here. Suggesting to remove the let keyword like i did here is probably not the best choice? But at least in the very simple case of let self =, it is probably what the user intended. (nevermind that self is not mut, because if the suggestion is applied, another diagnostic will suggest to add mut to the receiver)

Maybe a better diagnostic would briefly explain what is expected on the left-hand-side of an assignment expression where the self value is allowed, why it's not quite the same as a pattern, and that this is in fact a pattern where the self value is never allowed, or link to a documentation page explaining the difference.

If the method has no receiver ("Other cases", T::f()), then let self = (); will instead suggest adding a receiver. This is also wrong, because adding a receiver is not a valid fix to the issue (i.e. self value is never valid in a pattern).

Maybe the diagnostic in both cases should be identical, and only explain that self is not valid in this position, and that it has nothing to do with whether or not the method has a receiver or not?

The diagnostic for let self = (); in a toplevel function ("Other cases", g()) is already good, and simply states that the function cannot have a self receiver.

Other cases

trait T {
    fn f();
}
impl T for () {
    fn f() {
        let self = ();
    }
}

fn g() {
    let self = ();
}

Output from other cases

error[E0424]: expected unit struct, unit variant or constant, found module `self`
 --> src/main.rs:6:13
  |
5 |     fn f() {
  |        - this function doesn't have a `self` parameter
6 |         let self = ();
  |             ^^^^ `self` value is a keyword and may not be bound to variables or shadowed
  |
help: add a `self` receiver parameter to make the associated `fn` a method
  |
5 |     fn f(&self) {
  |          +++++

error[E0424]: expected unit struct, unit variant or constant, found module `self`
  --> src/main.rs:11:9
   |
10 | fn g() {
   |    - this function can't have a `self` parameter
11 |     let self = ();
   |         ^^^^ `self` value is a keyword and may not be bound to variables or shadowed

Rust Version

rustc 1.90.0-nightly (bdaba05a9 2025-06-27)
binary: rustc
commit-hash: bdaba05a953eb5abeba0011cdda2560d157aed2e
commit-date: 2025-06-27
host: x86_64-unknown-linux-gnu
release: 1.90.0-nightly
LLVM version: 20.1.7

Metadata

Metadata

Assignees

Labels

A-diagnosticsArea: Messages for errors, warnings, and lintsD-incorrectDiagnostics: A diagnostic that is giving misleading or incorrect information.D-papercutDiagnostics: An error or lint that needs small tweaks.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions