Skip to content

Commit 733a3eb

Browse files
Rollup merge of rust-lang#117110 - estebank:deref-field-suggestion, r=b-naber
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 rust-lang#78374 (comment)
2 parents d33b750 + 289ce57 commit 733a3eb

File tree

55 files changed

+491
-241
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+491
-241
lines changed

compiler/rustc_hir_typeck/src/expr.rs

+112-103
Original file line numberDiff line numberDiff line change
@@ -2191,7 +2191,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21912191
if let Some(field_name) =
21922192
find_best_match_for_name(&available_field_names, field.ident.name, None)
21932193
{
2194-
err.span_suggestion(
2194+
err.span_label(field.ident.span, "unknown field");
2195+
err.span_suggestion_verbose(
21952196
field.ident.span,
21962197
"a field with a similar name exists",
21972198
field_name,
@@ -2420,35 +2421,32 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24202421
ty: Ty<'tcx>,
24212422
) {
24222423
let Some(output_ty) = self.get_impl_future_output_ty(ty) else {
2424+
err.span_label(field_ident.span, "unknown field");
24232425
return;
24242426
};
2425-
let mut add_label = true;
2426-
if let ty::Adt(def, _) = output_ty.kind() {
2427-
// no field access on enum type
2428-
if !def.is_enum() {
2429-
if def
2430-
.non_enum_variant()
2431-
.fields
2432-
.iter()
2433-
.any(|field| field.ident(self.tcx) == field_ident)
2434-
{
2435-
add_label = false;
2436-
err.span_label(
2437-
field_ident.span,
2438-
"field not available in `impl Future`, but it is available in its `Output`",
2439-
);
2440-
err.span_suggestion_verbose(
2441-
base.span.shrink_to_hi(),
2442-
"consider `await`ing on the `Future` and access the field of its `Output`",
2443-
".await",
2444-
Applicability::MaybeIncorrect,
2445-
);
2446-
}
2447-
}
2427+
let ty::Adt(def, _) = output_ty.kind() else {
2428+
err.span_label(field_ident.span, "unknown field");
2429+
return;
2430+
};
2431+
// no field access on enum type
2432+
if def.is_enum() {
2433+
err.span_label(field_ident.span, "unknown field");
2434+
return;
24482435
}
2449-
if add_label {
2450-
err.span_label(field_ident.span, format!("field not found in `{ty}`"));
2436+
if !def.non_enum_variant().fields.iter().any(|field| field.ident(self.tcx) == field_ident) {
2437+
err.span_label(field_ident.span, "unknown field");
2438+
return;
24512439
}
2440+
err.span_label(
2441+
field_ident.span,
2442+
"field not available in `impl Future`, but it is available in its `Output`",
2443+
);
2444+
err.span_suggestion_verbose(
2445+
base.span.shrink_to_hi(),
2446+
"consider `await`ing on the `Future` and access the field of its `Output`",
2447+
".await",
2448+
Applicability::MaybeIncorrect,
2449+
);
24522450
}
24532451

24542452
fn ban_nonexisting_field(
@@ -2471,16 +2469,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24712469
ty::RawPtr(..) => {
24722470
self.suggest_first_deref_field(&mut err, expr, base, ident);
24732471
}
2474-
ty::Adt(def, _) if !def.is_enum() => {
2475-
self.suggest_fields_on_recordish(&mut err, expr, def, ident);
2476-
}
24772472
ty::Param(param_ty) => {
2473+
err.span_label(ident.span, "unknown field");
24782474
self.point_at_param_definition(&mut err, param_ty);
24792475
}
24802476
ty::Alias(ty::Opaque, _) => {
24812477
self.suggest_await_on_field_access(&mut err, ident, base, base_ty.peel_refs());
24822478
}
2483-
_ => {}
2479+
_ => {
2480+
err.span_label(ident.span, "unknown field");
2481+
}
24842482
}
24852483

24862484
self.suggest_fn_call(&mut err, base, base_ty, |output_ty| {
@@ -2633,34 +2631,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26332631
err.span_label(param_span, format!("type parameter '{param_name}' declared here"));
26342632
}
26352633

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-
26642634
fn maybe_suggest_array_indexing(
26652635
&self,
26662636
err: &mut Diagnostic,
@@ -2669,6 +2639,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26692639
field: Ident,
26702640
len: ty::Const<'tcx>,
26712641
) {
2642+
err.span_label(field.span, "unknown field");
26722643
if let (Some(len), Ok(user_index)) =
26732644
(len.try_eval_target_usize(self.tcx, self.param_env), field.as_str().parse::<u64>())
26742645
&& let Ok(base) = self.tcx.sess.source_map().span_to_snippet(base.span)
@@ -2691,6 +2662,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26912662
base: &hir::Expr<'_>,
26922663
field: Ident,
26932664
) {
2665+
err.span_label(field.span, "unknown field");
26942666
if let Ok(base) = self.tcx.sess.source_map().span_to_snippet(base.span) {
26952667
let msg = format!("`{base}` is a raw pointer; try dereferencing it");
26962668
let suggestion = format!("(*{base}).{field}");
@@ -2709,18 +2681,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27092681

27102682
let mut err = type_error_struct!(
27112683
self.tcx().sess,
2712-
field.span,
2684+
span,
27132685
expr_t,
27142686
E0609,
27152687
"no field `{field}` on type `{expr_t}`",
27162688
);
27172689

27182690
// try to add a suggestion in case the field is a nested field of a field of the Adt
27192691
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)
2692+
let (ty, unwrap) = if let ty::Adt(def, args) = expr_t.kind()
2693+
&& (self.tcx.is_diagnostic_item(sym::Result, def.did())
2694+
|| self.tcx.is_diagnostic_item(sym::Option, def.did()))
2695+
&& let Some(arg) = args.get(0)
2696+
&& let Some(ty) = arg.as_type()
27222697
{
2723-
let candidate_fields: Vec<_> = fields
2698+
(ty, "unwrap().")
2699+
} else {
2700+
(expr_t, "")
2701+
};
2702+
for (found_fields, args) in
2703+
self.get_field_candidates_considering_privacy(span, ty, mod_id, id)
2704+
{
2705+
let field_names = found_fields.iter().map(|field| field.name).collect::<Vec<_>>();
2706+
let candidate_fields: Vec<_> = found_fields
2707+
.into_iter()
27242708
.filter_map(|candidate_field| {
27252709
self.check_for_nested_field_satisfying(
27262710
span,
@@ -2729,15 +2713,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27292713
args,
27302714
vec![],
27312715
mod_id,
2716+
id,
27322717
)
27332718
})
27342719
.map(|mut field_path| {
27352720
field_path.pop();
27362721
field_path
27372722
.iter()
2738-
.map(|id| id.name.to_ident_string())
2739-
.collect::<Vec<String>>()
2740-
.join(".")
2723+
.map(|id| format!("{}.", id.name.to_ident_string()))
2724+
.collect::<String>()
27412725
})
27422726
.collect::<Vec<_>>();
27432727

@@ -2750,9 +2734,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27502734
if len > 1 { "some" } else { "one" },
27512735
if len > 1 { "have" } else { "has" },
27522736
),
2753-
candidate_fields.iter().map(|path| format!("{path}.")),
2737+
candidate_fields.iter().map(|path| format!("{unwrap}{path}")),
27542738
Applicability::MaybeIncorrect,
27552739
);
2740+
} else {
2741+
if let Some(field_name) = find_best_match_for_name(&field_names, field.name, None) {
2742+
err.span_suggestion_verbose(
2743+
field.span,
2744+
"a field with a similar name exists",
2745+
format!("{unwrap}{}", field_name),
2746+
Applicability::MaybeIncorrect,
2747+
);
2748+
} else if !field_names.is_empty() {
2749+
let is = if field_names.len() == 1 { " is" } else { "s are" };
2750+
err.note(format!(
2751+
"available field{is}: {}",
2752+
self.name_series_display(field_names),
2753+
));
2754+
}
27562755
}
27572756
}
27582757
err
@@ -2781,33 +2780,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
27812780
span: Span,
27822781
base_ty: Ty<'tcx>,
27832782
mod_id: DefId,
2784-
) -> Option<(impl Iterator<Item = &'tcx ty::FieldDef> + 'tcx, GenericArgsRef<'tcx>)> {
2783+
hir_id: hir::HirId,
2784+
) -> Vec<(Vec<&'tcx ty::FieldDef>, GenericArgsRef<'tcx>)> {
27852785
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_ty);
27862786

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

28132818
/// This method is called after we have encountered a missing field error to recursively
@@ -2820,6 +2825,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
28202825
subst: GenericArgsRef<'tcx>,
28212826
mut field_path: Vec<Ident>,
28222827
mod_id: DefId,
2828+
hir_id: HirId,
28232829
) -> Option<Vec<Ident>> {
28242830
debug!(
28252831
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
@@ -2835,20 +2841,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
28352841
let field_ty = candidate_field.ty(self.tcx, subst);
28362842
if matches(candidate_field, field_ty) {
28372843
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);
2844+
} else {
2845+
for (nested_fields, subst) in
2846+
self.get_field_candidates_considering_privacy(span, field_ty, mod_id, hir_id)
2847+
{
2848+
// recursively search fields of `candidate_field` if it's a ty::Adt
2849+
for field in nested_fields {
2850+
if let Some(field_path) = self.check_for_nested_field_satisfying(
2851+
span,
2852+
matches,
2853+
field,
2854+
subst,
2855+
field_path.clone(),
2856+
mod_id,
2857+
hir_id,
2858+
) {
2859+
return Some(field_path);
2860+
}
28522861
}
28532862
}
28542863
}

0 commit comments

Comments
 (0)