Skip to content

Commit 61cf3bf

Browse files
Suggest calling method if fn does not exist
1 parent 66c8c5a commit 61cf3bf

File tree

3 files changed

+177
-6
lines changed

3 files changed

+177
-6
lines changed

compiler/rustc_hir_analysis/src/check/callee.rs

+120-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use super::method::probe::{IsSuggestion, Mode, ProbeScope};
12
use super::method::MethodCallee;
23
use super::{DefIdOrName, Expectation, FnCtxt, TupleArgumentsFlag};
34
use crate::type_error_struct;
45

6+
use rustc_ast::util::parser::PREC_POSTFIX;
57
use rustc_errors::{struct_span_err, Applicability, Diagnostic, StashKey};
68
use rustc_hir as hir;
79
use rustc_hir::def::{self, Namespace, Res};
@@ -407,7 +409,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
407409
.diagnostic()
408410
.steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod)
409411
{
410-
diag.emit();
412+
// Try suggesting `foo(a)` -> `a.foo()` if possible.
413+
if let Some(ty) =
414+
self.suggest_call_as_method(
415+
&mut diag,
416+
segment,
417+
arg_exprs,
418+
call_expr,
419+
expected
420+
)
421+
{
422+
diag.emit();
423+
return ty;
424+
} else {
425+
diag.emit();
426+
}
411427
}
412428

413429
self.report_invalid_callee(call_expr, callee_expr, callee_ty, arg_exprs);
@@ -457,6 +473,105 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
457473
fn_sig.output()
458474
}
459475

476+
/// Attempts to reinterpret `method(rcvr, args...)` as `method.rcvr(args...)`
477+
/// and suggesting the fix if the method probe is successful.
478+
fn suggest_call_as_method(
479+
&self,
480+
diag: &mut Diagnostic,
481+
segment: &'tcx hir::PathSegment<'tcx>,
482+
arg_exprs: &'tcx [hir::Expr<'tcx>],
483+
call_expr: &'tcx hir::Expr<'tcx>,
484+
expected: Expectation<'tcx>,
485+
) -> Option<Ty<'tcx>> {
486+
if let [callee_expr, rest @ ..] = arg_exprs {
487+
let callee_ty = self.check_expr(callee_expr);
488+
// First, do a probe with `IsSuggestion(true)` to avoid emitting
489+
// any strange errors. If it's successful, then we'll do a true
490+
// method lookup.
491+
let Ok(pick) = self
492+
.probe_for_name(
493+
call_expr.span,
494+
Mode::MethodCall,
495+
segment.ident,
496+
IsSuggestion(true),
497+
callee_ty,
498+
call_expr.hir_id,
499+
// We didn't record the in scope traits during late resolution
500+
// so we need to probe AllTraits unfortunately
501+
ProbeScope::AllTraits,
502+
) else {
503+
return None;
504+
};
505+
506+
let pick = self.confirm_method(
507+
call_expr.span,
508+
callee_expr,
509+
call_expr,
510+
callee_ty,
511+
pick,
512+
segment,
513+
);
514+
if pick.illegal_sized_bound.is_some() {
515+
return None;
516+
}
517+
518+
let up_to_rcvr_span = segment.ident.span.until(callee_expr.span);
519+
let rest_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
520+
let rest_snippet = if let Some(first) = rest.first() {
521+
self.tcx
522+
.sess
523+
.source_map()
524+
.span_to_snippet(first.span.to(call_expr.span.shrink_to_hi()))
525+
} else {
526+
Ok(")".to_string())
527+
};
528+
529+
if let Ok(rest_snippet) = rest_snippet {
530+
let sugg = if callee_expr.precedence().order() >= PREC_POSTFIX {
531+
vec![
532+
(up_to_rcvr_span, "".to_string()),
533+
(rest_span, format!(".{}({rest_snippet}", segment.ident)),
534+
]
535+
} else {
536+
vec![
537+
(up_to_rcvr_span, "(".to_string()),
538+
(rest_span, format!(").{}({rest_snippet}", segment.ident)),
539+
]
540+
};
541+
let self_ty = self.resolve_vars_if_possible(pick.callee.sig.inputs()[0]);
542+
diag.multipart_suggestion(
543+
format!(
544+
"use the `.` operator to call the method `{}{}` on `{self_ty}`",
545+
self.tcx
546+
.associated_item(pick.callee.def_id)
547+
.trait_container(self.tcx)
548+
.map_or_else(
549+
|| String::new(),
550+
|trait_def_id| self.tcx.def_path_str(trait_def_id) + "::"
551+
),
552+
segment.ident
553+
),
554+
sugg,
555+
Applicability::MaybeIncorrect,
556+
);
557+
558+
// Let's check the method fully now
559+
let return_ty = self.check_method_argument_types(
560+
segment.ident.span,
561+
call_expr,
562+
Ok(pick.callee),
563+
rest,
564+
TupleArgumentsFlag::DontTupleArguments,
565+
expected,
566+
);
567+
568+
return Some(return_ty);
569+
}
570+
}
571+
572+
None
573+
}
574+
460575
fn report_invalid_callee(
461576
&self,
462577
call_expr: &'tcx hir::Expr<'tcx>,
@@ -475,10 +590,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
475590
def::CtorOf::Struct => "struct",
476591
def::CtorOf::Variant => "enum variant",
477592
};
478-
let removal_span =
479-
callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
480-
unit_variant =
481-
Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(qpath)));
593+
let removal_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
594+
unit_variant = Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(qpath)));
482595
}
483596

484597
let callee_ty = self.resolve_vars_if_possible(callee_ty);
@@ -541,7 +654,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
541654
};
542655

543656
if !self.maybe_suggest_bad_array_definition(&mut err, call_expr, callee_expr) {
544-
if let Some((maybe_def, output_ty, _)) = self.extract_callable_info(callee_expr, callee_ty)
657+
if let Some((maybe_def, output_ty, _)) =
658+
self.extract_callable_info(callee_expr, callee_ty)
545659
&& !self.type_is_sized_modulo_regions(self.param_env, output_ty, callee_expr.span)
546660
{
547661
let descr = match maybe_def {
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
struct Foo;
2+
3+
impl Foo {
4+
fn bar(self) {}
5+
}
6+
7+
fn main() {
8+
let x = cmp(&1, &2);
9+
//~^ ERROR cannot find function `cmp` in this scope
10+
//~| HELP use the `.` operator to call the method `Ord::cmp` on `&{integer}`
11+
12+
let y = len([1, 2, 3]);
13+
//~^ ERROR cannot find function `len` in this scope
14+
//~| HELP use the `.` operator to call the method `len` on `&[{integer}]`
15+
16+
let z = bar(Foo);
17+
//~^ ERROR cannot find function `bar` in this scope
18+
//~| HELP use the `.` operator to call the method `bar` on `Foo`
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
error[E0425]: cannot find function `cmp` in this scope
2+
--> $DIR/fn-to-method.rs:8:13
3+
|
4+
LL | let x = cmp(&1, &2);
5+
| ^^^ not found in this scope
6+
|
7+
help: use the `.` operator to call the method `Ord::cmp` on `&{integer}`
8+
|
9+
LL | let x = (&1).cmp(&2);
10+
| ~ ~~~~~~~~~
11+
12+
error[E0425]: cannot find function `len` in this scope
13+
--> $DIR/fn-to-method.rs:12:13
14+
|
15+
LL | let y = len([1, 2, 3]);
16+
| ^^^ not found in this scope
17+
|
18+
help: use the `.` operator to call the method `len` on `&[{integer}]`
19+
|
20+
LL - let y = len([1, 2, 3]);
21+
LL + let y = [1, 2, 3].len();
22+
|
23+
24+
error[E0425]: cannot find function `bar` in this scope
25+
--> $DIR/fn-to-method.rs:16:13
26+
|
27+
LL | let z = bar(Foo);
28+
| ^^^ not found in this scope
29+
|
30+
help: use the `.` operator to call the method `bar` on `Foo`
31+
|
32+
LL - let z = bar(Foo);
33+
LL + let z = Foo.bar();
34+
|
35+
36+
error: aborting due to 3 previous errors
37+
38+
For more information about this error, try `rustc --explain E0425`.

0 commit comments

Comments
 (0)