Skip to content

Commit 933341a

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 642bfb2 commit 933341a

29 files changed

+227
-192
lines changed

compiler/rustc_hir_typeck/src/expr.rs

+69-72
Original file line numberDiff line numberDiff line change
@@ -2417,9 +2417,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24172417
ty::RawPtr(..) => {
24182418
self.suggest_first_deref_field(&mut err, expr, base, ident);
24192419
}
2420-
ty::Adt(def, _) if !def.is_enum() => {
2421-
self.suggest_fields_on_recordish(&mut err, expr, def, ident);
2422-
}
24232420
ty::Param(param_ty) => {
24242421
self.point_at_param_definition(&mut err, param_ty);
24252422
}
@@ -2579,34 +2576,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
25792576
err.span_label(param_span, format!("type parameter '{param_name}' declared here"));
25802577
}
25812578

2582-
fn suggest_fields_on_recordish(
2583-
&self,
2584-
err: &mut Diagnostic,
2585-
expr: &hir::Expr<'_>,
2586-
def: ty::AdtDef<'tcx>,
2587-
field: Ident,
2588-
) {
2589-
let available_field_names = self.available_field_names(def.non_enum_variant(), expr, &[]);
2590-
if let Some(suggested_field_name) =
2591-
find_best_match_for_name(&available_field_names, field.name, None)
2592-
{
2593-
err.span_suggestion(
2594-
field.span,
2595-
"a field with a similar name exists",
2596-
suggested_field_name,
2597-
Applicability::MaybeIncorrect,
2598-
);
2599-
} else {
2600-
err.span_label(field.span, "unknown field");
2601-
if !available_field_names.is_empty() {
2602-
err.note(format!(
2603-
"available fields are: {}",
2604-
self.name_series_display(available_field_names),
2605-
));
2606-
}
2607-
}
2608-
}
2609-
26102579
fn maybe_suggest_array_indexing(
26112580
&self,
26122581
err: &mut Diagnostic,
@@ -2655,18 +2624,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26552624

26562625
let mut err = type_error_struct!(
26572626
self.tcx().sess,
2658-
field.span,
2627+
span,
26592628
expr_t,
26602629
E0609,
26612630
"no field `{field}` on type `{expr_t}`",
26622631
);
26632632

26642633
// try to add a suggestion in case the field is a nested field of a field of the Adt
26652634
let mod_id = self.tcx.parent_module(id).to_def_id();
2666-
if let Some((fields, args)) =
2667-
self.get_field_candidates_considering_privacy(span, expr_t, mod_id)
2635+
for (found_fields, args) in
2636+
self.get_field_candidates_considering_privacy(span, expr_t, mod_id, id)
26682637
{
2669-
let candidate_fields: Vec<_> = fields
2638+
let field_names = found_fields.iter().map(|field| field.name).collect::<Vec<_>>();
2639+
let candidate_fields: Vec<_> = found_fields
2640+
.into_iter()
26702641
.filter_map(|candidate_field| {
26712642
self.check_for_nested_field_satisfying(
26722643
span,
@@ -2675,6 +2646,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26752646
args,
26762647
vec![],
26772648
mod_id,
2649+
id,
26782650
)
26792651
})
26802652
.map(|mut field_path| {
@@ -2699,6 +2671,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26992671
candidate_fields.iter().map(|path| format!("{path}.")),
27002672
Applicability::MaybeIncorrect,
27012673
);
2674+
} else {
2675+
if let Some(field_name) = find_best_match_for_name(&field_names, field.name, None) {
2676+
err.span_suggestion(
2677+
field.span,
2678+
"a field with a similar name exists",
2679+
field_name,
2680+
Applicability::MaybeIncorrect,
2681+
);
2682+
} else if !field_names.is_empty() {
2683+
let is = if field_names.len() == 1 { " is" } else { "s are" };
2684+
err.note(format!(
2685+
"available field{is}: {}",
2686+
self.name_series_display(field_names),
2687+
));
2688+
}
27022689
}
27032690
}
27042691
err
@@ -2727,33 +2714,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27272714
span: Span,
27282715
base_ty: Ty<'tcx>,
27292716
mod_id: DefId,
2730-
) -> Option<(impl Iterator<Item = &'tcx ty::FieldDef> + 'tcx, GenericArgsRef<'tcx>)> {
2717+
hir_id: hir::HirId,
2718+
) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> {
27312719
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty);
27322720

2733-
for (base_t, _) in self.autoderef(span, base_ty) {
2734-
match base_t.kind() {
2735-
ty::Adt(base_def, args) if !base_def.is_enum() => {
2736-
let tcx = self.tcx;
2737-
let fields = &base_def.non_enum_variant().fields;
2738-
// Some struct, e.g. some that impl `Deref`, have all private fields
2739-
// because you're expected to deref them to access the _real_ fields.
2740-
// This, for example, will help us suggest accessing a field through a `Box<T>`.
2741-
if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) {
2742-
continue;
2721+
self.autoderef(span, base_ty)
2722+
.filter_map(move |(base_t, _)| {
2723+
match base_t.kind() {
2724+
ty::Adt(base_def, args) if !base_def.is_enum() => {
2725+
let tcx = self.tcx;
2726+
let fields = &base_def.non_enum_variant().fields;
2727+
// Some struct, e.g. some that impl `Deref`, have all private fields
2728+
// because you're expected to deref them to access the _real_ fields.
2729+
// This, for example, will help us suggest accessing a field through a `Box<T>`.
2730+
if fields.iter().all(|field| !field.vis.is_accessible_from(mod_id, tcx)) {
2731+
return None;
2732+
}
2733+
return Some((
2734+
fields
2735+
.iter()
2736+
.filter(move |field| {
2737+
field.vis.is_accessible_from(mod_id, tcx)
2738+
&& self.is_field_suggestable(field, hir_id, span)
2739+
})
2740+
// For compile-time reasons put a limit on number of fields we search
2741+
.take(100)
2742+
.collect::<Vec<_>>(),
2743+
*args,
2744+
));
27432745
}
2744-
return Some((
2745-
fields
2746-
.iter()
2747-
.filter(move |field| field.vis.is_accessible_from(mod_id, tcx))
2748-
// For compile-time reasons put a limit on number of fields we search
2749-
.take(100),
2750-
args,
2751-
));
2746+
_ => None,
27522747
}
2753-
_ => {}
2754-
}
2755-
}
2756-
None
2748+
})
2749+
.collect()
27572750
}
27582751

27592752
/// This method is called after we have encountered a missing field error to recursively
@@ -2766,6 +2759,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27662759
subst: GenericArgsRef<'tcx>,
27672760
mut field_path: Vec<Ident>,
27682761
mod_id: DefId,
2762+
hir_id: HirId,
27692763
) -> Option<Vec<Ident>> {
27702764
debug!(
27712765
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
@@ -2781,20 +2775,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27812775
let field_ty = candidate_field.ty(self.tcx, subst);
27822776
if matches(candidate_field, field_ty) {
27832777
return Some(field_path);
2784-
} else if let Some((nested_fields, subst)) =
2785-
self.get_field_candidates_considering_privacy(span, field_ty, mod_id)
2786-
{
2787-
// recursively search fields of `candidate_field` if it's a ty::Adt
2788-
for field in nested_fields {
2789-
if let Some(field_path) = self.check_for_nested_field_satisfying(
2790-
span,
2791-
matches,
2792-
field,
2793-
subst,
2794-
field_path.clone(),
2795-
mod_id,
2796-
) {
2797-
return Some(field_path);
2778+
} else {
2779+
for (nested_fields, subst) in
2780+
self.get_field_candidates_considering_privacy(span, field_ty, mod_id, hir_id)
2781+
{
2782+
// recursively search fields of `candidate_field` if it's a ty::Adt
2783+
for field in nested_fields {
2784+
if let Some(field_path) = self.check_for_nested_field_satisfying(
2785+
span,
2786+
matches,
2787+
field,
2788+
subst,
2789+
field_path.clone(),
2790+
mod_id,
2791+
hir_id,
2792+
) {
2793+
return Some(field_path);
2794+
}
27982795
}
27992796
}
28002797
}

compiler/rustc_hir_typeck/src/method/suggest.rs

+64-61
Original file line numberDiff line numberDiff line change
@@ -1870,69 +1870,72 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18701870
item_name: Ident,
18711871
return_type: Option<Ty<'tcx>>,
18721872
) {
1873-
if let SelfSource::MethodCall(expr) = source
1874-
&& let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id()
1875-
&& let Some((fields, args)) =
1876-
self.get_field_candidates_considering_privacy(span, actual, mod_id)
1877-
{
1878-
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id));
1873+
if let SelfSource::MethodCall(expr) = source {
1874+
let mod_id = self.tcx.parent_module(expr.hir_id).to_def_id();
1875+
for (fields, args) in
1876+
self.get_field_candidates_considering_privacy(span, actual, mod_id, expr.hir_id)
1877+
{
1878+
let call_expr = self.tcx.hir().expect_expr(self.tcx.hir().parent_id(expr.hir_id));
18791879

1880-
let lang_items = self.tcx.lang_items();
1881-
let never_mention_traits = [
1882-
lang_items.clone_trait(),
1883-
lang_items.deref_trait(),
1884-
lang_items.deref_mut_trait(),
1885-
self.tcx.get_diagnostic_item(sym::AsRef),
1886-
self.tcx.get_diagnostic_item(sym::AsMut),
1887-
self.tcx.get_diagnostic_item(sym::Borrow),
1888-
self.tcx.get_diagnostic_item(sym::BorrowMut),
1889-
];
1890-
let candidate_fields: Vec<_> = fields
1891-
.filter_map(|candidate_field| {
1892-
self.check_for_nested_field_satisfying(
1893-
span,
1894-
&|_, field_ty| {
1895-
self.lookup_probe_for_diagnostic(
1896-
item_name,
1897-
field_ty,
1898-
call_expr,
1899-
ProbeScope::TraitsInScope,
1900-
return_type,
1901-
)
1902-
.is_ok_and(|pick| {
1903-
!never_mention_traits
1904-
.iter()
1905-
.flatten()
1906-
.any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
1907-
})
1908-
},
1909-
candidate_field,
1910-
args,
1911-
vec![],
1912-
mod_id,
1913-
)
1914-
})
1915-
.map(|field_path| {
1916-
field_path
1917-
.iter()
1918-
.map(|id| id.name.to_ident_string())
1919-
.collect::<Vec<String>>()
1920-
.join(".")
1921-
})
1922-
.collect();
1880+
let lang_items = self.tcx.lang_items();
1881+
let never_mention_traits = [
1882+
lang_items.clone_trait(),
1883+
lang_items.deref_trait(),
1884+
lang_items.deref_mut_trait(),
1885+
self.tcx.get_diagnostic_item(sym::AsRef),
1886+
self.tcx.get_diagnostic_item(sym::AsMut),
1887+
self.tcx.get_diagnostic_item(sym::Borrow),
1888+
self.tcx.get_diagnostic_item(sym::BorrowMut),
1889+
];
1890+
let candidate_fields: Vec<_> = fields
1891+
.iter()
1892+
.filter_map(|candidate_field| {
1893+
self.check_for_nested_field_satisfying(
1894+
span,
1895+
&|_, field_ty| {
1896+
self.lookup_probe_for_diagnostic(
1897+
item_name,
1898+
field_ty,
1899+
call_expr,
1900+
ProbeScope::TraitsInScope,
1901+
return_type,
1902+
)
1903+
.is_ok_and(|pick| {
1904+
!never_mention_traits
1905+
.iter()
1906+
.flatten()
1907+
.any(|def_id| self.tcx.parent(pick.item.def_id) == *def_id)
1908+
})
1909+
},
1910+
candidate_field,
1911+
args,
1912+
vec![],
1913+
mod_id,
1914+
expr.hir_id,
1915+
)
1916+
})
1917+
.map(|field_path| {
1918+
field_path
1919+
.iter()
1920+
.map(|id| id.name.to_ident_string())
1921+
.collect::<Vec<String>>()
1922+
.join(".")
1923+
})
1924+
.collect();
19231925

1924-
let len = candidate_fields.len();
1925-
if len > 0 {
1926-
err.span_suggestions(
1927-
item_name.span.shrink_to_lo(),
1928-
format!(
1929-
"{} of the expressions' fields {} a method of the same name",
1930-
if len > 1 { "some" } else { "one" },
1931-
if len > 1 { "have" } else { "has" },
1932-
),
1933-
candidate_fields.iter().map(|path| format!("{path}.")),
1934-
Applicability::MaybeIncorrect,
1935-
);
1926+
let len = candidate_fields.len();
1927+
if len > 0 {
1928+
err.span_suggestions(
1929+
item_name.span.shrink_to_lo(),
1930+
format!(
1931+
"{} of the expressions' fields {} a method of the same name",
1932+
if len > 1 { "some" } else { "one" },
1933+
if len > 1 { "have" } else { "has" },
1934+
),
1935+
candidate_fields.iter().map(|path| format!("{path}.")),
1936+
Applicability::MaybeIncorrect,
1937+
);
1938+
}
19361939
}
19371940
}
19381941
}

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)