mirror of
https://github.com/rust-lang/rust.git
synced 2024-10-31 14:31:55 +00:00
add suggestion for nested fields
This commit is contained in:
parent
f2de221b00
commit
9946b54823
@ -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(
|
||||
|
@ -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
|
||||
}
|
@ -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`.
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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`.
|
Loading…
Reference in New Issue
Block a user