Skip to content

Commit b6c87c5

Browse files
committed
Implementation for 65853
This attempts to bring better error messages to invalid method calls, by applying some heuristics to identify common mistakes. The algorithm is inspired by Levenshtein distance and longest common sub-sequence. In essence, we treat the types of the function, and the types of the arguments you provided as two "words" and compute the edits to get from one to the other. We then modify that algorithm to detect 4 cases: - A function input is missing - An extra argument was provided - The type of an argument is straight up invalid - Two arguments have been swapped - A subset of the arguments have been shuffled (We detect the last two as separate cases so that we can detect two swaps, instead of 4 parameters permuted.) It helps to understand this argument by paying special attention to terminology: "inputs" refers to the inputs being *expected* by the function, and "arguments" refers to what has been provided at the call site. The basic sketch of the algorithm is as follows: - Construct a boolean grid, with a row for each argument, and a column for each input. The cell [i, j] is true if the i'th argument could satisfy the j'th input. - If we find an argument that could satisfy no inputs, provided for an input that can't be satisfied by any other argument, we consider this an "invalid type". - Extra arguments are those that can't satisfy any input, provided for an input that *could* be satisfied by another argument. - Missing inputs are inputs that can't be satisfied by any argument, where the provided argument could satisfy another input - Swapped / Permuted arguments are identified with a cycle detection algorithm. As each issue is found, we remove the relevant inputs / arguments and check for more issues. If we find no issues, we match up any "valid" arguments, and start again. Note that there's a lot of extra complexity: - We try to stay efficient on the happy path, only computing the diagonal until we find a problem, and then filling in the rest of the matrix. - Closure arguments are wrapped in a tuple and need to be unwrapped - We need to resolve closure types after the rest, to allow the most specific type constraints - We need to handle imported C functions that might be variadic in their inputs. I tried to document a lot of this in comments in the code and keep the naming clear.
1 parent e7575f9 commit b6c87c5

File tree

180 files changed

+10440
-3061
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

180 files changed

+10440
-3061
lines changed

compiler/rustc_infer/src/infer/error_reporting/mod.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
14481448
mut values: Option<ValuePairs<'tcx>>,
14491449
terr: &TypeError<'tcx>,
14501450
swap_secondary_and_primary: bool,
1451+
force_label: bool,
14511452
) {
14521453
let span = cause.span(self.tcx);
14531454
debug!("note_type_err cause={:?} values={:?}, terr={:?}", cause, values, terr);
@@ -1623,7 +1624,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
16231624
TypeError::ObjectUnsafeCoercion(_) => {}
16241625
_ => {
16251626
let mut label_or_note = |span: Span, msg: &str| {
1626-
if &[span] == diag.span.primary_spans() {
1627+
if force_label || &[span] == diag.span.primary_spans() {
16271628
diag.span_label(span, msg);
16281629
} else {
16291630
diag.span_note(span, msg);
@@ -2171,7 +2172,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
21712172
struct_span_err!(self.tcx.sess, span, E0644, "{}", failure_str)
21722173
}
21732174
};
2174-
self.note_type_err(&mut diag, &trace.cause, None, Some(trace.values), terr, false);
2175+
self.note_type_err(&mut diag, &trace.cause, None, Some(trace.values), terr, false, false);
21752176
diag
21762177
}
21772178

@@ -2765,15 +2766,15 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
27652766
}
27662767
}
27672768

2768-
enum FailureCode {
2769+
pub enum FailureCode {
27692770
Error0038(DefId),
27702771
Error0317(&'static str),
27712772
Error0580(&'static str),
27722773
Error0308(&'static str),
27732774
Error0644(&'static str),
27742775
}
27752776

2776-
trait ObligationCauseExt<'tcx> {
2777+
pub trait ObligationCauseExt<'tcx> {
27772778
fn as_failure_code(&self, terr: &TypeError<'tcx>) -> FailureCode;
27782779
fn as_requirement_str(&self) -> &'static str;
27792780
}

compiler/rustc_infer/src/infer/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -385,8 +385,8 @@ impl<'tcx> ValuePairs<'tcx> {
385385
/// See the `error_reporting` module for more details.
386386
#[derive(Clone, Debug)]
387387
pub struct TypeTrace<'tcx> {
388-
cause: ObligationCause<'tcx>,
389-
values: ValuePairs<'tcx>,
388+
pub cause: ObligationCause<'tcx>,
389+
pub values: ValuePairs<'tcx>,
390390
}
391391

392392
/// The origin of a `r1 <= r2` constraint.

compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1654,7 +1654,15 @@ impl<'a, 'tcx> InferCtxtPrivExt<'a, 'tcx> for InferCtxt<'a, 'tcx> {
16541654
}),
16551655
_ => None,
16561656
};
1657-
self.note_type_err(&mut diag, &obligation.cause, secondary_span, values, err, true);
1657+
self.note_type_err(
1658+
&mut diag,
1659+
&obligation.cause,
1660+
secondary_span,
1661+
values,
1662+
err,
1663+
true,
1664+
false,
1665+
);
16581666
self.note_obligation_cause(&mut diag, obligation);
16591667
diag.emit();
16601668
});

compiler/rustc_typeck/src/check/coercion.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> {
15021502
found,
15031503
expected,
15041504
None,
1505-
coercion_error,
1505+
Some(coercion_error),
15061506
);
15071507
}
15081508

compiler/rustc_typeck/src/check/compare_method.rs

+2
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ fn compare_predicate_entailment<'tcx>(
384384
})),
385385
&terr,
386386
false,
387+
false,
387388
);
388389

389390
return Err(diag.emit());
@@ -1074,6 +1075,7 @@ crate fn compare_const_impl<'tcx>(
10741075
})),
10751076
&terr,
10761077
false,
1078+
false,
10771079
);
10781080
diag.emit();
10791081
}

compiler/rustc_typeck/src/check/demand.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
2828
expr_ty: Ty<'tcx>,
2929
expected: Ty<'tcx>,
3030
expected_ty_expr: Option<&'tcx hir::Expr<'tcx>>,
31-
error: TypeError<'tcx>,
31+
error: Option<TypeError<'tcx>>,
3232
) {
3333
self.annotate_expected_due_to_let_ty(err, expr, error);
3434
self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr);
@@ -150,7 +150,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
150150
let expr_ty = self.resolve_vars_with_obligations(checked_ty);
151151
let mut err = self.report_mismatched_types(&cause, expected, expr_ty, e.clone());
152152

153-
self.emit_coerce_suggestions(&mut err, expr, expr_ty, expected, expected_ty_expr, e);
153+
self.emit_coerce_suggestions(&mut err, expr, expr_ty, expected, expected_ty_expr, Some(e));
154154

155155
(expected, Some(err))
156156
}
@@ -159,7 +159,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
159159
&self,
160160
err: &mut Diagnostic,
161161
expr: &hir::Expr<'_>,
162-
error: TypeError<'_>,
162+
error: Option<TypeError<'_>>,
163163
) {
164164
let parent = self.tcx.hir().get_parent_node(expr.hir_id);
165165
match (self.tcx.hir().find(parent), error) {
@@ -173,7 +173,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
173173
Some(hir::Node::Expr(hir::Expr {
174174
kind: hir::ExprKind::Assign(lhs, rhs, _), ..
175175
})),
176-
TypeError::Sorts(ExpectedFound { expected, .. }),
176+
Some(TypeError::Sorts(ExpectedFound { expected, .. })),
177177
) if rhs.hir_id == expr.hir_id && !expected.is_closure() => {
178178
// We ignore closures explicitly because we already point at them elsewhere.
179179
// Point at the assigned-to binding.

compiler/rustc_typeck/src/check/expr.rs

+8-6
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
259259
}
260260

261261
#[instrument(skip(self, expr), level = "debug")]
262-
fn check_expr_kind(
262+
pub(super) fn check_expr_kind(
263263
&self,
264264
expr: &'tcx hir::Expr<'tcx>,
265265
expected: Expectation<'tcx>,
@@ -1367,11 +1367,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13671367
) {
13681368
let tcx = self.tcx;
13691369

1370-
let adt_ty_hint = self
1371-
.expected_inputs_for_expected_output(span, expected, adt_ty, &[adt_ty])
1372-
.get(0)
1373-
.cloned()
1374-
.unwrap_or(adt_ty);
1370+
let expected_inputs =
1371+
self.expected_inputs_for_expected_output(span, expected, adt_ty, &[adt_ty]);
1372+
let adt_ty_hint = if let Some(expected_inputs) = expected_inputs {
1373+
expected_inputs.get(0).cloned().unwrap_or(adt_ty)
1374+
} else {
1375+
adt_ty
1376+
};
13751377
// re-link the regions that EIfEO can erase.
13761378
self.demand_eqtype(span, adt_ty_hint, adt_ty);
13771379

compiler/rustc_typeck/src/check/fn_ctxt/_impl.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -755,9 +755,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
755755
expected_ret: Expectation<'tcx>,
756756
formal_ret: Ty<'tcx>,
757757
formal_args: &[Ty<'tcx>],
758-
) -> Vec<Ty<'tcx>> {
758+
) -> Option<Vec<Ty<'tcx>>> {
759759
let formal_ret = self.resolve_vars_with_obligations(formal_ret);
760-
let Some(ret_ty) = expected_ret.only_has_type(self) else { return Vec::new() };
760+
let Some(ret_ty) = expected_ret.only_has_type(self) else { return None };
761761

762762
// HACK(oli-obk): This is a hack to keep RPIT and TAIT in sync wrt their behaviour.
763763
// Without it, the inference
@@ -779,7 +779,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
779779
if let ty::subst::GenericArgKind::Type(ty) = ty.unpack() {
780780
if let ty::Opaque(def_id, _) = *ty.kind() {
781781
if self.infcx.opaque_type_origin(def_id, DUMMY_SP).is_some() {
782-
return Vec::new();
782+
return None;
783783
}
784784
}
785785
}
@@ -820,7 +820,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
820820

821821
// Record all the argument types, with the substitutions
822822
// produced from the above subtyping unification.
823-
Ok(formal_args.iter().map(|&ty| self.resolve_vars_if_possible(ty)).collect())
823+
Ok(Some(formal_args.iter().map(|&ty| self.resolve_vars_if_possible(ty)).collect()))
824824
})
825825
.unwrap_or_default();
826826
debug!(?formal_args, ?formal_ret, ?expect_args, ?expected_ret);

0 commit comments

Comments
 (0)