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,