Skip to content

Commit 5e510de

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 28acba3 commit 5e510de

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
@@ -1935,69 +1935,72 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
19351935
item_name: Ident,
19361936
return_type: Option<Ty<'tcx>>,
19371937
) {
1938-
if let SelfSource::MethodCall(expr) = source
1939-
&& let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id()
1940-
&& let Some((fields, args)) =
1941-
self.get_field_candidates_considering_privacy(span, actual, mod_id)
1942-
{
1943-
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id));
1938+
if let SelfSource::MethodCall(expr) = source {
1939+
let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id();
1940+
for (fields, args) in
1941+
self.get_field_candidates_considering_privacy(span, actual, mod_id, expr.hir_id)
1942+
{
1943+
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id));
19441944

1945-
let lang_items = self.tcx.lang_items();
1946-
let never_mention_traits = [
1947-
lang_items.clone_trait(),
1948-
lang_items.deref_trait(),
1949-
lang_items.deref_mut_trait(),
1950-
self.tcx.get_diagnostic_item(sym::AsRef),
1951-
self.tcx.get_diagnostic_item(sym::AsMut),
1952-
self.tcx.get_diagnostic_item(sym::Borrow),
1953-
self.tcx.get_diagnostic_item(sym::BorrowMut),
1954-
];
1955-
let candidate_fields: Vec<_> = fields
1956-
.filter_map(|candidate_field| {
1957-
self.check_for_nested_field_satisfying(
1958-
span,
1959-
&|_, field_ty| {
1960-
self.lookup_probe_for_diagnostic(
1961-
item_name,
1962-
field_ty,
1963-
call_expr,
1964-
ProbeScope::TraitsInScope,
1965-
return_type,
1966-
)
1967-
.is_ok_and(|pick| {
1968-
!never_mention_traits
1969-
.iter()
1970-
.flatten()
1971-
.any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
1972-
})
1973-
},
1974-
candidate_field,
1975-
args,
1976-
vec![],
1977-
mod_id,
1978-
)
1979-
})
1980-
.map(|field_path| {
1981-
field_path
1982-
.iter()
1983-
.map(|id| id.name.to_ident_string())
1984-
.collect::<Vec<String>>()
1985-
.join(".")
1986-
})
1987-
.collect();
1945+
let lang_items = self.tcx.lang_items();
1946+
let never_mention_traits = [
1947+
lang_items.clone_trait(),
1948+
lang_items.deref_trait(),
1949+
lang_items.deref_mut_trait(),
1950+
self.tcx.get_diagnostic_item(sym::AsRef),
1951+
self.tcx.get_diagnostic_item(sym::AsMut),
1952+
self.tcx.get_diagnostic_item(sym::Borrow),
1953+
self.tcx.get_diagnostic_item(sym::BorrowMut),
1954+
];
1955+
let candidate_fields: Vec<_> = fields
1956+
.iter()
1957+
.filter_map(|candidate_field| {
1958+
self.check_for_nested_field_satisfying(
1959+
span,
1960+
&|_, field_ty| {
1961+
self.lookup_probe_for_diagnostic(
1962+
item_name,
1963+
field_ty,
1964+
call_expr,
1965+
ProbeScope::TraitsInScope,
1966+
return_type,
1967+
)
1968+
.is_ok_and(|pick| {
1969+
!never_mention_traits
1970+
.iter()
1971+
.flatten()
1972+
.any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
1973+
})
1974+
},
1975+
candidate_field,
1976+
args,
1977+
vec![],
1978+
mod_id,
1979+
expr.hir_id,
1980+
)
1981+
})
1982+
.map(|field_path| {
1983+
field_path
1984+
.iter()
1985+
.map(|id| id.name.to_ident_string())
1986+
.collect::<Vec<String>>()
1987+
.join(".")
1988+
})
1989+
.collect();
19881990

1989-
let len = candidate_fields.len();
1990-
if len > 0 {
1991-
err.span_suggestions(
1992-
item_name.span.shrink_to_lo(),
1993-
format!(
1994-
"{} of the expressions' fields {} a method of the same name",
1995-
if len > 1 { "some" } else { "one" },
1996-
if len > 1 { "have" } else { "has" },
1997-
),
1998-
candidate_fields.iter().map(|path| format!("{path}.")),
1999-
Applicability::MaybeIncorrect,
2000-
);
1991+
let len = candidate_fields.len();
1992+
if len > 0 {
1993+
err.span_suggestions(
1994+
item_name.span.shrink_to_lo(),
1995+
format!(
1996+
"{} of the expressions' fields {} a method of the same name",
1997+
if len > 1 { "some" } else { "one" },
1998+
if len > 1 { "have" } else { "has" },
1999+
),
2000+
candidate_fields.iter().map(|path| format!("{path}.")),
2001+
Applicability::MaybeIncorrect,
2002+
);
2003+
}
20012004
}
20022005
}
20032006
}

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)