Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggest calling method on nested field when struct is missing method #96372

Merged
merged 1 commit into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 16 additions & 20 deletions compiler/rustc_typeck/src/check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2277,14 +2277,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// 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(
if let Some(mut field_path) = self.check_for_nested_field_satisfying(
span,
field,
&|candidate_field, _| candidate_field.ident(self.tcx()) == field,
candidate_field,
substs,
vec![],
self.tcx.parent_module(id).to_def_id(),
) {
// field_path includes `field` that we're looking for, so pop it.
field_path.pop();

let field_path_str = field_path
.iter()
.map(|id| id.name.to_ident_string())
Expand All @@ -2304,7 +2307,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
err
}

fn get_field_candidates(
crate fn get_field_candidates(
&self,
span: Span,
base_t: Ty<'tcx>,
Expand All @@ -2329,49 +2332,42 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

/// This method is called after we have encountered a missing field error to recursively
/// search for the field
fn check_for_nested_field(
crate fn check_for_nested_field_satisfying(
&self,
span: Span,
target_field: Ident,
matches: &impl Fn(&ty::FieldDef, Ty<'tcx>) -> bool,
candidate_field: &ty::FieldDef,
subst: SubstsRef<'tcx>,
mut field_path: Vec<Ident>,
id: DefId,
) -> Option<Vec<Ident>> {
debug!(
"check_for_nested_field(span: {:?}, candidate_field: {:?}, field_path: {:?}",
"check_for_nested_field_satisfying(span: {:?}, candidate_field: {:?}, field_path: {:?}",
span, candidate_field, field_path
);

if candidate_field.ident(self.tcx) == target_field {
Some(field_path)
} else if field_path.len() > 3 {
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(self.tcx).normalize_to_macros_2_0());
let field_ty = candidate_field.ty(self.tcx, subst);
if let Some((nested_fields, subst)) = self.get_field_candidates(span, field_ty) {
for field in nested_fields.iter() {
let accessible = field.vis.is_accessible_from(id, self.tcx);
if accessible {
let ident = field.ident(self.tcx).normalize_to_macros_2_0();
if ident == target_field {
if field.vis.is_accessible_from(id, self.tcx) {
if matches(candidate_field, field_ty) {
return Some(field_path);
}
let field_path = field_path.clone();
if let Some(path) = self.check_for_nested_field(
} else if let Some(field_path) = self.check_for_nested_field_satisfying(
span,
target_field,
matches,
field,
subst,
field_path,
field_path.clone(),
id,
) {
return Some(path);
return Some(field_path);
}
}
}
Expand Down
42 changes: 41 additions & 1 deletion compiler/rustc_typeck/src/check/method/suggest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use rustc_trait_selection::traits::{
use std::cmp::Ordering;
use std::iter;

use super::probe::Mode;
use super::probe::{Mode, ProbeScope};
use super::{CandidateSource, MethodError, NoMatchData};

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
Expand Down Expand Up @@ -1129,6 +1129,46 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
label_span_not_found();
}

if let SelfSource::MethodCall(expr) = source
&& let Some((fields, substs)) = self.get_field_candidates(span, actual)
{
let call_expr =
self.tcx.hir().expect_expr(self.tcx.hir().get_parent_node(expr.hir_id));
for candidate_field in fields.iter() {
if let Some(field_path) = self.check_for_nested_field_satisfying(
span,
&|_, field_ty| {
self.lookup_probe(
span,
item_name,
field_ty,
call_expr,
ProbeScope::AllTraits,
)
.is_ok()
},
candidate_field,
substs,
vec![],
self.tcx.parent_module(expr.hir_id).to_def_id(),
) {
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(
item_name.span.shrink_to_lo(),
"one of the expressions' fields has a method of the same name",
format!("{field_path_str}."),
Applicability::MaybeIncorrect,
);
}
}
}

bound_spans.sort();
bound_spans.dedup();
for (span, msg) in bound_spans.into_iter() {
Expand Down
8 changes: 8 additions & 0 deletions src/test/ui/hrtb/issue-30786.migrate.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ note: the following trait bounds were not satisfied:
|
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
| --------- - ^^^^^^ unsatisfied trait bound introduced here
help: one of the expressions' fields has a method of the same name
|
LL | let filter = map.stream.filterx(|x: &_| true);
| +++++++

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
--> $DIR/issue-30786.rs:140:24
Expand All @@ -39,6 +43,10 @@ note: the following trait bounds were not satisfied:
|
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
| --------- - ^^^^^^ unsatisfied trait bound introduced here
help: one of the expressions' fields has a method of the same name
|
LL | let count = filter.stream.countx();
| +++++++

error: aborting due to 2 previous errors

Expand Down
8 changes: 8 additions & 0 deletions src/test/ui/hrtb/issue-30786.nll.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ note: the following trait bounds were not satisfied:
|
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
| --------- - ^^^^^^ unsatisfied trait bound introduced here
help: one of the expressions' fields has a method of the same name
|
LL | let filter = map.stream.filterx(|x: &_| true);
| +++++++

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
--> $DIR/issue-30786.rs:140:24
Expand All @@ -39,6 +43,10 @@ note: the following trait bounds were not satisfied:
|
LL | impl<T> StreamExt for T where for<'a> &'a mut T: Stream {}
| --------- - ^^^^^^ unsatisfied trait bound introduced here
help: one of the expressions' fields has a method of the same name
|
LL | let count = filter.stream.countx();
| +++++++

error: aborting due to 2 previous errors

Expand Down
23 changes: 23 additions & 0 deletions src/test/ui/suggestions/field-has-method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
struct Kind;

struct Ty {
kind: Kind,
}

impl Ty {
fn kind(&self) -> Kind {
todo!()
}
}

struct InferOk<T> {
value: T,
predicates: Vec<()>,
}

fn foo(i: InferOk<Ty>) {
let k = i.kind();
//~^ no method named `kind` found for struct `InferOk` in the current scope
}

fn main() {}
17 changes: 17 additions & 0 deletions src/test/ui/suggestions/field-has-method.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
error[E0599]: no method named `kind` found for struct `InferOk` in the current scope
--> $DIR/field-has-method.rs:19:15
|
LL | struct InferOk<T> {
| ----------------- method `kind` not found for this
...
LL | let k = i.kind();
| ^^^^ method not found in `InferOk<Ty>`
|
help: one of the expressions' fields has a method of the same name
|
LL | let k = i.value.kind();
| ++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0599`.