diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 8aa6c6d924a53..f489d6c64ea57 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -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( + 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::>() + .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, 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, + ) -> Option> { + 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( diff --git a/src/test/ui/suggestions/non-existent-field-present-in-subfield-recursion-limit.rs b/src/test/ui/suggestions/non-existent-field-present-in-subfield-recursion-limit.rs new file mode 100644 index 0000000000000..98b408daa022d --- /dev/null +++ b/src/test/ui/suggestions/non-existent-field-present-in-subfield-recursion-limit.rs @@ -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 +} diff --git a/src/test/ui/suggestions/non-existent-field-present-in-subfield-recursion-limit.stderr b/src/test/ui/suggestions/non-existent-field-present-in-subfield-recursion-limit.stderr new file mode 100644 index 0000000000000..b294f4da7db33 --- /dev/null +++ b/src/test/ui/suggestions/non-existent-field-present-in-subfield-recursion-limit.stderr @@ -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`. diff --git a/src/test/ui/suggestions/non-existent-field-present-in-subfield.fixed b/src/test/ui/suggestions/non-existent-field-present-in-subfield.fixed new file mode 100644 index 0000000000000..167548a89defa --- /dev/null +++ b/src/test/ui/suggestions/non-existent-field-present-in-subfield.fixed @@ -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 +} diff --git a/src/test/ui/suggestions/non-existent-field-present-in-subfield.rs b/src/test/ui/suggestions/non-existent-field-present-in-subfield.rs new file mode 100644 index 0000000000000..81cc1af4dff52 --- /dev/null +++ b/src/test/ui/suggestions/non-existent-field-present-in-subfield.rs @@ -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 +} diff --git a/src/test/ui/suggestions/non-existent-field-present-in-subfield.stderr b/src/test/ui/suggestions/non-existent-field-present-in-subfield.stderr new file mode 100644 index 0000000000000..ddb7476ec6e34 --- /dev/null +++ b/src/test/ui/suggestions/non-existent-field-present-in-subfield.stderr @@ -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`.