Skip to content
Open
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
105 changes: 65 additions & 40 deletions compiler/rustc_hir_typeck/src/demand.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use rustc_errors::{Applicability, Diag, MultiSpan, listify};
use rustc_hir::def::Res;
use rustc_errors::{Applicability, Diag, MultiSpan, listify, pluralize};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, find_attr};
use rustc_infer::infer::DefineOpaqueTypes;
Expand Down Expand Up @@ -28,7 +28,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
if expr_ty == expected {
return;
}
self.annotate_alternative_method_deref(err, expr, error);
self.annotate_alternative_method_deref_for_unop(err, expr, error);
self.explain_self_literal(err, expr, expected, expr_ty);

// Use `||` to give these suggestions a precedence
Expand Down Expand Up @@ -752,11 +752,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
hir::ExprKind::Path(hir::QPath::Resolved(
None,
hir::Path {
res:
hir::def::Res::Def(
hir::def::DefKind::Static { .. } | hir::def::DefKind::Const,
def_id,
),
res: hir::def::Res::Def(DefKind::Static { .. } | DefKind::Const, def_id),
..
},
)) => {
Expand Down Expand Up @@ -929,7 +925,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
false
}

fn annotate_alternative_method_deref(
fn annotate_alternative_method_deref_for_unop(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
Expand All @@ -949,7 +945,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let hir::ExprKind::Unary(hir::UnOp::Deref, deref) = lhs.kind else {
return;
};
let hir::ExprKind::MethodCall(path, base, args, _) = deref.kind else {
self.annotate_alternative_method_deref(err, deref, Some(expected))
}

#[tracing::instrument(skip(self, err), level = "debug")]
pub(crate) fn annotate_alternative_method_deref(
&self,
err: &mut Diag<'_>,
expr: &hir::Expr<'_>,
expected: Option<Ty<'tcx>>,
) {
let hir::ExprKind::MethodCall(path, base, args, _) = expr.kind else {
return;
};
let Some(self_ty) = self.typeck_results.borrow().expr_ty_adjusted_opt(base) else {
Expand All @@ -959,7 +965,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let Ok(pick) = self.lookup_probe_for_diagnostic(
path.ident,
self_ty,
deref,
expr,
probe::ProbeScope::TraitsInScope,
None,
) else {
Expand All @@ -969,10 +975,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let Ok(in_scope_methods) = self.probe_for_name_many(
probe::Mode::MethodCall,
path.ident,
Some(expected),
expected,
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
expr.hir_id,
probe::ProbeScope::TraitsInScope,
) else {
return;
Expand All @@ -984,45 +990,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let Ok(all_methods) = self.probe_for_name_many(
probe::Mode::MethodCall,
path.ident,
Some(expected),
expected,
probe::IsSuggestion(true),
self_ty,
deref.hir_id,
expr.hir_id,
probe::ProbeScope::AllTraits,
) else {
return;
};

let suggestions: Vec<_> = all_methods
.into_iter()
.filter(|c| c.item.def_id != pick.item.def_id)
.map(|c| {
.filter_map(|c| {
if c.item.def_id == pick.item.def_id {
return None;
}
let m = c.item;
let generic_args = ty::GenericArgs::for_item(self.tcx, m.def_id, |param, _| {
self.var_for_def(deref.span, param)
self.var_for_def(expr.span, param)
});
let mutability =
match self.tcx.fn_sig(m.def_id).skip_binder().input(0).skip_binder().kind() {
ty::Ref(_, _, hir::Mutability::Mut) => "&mut ",
ty::Ref(_, _, _) => "&",
_ => "",
};
vec![
(
deref.span.until(base.span),
format!(
"{}({}",
with_no_trimmed_paths!(
self.tcx.def_path_str_with_args(m.def_id, generic_args,)
),
mutability,
),
),
let fn_sig = self.tcx.fn_sig(m.def_id);
if fn_sig.skip_binder().inputs().skip_binder().len() != args.len() + 1 {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be running this logic 3 times: one for "all of the arguments apply", "the number of arguments match" and "only the name matches", in order. If any of the cases happens, break (as we get less and less confident the further away we get from things matching).

return None;
}
let rcvr_ty = fn_sig.skip_binder().input(0).skip_binder();
let (mutability, ty) = match rcvr_ty.kind() {
ty::Ref(_, ty, hir::Mutability::Mut) => ("&mut ", ty),
ty::Ref(_, ty, _) => ("&", ty),
_ => ("", &rcvr_ty),
};
let path = match self.tcx.assoc_parent(m.def_id) {
Some((_, DefKind::Impl { of_trait: true })) => {
// We have `impl Trait for T {}`, suggest `<T as Trait>::method`.
self.tcx.def_path_str_with_args(m.def_id, generic_args).to_string()
}
Some((_, DefKind::Impl { of_trait: false })) => {
if let ty::Adt(def, _) = ty.kind() {
// We have `impl T {}`, suggest `T::method`.
format!("{}::{}", self.tcx.def_path_str(def.did()), path.ident)
} else {
// This should be unreachable, as `impl &'a T {}` is invalid.
format!("{ty}::{}", path.ident)
}
}
// Fallback for arbitrary self types.
_ => with_no_trimmed_paths!(
self.tcx.def_path_str_with_args(m.def_id, generic_args)
)
.to_string(),
};
Some(vec![
(expr.span.until(base.span), format!("{path}({}", mutability)),
match &args {
[] => (base.span.shrink_to_hi().with_hi(deref.span.hi()), ")".to_string()),
[] => (base.span.shrink_to_hi().with_hi(expr.span.hi()), ")".to_string()),
[first, ..] => (base.span.between(first.span), ", ".to_string()),
},
]
])
})
.collect();
if suggestions.is_empty() {
Expand Down Expand Up @@ -1076,9 +1099,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
),
);
if suggestions.len() > other_methods_in_scope.len() {
let n = suggestions.len() - other_methods_in_scope.len();
err.note(format!(
"additionally, there are {} other available methods that aren't in scope",
suggestions.len() - other_methods_in_scope.len()
"additionally, there {are} {n} other available method{s} that {are}n't in scope",
are = pluralize!("is", n),
s = pluralize!(n),
));
}
err.multipart_suggestions(
Expand Down Expand Up @@ -1293,7 +1318,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let hir::def::Res::Def(kind, def_id) = path.res else {
return;
};
let callable_kind = if matches!(kind, hir::def::DefKind::Ctor(_, _)) {
let callable_kind = if matches!(kind, DefKind::Ctor(_, _)) {
CallableKind::Constructor
} else {
CallableKind::Function
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3186,6 +3186,8 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> {
);
return;
}

self.annotate_alternative_method_deref(err, self.call_expr, None);
}

/// A "softer" version of the `demand_compatible`, which checks types without persisting them,
Expand Down
37 changes: 37 additions & 0 deletions tests/ui/methods/shadowed-intrinsic-method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Can't use rustfix because we provide two suggestions:
// to remove the arg for `Borrow::borrow` or to call `Type::borrow`.
use std::borrow::Borrow;

struct A;

impl A { fn borrow(&mut self, _: ()) {} }

struct B;

fn main() {
// The fully-qualified path for items within functions is unnameable from outside that function.
impl B { fn borrow(&mut self, _: ()) {} }

struct C;
// The fully-qualified path for items within functions is unnameable from outside that function.
impl C { fn borrow(&mut self, _: ()) {} }

let mut a = A;
a.borrow(()); //~ ERROR E0061
// A::borrow(&mut a, ());
let mut b = B;
b.borrow(()); //~ ERROR E0061
// This currently suggests `main::<impl B>::borrow`, which is not correct, it should be
// B::borrow(&mut b, ());
let mut c = C;
c.borrow(()); //~ ERROR E0061
// This currently suggests `main::C::borrow`, which is not correct, it should be
// C::borrow(&mut c, ());
}

fn foo() {
let mut b = B;
b.borrow(()); //~ ERROR E0061
// This currently suggests `main::<impl B>::borrow`, which is not correct, it should be
// B::borrow(&mut b, ());
}
111 changes: 111 additions & 0 deletions tests/ui/methods/shadowed-intrinsic-method.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
error[E0061]: this method takes 0 arguments but 1 argument was supplied
--> $DIR/shadowed-intrinsic-method.rs:20:7
|
LL | a.borrow(());
| ^^^^^^ -- unexpected argument of type `()`
|
note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `A`
--> $DIR/shadowed-intrinsic-method.rs:20:7
|
LL | use std::borrow::Borrow;
| ------------------- `std::borrow::Borrow` imported here
...
LL | a.borrow(());
| ^^^^^^ refers to `std::borrow::Borrow::borrow`
note: method defined here
--> $SRC_DIR/core/src/borrow.rs:LL:COL
help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly
|
LL - a.borrow(());
LL + A::borrow(&mut a, ());
|
help: remove the extra argument
|
LL - a.borrow(());
LL + a.borrow();
|

error[E0061]: this method takes 0 arguments but 1 argument was supplied
--> $DIR/shadowed-intrinsic-method.rs:23:7
|
LL | b.borrow(());
| ^^^^^^ -- unexpected argument of type `()`
|
note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `main::<impl B>`
--> $DIR/shadowed-intrinsic-method.rs:23:7
|
LL | use std::borrow::Borrow;
| ------------------- `std::borrow::Borrow` imported here
...
LL | b.borrow(());
| ^^^^^^ refers to `std::borrow::Borrow::borrow`
note: method defined here
--> $SRC_DIR/core/src/borrow.rs:LL:COL
help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly
|
LL - b.borrow(());
LL + B::borrow(&mut b, ());
|
help: remove the extra argument
|
LL - b.borrow(());
LL + b.borrow();
|

error[E0061]: this method takes 0 arguments but 1 argument was supplied
--> $DIR/shadowed-intrinsic-method.rs:27:7
|
LL | c.borrow(());
| ^^^^^^ -- unexpected argument of type `()`
|
note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `main::C`
--> $DIR/shadowed-intrinsic-method.rs:27:7
|
LL | use std::borrow::Borrow;
| ------------------- `std::borrow::Borrow` imported here
...
LL | c.borrow(());
| ^^^^^^ refers to `std::borrow::Borrow::borrow`
note: method defined here
--> $SRC_DIR/core/src/borrow.rs:LL:COL
help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly
|
LL - c.borrow(());
LL + C::borrow(&mut c, ());
|
help: remove the extra argument
|
LL - c.borrow(());
LL + c.borrow();
|

error[E0061]: this method takes 0 arguments but 1 argument was supplied
--> $DIR/shadowed-intrinsic-method.rs:34:7
|
LL | b.borrow(());
| ^^^^^^ -- unexpected argument of type `()`
|
note: the `borrow` call is resolved to the method in `std::borrow::Borrow`, shadowing the method of the same name on the inherent impl for `main::<impl B>`
--> $DIR/shadowed-intrinsic-method.rs:34:7
|
LL | use std::borrow::Borrow;
| ------------------- `std::borrow::Borrow` imported here
...
LL | b.borrow(());
| ^^^^^^ refers to `std::borrow::Borrow::borrow`
note: method defined here
--> $SRC_DIR/core/src/borrow.rs:LL:COL
help: you might have meant to call the other method; you can use the fully-qualified path to call it explicitly
|
LL - b.borrow(());
LL + B::borrow(&mut b, ());
|
help: remove the extra argument
|
LL - b.borrow(());
LL + b.borrow();
|

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0061`.
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
fn main() {
let target: Target = create_target();
target.get(0); // correct arguments work
target.get(10.0); // (used to crash here)
target.unique_name(0); // correct arguments work
target.unique_name(10.0); // (used to crash here)
//~^ ERROR mismatched types
}

Expand All @@ -12,14 +12,14 @@ fn create_target<T>() -> T {

// unimplemented trait, but contains function with the same name
pub trait RandomTrait {
fn get(&mut self); // but less arguments
fn unique_name(&mut self); // but less arguments
}

struct Target;

impl Target {
// correct function with arguments
pub fn get(&self, data: i32) {
pub fn unique_name(&self, data: i32) {
unimplemented!()
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
error[E0308]: mismatched types
--> $DIR/diagnostic-method-lookup-returns-sig-with-fewer-args.rs:4:16
--> $DIR/diagnostic-method-lookup-returns-sig-with-fewer-args.rs:4:24
|
LL | target.get(10.0); // (used to crash here)
| --- ^^^^ expected `i32`, found floating-point number
LL | target.unique_name(10.0); // (used to crash here)
| ----------- ^^^^ expected `i32`, found floating-point number
| |
| arguments to this method are incorrect
|
note: method defined here
--> $DIR/diagnostic-method-lookup-returns-sig-with-fewer-args.rs:22:12
|
LL | pub fn get(&self, data: i32) {
| ^^^ ---------
LL | pub fn unique_name(&self, data: i32) {
| ^^^^^^^^^^^ ---------

error: aborting due to 1 previous error

Expand Down
Loading
Loading