add suggestion for nested fields

This commit is contained in:
b-naber 2021-01-28 18:26:31 +01:00
parent f2de221b00
commit 9946b54823
6 changed files with 275 additions and 10 deletions

View file

@ -36,6 +36,7 @@ use rustc_infer::infer;
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
use rustc_middle::ty;
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::Ty;
use rustc_middle::ty::TypeFoldable;
use rustc_middle::ty::{AdtKind, Visibility};
@ -46,8 +47,6 @@ use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_trait_selection::traits::{self, ObligationCauseCode};
use std::fmt::Display;
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn check_expr_eq_type(&self, expr: &'tcx hir::Expr<'tcx>, expected: Ty<'tcx>) {
let ty = self.check_expr_with_hint(expr, expected);
@ -1585,11 +1584,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
base: &'tcx hir::Expr<'tcx>,
field: Ident,
) -> Ty<'tcx> {
debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
let expr_t = self.check_expr(base);
let expr_t = self.structurally_resolved_type(base.span, expr_t);
let mut private_candidate = None;
let mut autoderef = self.autoderef(expr.span, expr_t);
while let Some((base_t, _)) = autoderef.next() {
debug!("base_t: {:?}", base_t);
match base_t.kind() {
ty::Adt(base_def, substs) if !base_def.is_enum() => {
debug!("struct named {:?}", base_t);
@ -1706,7 +1707,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
"ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}",
field, base, expr, expr_t
);
let mut err = self.no_such_field_err(field.span, field, expr_t);
let mut err = self.no_such_field_err(field, expr_t);
match *expr_t.peel_refs().kind() {
ty::Array(_, len) => {
@ -1880,21 +1881,120 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}
fn no_such_field_err<T: Display>(
fn no_such_field_err(
&self,
span: Span,
field: T,
expr_t: &ty::TyS<'_>,
field: Ident,
expr_t: &'tcx ty::TyS<'tcx>,
) -> DiagnosticBuilder<'_> {
type_error_struct!(
let span = field.span;
debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, expr_t);
let mut err = type_error_struct!(
self.tcx().sess,
span,
field.span,
expr_t,
E0609,
"no field `{}` on type `{}`",
field,
expr_t
)
);
// try to add a suggestion in case the field is a nested field of a field of the Adt
if let Some((fields, substs)) = self.get_field_candidates(span, &expr_t) {
for candidate_field in fields.iter() {
if let Some(field_path) =
self.check_for_nested_field(span, field, candidate_field, substs, vec![])
{
let field_path_str = field_path
.iter()
.map(|id| id.name.to_ident_string())
.collect::<Vec<String>>()
.join(".");
debug!("field_path_str: {:?}", field_path_str);
err.span_suggestion_verbose(
field.span.shrink_to_lo(),
"one of the expressions' fields has a field of the same name",
format!("{}.", field_path_str),
Applicability::MaybeIncorrect,
);
}
}
}
err
}
fn get_field_candidates(
&self,
span: Span,
base_t: Ty<'tcx>,
) -> Option<(&Vec<ty::FieldDef>, SubstsRef<'tcx>)> {
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_t);
let mut autoderef = self.autoderef(span, base_t);
while let Some((base_t, _)) = autoderef.next() {
match base_t.kind() {
ty::Adt(base_def, substs) if !base_def.is_enum() => {
let fields = &base_def.non_enum_variant().fields;
// For compile-time reasons put a limit on number of fields we search
if fields.len() > 100 {
return None;
}
return Some((fields, substs));
}
_ => {}
}
}
None
}
/// This method is called after we have encountered a missing field error to recursively
/// search for the field
fn check_for_nested_field(
&self,
span: Span,
target_field: Ident,
candidate_field: &ty::FieldDef,
subst: SubstsRef<'tcx>,
mut field_path: Vec<Ident>,
) -> Option<Vec<Ident>> {
debug!(
"check_for_nested_field(span: {:?}, candidate_field: {:?}, field_path: {:?}",
span, candidate_field, field_path
);
if candidate_field.ident == target_field {
Some(field_path)
} else if field_path.len() > 3 {
// For compile-time reasons and to avoid infinite recursion we only check for fields
// up to a depth of three
None
} else {
// recursively search fields of `candidate_field` if it's a ty::Adt
field_path.push(candidate_field.ident.normalize_to_macros_2_0());
let field_ty = candidate_field.ty(self.tcx, subst);
if let Some((nested_fields, _)) = self.get_field_candidates(span, &field_ty) {
for field in nested_fields.iter() {
let ident = field.ident.normalize_to_macros_2_0();
if ident == target_field {
return Some(field_path);
} else {
let field_path = field_path.clone();
if let Some(path) = self.check_for_nested_field(
span,
target_field,
field,
subst,
field_path,
) {
return Some(path);
}
}
}
}
None
}
}
fn check_expr_index(

View file

@ -0,0 +1,43 @@
// In rustc_typeck::check::expr::no_such_field_err we recursively
// look in subfields for the field. This recursive search is limited
// in depth for compile-time reasons and to avoid infinite recursion
// in case of cycles. This file tests that the limit in the recursion
// depth is enforced.
struct Foo {
first: Bar,
second: u32,
third: u32,
}
struct Bar {
bar: C,
}
struct C {
c: D,
}
struct D {
test: E,
}
struct E {
e: F,
}
struct F {
f: u32,
}
fn main() {
let f = F { f: 6 };
let e = E { e: f };
let d = D { test: e };
let c = C { c: d };
let bar = Bar { bar: c };
let fooer = Foo { first: bar, second: 4, third: 5 };
let test = fooer.f;
//~^ ERROR no field
}

View file

@ -0,0 +1,11 @@
error[E0609]: no field `f` on type `Foo`
--> $DIR/non-existent-field-present-in-subfield-recursion-limit.rs:41:22
|
LL | let test = fooer.f;
| ^ unknown field
|
= note: available fields are: `first`, `second`, `third`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0609`.

View file

@ -0,0 +1,42 @@
// run-rustfix
struct Foo {
first: Bar,
_second: u32,
_third: u32,
}
struct Bar {
bar: C,
}
struct C {
c: D,
}
struct D {
test: E,
}
struct E {
_e: F,
}
struct F {
_f: u32,
}
fn main() {
let f = F { _f: 6 };
let e = E { _e: f };
let d = D { test: e };
let c = C { c: d };
let bar = Bar { bar: c };
let fooer = Foo { first: bar, _second: 4, _third: 5 };
let _test = &fooer.first.bar.c;
//~^ ERROR no field
let _test2 = fooer.first.bar.c.test;
//~^ ERROR no field
}

View file

@ -0,0 +1,42 @@
// run-rustfix
struct Foo {
first: Bar,
_second: u32,
_third: u32,
}
struct Bar {
bar: C,
}
struct C {
c: D,
}
struct D {
test: E,
}
struct E {
_e: F,
}
struct F {
_f: u32,
}
fn main() {
let f = F { _f: 6 };
let e = E { _e: f };
let d = D { test: e };
let c = C { c: d };
let bar = Bar { bar: c };
let fooer = Foo { first: bar, _second: 4, _third: 5 };
let _test = &fooer.c;
//~^ ERROR no field
let _test2 = fooer.test;
//~^ ERROR no field
}

View file

@ -0,0 +1,27 @@
error[E0609]: no field `c` on type `Foo`
--> $DIR/non-existent-field-present-in-subfield.rs:37:24
|
LL | let _test = &fooer.c;
| ^ unknown field
|
= note: available fields are: `first`, `_second`, `_third`
help: one of the expressions' fields has a field of the same name
|
LL | let _test = &fooer.first.bar.c;
| ^^^^^^^^^^
error[E0609]: no field `test` on type `Foo`
--> $DIR/non-existent-field-present-in-subfield.rs:40:24
|
LL | let _test2 = fooer.test;
| ^^^^ unknown field
|
= note: available fields are: `first`, `_second`, `_third`
help: one of the expressions' fields has a field of the same name
|
LL | let _test2 = fooer.first.bar.c.test;
| ^^^^^^^^^^^^
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0609`.