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 if fn does not exist #102694

Merged
merged 3 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
126 changes: 120 additions & 6 deletions compiler/rustc_hir_analysis/src/check/callee.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use super::method::probe::{IsSuggestion, Mode, ProbeScope};
use super::method::MethodCallee;
use super::{DefIdOrName, Expectation, FnCtxt, TupleArgumentsFlag};
use crate::type_error_struct;

use rustc_ast::util::parser::PREC_POSTFIX;
use rustc_errors::{struct_span_err, Applicability, Diagnostic, StashKey};
use rustc_hir as hir;
use rustc_hir::def::{self, Namespace, Res};
Expand Down Expand Up @@ -407,7 +409,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.diagnostic()
.steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod)
{
diag.emit();
// Try suggesting `foo(a)` -> `a.foo()` if possible.
if let Some(ty) =
self.suggest_call_as_method(
&mut diag,
segment,
arg_exprs,
call_expr,
expected
)
{
diag.emit();
return ty;
} else {
diag.emit();
}
}

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

/// Attempts to reinterpret `method(rcvr, args...)` as `method.rcvr(args...)`
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
/// and suggesting the fix if the method probe is successful.
fn suggest_call_as_method(
&self,
diag: &mut Diagnostic,
segment: &'tcx hir::PathSegment<'tcx>,
arg_exprs: &'tcx [hir::Expr<'tcx>],
call_expr: &'tcx hir::Expr<'tcx>,
expected: Expectation<'tcx>,
) -> Option<Ty<'tcx>> {
if let [callee_expr, rest @ ..] = arg_exprs {
let callee_ty = self.check_expr(callee_expr);
// First, do a probe with `IsSuggestion(true)` to avoid emitting
// any strange errors. If it's successful, then we'll do a true
// method lookup.
let Ok(pick) = self
.probe_for_name(
call_expr.span,
Mode::MethodCall,
segment.ident,
IsSuggestion(true),
callee_ty,
call_expr.hir_id,
// We didn't record the in scope traits during late resolution
// so we need to probe AllTraits unfortunately
ProbeScope::AllTraits,
) else {
return None;
};

let pick = self.confirm_method(
call_expr.span,
callee_expr,
call_expr,
callee_ty,
pick,
segment,
);
if pick.illegal_sized_bound.is_some() {
return None;
}

let up_to_rcvr_span = segment.ident.span.until(callee_expr.span);
let rest_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
let rest_snippet = if let Some(first) = rest.first() {
self.tcx
.sess
.source_map()
.span_to_snippet(first.span.to(call_expr.span.shrink_to_hi()))
} else {
Ok(")".to_string())
};

if let Ok(rest_snippet) = rest_snippet {
let sugg = if callee_expr.precedence().order() >= PREC_POSTFIX {
vec![
(up_to_rcvr_span, "".to_string()),
(rest_span, format!(".{}({rest_snippet}", segment.ident)),
]
} else {
vec![
(up_to_rcvr_span, "(".to_string()),
(rest_span, format!(").{}({rest_snippet}", segment.ident)),
]
};
let self_ty = self.resolve_vars_if_possible(pick.callee.sig.inputs()[0]);
diag.multipart_suggestion(
format!(
"use the `.` operator to call the method `{}{}` on `{self_ty}`",
self.tcx
.associated_item(pick.callee.def_id)
.trait_container(self.tcx)
.map_or_else(
|| String::new(),
|trait_def_id| self.tcx.def_path_str(trait_def_id) + "::"
),
segment.ident
),
sugg,
Applicability::MaybeIncorrect,
);

// Let's check the method fully now
let return_ty = self.check_method_argument_types(
segment.ident.span,
call_expr,
Ok(pick.callee),
rest,
TupleArgumentsFlag::DontTupleArguments,
expected,
);

return Some(return_ty);
}
}

None
}

fn report_invalid_callee(
&self,
call_expr: &'tcx hir::Expr<'tcx>,
Expand All @@ -475,10 +590,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
def::CtorOf::Struct => "struct",
def::CtorOf::Variant => "enum variant",
};
let removal_span =
callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
unit_variant =
Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(qpath)));
let removal_span = callee_expr.span.shrink_to_hi().to(call_expr.span.shrink_to_hi());
unit_variant = Some((removal_span, descr, rustc_hir_pretty::qpath_to_string(qpath)));
}

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

if !self.maybe_suggest_bad_array_definition(&mut err, call_expr, callee_expr) {
if let Some((maybe_def, output_ty, _)) = self.extract_callable_info(callee_expr, callee_ty)
if let Some((maybe_def, output_ty, _)) =
self.extract_callable_info(callee_expr, callee_ty)
&& !self.type_is_sized_modulo_regions(self.param_env, output_ty, callee_expr.span)
{
let descr = match maybe_def {
Expand Down
19 changes: 19 additions & 0 deletions src/test/ui/suggestions/fn-to-method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
struct Foo;

impl Foo {
fn bar(self) {}
}

fn main() {
let x = cmp(&1, &2);
//~^ ERROR cannot find function `cmp` in this scope
//~| HELP use the `.` operator to call the method `Ord::cmp` on `&{integer}`

let y = len([1, 2, 3]);
//~^ ERROR cannot find function `len` in this scope
//~| HELP use the `.` operator to call the method `len` on `&[{integer}]`

let z = bar(Foo);
//~^ ERROR cannot find function `bar` in this scope
//~| HELP use the `.` operator to call the method `bar` on `Foo`
}
38 changes: 38 additions & 0 deletions src/test/ui/suggestions/fn-to-method.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
error[E0425]: cannot find function `cmp` in this scope
--> $DIR/fn-to-method.rs:8:13
|
LL | let x = cmp(&1, &2);
| ^^^ not found in this scope
|
help: use the `.` operator to call the method `Ord::cmp` on `&{integer}`
|
LL | let x = (&1).cmp(&2);
| ~ ~~~~~~~~~

error[E0425]: cannot find function `len` in this scope
--> $DIR/fn-to-method.rs:12:13
|
LL | let y = len([1, 2, 3]);
| ^^^ not found in this scope
|
help: use the `.` operator to call the method `len` on `&[{integer}]`
|
LL - let y = len([1, 2, 3]);
LL + let y = [1, 2, 3].len();
|

error[E0425]: cannot find function `bar` in this scope
--> $DIR/fn-to-method.rs:16:13
|
LL | let z = bar(Foo);
| ^^^ not found in this scope
|
help: use the `.` operator to call the method `bar` on `Foo`
|
LL - let z = bar(Foo);
LL + let z = Foo.bar();
|

error: aborting due to 3 previous errors

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