Skip to content

Commit

Permalink
Rollup merge of #102694 - compiler-errors:fn-to-method, r=davidtwco
Browse files Browse the repository at this point in the history
Suggest calling method if fn does not exist

I tried to split this up into two commits, the first where we stash the resolution error until typeck (which causes a bunch of diagnostics changes because the ordering of error messages change), then the second commit is the actual logic that actually implements the suggestion.

I am not in love with the presentation of the suggestion, so I could use some advice for how to format the actual messaging.

r? diagnostics

Fixes #102518
  • Loading branch information
matthiaskrgr committed Oct 6, 2022
2 parents 6d8cea6 + ea38370 commit a9b3441
Show file tree
Hide file tree
Showing 37 changed files with 559 additions and 361 deletions.
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 `rcvr.method(args...)`
/// 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

0 comments on commit a9b3441

Please sign in to comment.