Skip to content

Commit a9a389c

Browse files
Rollup merge of #116429 - fmease:clean-up-struct-field-suggs, r=compiler-errors
Diagnostics: Be more careful when suggesting struct fields Consolidate the various places which filter out struct fields that shouldn't be suggested into a single function. Previously, each of those code paths had slightly different and incomplete metrics for no good reason. Now, there's only a single 'complete' metric (namely `is_field_suggestable`) which also filters out hygienic fields that come from different syntax contexts. Fixes #116334.
2 parents cfce3a9 + 867cc41 commit a9a389c

10 files changed

+226
-145
lines changed

compiler/rustc_hir_typeck/src/expr.rs

+24-76
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ use rustc_infer::infer::DefineOpaqueTypes;
4141
use rustc_infer::infer::InferOk;
4242
use rustc_infer::traits::query::NoSolution;
4343
use rustc_infer::traits::ObligationCause;
44-
use rustc_middle::middle::stability;
4544
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
4645
use rustc_middle::ty::error::{
4746
ExpectedFound,
@@ -1585,12 +1584,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15851584
self.check_expr_struct_fields(
15861585
adt_ty,
15871586
expected,
1588-
expr.hir_id,
1587+
expr,
15891588
qpath.span(),
15901589
variant,
15911590
fields,
15921591
base_expr,
1593-
expr.span,
15941592
);
15951593

15961594
self.require_type_is_sized(adt_ty, expr.span, traits::StructInitializerSized);
@@ -1601,12 +1599,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16011599
&self,
16021600
adt_ty: Ty<'tcx>,
16031601
expected: Expectation<'tcx>,
1604-
expr_id: hir::HirId,
1602+
expr: &hir::Expr<'_>,
16051603
span: Span,
16061604
variant: &'tcx ty::VariantDef,
16071605
ast_fields: &'tcx [hir::ExprField<'tcx>],
16081606
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
1609-
expr_span: Span,
16101607
) {
16111608
let tcx = self.tcx;
16121609

@@ -1646,7 +1643,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16461643
// struct-like enums (yet...), but it's definitely not
16471644
// a bug to have constructed one.
16481645
if adt_kind != AdtKind::Enum {
1649-
tcx.check_stability(v_field.did, Some(expr_id), field.span, None);
1646+
tcx.check_stability(v_field.did, Some(expr.hir_id), field.span, None);
16501647
}
16511648

16521649
self.field_ty(field.span, v_field, args)
@@ -1662,10 +1659,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16621659
self.report_unknown_field(
16631660
adt_ty,
16641661
variant,
1662+
expr,
16651663
field,
16661664
ast_fields,
16671665
adt.variant_descr(),
1668-
expr_span,
16691666
)
16701667
};
16711668

@@ -1731,7 +1728,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17311728
.iter()
17321729
.map(|f| {
17331730
let fru_ty = self
1734-
.normalize(expr_span, self.field_ty(base_expr.span, f, fresh_args));
1731+
.normalize(expr.span, self.field_ty(base_expr.span, f, fresh_args));
17351732
let ident = self.tcx.adjust_ident(f.ident(self.tcx), variant.def_id);
17361733
if let Some(_) = remaining_fields.remove(&ident) {
17371734
let target_ty = self.field_ty(base_expr.span, f, args);
@@ -1814,7 +1811,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18141811
ty::Adt(adt, args) if adt.is_struct() => variant
18151812
.fields
18161813
.iter()
1817-
.map(|f| self.normalize(expr_span, f.ty(self.tcx, args)))
1814+
.map(|f| self.normalize(expr.span, f.ty(self.tcx, args)))
18181815
.collect(),
18191816
_ => {
18201817
self.tcx
@@ -1824,13 +1821,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18241821
}
18251822
}
18261823
};
1827-
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr_id, fru_tys);
1824+
self.typeck_results.borrow_mut().fru_field_types_mut().insert(expr.hir_id, fru_tys);
18281825
} else if adt_kind != AdtKind::Union && !remaining_fields.is_empty() {
18291826
debug!(?remaining_fields);
18301827
let private_fields: Vec<&ty::FieldDef> = variant
18311828
.fields
18321829
.iter()
1833-
.filter(|field| !field.vis.is_accessible_from(tcx.parent_module(expr_id), tcx))
1830+
.filter(|field| !field.vis.is_accessible_from(tcx.parent_module(expr.hir_id), tcx))
18341831
.collect();
18351832

18361833
if !private_fields.is_empty() {
@@ -2049,16 +2046,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
20492046
&self,
20502047
ty: Ty<'tcx>,
20512048
variant: &'tcx ty::VariantDef,
2049+
expr: &hir::Expr<'_>,
20522050
field: &hir::ExprField<'_>,
20532051
skip_fields: &[hir::ExprField<'_>],
20542052
kind_name: &str,
2055-
expr_span: Span,
20562053
) -> ErrorGuaranteed {
20572054
if variant.is_recovered() {
20582055
let guar = self
20592056
.tcx
20602057
.sess
2061-
.delay_span_bug(expr_span, "parser recovered but no error was emitted");
2058+
.delay_span_bug(expr.span, "parser recovered but no error was emitted");
20622059
self.set_tainted_by_errors(guar);
20632060
return guar;
20642061
}
@@ -2102,7 +2099,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21022099
);
21032100
err.span_label(field.ident.span, "field does not exist");
21042101
err.span_suggestion_verbose(
2105-
expr_span,
2102+
expr.span,
21062103
format!(
21072104
"`{adt}::{variant}` is a tuple {kind_name}, use the appropriate syntax",
21082105
adt = ty,
@@ -2120,7 +2117,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21202117
err.span_label(variant_ident_span, format!("`{ty}` defined here"));
21212118
err.span_label(field.ident.span, "field does not exist");
21222119
err.span_suggestion_verbose(
2123-
expr_span,
2120+
expr.span,
21242121
format!("`{ty}` is a tuple {kind_name}, use the appropriate syntax",),
21252122
format!("{ty}(/* fields */)"),
21262123
Applicability::HasPlaceholders,
@@ -2129,9 +2126,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21292126
},
21302127
_ => {
21312128
// prevent all specified fields from being suggested
2132-
let skip_fields: Vec<_> = skip_fields.iter().map(|x| x.ident.name).collect();
2129+
let available_field_names = self.available_field_names(variant, expr, skip_fields);
21332130
if let Some(field_name) =
2134-
self.suggest_field_name(variant, field.ident.name, &skip_fields, expr_span)
2131+
find_best_match_for_name(&available_field_names, field.ident.name, None)
21352132
{
21362133
err.span_suggestion(
21372134
field.ident.span,
@@ -2153,10 +2150,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21532150
format!("`{ty}` does not have this field"),
21542151
);
21552152
}
2156-
let mut available_field_names =
2157-
self.available_field_names(variant, expr_span);
2158-
available_field_names
2159-
.retain(|name| skip_fields.iter().all(|skip| name != skip));
21602153
if available_field_names.is_empty() {
21612154
err.note("all struct fields are already assigned");
21622155
} else {
@@ -2174,63 +2167,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
21742167
err.emit()
21752168
}
21762169

2177-
// Return a hint about the closest match in field names
2178-
fn suggest_field_name(
2179-
&self,
2180-
variant: &'tcx ty::VariantDef,
2181-
field: Symbol,
2182-
skip: &[Symbol],
2183-
// The span where stability will be checked
2184-
span: Span,
2185-
) -> Option<Symbol> {
2186-
let names = variant
2187-
.fields
2188-
.iter()
2189-
.filter_map(|field| {
2190-
// ignore already set fields and private fields from non-local crates
2191-
// and unstable fields.
2192-
if skip.iter().any(|&x| x == field.name)
2193-
|| (!variant.def_id.is_local() && !field.vis.is_public())
2194-
|| matches!(
2195-
self.tcx.eval_stability(field.did, None, span, None),
2196-
stability::EvalResult::Deny { .. }
2197-
)
2198-
{
2199-
None
2200-
} else {
2201-
Some(field.name)
2202-
}
2203-
})
2204-
.collect::<Vec<Symbol>>();
2205-
2206-
find_best_match_for_name(&names, field, None)
2207-
}
2208-
22092170
fn available_field_names(
22102171
&self,
22112172
variant: &'tcx ty::VariantDef,
2212-
access_span: Span,
2173+
expr: &hir::Expr<'_>,
2174+
skip_fields: &[hir::ExprField<'_>],
22132175
) -> Vec<Symbol> {
2214-
let body_owner_hir_id = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
22152176
variant
22162177
.fields
22172178
.iter()
22182179
.filter(|field| {
2219-
let def_scope = self
2220-
.tcx
2221-
.adjust_ident_and_get_scope(
2222-
field.ident(self.tcx),
2223-
variant.def_id,
2224-
body_owner_hir_id,
2225-
)
2226-
.1;
2227-
field.vis.is_accessible_from(def_scope, self.tcx)
2228-
&& !matches!(
2229-
self.tcx.eval_stability(field.did, None, access_span, None),
2230-
stability::EvalResult::Deny { .. }
2231-
)
2180+
skip_fields.iter().all(|&skip| skip.ident.name != field.name)
2181+
&& self.is_field_suggestable(field, expr.hir_id, expr.span)
22322182
})
2233-
.filter(|field| !self.tcx.is_doc_hidden(field.did))
22342183
.map(|field| field.name)
22352184
.collect()
22362185
}
@@ -2460,7 +2409,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
24602409
self.suggest_first_deref_field(&mut err, expr, base, ident);
24612410
}
24622411
ty::Adt(def, _) if !def.is_enum() => {
2463-
self.suggest_fields_on_recordish(&mut err, def, ident, expr.span);
2412+
self.suggest_fields_on_recordish(&mut err, expr, def, ident);
24642413
}
24652414
ty::Param(param_ty) => {
24662415
self.point_at_param_definition(&mut err, param_ty);
@@ -2622,12 +2571,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26222571
fn suggest_fields_on_recordish(
26232572
&self,
26242573
err: &mut Diagnostic,
2574+
expr: &hir::Expr<'_>,
26252575
def: ty::AdtDef<'tcx>,
26262576
field: Ident,
2627-
access_span: Span,
26282577
) {
2578+
let available_field_names = self.available_field_names(def.non_enum_variant(), expr, &[]);
26292579
if let Some(suggested_field_name) =
2630-
self.suggest_field_name(def.non_enum_variant(), field.name, &[], access_span)
2580+
find_best_match_for_name(&available_field_names, field.name, None)
26312581
{
26322582
err.span_suggestion(
26332583
field.span,
@@ -2637,12 +2587,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
26372587
);
26382588
} else {
26392589
err.span_label(field.span, "unknown field");
2640-
let struct_variant_def = def.non_enum_variant();
2641-
let field_names = self.available_field_names(struct_variant_def, access_span);
2642-
if !field_names.is_empty() {
2590+
if !available_field_names.is_empty() {
26432591
err.note(format!(
26442592
"available fields are: {}",
2645-
self.name_series_display(field_names),
2593+
self.name_series_display(available_field_names),
26462594
));
26472595
}
26482596
}

compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs

+19
Original file line numberDiff line numberDiff line change
@@ -1685,4 +1685,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16851685
false
16861686
}
16871687
}
1688+
1689+
pub(crate) fn is_field_suggestable(
1690+
&self,
1691+
field: &ty::FieldDef,
1692+
hir_id: HirId,
1693+
span: Span,
1694+
) -> bool {
1695+
// The field must be visible in the containing module.
1696+
field.vis.is_accessible_from(self.tcx.parent_module(hir_id), self.tcx)
1697+
// The field must not be unstable.
1698+
&& !matches!(
1699+
self.tcx.eval_stability(field.did, None, rustc_span::DUMMY_SP, None),
1700+
rustc_middle::middle::stability::EvalResult::Deny { .. }
1701+
)
1702+
// If the field is from an external crate it must not be `doc(hidden)`.
1703+
&& (field.did.is_local() || !self.tcx.is_doc_hidden(field.did))
1704+
// If the field is hygienic it must come from the same syntax context.
1705+
&& self.tcx.def_ident_span(field.did).unwrap().normalize_to_macros_2_0().eq_ctxt(span)
1706+
}
16881707
}

compiler/rustc_hir_typeck/src/pat.rs

+15-26
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ use rustc_hir::pat_util::EnumerateAndAdjustIterator;
1212
use rustc_hir::{HirId, Pat, PatKind};
1313
use rustc_infer::infer;
1414
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
15-
use rustc_middle::middle::stability::EvalResult;
1615
use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeVisitableExt};
1716
use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS;
1817
use rustc_span::edit_distance::find_best_match_for_name;
@@ -1408,6 +1407,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14081407
adt.variant_descr(),
14091408
&inexistent_fields,
14101409
&mut unmentioned_fields,
1410+
pat,
14111411
variant,
14121412
args,
14131413
))
@@ -1434,15 +1434,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
14341434
let accessible_unmentioned_fields: Vec<_> = unmentioned_fields
14351435
.iter()
14361436
.copied()
1437-
.filter(|(field, _)| {
1438-
field.vis.is_accessible_from(tcx.parent_module(pat.hir_id), tcx)
1439-
&& !matches!(
1440-
tcx.eval_stability(field.did, None, DUMMY_SP, None),
1441-
EvalResult::Deny { .. }
1442-
)
1443-
// We only want to report the error if it is hidden and not local
1444-
&& !(tcx.is_doc_hidden(field.did) && !field.did.is_local())
1445-
})
1437+
.filter(|(field, _)| self.is_field_suggestable(field, pat.hir_id, pat.span))
14461438
.collect();
14471439

14481440
if !has_rest_pat {
@@ -1578,12 +1570,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15781570
kind_name: &str,
15791571
inexistent_fields: &[&hir::PatField<'tcx>],
15801572
unmentioned_fields: &mut Vec<(&'tcx ty::FieldDef, Ident)>,
1573+
pat: &'tcx Pat<'tcx>,
15811574
variant: &ty::VariantDef,
15821575
args: &'tcx ty::List<ty::GenericArg<'tcx>>,
15831576
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
15841577
let tcx = self.tcx;
1585-
let (field_names, t, plural) = if inexistent_fields.len() == 1 {
1586-
(format!("a field named `{}`", inexistent_fields[0].ident), "this", "")
1578+
let (field_names, t, plural) = if let [field] = inexistent_fields {
1579+
(format!("a field named `{}`", field.ident), "this", "")
15871580
} else {
15881581
(
15891582
format!(
@@ -1620,10 +1613,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16201613
),
16211614
);
16221615

1623-
if unmentioned_fields.len() == 1 {
1624-
let input =
1625-
unmentioned_fields.iter().map(|(_, field)| field.name).collect::<Vec<_>>();
1626-
let suggested_name = find_best_match_for_name(&input, pat_field.ident.name, None);
1616+
if let [(field_def, field)] = unmentioned_fields.as_slice()
1617+
&& self.is_field_suggestable(field_def, pat.hir_id, pat.span)
1618+
{
1619+
let suggested_name =
1620+
find_best_match_for_name(&[field.name], pat_field.ident.name, None);
16271621
if let Some(suggested_name) = suggested_name {
16281622
err.span_suggestion(
16291623
pat_field.ident.span,
@@ -1646,22 +1640,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16461640
PatKind::Lit(expr)
16471641
if !self.can_coerce(
16481642
self.typeck_results.borrow().expr_ty(expr),
1649-
self.field_ty(
1650-
unmentioned_fields[0].1.span,
1651-
unmentioned_fields[0].0,
1652-
args,
1653-
),
1643+
self.field_ty(field.span, field_def, args),
16541644
) => {}
16551645
_ => {
1656-
let unmentioned_field = unmentioned_fields[0].1.name;
16571646
err.span_suggestion_short(
16581647
pat_field.ident.span,
16591648
format!(
16601649
"`{}` has a field named `{}`",
16611650
tcx.def_path_str(variant.def_id),
1662-
unmentioned_field
1651+
field.name,
16631652
),
1664-
unmentioned_field.to_string(),
1653+
field.name,
16651654
Applicability::MaybeIncorrect,
16661655
);
16671656
}
@@ -1871,8 +1860,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18711860
fields: &'tcx [hir::PatField<'tcx>],
18721861
) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
18731862
let inaccessible = if have_inaccessible_fields { " and inaccessible fields" } else { "" };
1874-
let field_names = if unmentioned_fields.len() == 1 {
1875-
format!("field `{}`{}", unmentioned_fields[0].1, inaccessible)
1863+
let field_names = if let [(_, field)] = unmentioned_fields {
1864+
format!("field `{field}`{inaccessible}")
18761865
} else {
18771866
let fields = unmentioned_fields
18781867
.iter()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#[derive(Default)]
2+
pub struct B {
3+
#[doc(hidden)]
4+
pub hello: i32,
5+
pub bye: i32,
6+
}

0 commit comments

Comments
 (0)