Skip to content

Commit

Permalink
Auto merge of rust-lang#116089 - estebank:issue-115992-2, r=compiler-…
Browse files Browse the repository at this point in the history
…errors

When suggesting `self.x` for `S { x }`, use `S { x: self.x }`

Fix rust-lang#115992.

r? `@compiler-errors`

Follow up to rust-lang#116086.
  • Loading branch information
bors committed Sep 29, 2023
2 parents a327e75 + d00c7e7 commit c1f86f0
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 75 deletions.
20 changes: 19 additions & 1 deletion compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4140,6 +4140,12 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
});
}

fn resolve_expr_field(&mut self, f: &'ast ExprField, e: &'ast Expr) {
self.resolve_expr(&f.expr, Some(e));
self.visit_ident(f.ident);
walk_list!(self, visit_attribute, f.attrs.iter());
}

fn resolve_expr(&mut self, expr: &'ast Expr, parent: Option<&'ast Expr>) {
// First, record candidate traits for this expression if it could
// result in the invocation of a method call.
Expand All @@ -4155,7 +4161,19 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {

ExprKind::Struct(ref se) => {
self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct);
visit::walk_expr(self, expr);
// This is the same as `visit::walk_expr(self, expr);`, but we want to pass the
// parent in for accurate suggestions when encountering `Foo { bar }` that should
// have been `Foo { bar: self.bar }`.
if let Some(qself) = &se.qself {
self.visit_ty(&qself.ty);
}
self.visit_path(&se.path, expr.id);
walk_list!(self, resolve_expr_field, &se.fields, expr);
match &se.rest {
StructRest::Base(expr) => self.visit_expr(expr),
StructRest::Rest(_span) => {}
StructRest::None => {}
}
}

ExprKind::Break(Some(label), _) | ExprKind::Continue(Some(label)) => {
Expand Down
90 changes: 69 additions & 21 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type Res = def::Res<ast::NodeId>;

/// A field or associated item from self type suggested in case of resolution failure.
enum AssocSuggestion {
Field,
Field(Span),
MethodWithSelf { called: bool },
AssocFn { called: bool },
AssocType,
Expand All @@ -51,7 +51,7 @@ enum AssocSuggestion {
impl AssocSuggestion {
fn action(&self) -> &'static str {
match self {
AssocSuggestion::Field => "use the available field",
AssocSuggestion::Field(_) => "use the available field",
AssocSuggestion::MethodWithSelf { called: true } => {
"call the method with the fully-qualified path"
}
Expand Down Expand Up @@ -215,7 +215,8 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
}
} else {
let mut span_label = None;
let item_span = path.last().unwrap().ident.span;
let item_ident = path.last().unwrap().ident;
let item_span = item_ident.span;
let (mod_prefix, mod_str, module, suggestion) = if path.len() == 1 {
debug!(?self.diagnostic_metadata.current_impl_items);
debug!(?self.diagnostic_metadata.current_function);
Expand All @@ -231,9 +232,35 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
})
{
let sp = item_span.shrink_to_lo();

// Account for `Foo { field }` when suggesting `self.field` so we result on
// `Foo { field: self.field }`.
let field = match source {
PathSource::Expr(Some(Expr { kind: ExprKind::Struct(expr), .. })) => {
expr.fields.iter().find(|f| f.ident == item_ident)
}
_ => None,
};
let pre = if let Some(field) = field && field.is_shorthand {
format!("{item_ident}: ")
} else {
String::new()
};
// Ensure we provide a structured suggestion for an assoc fn only for
// expressions that are actually a fn call.
let is_call = match field {
Some(ast::ExprField { expr, .. }) => {
matches!(expr.kind, ExprKind::Call(..))
}
_ => matches!(
source,
PathSource::Expr(Some(Expr { kind: ExprKind::Call(..), ..})),
),
};

match &item.kind {
AssocItemKind::Fn(fn_)
if !sig.decl.has_self() && fn_.sig.decl.has_self() => {
if (!sig.decl.has_self() || !is_call) && fn_.sig.decl.has_self() => {
// Ensure that we only suggest `self.` if `self` is available,
// you can't call `fn foo(&self)` from `fn bar()` (#115992).
// We also want to mention that the method exists.
Expand All @@ -243,20 +270,28 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
));
None
}
AssocItemKind::Fn(fn_)
if !fn_.sig.decl.has_self() && !is_call => {
span_label = Some((
item.ident.span,
"an associated function by that name is available on `Self` here",
));
None
}
AssocItemKind::Fn(fn_) if fn_.sig.decl.has_self() => Some((
sp,
"consider using the method on `Self`",
"self.".to_string(),
format!("{pre}self."),
)),
AssocItemKind::Fn(_) => Some((
sp,
"consider using the associated function on `Self`",
"Self::".to_string(),
format!("{pre}Self::"),
)),
AssocItemKind::Const(..) => Some((
sp,
"consider using the associated constant on `Self`",
"Self::".to_string(),
format!("{pre}Self::"),
)),
_ => None
}
Expand Down Expand Up @@ -621,17 +656,30 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
self.lookup_assoc_candidate(ident, ns, is_expected, source.is_call())
{
let self_is_available = self.self_value_is_available(path[0].ident.span);
// Account for `Foo { field }` when suggesting `self.field` so we result on
// `Foo { field: self.field }`.
let pre = match source {
PathSource::Expr(Some(Expr { kind: ExprKind::Struct(expr), .. }))
if expr
.fields
.iter()
.any(|f| f.ident == path[0].ident && f.is_shorthand) =>
{
format!("{path_str}: ")
}
_ => String::new(),
};
match candidate {
AssocSuggestion::Field => {
AssocSuggestion::Field(field_span) => {
if self_is_available {
err.span_suggestion(
span,
err.span_suggestion_verbose(
span.shrink_to_lo(),
"you might have meant to use the available field",
format!("self.{path_str}"),
format!("{pre}self."),
Applicability::MachineApplicable,
);
} else {
err.span_label(span, "a field by this name exists in `Self`");
err.span_label(field_span, "a field by that name exists in `Self`");
}
}
AssocSuggestion::MethodWithSelf { called } if self_is_available => {
Expand All @@ -640,21 +688,21 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
} else {
"you might have meant to refer to the method"
};
err.span_suggestion(
span,
err.span_suggestion_verbose(
span.shrink_to_lo(),
msg,
format!("self.{path_str}"),
"self.".to_string(),
Applicability::MachineApplicable,
);
}
AssocSuggestion::MethodWithSelf { .. }
| AssocSuggestion::AssocFn { .. }
| AssocSuggestion::AssocConst
| AssocSuggestion::AssocType => {
err.span_suggestion(
span,
err.span_suggestion_verbose(
span.shrink_to_lo(),
format!("you might have meant to {}", candidate.action()),
format!("Self::{path_str}"),
"Self::".to_string(),
Applicability::MachineApplicable,
);
}
Expand Down Expand Up @@ -1667,11 +1715,11 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> {
resolution.full_res()
{
if let Some(field_ids) = self.r.field_def_ids(did) {
if field_ids
if let Some(field_id) = field_ids
.iter()
.any(|&field_id| ident.name == self.r.tcx.item_name(field_id))
.find(|&&field_id| ident.name == self.r.tcx.item_name(field_id))
{
return Some(AssocSuggestion::Field);
return Some(AssocSuggestion::Field(self.r.def_span(*field_id)));
}
}
}
Expand Down
14 changes: 12 additions & 2 deletions tests/ui/resolve/associated-fn-called-as-fn.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ error[E0425]: cannot find function `collect_primary` in this scope
--> $DIR/associated-fn-called-as-fn.rs:6:30
|
LL | '0'..='9' => collect_primary(&c),
| ^^^^^^^^^^^^^^^ help: you might have meant to call the associated function: `Self::collect_primary`
| ^^^^^^^^^^^^^^^
|
help: you might have meant to call the associated function
|
LL | '0'..='9' => Self::collect_primary(&c),
| ++++++

error[E0425]: cannot find function `collect_primary` in this scope
--> $DIR/associated-fn-called-as-fn.rs:23:30
|
LL | '0'..='9' => collect_primary(&c),
| ^^^^^^^^^^^^^^^ help: you might have meant to call the associated function: `Self::collect_primary`
| ^^^^^^^^^^^^^^^
|
help: you might have meant to call the associated function
|
LL | '0'..='9' => Self::collect_primary(&c),
| ++++++

error: aborting due to 2 previous errors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ impl Foo {
field; //~ ERROR cannot find value `field` in this scope
Foo { field } //~ ERROR cannot find value `field` in this scope
}
fn clone(&self) -> Foo {
Foo { field } //~ ERROR cannot find value `field` in this scope
}
}
fn main() {}
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
error[E0425]: cannot find value `field` in this scope
--> $DIR/field-and-method-in-self-not-available-in-assoc-fn.rs:11:9
|
LL | field: u32,
| ---------- a field by that name exists in `Self`
...
LL | fn field(&self) -> u32 {
| ----- a method by that name is available on `Self` here
...
LL | field;
| ^^^^^ a field by this name exists in `Self`
| ^^^^^

error[E0425]: cannot find value `field` in this scope
--> $DIR/field-and-method-in-self-not-available-in-assoc-fn.rs:12:15
|
LL | field: u32,
| ---------- a field by that name exists in `Self`
...
LL | fn field(&self) -> u32 {
| ----- a method by that name is available on `Self` here
...
LL | Foo { field }
| ^^^^^ a field by this name exists in `Self`
| ^^^^^

error[E0425]: cannot find value `field` in this scope
--> $DIR/field-and-method-in-self-not-available-in-assoc-fn.rs:15:15
|
LL | fn field(&self) -> u32 {
| ----- a method by that name is available on `Self` here
...
LL | Foo { field }
| ^^^^^
|
help: you might have meant to use the available field
|
LL | Foo { field: self.field }
| ++++++++++++

error: aborting due to 2 previous errors
error: aborting due to 3 previous errors

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

0 comments on commit c1f86f0

Please sign in to comment.