Skip to content

Commit 9946b54

Browse files
committed
add suggestion for nested fields
1 parent f2de221 commit 9946b54

6 files changed

+275
-10
lines changed

compiler/rustc_typeck/src/check/expr.rs

+110-10
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use rustc_infer::infer;
3636
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
3737
use rustc_middle::ty;
3838
use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase};
39+
use rustc_middle::ty::subst::SubstsRef;
3940
use rustc_middle::ty::Ty;
4041
use rustc_middle::ty::TypeFoldable;
4142
use rustc_middle::ty::{AdtKind, Visibility};
@@ -46,8 +47,6 @@ use rustc_span::source_map::Span;
4647
use rustc_span::symbol::{kw, sym, Ident, Symbol};
4748
use rustc_trait_selection::traits::{self, ObligationCauseCode};
4849

49-
use std::fmt::Display;
50-
5150
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5251
fn check_expr_eq_type(&self, expr: &'tcx hir::Expr<'tcx>, expected: Ty<'tcx>) {
5352
let ty = self.check_expr_with_hint(expr, expected);
@@ -1585,11 +1584,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15851584
base: &'tcx hir::Expr<'tcx>,
15861585
field: Ident,
15871586
) -> Ty<'tcx> {
1587+
debug!("check_field(expr: {:?}, base: {:?}, field: {:?})", expr, base, field);
15881588
let expr_t = self.check_expr(base);
15891589
let expr_t = self.structurally_resolved_type(base.span, expr_t);
15901590
let mut private_candidate = None;
15911591
let mut autoderef = self.autoderef(expr.span, expr_t);
15921592
while let Some((base_t, _)) = autoderef.next() {
1593+
debug!("base_t: {:?}", base_t);
15931594
match base_t.kind() {
15941595
ty::Adt(base_def, substs) if !base_def.is_enum() => {
15951596
debug!("struct named {:?}", base_t);
@@ -1706,7 +1707,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
17061707
"ban_nonexisting_field: field={:?}, base={:?}, expr={:?}, expr_ty={:?}",
17071708
field, base, expr, expr_t
17081709
);
1709-
let mut err = self.no_such_field_err(field.span, field, expr_t);
1710+
let mut err = self.no_such_field_err(field, expr_t);
17101711

17111712
match *expr_t.peel_refs().kind() {
17121713
ty::Array(_, len) => {
@@ -1880,21 +1881,120 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18801881
}
18811882
}
18821883

1883-
fn no_such_field_err<T: Display>(
1884+
fn no_such_field_err(
18841885
&self,
1885-
span: Span,
1886-
field: T,
1887-
expr_t: &ty::TyS<'_>,
1886+
field: Ident,
1887+
expr_t: &'tcx ty::TyS<'tcx>,
18881888
) -> DiagnosticBuilder<'_> {
1889-
type_error_struct!(
1889+
let span = field.span;
1890+
debug!("no_such_field_err(span: {:?}, field: {:?}, expr_t: {:?})", span, field, expr_t);
1891+
1892+
let mut err = type_error_struct!(
18901893
self.tcx().sess,
1891-
span,
1894+
field.span,
18921895
expr_t,
18931896
E0609,
18941897
"no field `{}` on type `{}`",
18951898
field,
18961899
expr_t
1897-
)
1900+
);
1901+
1902+
// try to add a suggestion in case the field is a nested field of a field of the Adt
1903+
if let Some((fields, substs)) = self.get_field_candidates(span, &expr_t) {
1904+
for candidate_field in fields.iter() {
1905+
if let Some(field_path) =
1906+
self.check_for_nested_field(span, field, candidate_field, substs, vec![])
1907+
{
1908+
let field_path_str = field_path
1909+
.iter()
1910+
.map(|id| id.name.to_ident_string())
1911+
.collect::<Vec<String>>()
1912+
.join(".");
1913+
debug!("field_path_str: {:?}", field_path_str);
1914+
1915+
err.span_suggestion_verbose(
1916+
field.span.shrink_to_lo(),
1917+
"one of the expressions' fields has a field of the same name",
1918+
format!("{}.", field_path_str),
1919+
Applicability::MaybeIncorrect,
1920+
);
1921+
}
1922+
}
1923+
}
1924+
err
1925+
}
1926+
1927+
fn get_field_candidates(
1928+
&self,
1929+
span: Span,
1930+
base_t: Ty<'tcx>,
1931+
) -> Option<(&Vec<ty::FieldDef>, SubstsRef<'tcx>)> {
1932+
debug!("get_field_candidates(span: {:?}, base_t: {:?}", span, base_t);
1933+
1934+
let mut autoderef = self.autoderef(span, base_t);
1935+
while let Some((base_t, _)) = autoderef.next() {
1936+
match base_t.kind() {
1937+
ty::Adt(base_def, substs) if !base_def.is_enum() => {
1938+
let fields = &base_def.non_enum_variant().fields;
1939+
// For compile-time reasons put a limit on number of fields we search
1940+
if fields.len() > 100 {
1941+
return None;
1942+
}
1943+
return Some((fields, substs));
1944+
}
1945+
_ => {}
1946+
}
1947+
}
1948+
None
1949+
}
1950+
1951+
/// This method is called after we have encountered a missing field error to recursively
1952+
/// search for the field
1953+
fn check_for_nested_field(
1954+
&self,
1955+
span: Span,
1956+
target_field: Ident,
1957+
candidate_field: &ty::FieldDef,
1958+
subst: SubstsRef<'tcx>,
1959+
mut field_path: Vec<Ident>,
1960+
) -> Option<Vec<Ident>> {
1961+
debug!(
1962+
"check_for_nested_field(span: {:?}, candidate_field: {:?}, field_path: {:?}",
1963+
span, candidate_field, field_path
1964+
);
1965+
1966+
if candidate_field.ident == target_field {
1967+
Some(field_path)
1968+
} else if field_path.len() > 3 {
1969+
// For compile-time reasons and to avoid infinite recursion we only check for fields
1970+
// up to a depth of three
1971+
None
1972+
} else {
1973+
// recursively search fields of `candidate_field` if it's a ty::Adt
1974+
1975+
field_path.push(candidate_field.ident.normalize_to_macros_2_0());
1976+
let field_ty = candidate_field.ty(self.tcx, subst);
1977+
if let Some((nested_fields, _)) = self.get_field_candidates(span, &field_ty) {
1978+
for field in nested_fields.iter() {
1979+
let ident = field.ident.normalize_to_macros_2_0();
1980+
if ident == target_field {
1981+
return Some(field_path);
1982+
} else {
1983+
let field_path = field_path.clone();
1984+
if let Some(path) = self.check_for_nested_field(
1985+
span,
1986+
target_field,
1987+
field,
1988+
subst,
1989+
field_path,
1990+
) {
1991+
return Some(path);
1992+
}
1993+
}
1994+
}
1995+
}
1996+
None
1997+
}
18981998
}
18991999

19002000
fn check_expr_index(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// In rustc_typeck::check::expr::no_such_field_err we recursively
2+
// look in subfields for the field. This recursive search is limited
3+
// in depth for compile-time reasons and to avoid infinite recursion
4+
// in case of cycles. This file tests that the limit in the recursion
5+
// depth is enforced.
6+
7+
struct Foo {
8+
first: Bar,
9+
second: u32,
10+
third: u32,
11+
}
12+
13+
struct Bar {
14+
bar: C,
15+
}
16+
17+
struct C {
18+
c: D,
19+
}
20+
21+
struct D {
22+
test: E,
23+
}
24+
25+
struct E {
26+
e: F,
27+
}
28+
29+
struct F {
30+
f: u32,
31+
}
32+
33+
fn main() {
34+
let f = F { f: 6 };
35+
let e = E { e: f };
36+
let d = D { test: e };
37+
let c = C { c: d };
38+
let bar = Bar { bar: c };
39+
let fooer = Foo { first: bar, second: 4, third: 5 };
40+
41+
let test = fooer.f;
42+
//~^ ERROR no field
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0609]: no field `f` on type `Foo`
2+
--> $DIR/non-existent-field-present-in-subfield-recursion-limit.rs:41:22
3+
|
4+
LL | let test = fooer.f;
5+
| ^ unknown field
6+
|
7+
= note: available fields are: `first`, `second`, `third`
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0609`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// run-rustfix
2+
3+
struct Foo {
4+
first: Bar,
5+
_second: u32,
6+
_third: u32,
7+
}
8+
9+
struct Bar {
10+
bar: C,
11+
}
12+
13+
struct C {
14+
c: D,
15+
}
16+
17+
struct D {
18+
test: E,
19+
}
20+
21+
struct E {
22+
_e: F,
23+
}
24+
25+
struct F {
26+
_f: u32,
27+
}
28+
29+
fn main() {
30+
let f = F { _f: 6 };
31+
let e = E { _e: f };
32+
let d = D { test: e };
33+
let c = C { c: d };
34+
let bar = Bar { bar: c };
35+
let fooer = Foo { first: bar, _second: 4, _third: 5 };
36+
37+
let _test = &fooer.first.bar.c;
38+
//~^ ERROR no field
39+
40+
let _test2 = fooer.first.bar.c.test;
41+
//~^ ERROR no field
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// run-rustfix
2+
3+
struct Foo {
4+
first: Bar,
5+
_second: u32,
6+
_third: u32,
7+
}
8+
9+
struct Bar {
10+
bar: C,
11+
}
12+
13+
struct C {
14+
c: D,
15+
}
16+
17+
struct D {
18+
test: E,
19+
}
20+
21+
struct E {
22+
_e: F,
23+
}
24+
25+
struct F {
26+
_f: u32,
27+
}
28+
29+
fn main() {
30+
let f = F { _f: 6 };
31+
let e = E { _e: f };
32+
let d = D { test: e };
33+
let c = C { c: d };
34+
let bar = Bar { bar: c };
35+
let fooer = Foo { first: bar, _second: 4, _third: 5 };
36+
37+
let _test = &fooer.c;
38+
//~^ ERROR no field
39+
40+
let _test2 = fooer.test;
41+
//~^ ERROR no field
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error[E0609]: no field `c` on type `Foo`
2+
--> $DIR/non-existent-field-present-in-subfield.rs:37:24
3+
|
4+
LL | let _test = &fooer.c;
5+
| ^ unknown field
6+
|
7+
= note: available fields are: `first`, `_second`, `_third`
8+
help: one of the expressions' fields has a field of the same name
9+
|
10+
LL | let _test = &fooer.first.bar.c;
11+
| ^^^^^^^^^^
12+
13+
error[E0609]: no field `test` on type `Foo`
14+
--> $DIR/non-existent-field-present-in-subfield.rs:40:24
15+
|
16+
LL | let _test2 = fooer.test;
17+
| ^^^^ unknown field
18+
|
19+
= note: available fields are: `first`, `_second`, `_third`
20+
help: one of the expressions' fields has a field of the same name
21+
|
22+
LL | let _test2 = fooer.first.bar.c.test;
23+
| ^^^^^^^^^^^^
24+
25+
error: aborting due to 2 previous errors
26+
27+
For more information about this error, try `rustc --explain E0609`.

0 commit comments

Comments
 (0)