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<File>,)` in the current scope
  --> $DIR/missing-field-access.rs:11:15
   |
LL |     let x = f.get_ref();
   |               ^^^^^^^ method not found in `(BufReader<File>,)`
   |
help: one of the expressions' fields has a method of the same name
   |
LL |     let x = f.0.get_ref();
   |               ++
```
This commit is contained in:
Esteban Küber
2025-07-29 18:39:16 +00:00
parent 2fd855fbfc
commit 26c12c7462
12 changed files with 174 additions and 22 deletions

View File

@@ -3321,18 +3321,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
} else { } else {
(base_ty, "") (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) 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::<Vec<_>>(); let field_names = found_fields.iter().map(|field| field.0.name).collect::<Vec<_>>();
let mut candidate_fields: Vec<_> = found_fields let mut candidate_fields: Vec<_> = found_fields
.into_iter() .into_iter()
.filter_map(|candidate_field| { .filter_map(|candidate_field| {
self.check_for_nested_field_satisfying_condition_for_diag( self.check_for_nested_field_satisfying_condition_for_diag(
span, span,
&|candidate_field, _| candidate_field.ident(self.tcx()) == field, &|candidate_field, _| candidate_field == field,
candidate_field, candidate_field,
args,
vec![], vec![],
mod_id, mod_id,
expr.hir_id, expr.hir_id,
@@ -3396,7 +3395,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
base_ty: Ty<'tcx>, base_ty: Ty<'tcx>,
mod_id: DefId, mod_id: DefId,
hir_id: HirId, hir_id: HirId,
) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> { ) -> Vec<Vec<(Ident, Ty<'tcx>)>> {
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty); debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty);
let mut autoderef = self.autoderef(span, base_ty).silence_errors(); 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)) { if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) {
return None; return None;
} }
return Some(( return Some(
fields fields
.iter() .iter()
.filter(move |field| { .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 // For compile-time reasons put a limit on number of fields we search
.take(100) .take(100)
.map(|field_def| {
(
field_def.ident(self.tcx).normalize_to_macros_2_0(),
field_def.ty(self.tcx, args),
)
})
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
*args, );
)); }
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::<Vec<_>>(),
);
} }
_ => None, _ => None,
} }
@@ -3446,9 +3461,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
pub(crate) fn check_for_nested_field_satisfying_condition_for_diag( pub(crate) fn check_for_nested_field_satisfying_condition_for_diag(
&self, &self,
span: Span, span: Span,
matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool, matches: &impl Fn(Ident, Ty<'tcx>) -> bool,
candidate_field: &ty::FieldDef, candidate_field: (Ident, Ty<'tcx>),
subst: GenericArgsRef<'tcx>,
mut field_path: Vec<Ident>, mut field_path: Vec<Ident>,
mod_id: DefId, mod_id: DefId,
hir_id: HirId, hir_id: HirId,
@@ -3463,16 +3477,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// up to a depth of three // up to a depth of three
None None
} else { } else {
field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0()); field_path.push(candidate_field.0);
let field_ty = candidate_field.ty(self.tcx, subst); let field_ty = candidate_field.1;
if matches(candidate_field, field_ty) { if matches(candidate_field.0, field_ty) {
return Some(field_path); return Some(field_path);
} else { } else {
for (nested_fields, subst) in self for nested_fields in self.get_field_candidates_considering_privacy_for_diag(
.get_field_candidates_considering_privacy_for_diag(
span, field_ty, mod_id, hir_id, span, field_ty, mod_id, hir_id,
) ) {
{
// recursively search fields of `candidate_field` if it's a ty::Adt // recursively search fields of `candidate_field` if it's a ty::Adt
for field in nested_fields { for field in nested_fields {
if let Some(field_path) = self if let Some(field_path) = self
@@ -3480,7 +3492,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
span, span,
matches, matches,
field, field,
subst,
field_path.clone(), field_path.clone(),
mod_id, mod_id,
hir_id, hir_id,

View File

@@ -2792,7 +2792,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
) { ) {
if let SelfSource::MethodCall(expr) = source { if let SelfSource::MethodCall(expr) = source {
let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id(); 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, span,
actual, actual,
mod_id, mod_id,
@@ -2831,7 +2831,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}) })
}, },
candidate_field, candidate_field,
args,
vec![], vec![],
mod_id, mod_id,
expr.hir_id, expr.hir_id,

View File

@@ -12,6 +12,8 @@ error[E0609]: no field `dummy` on type `&(A, B)`
| |
LL | fn get(&self) -> usize { self.dummy } LL | fn get(&self) -> usize { self.dummy }
| ^^^^^ unknown field | ^^^^^ unknown field
|
= note: available fields are: `0`, `1`
error: aborting due to 2 previous errors error: aborting due to 2 previous errors

View File

@@ -3,6 +3,12 @@ error[E0609]: no field `1` on type `(usize,)`
| |
LL | let a: [isize; TUP.1]; LL | let a: [isize; TUP.1];
| ^ unknown field | ^ 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 error: aborting due to 1 previous error

View File

@@ -4,6 +4,7 @@ error[E0609]: no field `field` on type `(..., ..., ..., ...)`
LL | x.field; LL | x.field;
| ^^^^^ unknown 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: 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 = note: consider using `--verbose` to print the full type name to the console

View File

@@ -15,60 +15,108 @@ error[E0609]: no field `_0` on type `(u8, u8)`
| |
LL | offset_of!((u8, u8), _0); 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)` error[E0609]: no field `01` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:7:26 --> $DIR/offset-of-tuple-field.rs:7:26
| |
LL | offset_of!((u8, u8), 01); 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)` error[E0609]: no field `1e2` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:8:26 --> $DIR/offset-of-tuple-field.rs:8:26
| |
LL | offset_of!((u8, u8), 1e2); LL | offset_of!((u8, u8), 1e2);
| ^^^ | ^^^
|
= note: available fields are: `0`, `1`
error[E0609]: no field `1_` on type `(u8, u8)` error[E0609]: no field `1_` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:9:26 --> $DIR/offset-of-tuple-field.rs:9:26
| |
LL | offset_of!((u8, u8), 1_u8); 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)` error[E0609]: no field `1e2` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:12:35 --> $DIR/offset-of-tuple-field.rs:12:35
| |
LL | builtin # offset_of((u8, u8), 1e2); LL | builtin # offset_of((u8, u8), 1e2);
| ^^^ | ^^^
|
= note: available fields are: `0`, `1`
error[E0609]: no field `_0` on type `(u8, u8)` error[E0609]: no field `_0` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:13:35 --> $DIR/offset-of-tuple-field.rs:13:35
| |
LL | builtin # offset_of((u8, u8), _0); 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)` error[E0609]: no field `01` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:14:35 --> $DIR/offset-of-tuple-field.rs:14:35
| |
LL | builtin # offset_of((u8, u8), 01); 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)` error[E0609]: no field `1_` on type `(u8, u8)`
--> $DIR/offset-of-tuple-field.rs:15:35 --> $DIR/offset-of-tuple-field.rs:15:35
| |
LL | builtin # offset_of((u8, u8), 1_u8); 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)` error[E0609]: no field `2` on type `(u8, u16)`
--> $DIR/offset-of-tuple-field.rs:18:47 --> $DIR/offset-of-tuple-field.rs:18:47
| |
LL | offset_of!(((u8, u16), (u32, u16, u8)), 0.2); 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)` error[E0609]: no field `1e2` on type `(u8, u16)`
--> $DIR/offset-of-tuple-field.rs:19:47 --> $DIR/offset-of-tuple-field.rs:19:47
| |
LL | offset_of!(((u8, u16), (u32, u16, u8)), 0.1e2); LL | offset_of!(((u8, u16), (u32, u16, u8)), 0.1e2);
| ^^^ | ^^^
|
= note: available fields are: `0`, `1`
error[E0609]: no field `0` on type `u8` error[E0609]: no field `0` on type `u8`
--> $DIR/offset-of-tuple-field.rs:21:49 --> $DIR/offset-of-tuple-field.rs:21:49

View File

@@ -305,6 +305,8 @@ error[E0609]: no field `1e1` on type `(u8, u8)`
| |
LL | { s.1.1e1; } LL | { s.1.1e1; }
| ^^^ unknown field | ^^^ unknown field
|
= note: available fields are: `0`, `1`
error[E0609]: no field `0x1e1` on type `S` error[E0609]: no field `0x1e1` on type `S`
--> $DIR/float-field.rs:34:9 --> $DIR/float-field.rs:34:9
@@ -343,12 +345,16 @@ error[E0609]: no field `f32` on type `(u8, u8)`
| |
LL | { s.1.f32; } LL | { s.1.f32; }
| ^^^ unknown field | ^^^ unknown field
|
= note: available fields are: `0`, `1`
error[E0609]: no field `1e1` on type `(u8, u8)` error[E0609]: no field `1e1` on type `(u8, u8)`
--> $DIR/float-field.rs:71:9 --> $DIR/float-field.rs:71:9
| |
LL | { s.1.1e1f32; } LL | { s.1.1e1f32; }
| ^^^^^^^^ unknown field | ^^^^^^^^ unknown field
|
= note: available fields are: `0`, `1`
error: aborting due to 57 previous errors error: aborting due to 57 previous errors

View File

@@ -3,12 +3,16 @@ error[E0609]: no field `ab` on type `(Box<(dyn Fn(Option<A>) -> Option<B> + 'sta
| |
LL | let (ab, ba) = (i.ab, i.ba); LL | let (ab, ba) = (i.ab, i.ba);
| ^^ unknown field | ^^ unknown field
|
= note: available fields are: `0`, `1`
error[E0609]: no field `ba` on type `(Box<(dyn Fn(Option<A>) -> Option<B> + 'static)>, Box<(dyn Fn(Option<B>) -> Option<A> + 'static)>)` error[E0609]: no field `ba` on type `(Box<(dyn Fn(Option<A>) -> Option<B> + 'static)>, Box<(dyn Fn(Option<B>) -> Option<A> + 'static)>)`
--> $DIR/well-formed-recursion-limit.rs:12:29 --> $DIR/well-formed-recursion-limit.rs:12:29
| |
LL | let (ab, ba) = (i.ab, i.ba); LL | let (ab, ba) = (i.ab, i.ba);
| ^^ unknown field | ^^ unknown field
|
= note: available fields are: `0`, `1`
error[E0275]: overflow assigning `_` to `Option<_>` error[E0275]: overflow assigning `_` to `Option<_>`
--> $DIR/well-formed-recursion-limit.rs:15:33 --> $DIR/well-formed-recursion-limit.rs:15:33

View File

@@ -3,18 +3,32 @@ error[E0609]: no field `1` on type `(((),),)`
| |
LL | let _ = (((),),).1.0; LL | let _ = (((),),).1.0;
| ^ unknown field | ^ 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 `((),)` error[E0609]: no field `1` on type `((),)`
--> $DIR/index-invalid.rs:4:24 --> $DIR/index-invalid.rs:4:24
| |
LL | let _ = (((),),).0.1; LL | let _ = (((),),).0.1;
| ^ unknown field | ^ 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 `(((),),)` error[E0609]: no field `000` on type `(((),),)`
--> $DIR/index-invalid.rs:6:22 --> $DIR/index-invalid.rs:6:22
| |
LL | let _ = (((),),).000.000; LL | let _ = (((),),).000.000;
| ^^^ unknown field | ^^^ unknown field
|
= note: available field is: `0`
error: aborting due to 3 previous errors error: aborting due to 3 previous errors

View File

@@ -0,0 +1,17 @@
use std::{fs::File, io::BufReader};
struct F(BufReader<File>);
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
}

View File

@@ -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<File>);
| -------- 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<File>,)` in the current scope
--> $DIR/missing-field-access.rs:11:15
|
LL | let x = f.get_ref();
| ^^^^^^^ method not found in `(BufReader<File>,)`
|
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`.

View File

@@ -15,6 +15,12 @@ error[E0609]: no field `2` on type `({integer}, {integer})`
| |
LL | tuple.2; LL | tuple.2;
| ^ unknown field | ^ unknown field
|
help: a field with a similar name exists
|
LL - tuple.2;
LL + tuple.0;
|
error: aborting due to 2 previous errors error: aborting due to 2 previous errors