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 {
(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::<Vec<_>>();
let field_names = found_fields.iter().map(|field| field.0.name).collect::<Vec<_>>();
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<Vec<(Ident, Ty<'tcx>)>> {
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::<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,
}
@@ -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<Ident>,
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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

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);
| ^^ 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)>)`
--> $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

View File

@@ -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

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;
| ^ unknown field
|
help: a field with a similar name exists
|
LL - tuple.2;
LL + tuple.0;
|
error: aborting due to 2 previous errors