Skip to content

Commit af68e2f

Browse files
committed
Tweak output
- Only point at a the single expression where the found type was first inferred. - Find method call argument that might have caused the found type to be inferred. - Provide structured suggestion. - Apply some review comments. - Tweak wording.
1 parent c5d21c2 commit af68e2f

File tree

7 files changed

+242
-59
lines changed

7 files changed

+242
-59
lines changed

compiler/rustc_hir_typeck/src/demand.rs

+118-47
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::FnCtxt;
22
use rustc_ast::util::parser::PREC_POSTFIX;
3+
use rustc_data_structures::fx::FxHashMap;
34
use rustc_errors::MultiSpan;
45
use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed};
56
use rustc_hir as hir;
@@ -14,12 +15,14 @@ use rustc_middle::ty::adjustment::AllowTwoPhase;
1415
use rustc_middle::ty::error::{ExpectedFound, TypeError};
1516
use rustc_middle::ty::fold::TypeFolder;
1617
use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths};
18+
use rustc_middle::ty::relate::TypeRelation;
1719
use rustc_middle::ty::{
1820
self, Article, AssocItem, Ty, TyCtxt, TypeAndMut, TypeSuperFoldable, TypeVisitable,
1921
};
2022
use rustc_span::symbol::{sym, Symbol};
2123
use rustc_span::{BytePos, Span};
2224
use rustc_trait_selection::infer::InferCtxtExt as _;
25+
use rustc_trait_selection::traits::error_reporting::method_chain::CollectAllMismatches;
2326
use rustc_trait_selection::traits::ObligationCause;
2427

2528
use super::method::probe;
@@ -44,7 +47,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
4447
self.annotate_alternative_method_deref(err, expr, error);
4548

4649
// Use `||` to give these suggestions a precedence
47-
let _ = self.suggest_missing_parentheses(err, expr)
50+
let suggested = self.suggest_missing_parentheses(err, expr)
4851
|| self.suggest_remove_last_method_call(err, expr, expected)
4952
|| self.suggest_associated_const(err, expr, expected)
5053
|| self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr)
@@ -57,8 +60,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5760
|| self.suggest_block_to_brackets_peeling_refs(err, expr, expr_ty, expected)
5861
|| self.suggest_copied_or_cloned(err, expr, expr_ty, expected)
5962
|| self.suggest_into(err, expr, expr_ty, expected)
60-
|| self.suggest_floating_point_literal(err, expr, expected)
61-
|| self.point_inference_types(err, expr);
63+
|| self.suggest_floating_point_literal(err, expr, expected);
64+
if !suggested {
65+
self.point_at_expr_source_of_inferred_type(err, expr, expr_ty, expected);
66+
}
6267
}
6368

6469
pub fn emit_coerce_suggestions(
@@ -210,7 +215,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
210215
(expected, Some(err))
211216
}
212217

213-
fn point_inference_types(&self, err: &mut Diagnostic, expr: &hir::Expr<'_>) -> bool {
218+
fn point_at_expr_source_of_inferred_type(
219+
&self,
220+
err: &mut Diagnostic,
221+
expr: &hir::Expr<'_>,
222+
found: Ty<'tcx>,
223+
expected: Ty<'tcx>,
224+
) -> bool {
214225
let tcx = self.tcx;
215226
let map = self.tcx.hir();
216227

@@ -250,25 +261,17 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
250261
let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind else { return false; };
251262
let [hir::PathSegment { ident, args: None, .. }] = p.segments else { return false; };
252263
let hir::def::Res::Local(hir_id) = p.res else { return false; };
253-
let Some(node) = map.find(hir_id) else { return false; };
254-
let hir::Node::Pat(pat) = node else { return false; };
264+
let Some(hir::Node::Pat(pat)) = map.find(hir_id) else { return false; };
255265
let parent = map.get_parent_node(pat.hir_id);
256266
let Some(hir::Node::Local(hir::Local {
257267
ty: None,
258268
init: Some(init),
259269
..
260270
})) = map.find(parent) else { return false; };
261-
262-
let ty = self.node_ty(init.hir_id);
271+
let Some(ty) = self.node_ty_opt(init.hir_id) else { return false; };
263272
if ty.is_closure() || init.span.overlaps(expr.span) || pat.span.from_expansion() {
264273
return false;
265274
}
266-
let mut span_labels = vec![(
267-
init.span,
268-
with_forced_trimmed_paths!(format!(
269-
"here the type of `{ident}` is inferred to be `{ty}`",
270-
)),
271-
)];
272275

273276
// Locate all the usages of the relevant binding.
274277
struct FindExprs<'hir> {
@@ -296,71 +299,139 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
296299
expr_finder.visit_expr(body.value);
297300
let mut eraser = TypeEraser { tcx };
298301
let mut prev = eraser.fold_ty(ty);
302+
let mut prev_span = None;
299303

300-
for ex in expr_finder.uses {
301-
if ex.span.overlaps(expr.span) { break; }
302-
let parent = map.get_parent_node(ex.hir_id);
304+
for binding in expr_finder.uses {
305+
// In every expression where the binding is referenced, we will look at that
306+
// expression's type and see if it is where the incorrect found type was fully
307+
// "materialized" and point at it. We will also try to provide a suggestion there.
308+
let parent = map.get_parent_node(binding.hir_id);
303309
if let Some(hir::Node::Expr(expr))
304310
| Some(hir::Node::Stmt(hir::Stmt {
305311
kind: hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr),
306312
..
307313
})) = &map.find(parent)
308-
&& let hir::ExprKind::MethodCall(s, rcvr, args, span) = expr.kind
309-
&& rcvr.hir_id == ex.hir_id
314+
&& let hir::ExprKind::MethodCall(s, rcvr, args, _span) = expr.kind
315+
&& rcvr.hir_id == binding.hir_id
316+
&& let Some(def_id) = self.typeck_results.borrow().type_dependent_def_id(expr.hir_id)
310317
{
311-
let ty = if let Ok(m) = self.lookup_method(ty, s, span, expr, rcvr, args) {
312-
// We get the self type from `lookup_method` because the `rcvr` node
313-
// type will not have had any adjustments from the fn arguments.
314-
let ty = m.sig.inputs_and_output[0];
315-
match ty.kind() {
316-
// Remove one layer of references to account for `&mut self` and
317-
// `&self`, so that we can compare it against the binding.
318-
ty::Ref(_, ty, _) => *ty,
319-
_ => ty,
318+
// We special case methods, because they can influence inference through the
319+
// call's arguments and we can provide a more explicit span.
320+
let sig = self.tcx.fn_sig(def_id);
321+
let def_self_ty = sig.input(0).skip_binder();
322+
let rcvr_ty = self.node_ty(rcvr.hir_id);
323+
// Get the evaluated type *after* calling the method call, so that the influence
324+
// of the arguments can be reflected in the receiver type. The receiver
325+
// expression has the type *before* theis analysis is done.
326+
let ty = match self.lookup_probe(s.ident, rcvr_ty, expr, probe::ProbeScope::TraitsInScope) {
327+
Ok(pick) => pick.self_ty,
328+
Err(_) => rcvr_ty,
329+
};
330+
// Remove one layer of references to account for `&mut self` and
331+
// `&self`, so that we can compare it against the binding.
332+
let (ty, def_self_ty) = match (ty.kind(), def_self_ty.kind()) {
333+
(ty::Ref(_, ty, a), ty::Ref(_, self_ty, b)) if a == b => (*ty, *self_ty),
334+
_ => (ty, def_self_ty),
335+
};
336+
let mut param_args = FxHashMap::default();
337+
let mut param_expected = FxHashMap::default();
338+
let mut param_found = FxHashMap::default();
339+
if self.can_eq(self.param_env, ty, found).is_ok() {
340+
// We only point at the first place where the found type was inferred.
341+
for (i, param_ty) in sig.inputs().skip_binder().iter().skip(1).enumerate() {
342+
if def_self_ty.contains(*param_ty) && let ty::Param(_) = param_ty.kind() {
343+
// We found an argument that references a type parameter in `Self`,
344+
// so we assume that this is the argument that caused the found
345+
// type, which we know already because of `can_eq` above was first
346+
// inferred in this method call.
347+
let arg = &args[i];
348+
let arg_ty = self.node_ty(arg.hir_id);
349+
err.span_label(
350+
arg.span,
351+
&format!(
352+
"this is of type `{arg_ty}`, which makes `{ident}` to be \
353+
inferred as `{ty}`",
354+
),
355+
);
356+
param_args.insert(param_ty, (arg, arg_ty));
357+
}
320358
}
321-
} else {
322-
self.node_ty(rcvr.hir_id)
359+
}
360+
361+
// Here we find, for a type param `T`, the type that `T` is in the current
362+
// method call *and* in the original expected type. That way, we can see if we
363+
// can give any structured suggestion for the function argument.
364+
let mut c = CollectAllMismatches {
365+
infcx: &self.infcx,
366+
param_env: self.param_env,
367+
errors: vec![],
323368
};
369+
let _ = c.relate(def_self_ty, ty);
370+
for error in c.errors {
371+
if let TypeError::Sorts(error) = error {
372+
param_found.insert(error.expected, error.found);
373+
}
374+
}
375+
c.errors = vec![];
376+
let _ = c.relate(def_self_ty, expected);
377+
for error in c.errors {
378+
if let TypeError::Sorts(error) = error {
379+
param_expected.insert(error.expected, error.found);
380+
}
381+
}
382+
for (param, (arg,arg_ty)) in param_args.iter() {
383+
let Some(expected) = param_expected.get(param) else { continue; };
384+
let Some(found) = param_found.get(param) else { continue; };
385+
if self.can_eq(self.param_env, *arg_ty, *found).is_err() { continue; }
386+
self.suggest_deref_ref_or_into(err, arg, *expected, *found, None);
387+
}
388+
324389
let ty = eraser.fold_ty(ty);
325390
if ty.references_error() {
326391
break;
327392
}
328-
if ty != prev {
329-
span_labels.push((
393+
if ty != prev
394+
&& param_args.is_empty()
395+
&& self.can_eq(self.param_env, ty, found).is_ok()
396+
{
397+
// We only point at the first place where the found type was inferred.
398+
err.span_label(
330399
s.ident.span,
331400
with_forced_trimmed_paths!(format!(
332401
"here the type of `{ident}` is inferred to be `{ty}`",
333402
)),
334-
));
335-
prev = ty;
403+
);
404+
break;
336405
}
406+
prev = ty;
337407
} else {
338-
let ty = eraser.fold_ty(self.node_ty(ex.hir_id));
408+
let ty = eraser.fold_ty(self.node_ty(binding.hir_id));
339409
if ty.references_error() {
340410
break;
341411
}
342-
if ty != prev {
343-
span_labels.push((
344-
ex.span,
412+
if ty != prev && let Some(span) = prev_span && self.can_eq(self.param_env, ty, found).is_ok() {
413+
// We only point at the first place where the found type was inferred.
414+
// We use the *previous* span because if the type is known *here* it means
415+
// it was *evaluated earlier*. We don't do this for method calls because we
416+
// evaluate the method's self type eagerly, but not in any other case.
417+
err.span_label(
418+
span,
345419
with_forced_trimmed_paths!(format!(
346420
"here the type of `{ident}` is inferred to be `{ty}`",
347421
)),
348-
));
422+
);
423+
break;
349424
}
350425
prev = ty;
351426
}
352-
if ex.hir_id == expr.hir_id {
353-
// Stop showing spans after the error type was emitted.
427+
if binding.hir_id == expr.hir_id {
428+
// Do not look at expressions that come after the expression we were originally
429+
// evaluating and had a type error.
354430
break;
355431
}
432+
prev_span = Some(binding.span);
356433
}
357434
}
358-
if span_labels.len() < 2 {
359-
return false;
360-
}
361-
for (sp, label) in span_labels {
362-
err.span_label(sp, &label);
363-
}
364435
true
365436
}
366437

src/test/ui/type/type-check/assignment-in-if.stderr

+3-12
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,7 @@ LL | x == 5
6767
error[E0308]: mismatched types
6868
--> $DIR/assignment-in-if.rs:44:18
6969
|
70-
LL | let x = 1;
71-
| - here the type of `x` is inferred to be `{integer}`
72-
...
73-
LL | println!("{}", x);
70+
LL | if y = (Foo { foo: x }) {
7471
| - here the type of `x` is inferred to be `usize`
7572
...
7673
LL | if x == x && x = x && x == x {
@@ -81,10 +78,7 @@ LL | if x == x && x = x && x == x {
8178
error[E0308]: mismatched types
8279
--> $DIR/assignment-in-if.rs:44:22
8380
|
84-
LL | let x = 1;
85-
| - here the type of `x` is inferred to be `{integer}`
86-
...
87-
LL | println!("{}", x);
81+
LL | if y = (Foo { foo: x }) {
8882
| - here the type of `x` is inferred to be `usize`
8983
...
9084
LL | if x == x && x = x && x == x {
@@ -104,10 +98,7 @@ LL | if x == x && x == x && x == x {
10498
error[E0308]: mismatched types
10599
--> $DIR/assignment-in-if.rs:51:28
106100
|
107-
LL | let x = 1;
108-
| - here the type of `x` is inferred to be `{integer}`
109-
...
110-
LL | println!("{}", x);
101+
LL | if y = (Foo { foo: x }) {
111102
| - here the type of `x` is inferred to be `usize`
112103
...
113104
LL | if x == x && x == x && x = x {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
fn bar(_: Vec<i32>) {}
2+
fn baz(_: &Vec<&i32>) {}
3+
fn main() {
4+
let v = vec![&1];
5+
bar(v); //~ ERROR E0308
6+
let v = vec![];
7+
baz(&v);
8+
baz(&v);
9+
bar(v); //~ ERROR E0308
10+
let v = vec![];
11+
baz(&v);
12+
bar(v); //~ ERROR E0308
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/point-at-inference-2.rs:5:9
3+
|
4+
LL | bar(v);
5+
| --- ^ expected `i32`, found `&{integer}`
6+
| |
7+
| arguments to this function are incorrect
8+
|
9+
= note: expected struct `Vec<i32>`
10+
found struct `Vec<&{integer}>`
11+
note: function defined here
12+
--> $DIR/point-at-inference-2.rs:1:4
13+
|
14+
LL | fn bar(_: Vec<i32>) {}
15+
| ^^^ -----------
16+
17+
error[E0308]: mismatched types
18+
--> $DIR/point-at-inference-2.rs:9:9
19+
|
20+
LL | baz(&v);
21+
| - here the type of `v` is inferred to be `Vec<&i32>`
22+
LL | baz(&v);
23+
LL | bar(v);
24+
| --- ^ expected `i32`, found `&i32`
25+
| |
26+
| arguments to this function are incorrect
27+
|
28+
= note: expected struct `Vec<i32>`
29+
found struct `Vec<&i32>`
30+
note: function defined here
31+
--> $DIR/point-at-inference-2.rs:1:4
32+
|
33+
LL | fn bar(_: Vec<i32>) {}
34+
| ^^^ -----------
35+
36+
error[E0308]: mismatched types
37+
--> $DIR/point-at-inference-2.rs:12:9
38+
|
39+
LL | baz(&v);
40+
| - here the type of `v` is inferred to be `Vec<&i32>`
41+
LL | bar(v);
42+
| --- ^ expected `i32`, found `&i32`
43+
| |
44+
| arguments to this function are incorrect
45+
|
46+
= note: expected struct `Vec<i32>`
47+
found struct `Vec<&i32>`
48+
note: function defined here
49+
--> $DIR/point-at-inference-2.rs:1:4
50+
|
51+
LL | fn bar(_: Vec<i32>) {}
52+
| ^^^ -----------
53+
54+
error: aborting due to 3 previous errors
55+
56+
For more information about this error, try `rustc --explain E0308`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-rustfix
2+
fn bar(_: Vec<i32>) {}
3+
fn baz(_: &impl std::any::Any) {}
4+
fn main() {
5+
let v = vec![1, 2, 3, 4, 5];
6+
let mut foo = vec![];
7+
baz(&foo);
8+
for i in &v {
9+
foo.push(*i);
10+
}
11+
baz(&foo);
12+
bar(foo); //~ ERROR E0308
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// run-rustfix
2+
fn bar(_: Vec<i32>) {}
3+
fn baz(_: &impl std::any::Any) {}
4+
fn main() {
5+
let v = vec![1, 2, 3, 4, 5];
6+
let mut foo = vec![];
7+
baz(&foo);
8+
for i in &v {
9+
foo.push(i);
10+
}
11+
baz(&foo);
12+
bar(foo); //~ ERROR E0308
13+
}

0 commit comments

Comments
 (0)