Skip to content

Commit dfa7539

Browse files
committed
Suggest field typo through derefs
Take into account implicit dereferences when suggesting fields. ``` error[E0609]: no field `longname` on type `Arc<S>` --> $DIR/suggest-field-through-deref.rs:10:15 | LL | let _ = x.longname; | ^^^^^^^^ help: a field with a similar name exists: `long_name` ``` CC #78374 (comment)
1 parent 1be1e84 commit dfa7539

29 files changed

+227
-192
lines changed

compiler/rustc_hir_typeck/src/expr.rs

+69-72
Original file line numberDiff line numberDiff line change
@@ -2471,9 +2471,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24712471
ty::RawPtr(..) => {
24722472
self.suggest_first_deref_field(&mut err, expr, base, ident);
24732473
}
2474-
ty::Adt(def, _) if !def.is_enum() => {
2475-
self.suggest_fields_on_recordish(&mut err, expr, def, ident);
2476-
}
24772474
ty::Param(param_ty) => {
24782475
self.point_at_param_definition(&mut err, param_ty);
24792476
}
@@ -2633,34 +2630,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26332630
err.span_label(param_span, format!("type parameter '{param_name}' declared here"));
26342631
}
26352632

2636-
fn suggest_fields_on_recordish(
2637-
&self,
2638-
err: &mut Diagnostic,
2639-
expr: &hir::Expr<'_>,
2640-
def: ty::AdtDef<'tcx>,
2641-
field: Ident,
2642-
) {
2643-
let available_field_names = self.available_field_names(def.non_enum_variant(), expr, &[]);
2644-
if let Some(suggested_field_name) =
2645-
find_best_match_for_name(&available_field_names, field.name, None)
2646-
{
2647-
err.span_suggestion(
2648-
field.span,
2649-
"a field with a similar name exists",
2650-
suggested_field_name,
2651-
Applicability::MaybeIncorrect,
2652-
);
2653-
} else {
2654-
err.span_label(field.span, "unknown field");
2655-
if !available_field_names.is_empty() {
2656-
err.note(format!(
2657-
"available fields are: {}",
2658-
self.name_series_display(available_field_names),
2659-
));
2660-
}
2661-
}
2662-
}
2663-
26642633
fn maybe_suggest_array_indexing(
26652634
&self,
26662635
err: &mut Diagnostic,
@@ -2709,18 +2678,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27092678

27102679
let mut err = type_error_struct!(
27112680
self.tcx().sess,
2712-
field.span,
2681+
span,
27132682
expr_t,
27142683
E0609,
27152684
"no field `{field}` on type `{expr_t}`",
27162685
);
27172686

27182687
// try to add a suggestion in case the field is a nested field of a field of the Adt
27192688
let mod_id = self.tcx.parent_module(id).to_def_id();
2720-
if let Some((fields, args)) =
2721-
self.get_field_candidates_considering_privacy(span, expr_t, mod_id)
2689+
for (found_fields, args) in
2690+
self.get_field_candidates_considering_privacy(span, expr_t, mod_id, id)
27222691
{
2723-
let candidate_fields: Vec<_> = fields
2692+
let field_names = found_fields.iter().map(|field| field.name).collect::<Vec<_>>();
2693+
let candidate_fields: Vec<_> = found_fields
2694+
.into_iter()
27242695
.filter_map(|candidate_field| {
27252696
self.check_for_nested_field_satisfying(
27262697
span,
@@ -2729,6 +2700,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27292700
args,
27302701
vec![],
27312702
mod_id,
2703+
id,
27322704
)
27332705
})
27342706
.map(|mut field_path| {
@@ -2753,6 +2725,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27532725
candidate_fields.iter().map(|path| format!("{path}.")),
27542726
Applicability::MaybeIncorrect,
27552727
);
2728+
} else {
2729+
if let Some(field_name) = find_best_match_for_name(&field_names, field.name, None) {
2730+
err.span_suggestion(
2731+
field.span,
2732+
"a field with a similar name exists",
2733+
field_name,
2734+
Applicability::MaybeIncorrect,
2735+
);
2736+
} else if !field_names.is_empty() {
2737+
let is = if field_names.len() == 1 { " is" } else { "s are" };
2738+
err.note(format!(
2739+
"available field{is}: {}",
2740+
self.name_series_display(field_names),
2741+
));
2742+
}
27562743
}
27572744
}
27582745
err
@@ -2781,33 +2768,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27812768
span: Span,
27822769
base_ty: Ty<'tcx>,
27832770
mod_id: DefId,
2784-
) -> Option<(impl Iterator<Item = &'tcx ty::FieldDef> + 'tcx, GenericArgsRef<'tcx>)> {
2771+
hir_id: hir::HirId,
2772+
) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> {
27852773
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty);
27862774

2787-
for (base_t, _) in self.autoderef(span, base_ty) {
2788-
match base_t.kind() {
2789-
ty::Adt(base_def, args) if !base_def.is_enum() => {
2790-
let tcx = self.tcx;
2791-
let fields = &base_def.non_enum_variant().fields;
2792-
// Some struct, e.g. some that impl `Deref`, have all private fields
2793-
// because you're expected to deref them to access the _real_ fields.
2794-
// This, for example, will help us suggest accessing a field through a `Box<T>`.
2795-
if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) {
2796-
continue;
2775+
self.autoderef(span, base_ty)
2776+
.filter_map(move |(base_t, _)| {
2777+
match base_t.kind() {
2778+
ty::Adt(base_def, args) if !base_def.is_enum() => {
2779+
let tcx = self.tcx;
2780+
let fields = &base_def.non_enum_variant().fields;
2781+
// Some struct, e.g. some that impl `Deref`, have all private fields
2782+
// because you're expected to deref them to access the _real_ fields.
2783+
// This, for example, will help us suggest accessing a field through a `Box<T>`.
2784+
if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) {
2785+
return None;
2786+
}
2787+
return Some((
2788+
fields
2789+
.iter()
2790+
.filter(move |field| {
2791+
field.vis.is_accessible_from(mod_id, tcx)
2792+
&& self.is_field_suggestable(field, hir_id, span)
2793+
})
2794+
// For compile-time reasons put a limit on number of fields we search
2795+
.take(100)
2796+
.collect::<Vec<_>>(),
2797+
*args,
2798+
));
27972799
}
2798-
return Some((
2799-
fields
2800-
.iter()
2801-
.filter(move |field| field.vis.is_accessible_from(mod_id, tcx))
2802-
// For compile-time reasons put a limit on number of fields we search
2803-
.take(100),
2804-
args,
2805-
));
2800+
_ => None,
28062801
}
2807-
_ => {}
2808-
}
2809-
}
2810-
None
2802+
})
2803+
.collect()
28112804
}
28122805

28132806
/// This method is called after we have encountered a missing field error to recursively
@@ -2820,6 +2813,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
28202813
subst: GenericArgsRef<'tcx>,
28212814
mut field_path: Vec<Ident>,
28222815
mod_id: DefId,
2816+
hir_id: HirId,
28232817
) -> Option<Vec<Ident>> {
28242818
debug!(
28252819
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
@@ -2835,20 +2829,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
28352829
let field_ty = candidate_field.ty(self.tcx, subst);
28362830
if matches(candidate_field, field_ty) {
28372831
return Some(field_path);
2838-
} else if let Some((nested_fields, subst)) =
2839-
self.get_field_candidates_considering_privacy(span, field_ty, mod_id)
2840-
{
2841-
// recursively search fields of `candidate_field` if it's a ty::Adt
2842-
for field in nested_fields {
2843-
if let Some(field_path) = self.check_for_nested_field_satisfying(
2844-
span,
2845-
matches,
2846-
field,
2847-
subst,
2848-
field_path.clone(),
2849-
mod_id,
2850-
) {
2851-
return Some(field_path);
2832+
} else {
2833+
for (nested_fields, subst) in
2834+
self.get_field_candidates_considering_privacy(span, field_ty, mod_id, hir_id)
2835+
{
2836+
// recursively search fields of `candidate_field` if it's a ty::Adt
2837+
for field in nested_fields {
2838+
if let Some(field_path) = self.check_for_nested_field_satisfying(
2839+
span,
2840+
matches,
2841+
field,
2842+
subst,
2843+
field_path.clone(),
2844+
mod_id,
2845+
hir_id,
2846+
) {
2847+
return Some(field_path);
2848+
}
28522849
}
28532850
}
28542851
}

compiler/rustc_hir_typeck/src/method/suggest.rs

+64-61
Original file line numberDiff line numberDiff line change
@@ -1983,69 +1983,72 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19831983
item_name: Ident,
19841984
return_type: Option<Ty<'tcx>>,
19851985
) {
1986-
if let SelfSource::MethodCall(expr) = source
1987-
&& let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id()
1988-
&& let Some((fields, args)) =
1989-
self.get_field_candidates_considering_privacy(span, actual, mod_id)
1990-
{
1991-
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id));
1986+
if let SelfSource::MethodCall(expr) = source {
1987+
let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id();
1988+
for (fields, args) in
1989+
self.get_field_candidates_considering_privacy(span, actual, mod_id, expr.hir_id)
1990+
{
1991+
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id));
19921992

1993-
let lang_items = self.tcx.lang_items();
1994-
let never_mention_traits = [
1995-
lang_items.clone_trait(),
1996-
lang_items.deref_trait(),
1997-
lang_items.deref_mut_trait(),
1998-
self.tcx.get_diagnostic_item(sym::AsRef),
1999-
self.tcx.get_diagnostic_item(sym::AsMut),
2000-
self.tcx.get_diagnostic_item(sym::Borrow),
2001-
self.tcx.get_diagnostic_item(sym::BorrowMut),
2002-
];
2003-
let candidate_fields: Vec<_> = fields
2004-
.filter_map(|candidate_field| {
2005-
self.check_for_nested_field_satisfying(
2006-
span,
2007-
&|_, field_ty| {
2008-
self.lookup_probe_for_diagnostic(
2009-
item_name,
2010-
field_ty,
2011-
call_expr,
2012-
ProbeScope::TraitsInScope,
2013-
return_type,
2014-
)
2015-
.is_ok_and(|pick| {
2016-
!never_mention_traits
2017-
.iter()
2018-
.flatten()
2019-
.any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
2020-
})
2021-
},
2022-
candidate_field,
2023-
args,
2024-
vec![],
2025-
mod_id,
2026-
)
2027-
})
2028-
.map(|field_path| {
2029-
field_path
2030-
.iter()
2031-
.map(|id| id.name.to_ident_string())
2032-
.collect::<Vec<String>>()
2033-
.join(".")
2034-
})
2035-
.collect();
1993+
let lang_items = self.tcx.lang_items();
1994+
let never_mention_traits = [
1995+
lang_items.clone_trait(),
1996+
lang_items.deref_trait(),
1997+
lang_items.deref_mut_trait(),
1998+
self.tcx.get_diagnostic_item(sym::AsRef),
1999+
self.tcx.get_diagnostic_item(sym::AsMut),
2000+
self.tcx.get_diagnostic_item(sym::Borrow),
2001+
self.tcx.get_diagnostic_item(sym::BorrowMut),
2002+
];
2003+
let candidate_fields: Vec<_> = fields
2004+
.iter()
2005+
.filter_map(|candidate_field| {
2006+
self.check_for_nested_field_satisfying(
2007+
span,
2008+
&|_, field_ty| {
2009+
self.lookup_probe_for_diagnostic(
2010+
item_name,
2011+
field_ty,
2012+
call_expr,
2013+
ProbeScope::TraitsInScope,
2014+
return_type,
2015+
)
2016+
.is_ok_and(|pick| {
2017+
!never_mention_traits
2018+
.iter()
2019+
.flatten()
2020+
.any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
2021+
})
2022+
},
2023+
candidate_field,
2024+
args,
2025+
vec![],
2026+
mod_id,
2027+
expr.hir_id,
2028+
)
2029+
})
2030+
.map(|field_path| {
2031+
field_path
2032+
.iter()
2033+
.map(|id| id.name.to_ident_string())
2034+
.collect::<Vec<String>>()
2035+
.join(".")
2036+
})
2037+
.collect();
20362038

2037-
let len = candidate_fields.len();
2038-
if len > 0 {
2039-
err.span_suggestions(
2040-
item_name.span.shrink_to_lo(),
2041-
format!(
2042-
"{} of the expressions' fields {} a method of the same name",
2043-
if len > 1 { "some" } else { "one" },
2044-
if len > 1 { "have" } else { "has" },
2045-
),
2046-
candidate_fields.iter().map(|path| format!("{path}.")),
2047-
Applicability::MaybeIncorrect,
2048-
);
2039+
let len = candidate_fields.len();
2040+
if len > 0 {
2041+
err.span_suggestions(
2042+
item_name.span.shrink_to_lo(),
2043+
format!(
2044+
"{} of the expressions' fields {} a method of the same name",
2045+
if len > 1 { "some" } else { "one" },
2046+
if len > 1 { "have" } else { "has" },
2047+
),
2048+
candidate_fields.iter().map(|path| format!("{path}.")),
2049+
Applicability::MaybeIncorrect,
2050+
);
2051+
}
20492052
}
20502053
}
20512054
}

tests/ui/async-await/suggest-switching-edition-on-await-cargo.rs

-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ fn await_on_struct_missing() {
1010
let x = S;
1111
x.await;
1212
//~^ ERROR no field `await` on type
13-
//~| NOTE unknown field
1413
//~| NOTE to `.await` a `Future`, switch to Rust 2018
1514
//~| HELP set `edition = "2021"` in `Cargo.toml`
1615
//~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide
@@ -32,7 +31,6 @@ fn await_on_struct_similar() {
3231
fn await_on_63533(x: Pin<&mut dyn Future<Output = ()>>) {
3332
x.await;
3433
//~^ ERROR no field `await` on type
35-
//~| NOTE unknown field
3634
//~| NOTE to `.await` a `Future`, switch to Rust 2018
3735
//~| HELP set `edition = "2021"` in `Cargo.toml`
3836
//~| NOTE for more on editions, read https://doc.rust-lang.org/edition-guide

tests/ui/async-await/suggest-switching-edition-on-await-cargo.stderr

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ error[E0609]: no field `await` on type `await_on_struct_missing::S`
22
--> $DIR/suggest-switching-edition-on-await-cargo.rs:11:7
33
|
44
LL | x.await;
5-
| ^^^^^ unknown field
5+
| ^^^^^
66
|
77
= note: to `.await` a `Future`, switch to Rust 2018 or later
88
= help: set `edition = "2021"` in `Cargo.toml`
99
= note: for more on editions, read https://doc.rust-lang.org/edition-guide
1010

1111
error[E0609]: no field `await` on type `await_on_struct_similar::S`
12-
--> $DIR/suggest-switching-edition-on-await-cargo.rs:24:7
12+
--> $DIR/suggest-switching-edition-on-await-cargo.rs:23:7
1313
|
1414
LL | x.await;
1515
| ^^^^^ help: a field with a similar name exists: `awai`
@@ -19,17 +19,17 @@ LL | x.await;
1919
= note: for more on editions, read https://doc.rust-lang.org/edition-guide
2020

2121
error[E0609]: no field `await` on type `Pin<&mut dyn Future<Output = ()>>`
22-
--> $DIR/suggest-switching-edition-on-await-cargo.rs:33:7
22+
--> $DIR/suggest-switching-edition-on-await-cargo.rs:32:7
2323
|
2424
LL | x.await;
25-
| ^^^^^ unknown field
25+
| ^^^^^
2626
|
2727
= note: to `.await` a `Future`, switch to Rust 2018 or later
2828
= help: set `edition = "2021"` in `Cargo.toml`
2929
= note: for more on editions, read https://doc.rust-lang.org/edition-guide
3030

3131
error[E0609]: no field `await` on type `impl Future<Output = ()>`
32-
--> $DIR/suggest-switching-edition-on-await-cargo.rs:42:7
32+
--> $DIR/suggest-switching-edition-on-await-cargo.rs:40:7
3333
|
3434
LL | x.await;
3535
| ^^^^^

0 commit comments

Comments
 (0)