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 2 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
1 change: 1 addition & 0 deletions compiler/rustc_errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,7 @@ pub enum StashKey {
ItemNoType,
UnderscoreForArrayLengths,
EarlySyntaxWarning,
CallIntoMethod,
}

fn default_track_diagnostic(_: &Diagnostic) {}
Expand Down
142 changes: 136 additions & 6 deletions compiler/rustc_hir_analysis/src/check/callee.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use super::method::probe::{IsSuggestion, Mode, ProbeScope};
use super::method::MethodCallee;
use super::{DefIdOrName, Expectation, FnCtxt, TupleArgumentsFlag};
use crate::type_error_struct;

use rustc_errors::{struct_span_err, Applicability, Diagnostic};
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};
use rustc_hir::def_id::DefId;
Expand Down Expand Up @@ -60,6 +62,7 @@ pub fn check_legal_trait_for_method_call(
}
}

#[derive(Debug)]
enum CallStep<'tcx> {
Builtin(Ty<'tcx>),
DeferredClosure(LocalDefId, ty::FnSig<'tcx>),
Expand Down Expand Up @@ -188,6 +191,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
return None;
}

ty::Error(_) => {
return None;
}

_ => {}
}

Expand Down Expand Up @@ -394,6 +401,31 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
ty::FnPtr(sig) => (sig, None),
_ => {
if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &callee_expr.kind
&& let [segment] = path.segments
&& let Some(mut diag) = self
.tcx
.sess
.diagnostic()
.steal_diagnostic(segment.ident.span, StashKey::CallIntoMethod)
{
// 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);

// This is the "default" function signature, used in case of error.
Expand Down Expand Up @@ -441,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 @@ -459,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 @@ -525,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
10 changes: 8 additions & 2 deletions compiler/rustc_resolve/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,15 @@ impl<'a> Resolver<'a> {
}

fn report_with_use_injections(&mut self, krate: &Crate) {
for UseError { mut err, candidates, def_id, instead, suggestion, path } in
for UseError { mut err, candidates, def_id, instead, suggestion, path, is_call } in
self.use_injections.drain(..)
{
let (span, found_use) = if let Some(def_id) = def_id.as_local() {
UsePlacementFinder::check(krate, self.def_id_to_node_id[def_id])
} else {
(None, FoundUse::No)
};

if !candidates.is_empty() {
show_candidates(
&self.session,
Expand All @@ -140,10 +141,15 @@ impl<'a> Resolver<'a> {
IsPattern::No,
path,
);
err.emit();
} else if let Some((span, msg, sugg, appl)) = suggestion {
err.span_suggestion(span, msg, sugg, appl);
err.emit();
} else if let [segment] = path.as_slice() && is_call {
err.stash(segment.ident.span, rustc_errors::StashKey::CallIntoMethod);
} else {
err.emit();
}
err.emit();
}
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_resolve/src/late.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3263,6 +3263,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
instead,
suggestion,
path: path.into(),
is_call: source.is_call(),
});
}

Expand Down Expand Up @@ -3327,6 +3328,7 @@ impl<'a: 'ast, 'b, 'ast> LateResolutionVisitor<'a, 'b, 'ast> {
instead: false,
suggestion: None,
path: path.into(),
is_call: source.is_call(),
});
} else {
err.cancel();
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_resolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,8 @@ struct UseError<'a> {
/// Path `Segment`s at the place of use that failed. Used for accurate suggestion after telling
/// the user to import the item directly.
path: Vec<Segment>,
/// Whether the expected source is a call
is_call: bool,
}

#[derive(Clone, Copy, PartialEq, Debug)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
error[E0423]: expected function, tuple struct or tuple variant, found enum `Option`
--> $DIR/issue-43871-enum-instead-of-variant.rs:19:13
|
LL | let x = Option(1);
| ^^^^^^ help: try to construct one of the enum's variants: `std::option::Option::Some`
|
= help: you might have meant to construct the enum's non-tuple variant

error[E0532]: expected tuple struct or tuple variant, found enum `Option`
--> $DIR/issue-43871-enum-instead-of-variant.rs:21:12
|
Expand All @@ -27,6 +19,14 @@ note: the enum is defined here
LL | enum Example { Ex(String), NotEx }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0423]: expected function, tuple struct or tuple variant, found enum `Option`
--> $DIR/issue-43871-enum-instead-of-variant.rs:19:13
|
LL | let x = Option(1);
| ^^^^^^ help: try to construct one of the enum's variants: `std::option::Option::Some`
|
= help: you might have meant to construct the enum's non-tuple variant

error[E0423]: expected function, tuple struct or tuple variant, found enum `Void`
--> $DIR/issue-43871-enum-instead-of-variant.rs:31:13
|
Expand Down
46 changes: 23 additions & 23 deletions src/test/ui/empty/empty-struct-braces-expr.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,6 @@ help: a unit struct with a similar name exists
LL | let e1 = XEmpty2;
| ~~~~~~~

error[E0423]: expected function, tuple struct or tuple variant, found struct `Empty1`
--> $DIR/empty-struct-braces-expr.rs:16:14
|
LL | struct Empty1 {}
| ---------------- `Empty1` defined here
...
LL | let e1 = Empty1();
| ^^^^^^^^
|
::: $DIR/auxiliary/empty-struct.rs:2:1
|
LL | pub struct XEmpty2;
| ------------------ similarly named unit struct `XEmpty2` defined here
|
help: use struct literal syntax instead
|
LL | let e1 = Empty1 {};
| ~~~~~~~~~
help: a unit struct with a similar name exists
|
LL | let e1 = XEmpty2();
| ~~~~~~~

error[E0423]: expected value, found struct variant `E::Empty3`
--> $DIR/empty-struct-braces-expr.rs:18:14
|
Expand Down Expand Up @@ -84,6 +61,29 @@ help: a unit struct with a similar name exists
LL | let xe1 = XEmpty2;
| ~~~~~~~

error[E0423]: expected function, tuple struct or tuple variant, found struct `Empty1`
--> $DIR/empty-struct-braces-expr.rs:16:14
|
LL | struct Empty1 {}
| ---------------- `Empty1` defined here
...
LL | let e1 = Empty1();
| ^^^^^^^^
|
::: $DIR/auxiliary/empty-struct.rs:2:1
|
LL | pub struct XEmpty2;
| ------------------ similarly named unit struct `XEmpty2` defined here
|
help: use struct literal syntax instead
|
LL | let e1 = Empty1 {};
| ~~~~~~~~~
help: a unit struct with a similar name exists
|
LL | let e1 = XEmpty2();
| ~~~~~~~

error[E0423]: expected function, tuple struct or tuple variant, found struct `XEmpty1`
--> $DIR/empty-struct-braces-expr.rs:23:15
|
Expand Down
22 changes: 11 additions & 11 deletions src/test/ui/error-codes/E0423.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ help: surround the struct literal with parentheses
LL | for _ in (std::ops::Range { start: 0, end: 10 }) {}
| + +

error[E0423]: expected value, found struct `T`
--> $DIR/E0423.rs:14:8
|
LL | if T {} == T {} { println!("Ok"); }
| ^ not a value
|
help: surround the struct literal with parentheses
|
LL | if (T {}) == T {} { println!("Ok"); }
| + +

error[E0423]: expected function, tuple struct or tuple variant, found struct `Foo`
--> $DIR/E0423.rs:4:13
|
Expand All @@ -47,17 +58,6 @@ help: a function with a similar name exists
LL | let f = foo();
| ~~~

error[E0423]: expected value, found struct `T`
--> $DIR/E0423.rs:14:8
|
LL | if T {} == T {} { println!("Ok"); }
| ^ not a value
|
help: surround the struct literal with parentheses
|
LL | if (T {}) == T {} { println!("Ok"); }
| + +

error: aborting due to 5 previous errors

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