From 3b2c8c0b1d572b783727c23bf7b69eb65d37abff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 29 Jul 2025 18:39:16 +0000 Subject: [PATCH 1/4] Account for bare tuples in field searching logic When looking for the field names and types of a given type, account for tuples. This allows suggestions for incorrectly nested field accesses and field name typos to trigger as intended. Previously these suggestions only worked on `ty::Adt`, including tuple structs which are no different to tuples, so they should behave the same in suggestions. ``` error[E0599]: no method named `get_ref` found for tuple `(BufReader,)` in the current scope --> $DIR/missing-field-access.rs:11:15 | LL | let x = f.get_ref(); | ^^^^^^^ method not found in `(BufReader,)` | help: one of the expressions' fields has a method of the same name | LL | let x = f.0.get_ref(); | ++ ``` --- compiler/rustc_hir_typeck/src/expr.rs | 51 +++++++++++-------- .../rustc_hir_typeck/src/method/suggest.rs | 3 +- .../coherence/coherence-tuple-conflict.stderr | 2 + tests/ui/consts/issue-19244-1.stderr | 6 +++ tests/ui/diagnostic-width/long-E0609.stderr | 1 + .../ui/offset-of/offset-of-tuple-field.stderr | 48 +++++++++++++++++ tests/ui/parser/float-field.stderr | 6 +++ .../traits/well-formed-recursion-limit.stderr | 4 ++ tests/ui/tuple/index-invalid.stderr | 14 +++++ tests/ui/tuple/missing-field-access.rs | 17 +++++++ tests/ui/tuple/missing-field-access.stderr | 38 ++++++++++++++ .../ui/tuple/tuple-index-out-of-bounds.stderr | 6 +++ 12 files changed, 174 insertions(+), 22 deletions(-) create mode 100644 tests/ui/tuple/missing-field-access.rs create mode 100644 tests/ui/tuple/missing-field-access.stderr diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 08e8164078cb4..8093f00fa9e63 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -3321,18 +3321,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { (base_ty, "") }; - for (found_fields, args) in + for found_fields in self.get_field_candidates_considering_privacy_for_diag(span, ty, mod_id, expr.hir_id) { - let field_names = found_fields.iter().map(|field| field.name).collect::>(); + let field_names = found_fields.iter().map(|field| field.0.name).collect::>(); let mut candidate_fields: Vec<_> = found_fields .into_iter() .filter_map(|candidate_field| { self.check_for_nested_field_satisfying_condition_for_diag( span, - &|candidate_field, _| candidate_field.ident(self.tcx()) == field, + &|candidate_field, _| candidate_field == field, candidate_field, - args, vec![], mod_id, expr.hir_id, @@ -3396,7 +3395,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { base_ty: Ty<'tcx>, mod_id: DefId, hir_id: HirId, - ) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> { + ) -> Vec)>> { debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty); let mut autoderef = self.autoderef(span, base_ty).silence_errors(); @@ -3422,7 +3421,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) { return None; } - return Some(( + return Some( fields .iter() .filter(move |field| { @@ -3431,9 +3430,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) // For compile-time reasons put a limit on number of fields we search .take(100) + .map(|field_def| { + ( + field_def.ident(self.tcx).normalize_to_macros_2_0(), + field_def.ty(self.tcx, args), + ) + }) + .collect::>(), + ); + } + ty::Tuple(types) => { + return Some( + types + .iter() + .enumerate() + // For compile-time reasons put a limit on number of fields we search + .take(100) + .map(|(i, ty)| (Ident::from_str(&i.to_string()), ty)) .collect::>(), - *args, - )); + ); } _ => None, } @@ -3446,9 +3461,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn check_for_nested_field_satisfying_condition_for_diag( &self, span: Span, - matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool, - candidate_field: &ty::FieldDef, - subst: GenericArgsRef<'tcx>, + matches: &impl Fn(Ident, Ty<'tcx>) -> bool, + candidate_field: (Ident, Ty<'tcx>), mut field_path: Vec, mod_id: DefId, hir_id: HirId, @@ -3463,16 +3477,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // up to a depth of three None } else { - field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0()); - let field_ty = candidate_field.ty(self.tcx, subst); - if matches(candidate_field, field_ty) { + field_path.push(candidate_field.0); + let field_ty = candidate_field.1; + if matches(candidate_field.0, field_ty) { return Some(field_path); } else { - for (nested_fields, subst) in self - .get_field_candidates_considering_privacy_for_diag( - span, field_ty, mod_id, hir_id, - ) - { + for nested_fields in self.get_field_candidates_considering_privacy_for_diag( + span, field_ty, mod_id, hir_id, + ) { // recursively search fields of `candidate_field` if it's a ty::Adt for field in nested_fields { if let Some(field_path) = self @@ -3480,7 +3492,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { span, matches, field, - subst, field_path.clone(), mod_id, hir_id, diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index e64af8fb7b38f..814975caf36c0 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -2792,7 +2792,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { if let SelfSource::MethodCall(expr) = source { let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id(); - for (fields, args) in self.get_field_candidates_considering_privacy_for_diag( + for fields in self.get_field_candidates_considering_privacy_for_diag( span, actual, mod_id, @@ -2831,7 +2831,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { }) }, candidate_field, - args, vec![], mod_id, expr.hir_id, diff --git a/tests/ui/coherence/coherence-tuple-conflict.stderr b/tests/ui/coherence/coherence-tuple-conflict.stderr index 95f9a1a8841c8..8ce65f79aca98 100644 --- a/tests/ui/coherence/coherence-tuple-conflict.stderr +++ b/tests/ui/coherence/coherence-tuple-conflict.stderr @@ -12,6 +12,8 @@ error[E0609]: no field `dummy` on type `&(A, B)` | LL | fn get(&self) -> usize { self.dummy } | ^^^^^ unknown field + | + = note: available fields are: `0`, `1` error: aborting due to 2 previous errors diff --git a/tests/ui/consts/issue-19244-1.stderr b/tests/ui/consts/issue-19244-1.stderr index 9c1336402e649..7d78f954efeb7 100644 --- a/tests/ui/consts/issue-19244-1.stderr +++ b/tests/ui/consts/issue-19244-1.stderr @@ -3,6 +3,12 @@ error[E0609]: no field `1` on type `(usize,)` | LL | let a: [isize; TUP.1]; | ^ unknown field + | +help: a field with a similar name exists + | +LL - let a: [isize; TUP.1]; +LL + let a: [isize; TUP.0]; + | error: aborting due to 1 previous error diff --git a/tests/ui/diagnostic-width/long-E0609.stderr b/tests/ui/diagnostic-width/long-E0609.stderr index 36ef85457468e..70092ea34bc13 100644 --- a/tests/ui/diagnostic-width/long-E0609.stderr +++ b/tests/ui/diagnostic-width/long-E0609.stderr @@ -4,6 +4,7 @@ error[E0609]: no field `field` on type `(..., ..., ..., ...)` LL | x.field; | ^^^^^ unknown field | + = note: available fields are: `0`, `1`, `2`, `3` = note: the full name for the type has been written to '$TEST_BUILD_DIR/long-E0609.long-type-$LONG_TYPE_HASH.txt' = note: consider using `--verbose` to print the full type name to the console diff --git a/tests/ui/offset-of/offset-of-tuple-field.stderr b/tests/ui/offset-of/offset-of-tuple-field.stderr index 4da0d85165035..e4a5ed09e13ea 100644 --- a/tests/ui/offset-of/offset-of-tuple-field.stderr +++ b/tests/ui/offset-of/offset-of-tuple-field.stderr @@ -15,60 +15,108 @@ error[E0609]: no field `_0` on type `(u8, u8)` | LL | offset_of!((u8, u8), _0); | ^^ + | +help: a field with a similar name exists + | +LL - offset_of!((u8, u8), _0); +LL + offset_of!((u8, u8), 0); + | error[E0609]: no field `01` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:7:26 | LL | offset_of!((u8, u8), 01); | ^^ + | +help: a field with a similar name exists + | +LL - offset_of!((u8, u8), 01); +LL + offset_of!((u8, u8), 0); + | error[E0609]: no field `1e2` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:8:26 | LL | offset_of!((u8, u8), 1e2); | ^^^ + | + = note: available fields are: `0`, `1` error[E0609]: no field `1_` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:9:26 | LL | offset_of!((u8, u8), 1_u8); | ^^^^ + | +help: a field with a similar name exists + | +LL - offset_of!((u8, u8), 1_u8); +LL + offset_of!((u8, u8), 1); + | error[E0609]: no field `1e2` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:12:35 | LL | builtin # offset_of((u8, u8), 1e2); | ^^^ + | + = note: available fields are: `0`, `1` error[E0609]: no field `_0` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:13:35 | LL | builtin # offset_of((u8, u8), _0); | ^^ + | +help: a field with a similar name exists + | +LL - builtin # offset_of((u8, u8), _0); +LL + builtin # offset_of((u8, u8), 0); + | error[E0609]: no field `01` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:14:35 | LL | builtin # offset_of((u8, u8), 01); | ^^ + | +help: a field with a similar name exists + | +LL - builtin # offset_of((u8, u8), 01); +LL + builtin # offset_of((u8, u8), 0); + | error[E0609]: no field `1_` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:15:35 | LL | builtin # offset_of((u8, u8), 1_u8); | ^^^^ + | +help: a field with a similar name exists + | +LL - builtin # offset_of((u8, u8), 1_u8); +LL + builtin # offset_of((u8, u8), 1); + | error[E0609]: no field `2` on type `(u8, u16)` --> $DIR/offset-of-tuple-field.rs:18:47 | LL | offset_of!(((u8, u16), (u32, u16, u8)), 0.2); | ^ + | +help: a field with a similar name exists + | +LL - offset_of!(((u8, u16), (u32, u16, u8)), 0.2); +LL + offset_of!(((u8, u16), (u32, u16, u8)), 0.0); + | error[E0609]: no field `1e2` on type `(u8, u16)` --> $DIR/offset-of-tuple-field.rs:19:47 | LL | offset_of!(((u8, u16), (u32, u16, u8)), 0.1e2); | ^^^ + | + = note: available fields are: `0`, `1` error[E0609]: no field `0` on type `u8` --> $DIR/offset-of-tuple-field.rs:21:49 diff --git a/tests/ui/parser/float-field.stderr b/tests/ui/parser/float-field.stderr index 0cc1b0767dc74..078d16a411729 100644 --- a/tests/ui/parser/float-field.stderr +++ b/tests/ui/parser/float-field.stderr @@ -305,6 +305,8 @@ error[E0609]: no field `1e1` on type `(u8, u8)` | LL | { s.1.1e1; } | ^^^ unknown field + | + = note: available fields are: `0`, `1` error[E0609]: no field `0x1e1` on type `S` --> $DIR/float-field.rs:34:9 @@ -343,12 +345,16 @@ error[E0609]: no field `f32` on type `(u8, u8)` | LL | { s.1.f32; } | ^^^ unknown field + | + = note: available fields are: `0`, `1` error[E0609]: no field `1e1` on type `(u8, u8)` --> $DIR/float-field.rs:71:9 | LL | { s.1.1e1f32; } | ^^^^^^^^ unknown field + | + = note: available fields are: `0`, `1` error: aborting due to 57 previous errors diff --git a/tests/ui/traits/well-formed-recursion-limit.stderr b/tests/ui/traits/well-formed-recursion-limit.stderr index e0270ecabbd8d..a4c85c4fcbdf3 100644 --- a/tests/ui/traits/well-formed-recursion-limit.stderr +++ b/tests/ui/traits/well-formed-recursion-limit.stderr @@ -3,12 +3,16 @@ error[E0609]: no field `ab` on type `(Box<(dyn Fn(Option) -> Option + 'sta | LL | let (ab, ba) = (i.ab, i.ba); | ^^ unknown field + | + = note: available fields are: `0`, `1` error[E0609]: no field `ba` on type `(Box<(dyn Fn(Option) -> Option + 'static)>, Box<(dyn Fn(Option) -> Option + 'static)>)` --> $DIR/well-formed-recursion-limit.rs:12:29 | LL | let (ab, ba) = (i.ab, i.ba); | ^^ unknown field + | + = note: available fields are: `0`, `1` error[E0275]: overflow assigning `_` to `Option<_>` --> $DIR/well-formed-recursion-limit.rs:15:33 diff --git a/tests/ui/tuple/index-invalid.stderr b/tests/ui/tuple/index-invalid.stderr index ae2c275f52cd9..acc4134d1a653 100644 --- a/tests/ui/tuple/index-invalid.stderr +++ b/tests/ui/tuple/index-invalid.stderr @@ -3,18 +3,32 @@ error[E0609]: no field `1` on type `(((),),)` | LL | let _ = (((),),).1.0; | ^ unknown field + | +help: a field with a similar name exists + | +LL - let _ = (((),),).1.0; +LL + let _ = (((),),).0.0; + | error[E0609]: no field `1` on type `((),)` --> $DIR/index-invalid.rs:4:24 | LL | let _ = (((),),).0.1; | ^ unknown field + | +help: a field with a similar name exists + | +LL - let _ = (((),),).0.1; +LL + let _ = (((),),).0.0; + | error[E0609]: no field `000` on type `(((),),)` --> $DIR/index-invalid.rs:6:22 | LL | let _ = (((),),).000.000; | ^^^ unknown field + | + = note: available field is: `0` error: aborting due to 3 previous errors diff --git a/tests/ui/tuple/missing-field-access.rs b/tests/ui/tuple/missing-field-access.rs new file mode 100644 index 0000000000000..4ccd759ccd245 --- /dev/null +++ b/tests/ui/tuple/missing-field-access.rs @@ -0,0 +1,17 @@ +use std::{fs::File, io::BufReader}; + +struct F(BufReader); + +fn main() { + let f = F(BufReader::new(File::open("x").unwrap())); + let x = f.get_ref(); //~ ERROR E0599 + //~^ HELP one of the expressions' fields has a method of the same name + //~| HELP consider pinning the expression + let f = (BufReader::new(File::open("x").unwrap()), ); + let x = f.get_ref(); //~ ERROR E0599 + //~^ HELP one of the expressions' fields has a method of the same name + //~| HELP consider pinning the expression + + // FIXME(estebank): the pinning suggestion should not be included in either case. + // https://github.com/rust-lang/rust/issues/144602 +} diff --git a/tests/ui/tuple/missing-field-access.stderr b/tests/ui/tuple/missing-field-access.stderr new file mode 100644 index 0000000000000..711a8906d6276 --- /dev/null +++ b/tests/ui/tuple/missing-field-access.stderr @@ -0,0 +1,38 @@ +error[E0599]: no method named `get_ref` found for struct `F` in the current scope + --> $DIR/missing-field-access.rs:7:15 + | +LL | struct F(BufReader); + | -------- method `get_ref` not found for this struct +... +LL | let x = f.get_ref(); + | ^^^^^^^ method not found in `F` + | +help: one of the expressions' fields has a method of the same name + | +LL | let x = f.0.get_ref(); + | ++ +help: consider pinning the expression + | +LL ~ let mut pinned = std::pin::pin!(f); +LL ~ let x = pinned.as_ref().get_ref(); + | + +error[E0599]: no method named `get_ref` found for tuple `(BufReader,)` in the current scope + --> $DIR/missing-field-access.rs:11:15 + | +LL | let x = f.get_ref(); + | ^^^^^^^ method not found in `(BufReader,)` + | +help: one of the expressions' fields has a method of the same name + | +LL | let x = f.0.get_ref(); + | ++ +help: consider pinning the expression + | +LL ~ let mut pinned = std::pin::pin!(f); +LL ~ let x = pinned.as_ref().get_ref(); + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/tuple/tuple-index-out-of-bounds.stderr b/tests/ui/tuple/tuple-index-out-of-bounds.stderr index 8b3c835c3e3ce..728276909096e 100644 --- a/tests/ui/tuple/tuple-index-out-of-bounds.stderr +++ b/tests/ui/tuple/tuple-index-out-of-bounds.stderr @@ -15,6 +15,12 @@ error[E0609]: no field `2` on type `({integer}, {integer})` | LL | tuple.2; | ^ unknown field + | +help: a field with a similar name exists + | +LL - tuple.2; +LL + tuple.0; + | error: aborting due to 2 previous errors From 53eb6509e25298481789b6ab6193fa177c383ff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 29 Jul 2025 20:14:02 +0000 Subject: [PATCH 2/4] Do not suggest pinning missing `.get_ref()` When suggesting field access which would encounter a method not found, do not suggest pinning when those methods are on `impl Pin` itself. ``` error[E0599]: no method named `get_ref` found for tuple `(BufReader,)` in the current scope --> $DIR/missing-field-access.rs:11:15 | LL | let x = f.get_ref(); | ^^^^^^^ method not found in `(BufReader,)` | help: one of the expressions' fields has a method of the same name | LL | let x = f.0.get_ref(); | ++ ``` instead of ``` error[E0599]: no method named `get_ref` found for tuple `(BufReader,)` in the current scope --> $DIR/missing-field-access.rs:11:15 | LL | let x = f.get_ref(); | ^^^^^^^ method not found in `(BufReader,)` | help: one of the expressions' fields has a method of the same name | LL | let x = f.0.get_ref(); | ++ help: consider pinning the expression | LL ~ let mut pinned = std::pin::pin!(f); LL ~ let x = pinned.as_ref().get_ref(); | ``` --- compiler/rustc_hir_typeck/src/method/suggest.rs | 9 ++++++++- tests/ui/tuple/missing-field-access.rs | 9 ++++----- tests/ui/tuple/missing-field-access.stderr | 14 ++------------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 814975caf36c0..ab893ec6e5bb1 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3585,6 +3585,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.lang_items().deref_trait(), self.tcx.lang_items().deref_mut_trait(), self.tcx.lang_items().drop_trait(), + self.tcx.lang_items().pin_type(), self.tcx.get_diagnostic_item(sym::AsRef), ]; // Try alternative arbitrary self types that could fulfill this call. @@ -3670,7 +3671,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) { debug!("try_alt_rcvr: pick candidate {:?}", pick); - let did = Some(pick.item.container_id(self.tcx)); + let did = pick.item.trait_container(self.tcx); // We don't want to suggest a container type when the missing // method is `.clone()` or `.deref()` otherwise we'd suggest // `Arc::new(foo).clone()`, which is far from what the user wants. @@ -3734,6 +3735,12 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We skip some common traits that we don't want to consider because autoderefs // would take care of them. && !skippable.contains(&Some(pick.item.container_id(self.tcx))) + && !skippable.contains(&pick.item.impl_container(self.tcx).and_then(|did| { + match self.tcx.type_of(did).instantiate_identity().kind() { + ty::Adt(def, _) => Some(def.did()), + _ => None, + } + })) // We don't want to go through derefs. && pick.autoderefs == 0 // Check that the method of the same name that was found on the new `Pin` diff --git a/tests/ui/tuple/missing-field-access.rs b/tests/ui/tuple/missing-field-access.rs index 4ccd759ccd245..b94b7cf977cde 100644 --- a/tests/ui/tuple/missing-field-access.rs +++ b/tests/ui/tuple/missing-field-access.rs @@ -1,3 +1,7 @@ +// Ensure that suggestions to search for missing intermediary field accesses are available for both +// tuple structs *and* regular tuples. +// Ensure that we do not suggest pinning the expression just because `Pin::get_ref` exists. +// https://github.com/rust-lang/rust/issues/144602 use std::{fs::File, io::BufReader}; struct F(BufReader); @@ -6,12 +10,7 @@ fn main() { let f = F(BufReader::new(File::open("x").unwrap())); let x = f.get_ref(); //~ ERROR E0599 //~^ HELP one of the expressions' fields has a method of the same name - //~| HELP consider pinning the expression let f = (BufReader::new(File::open("x").unwrap()), ); let x = f.get_ref(); //~ ERROR E0599 //~^ HELP one of the expressions' fields has a method of the same name - //~| HELP consider pinning the expression - - // FIXME(estebank): the pinning suggestion should not be included in either case. - // https://github.com/rust-lang/rust/issues/144602 } diff --git a/tests/ui/tuple/missing-field-access.stderr b/tests/ui/tuple/missing-field-access.stderr index 711a8906d6276..fd9f01f8ff6ac 100644 --- a/tests/ui/tuple/missing-field-access.stderr +++ b/tests/ui/tuple/missing-field-access.stderr @@ -1,5 +1,5 @@ error[E0599]: no method named `get_ref` found for struct `F` in the current scope - --> $DIR/missing-field-access.rs:7:15 + --> $DIR/missing-field-access.rs:11:15 | LL | struct F(BufReader); | -------- method `get_ref` not found for this struct @@ -11,14 +11,9 @@ help: one of the expressions' fields has a method of the same name | LL | let x = f.0.get_ref(); | ++ -help: consider pinning the expression - | -LL ~ let mut pinned = std::pin::pin!(f); -LL ~ let x = pinned.as_ref().get_ref(); - | error[E0599]: no method named `get_ref` found for tuple `(BufReader,)` in the current scope - --> $DIR/missing-field-access.rs:11:15 + --> $DIR/missing-field-access.rs:14:15 | LL | let x = f.get_ref(); | ^^^^^^^ method not found in `(BufReader,)` @@ -27,11 +22,6 @@ help: one of the expressions' fields has a method of the same name | LL | let x = f.0.get_ref(); | ++ -help: consider pinning the expression - | -LL ~ let mut pinned = std::pin::pin!(f); -LL ~ let x = pinned.as_ref().get_ref(); - | error: aborting due to 2 previous errors From 775e287eb54d05cec0e16972e9191fa0842a17e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 30 Jul 2025 16:21:40 +0000 Subject: [PATCH 3/4] Do not provide field typo suggestions for tuples and tuple structs --- compiler/rustc_hir_typeck/src/expr.rs | 4 ++++ tests/ui/consts/issue-19244-1.stderr | 6 +----- tests/ui/error-codes/ex-E0612.stderr | 6 +----- ...073-zero-padded-tuple-struct-indices.stderr | 6 +----- .../ui/offset-of/offset-of-tuple-field.stderr | 18 +++--------------- tests/ui/tuple/index-invalid.stderr | 12 ++---------- .../ui/tuple/tuple-index-out-of-bounds.stderr | 12 ++---------- 7 files changed, 14 insertions(+), 50 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 8093f00fa9e63..4c3f27cdeec3d 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -2745,6 +2745,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let available_field_names = self.available_field_names(variant, expr, skip_fields); if let Some(field_name) = find_best_match_for_name(&available_field_names, field.ident.name, None) + && !(field.ident.name.as_str().parse::().is_ok() + && field_name.as_str().parse::().is_ok()) { err.span_label(field.ident.span, "unknown field"); err.span_suggestion_verbose( @@ -3360,6 +3362,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); } else if let Some(field_name) = find_best_match_for_name(&field_names, field.name, None) + && !(field.name.as_str().parse::().is_ok() + && field_name.as_str().parse::().is_ok()) { err.span_suggestion_verbose( field.span, diff --git a/tests/ui/consts/issue-19244-1.stderr b/tests/ui/consts/issue-19244-1.stderr index 7d78f954efeb7..98a33817b4c8a 100644 --- a/tests/ui/consts/issue-19244-1.stderr +++ b/tests/ui/consts/issue-19244-1.stderr @@ -4,11 +4,7 @@ error[E0609]: no field `1` on type `(usize,)` LL | let a: [isize; TUP.1]; | ^ unknown field | -help: a field with a similar name exists - | -LL - let a: [isize; TUP.1]; -LL + let a: [isize; TUP.0]; - | + = note: available field is: `0` error: aborting due to 1 previous error diff --git a/tests/ui/error-codes/ex-E0612.stderr b/tests/ui/error-codes/ex-E0612.stderr index 54451d3d4526e..e6062f6061db4 100644 --- a/tests/ui/error-codes/ex-E0612.stderr +++ b/tests/ui/error-codes/ex-E0612.stderr @@ -4,11 +4,7 @@ error[E0609]: no field `1` on type `Foo` LL | y.1; | ^ unknown field | -help: a field with a similar name exists - | -LL - y.1; -LL + y.0; - | + = note: available field is: `0` error: aborting due to 1 previous error diff --git a/tests/ui/issues/issue-47073-zero-padded-tuple-struct-indices.stderr b/tests/ui/issues/issue-47073-zero-padded-tuple-struct-indices.stderr index 0a6fe24d5e349..016bf950f1d5a 100644 --- a/tests/ui/issues/issue-47073-zero-padded-tuple-struct-indices.stderr +++ b/tests/ui/issues/issue-47073-zero-padded-tuple-struct-indices.stderr @@ -4,11 +4,7 @@ error[E0609]: no field `00` on type `Verdict` LL | let _condemned = justice.00; | ^^ unknown field | -help: a field with a similar name exists - | -LL - let _condemned = justice.00; -LL + let _condemned = justice.0; - | + = note: available fields are: `0`, `1` error[E0609]: no field `001` on type `Verdict` --> $DIR/issue-47073-zero-padded-tuple-struct-indices.rs:10:31 diff --git a/tests/ui/offset-of/offset-of-tuple-field.stderr b/tests/ui/offset-of/offset-of-tuple-field.stderr index e4a5ed09e13ea..01622c5fa2da6 100644 --- a/tests/ui/offset-of/offset-of-tuple-field.stderr +++ b/tests/ui/offset-of/offset-of-tuple-field.stderr @@ -28,11 +28,7 @@ error[E0609]: no field `01` on type `(u8, u8)` LL | offset_of!((u8, u8), 01); | ^^ | -help: a field with a similar name exists - | -LL - offset_of!((u8, u8), 01); -LL + offset_of!((u8, u8), 0); - | + = note: available fields are: `0`, `1` error[E0609]: no field `1e2` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:8:26 @@ -80,11 +76,7 @@ error[E0609]: no field `01` on type `(u8, u8)` LL | builtin # offset_of((u8, u8), 01); | ^^ | -help: a field with a similar name exists - | -LL - builtin # offset_of((u8, u8), 01); -LL + builtin # offset_of((u8, u8), 0); - | + = note: available fields are: `0`, `1` error[E0609]: no field `1_` on type `(u8, u8)` --> $DIR/offset-of-tuple-field.rs:15:35 @@ -104,11 +96,7 @@ error[E0609]: no field `2` on type `(u8, u16)` LL | offset_of!(((u8, u16), (u32, u16, u8)), 0.2); | ^ | -help: a field with a similar name exists - | -LL - offset_of!(((u8, u16), (u32, u16, u8)), 0.2); -LL + offset_of!(((u8, u16), (u32, u16, u8)), 0.0); - | + = note: available fields are: `0`, `1` error[E0609]: no field `1e2` on type `(u8, u16)` --> $DIR/offset-of-tuple-field.rs:19:47 diff --git a/tests/ui/tuple/index-invalid.stderr b/tests/ui/tuple/index-invalid.stderr index acc4134d1a653..fee09b7947c12 100644 --- a/tests/ui/tuple/index-invalid.stderr +++ b/tests/ui/tuple/index-invalid.stderr @@ -4,11 +4,7 @@ error[E0609]: no field `1` on type `(((),),)` LL | let _ = (((),),).1.0; | ^ unknown field | -help: a field with a similar name exists - | -LL - let _ = (((),),).1.0; -LL + let _ = (((),),).0.0; - | + = note: available field is: `0` error[E0609]: no field `1` on type `((),)` --> $DIR/index-invalid.rs:4:24 @@ -16,11 +12,7 @@ error[E0609]: no field `1` on type `((),)` LL | let _ = (((),),).0.1; | ^ unknown field | -help: a field with a similar name exists - | -LL - let _ = (((),),).0.1; -LL + let _ = (((),),).0.0; - | + = note: available field is: `0` error[E0609]: no field `000` on type `(((),),)` --> $DIR/index-invalid.rs:6:22 diff --git a/tests/ui/tuple/tuple-index-out-of-bounds.stderr b/tests/ui/tuple/tuple-index-out-of-bounds.stderr index 728276909096e..2be9d5631f781 100644 --- a/tests/ui/tuple/tuple-index-out-of-bounds.stderr +++ b/tests/ui/tuple/tuple-index-out-of-bounds.stderr @@ -4,11 +4,7 @@ error[E0609]: no field `2` on type `Point` LL | origin.2; | ^ unknown field | -help: a field with a similar name exists - | -LL - origin.2; -LL + origin.0; - | + = note: available fields are: `0`, `1` error[E0609]: no field `2` on type `({integer}, {integer})` --> $DIR/tuple-index-out-of-bounds.rs:12:11 @@ -16,11 +12,7 @@ error[E0609]: no field `2` on type `({integer}, {integer})` LL | tuple.2; | ^ unknown field | -help: a field with a similar name exists - | -LL - tuple.2; -LL + tuple.0; - | + = note: available fields are: `0`, `1` error: aborting due to 2 previous errors From f47df01cadffa158d35cd48d1da70a22be98b024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 30 Jul 2025 16:32:15 +0000 Subject: [PATCH 4/4] review comment --- compiler/rustc_hir_typeck/src/method/suggest.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index ab893ec6e5bb1..7c9254cb9f8ef 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3585,7 +3585,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { self.tcx.lang_items().deref_trait(), self.tcx.lang_items().deref_mut_trait(), self.tcx.lang_items().drop_trait(), - self.tcx.lang_items().pin_type(), self.tcx.get_diagnostic_item(sym::AsRef), ]; // Try alternative arbitrary self types that could fulfill this call. @@ -3720,8 +3719,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { && !alt_rcvr_sugg // `T: !Unpin` && !unpin - // The method isn't `as_ref`, as it would provide a wrong suggestion for `Pin`. - && sym::as_ref != item_name.name // Either `Pin::as_ref` or `Pin::as_mut`. && let Some(pin_call) = pin_call // Search for `item_name` as a method accessible on `Pin`. @@ -3735,12 +3732,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // We skip some common traits that we don't want to consider because autoderefs // would take care of them. && !skippable.contains(&Some(pick.item.container_id(self.tcx))) - && !skippable.contains(&pick.item.impl_container(self.tcx).and_then(|did| { + // Do not suggest pinning when the method is directly on `Pin`. + && pick.item.impl_container(self.tcx).map_or(true, |did| { match self.tcx.type_of(did).instantiate_identity().kind() { - ty::Adt(def, _) => Some(def.did()), - _ => None, + ty::Adt(def, _) => Some(def.did()) != self.tcx.lang_items().pin_type(), + _ => true, } - })) + }) // We don't want to go through derefs. && pick.autoderefs == 0 // Check that the method of the same name that was found on the new `Pin`