Skip to content

Commit bb6265a

Browse files
Rollup merge of #96372 - compiler-errors:field-method-suggest, r=oli-obk
Suggest calling method on nested field when struct is missing method Similar to the suggestion to change `x.field` to `x.nested.field`, implement a similar suggestion for when `x.method()` should be replaced with `x.nested.method()`.
2 parents 52fefb0 + dff7f25 commit bb6265a

File tree

6 files changed

+113
-21
lines changed

6 files changed

+113
-21
lines changed

compiler/rustc_typeck/src/check/expr.rs

+16-20
Original file line numberDiff line numberDiff line change
@@ -2285,14 +2285,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
22852285
// try to add a suggestion in case the field is a nested field of a field of the Adt
22862286
if let Some((fields, substs)) = self.get_field_candidates(span, expr_t) {
22872287
for candidate_field in fields.iter() {
2288-
if let Some(field_path) = self.check_for_nested_field(
2288+
if let Some(mut field_path) = self.check_for_nested_field_satisfying(
22892289
span,
2290-
field,
2290+
&|candidate_field, _| candidate_field.ident(self.tcx()) == field,
22912291
candidate_field,
22922292
substs,
22932293
vec![],
22942294
self.tcx.parent_module(id).to_def_id(),
22952295
) {
2296+
// field_path includes `field` that we're looking for, so pop it.
2297+
field_path.pop();
2298+
22962299
let field_path_str = field_path
22972300
.iter()
22982301
.map(|id| id.name.to_ident_string())
@@ -2312,7 +2315,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
23122315
err
23132316
}
23142317

2315-
fn get_field_candidates(
2318+
crate fn get_field_candidates(
23162319
&self,
23172320
span: Span,
23182321
base_t: Ty<'tcx>,
@@ -2337,49 +2340,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
23372340

23382341
/// This method is called after we have encountered a missing field error to recursively
23392342
/// search for the field
2340-
fn check_for_nested_field(
2343+
crate fn check_for_nested_field_satisfying(
23412344
&self,
23422345
span: Span,
2343-
target_field: Ident,
2346+
matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool,
23442347
candidate_field: &ty::FieldDef,
23452348
subst: SubstsRef<'tcx>,
23462349
mut field_path: Vec<Ident>,
23472350
id: DefId,
23482351
) -> Option<Vec<Ident>> {
23492352
debug!(
2350-
"check_for_nested_field(span: {:?}, candidate_field: {:?}, field_path: {:?}",
2353+
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
23512354
span, candidate_field, field_path
23522355
);
23532356

2354-
if candidate_field.ident(self.tcx) == target_field {
2355-
Some(field_path)
2356-
} else if field_path.len() > 3 {
2357+
if field_path.len() > 3 {
23572358
// For compile-time reasons and to avoid infinite recursion we only check for fields
23582359
// up to a depth of three
23592360
None
23602361
} else {
23612362
// recursively search fields of `candidate_field` if it's a ty::Adt
2362-
23632363
field_path.push(candidate_field.ident(self.tcx).normalize_to_macros_2_0());
23642364
let field_ty = candidate_field.ty(self.tcx, subst);
23652365
if let Some((nested_fields, subst)) = self.get_field_candidates(span, field_ty) {
23662366
for field in nested_fields.iter() {
2367-
let accessible = field.vis.is_accessible_from(id, self.tcx);
2368-
if accessible {
2369-
let ident = field.ident(self.tcx).normalize_to_macros_2_0();
2370-
if ident == target_field {
2367+
if field.vis.is_accessible_from(id, self.tcx) {
2368+
if matches(candidate_field, field_ty) {
23712369
return Some(field_path);
2372-
}
2373-
let field_path = field_path.clone();
2374-
if let Some(path) = self.check_for_nested_field(
2370+
} else if let Some(field_path) = self.check_for_nested_field_satisfying(
23752371
span,
2376-
target_field,
2372+
matches,
23772373
field,
23782374
subst,
2379-
field_path,
2375+
field_path.clone(),
23802376
id,
23812377
) {
2382-
return Some(path);
2378+
return Some(field_path);
23832379
}
23842380
}
23852381
}

compiler/rustc_typeck/src/check/method/suggest.rs

+41-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use rustc_trait_selection::traits::{
2828
use std::cmp::Ordering;
2929
use std::iter;
3030

31-
use super::probe::Mode;
31+
use super::probe::{Mode, ProbeScope};
3232
use super::{CandidateSource, MethodError, NoMatchData};
3333

3434
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
@@ -1129,6 +1129,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11291129
label_span_not_found();
11301130
}
11311131

1132+
if let SelfSource::MethodCall(expr) = source
1133+
&& let Some((fields, substs)) = self.get_field_candidates(span, actual)
1134+
{
1135+
let call_expr =
1136+
self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
1137+
for candidate_field in fields.iter() {
1138+
if let Some(field_path) = self.check_for_nested_field_satisfying(
1139+
span,
1140+
&|_, field_ty| {
1141+
self.lookup_probe(
1142+
span,
1143+
item_name,
1144+
field_ty,
1145+
call_expr,
1146+
ProbeScope::AllTraits,
1147+
)
1148+
.is_ok()
1149+
},
1150+
candidate_field,
1151+
substs,
1152+
vec![],
1153+
self.tcx.parent_module(expr.hir_id).to_def_id(),
1154+
) {
1155+
let field_path_str = field_path
1156+
.iter()
1157+
.map(|id| id.name.to_ident_string())
1158+
.collect::<Vec<String>>()
1159+
.join(".");
1160+
debug!("field_path_str: {:?}", field_path_str);
1161+
1162+
err.span_suggestion_verbose(
1163+
item_name.span.shrink_to_lo(),
1164+
"one of the expressions' fields has a method of the same name",
1165+
format!("{field_path_str}."),
1166+
Applicability::MaybeIncorrect,
1167+
);
1168+
}
1169+
}
1170+
}
1171+
11321172
bound_spans.sort();
11331173
bound_spans.dedup();
11341174
for (span, msg) in bound_spans.into_iter() {

src/test/ui/hrtb/issue-30786.migrate.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ note: the following trait bounds were not satisfied:
1818
|
1919
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
2020
| --------- - ^^^^^^ unsatisfied trait bound introduced here
21+
help: one of the expressions' fields has a method of the same name
22+
|
23+
LL | let filter = map.stream.filterx(|x: &_| true);
24+
| +++++++
2125

2226
error[E0599]: the method `countx` exists for struct `Filter<Map<Repeat, for<'r> fn(&'r u64) -> &'r u64 {identity::<u64>}>, [closure@$DIR/issue-30786.rs:139:30: 139:42]>`, but its trait bounds were not satisfied
2327
--> $DIR/issue-30786.rs:140:24
@@ -39,6 +43,10 @@ note: the following trait bounds were not satisfied:
3943
|
4044
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
4145
| --------- - ^^^^^^ unsatisfied trait bound introduced here
46+
help: one of the expressions' fields has a method of the same name
47+
|
48+
LL | let count = filter.stream.countx();
49+
| +++++++
4250

4351
error: aborting due to 2 previous errors
4452

src/test/ui/hrtb/issue-30786.nll.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ note: the following trait bounds were not satisfied:
1818
|
1919
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
2020
| --------- - ^^^^^^ unsatisfied trait bound introduced here
21+
help: one of the expressions' fields has a method of the same name
22+
|
23+
LL | let filter = map.stream.filterx(|x: &_| true);
24+
| +++++++
2125

2226
error[E0599]: the method `countx` exists for struct `Filter<Map<Repeat, for<'r> fn(&'r u64) -> &'r u64 {identity::<u64>}>, [closure@$DIR/issue-30786.rs:139:30: 139:42]>`, but its trait bounds were not satisfied
2327
--> $DIR/issue-30786.rs:140:24
@@ -39,6 +43,10 @@ note: the following trait bounds were not satisfied:
3943
|
4044
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
4145
| --------- - ^^^^^^ unsatisfied trait bound introduced here
46+
help: one of the expressions' fields has a method of the same name
47+
|
48+
LL | let count = filter.stream.countx();
49+
| +++++++
4250

4351
error: aborting due to 2 previous errors
4452

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
struct Kind;
2+
3+
struct Ty {
4+
kind: Kind,
5+
}
6+
7+
impl Ty {
8+
fn kind(&self) -> Kind {
9+
todo!()
10+
}
11+
}
12+
13+
struct InferOk<T> {
14+
value: T,
15+
predicates: Vec<()>,
16+
}
17+
18+
fn foo(i: InferOk<Ty>) {
19+
let k = i.kind();
20+
//~^ no method named `kind` found for struct `InferOk` in the current scope
21+
}
22+
23+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0599]: no method named `kind` found for struct `InferOk` in the current scope
2+
--> $DIR/field-has-method.rs:19:15
3+
|
4+
LL | struct InferOk<T> {
5+
| ----------------- method `kind` not found for this
6+
...
7+
LL | let k = i.kind();
8+
| ^^^^ method not found in `InferOk<Ty>`
9+
|
10+
help: one of the expressions' fields has a method of the same name
11+
|
12+
LL | let k = i.value.kind();
13+
| ++++++
14+
15+
error: aborting due to previous error
16+
17+
For more information about this error, try `rustc --explain E0599`.

0 commit comments

Comments
 (0)