Skip to content

Commit b6efe90

Browse files
Rollup merge of rust-lang#106400 - estebank:type-errs, r=compiler-errors
Point at expressions where inference refines an unexpected type Fix rust-lang#106355. Fix rust-lang#14007. (!) ``` error[E0308]: mismatched types --> src/test/ui/type/type-check/point-at-inference.rs:12:9 | 9 | foo.push(i); | - this is of type `&{integer}`, which makes `foo` to be inferred as `Vec<&{integer}>` ... 12 | bar(foo); | --- ^^^ expected `i32`, found `&{integer}` | | | arguments to this function are incorrect | = note: expected struct `Vec<i32>` found struct `Vec<&{integer}>` note: function defined here --> src/test/ui/type/type-check/point-at-inference.rs:2:4 | 2 | fn bar(_: Vec<i32>) {} | ^^^ ----------- help: consider dereferencing the borrow | 9 | foo.push(*i); | + ```
2 parents 214b33b + 25cf71e commit b6efe90

13 files changed

+411
-4
lines changed

compiler/rustc_hir_typeck/src/demand.rs

+222-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
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;
67
use rustc_hir::def::CtorKind;
8+
use rustc_hir::intravisit::Visitor;
79
use rustc_hir::lang_items::LangItem;
810
use rustc_hir::{is_range_literal, Node};
911
use rustc_infer::infer::InferOk;
1012
use rustc_middle::lint::in_external_macro;
1113
use rustc_middle::middle::stability::EvalResult;
1214
use rustc_middle::ty::adjustment::AllowTwoPhase;
1315
use rustc_middle::ty::error::{ExpectedFound, TypeError};
14-
use rustc_middle::ty::print::with_no_trimmed_paths;
15-
use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut};
16+
use rustc_middle::ty::fold::{BottomUpFolder, TypeFolder};
17+
use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths};
18+
use rustc_middle::ty::relate::TypeRelation;
19+
use rustc_middle::ty::{self, Article, AssocItem, Ty, TypeAndMut, TypeVisitable};
1620
use rustc_span::symbol::{sym, Symbol};
1721
use rustc_span::{BytePos, Span};
1822
use rustc_trait_selection::infer::InferCtxtExt as _;
23+
use rustc_trait_selection::traits::error_reporting::method_chain::CollectAllMismatches;
1924
use rustc_trait_selection::traits::ObligationCause;
2025

2126
use super::method::probe;
@@ -40,7 +45,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
4045
self.annotate_alternative_method_deref(err, expr, error);
4146

4247
// Use `||` to give these suggestions a precedence
43-
let _ = self.suggest_missing_parentheses(err, expr)
48+
let suggested = self.suggest_missing_parentheses(err, expr)
4449
|| self.suggest_remove_last_method_call(err, expr, expected)
4550
|| self.suggest_associated_const(err, expr, expected)
4651
|| self.suggest_deref_ref_or_into(err, expr, expected, expr_ty, expected_ty_expr)
@@ -54,6 +59,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
5459
|| self.suggest_copied_or_cloned(err, expr, expr_ty, expected)
5560
|| self.suggest_into(err, expr, expr_ty, expected)
5661
|| self.suggest_floating_point_literal(err, expr, expected);
62+
if !suggested {
63+
self.point_at_expr_source_of_inferred_type(err, expr, expr_ty, expected);
64+
}
5765
}
5866

5967
pub fn emit_coerce_suggestions(
@@ -205,6 +213,217 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
205213
(expected, Some(err))
206214
}
207215

216+
pub fn point_at_expr_source_of_inferred_type(
217+
&self,
218+
err: &mut Diagnostic,
219+
expr: &hir::Expr<'_>,
220+
found: Ty<'tcx>,
221+
expected: Ty<'tcx>,
222+
) -> bool {
223+
let map = self.tcx.hir();
224+
225+
let hir::ExprKind::Path(hir::QPath::Resolved(None, p)) = expr.kind else { return false; };
226+
let [hir::PathSegment { ident, args: None, .. }] = p.segments else { return false; };
227+
let hir::def::Res::Local(hir_id) = p.res else { return false; };
228+
let Some(hir::Node::Pat(pat)) = map.find(hir_id) else { return false; };
229+
let parent = map.get_parent_node(pat.hir_id);
230+
let Some(hir::Node::Local(hir::Local {
231+
ty: None,
232+
init: Some(init),
233+
..
234+
})) = map.find(parent) else { return false; };
235+
let Some(ty) = self.node_ty_opt(init.hir_id) else { return false; };
236+
if ty.is_closure() || init.span.overlaps(expr.span) || pat.span.from_expansion() {
237+
return false;
238+
}
239+
240+
// Locate all the usages of the relevant binding.
241+
struct FindExprs<'hir> {
242+
hir_id: hir::HirId,
243+
uses: Vec<&'hir hir::Expr<'hir>>,
244+
}
245+
impl<'v> Visitor<'v> for FindExprs<'v> {
246+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
247+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = ex.kind
248+
&& let hir::def::Res::Local(hir_id) = path.res
249+
&& hir_id == self.hir_id
250+
{
251+
self.uses.push(ex);
252+
}
253+
hir::intravisit::walk_expr(self, ex);
254+
}
255+
}
256+
257+
let mut expr_finder = FindExprs { hir_id, uses: vec![] };
258+
let id = map.get_parent_item(hir_id);
259+
let hir_id: hir::HirId = id.into();
260+
261+
let Some(node) = map.find(hir_id) else { return false; };
262+
let Some(body_id) = node.body_id() else { return false; };
263+
let body = map.body(body_id);
264+
expr_finder.visit_expr(body.value);
265+
// Hack to make equality checks on types with inference variables and regions useful.
266+
let mut eraser = BottomUpFolder {
267+
tcx: self.tcx,
268+
lt_op: |_| self.tcx.lifetimes.re_erased,
269+
ct_op: |c| c,
270+
ty_op: |t| match *t.kind() {
271+
ty::Infer(ty::TyVar(vid)) => self.tcx.mk_ty_infer(ty::TyVar(self.root_var(vid))),
272+
ty::Infer(ty::IntVar(_)) => {
273+
self.tcx.mk_ty_infer(ty::IntVar(ty::IntVid { index: 0 }))
274+
}
275+
ty::Infer(ty::FloatVar(_)) => {
276+
self.tcx.mk_ty_infer(ty::FloatVar(ty::FloatVid { index: 0 }))
277+
}
278+
_ => t,
279+
},
280+
};
281+
let mut prev = eraser.fold_ty(ty);
282+
let mut prev_span = None;
283+
284+
for binding in expr_finder.uses {
285+
// In every expression where the binding is referenced, we will look at that
286+
// expression's type and see if it is where the incorrect found type was fully
287+
// "materialized" and point at it. We will also try to provide a suggestion there.
288+
let parent = map.get_parent_node(binding.hir_id);
289+
if let Some(hir::Node::Expr(expr))
290+
| Some(hir::Node::Stmt(hir::Stmt {
291+
kind: hir::StmtKind::Expr(expr) | hir::StmtKind::Semi(expr),
292+
..
293+
})) = &map.find(parent)
294+
&& let hir::ExprKind::MethodCall(segment, rcvr, args, _span) = expr.kind
295+
&& rcvr.hir_id == binding.hir_id
296+
&& let Some(def_id) = self.typeck_results.borrow().type_dependent_def_id(expr.hir_id)
297+
{
298+
// We special case methods, because they can influence inference through the
299+
// call's arguments and we can provide a more explicit span.
300+
let sig = self.tcx.fn_sig(def_id);
301+
let def_self_ty = sig.input(0).skip_binder();
302+
let rcvr_ty = self.node_ty(rcvr.hir_id);
303+
// Get the evaluated type *after* calling the method call, so that the influence
304+
// of the arguments can be reflected in the receiver type. The receiver
305+
// expression has the type *before* theis analysis is done.
306+
let ty = match self.lookup_probe(
307+
segment.ident,
308+
rcvr_ty,
309+
expr,
310+
probe::ProbeScope::TraitsInScope,
311+
) {
312+
Ok(pick) => pick.self_ty,
313+
Err(_) => rcvr_ty,
314+
};
315+
// Remove one layer of references to account for `&mut self` and
316+
// `&self`, so that we can compare it against the binding.
317+
let (ty, def_self_ty) = match (ty.kind(), def_self_ty.kind()) {
318+
(ty::Ref(_, ty, a), ty::Ref(_, self_ty, b)) if a == b => (*ty, *self_ty),
319+
_ => (ty, def_self_ty),
320+
};
321+
let mut param_args = FxHashMap::default();
322+
let mut param_expected = FxHashMap::default();
323+
let mut param_found = FxHashMap::default();
324+
if self.can_eq(self.param_env, ty, found).is_ok() {
325+
// We only point at the first place where the found type was inferred.
326+
for (i, param_ty) in sig.inputs().skip_binder().iter().skip(1).enumerate() {
327+
if def_self_ty.contains(*param_ty) && let ty::Param(_) = param_ty.kind() {
328+
// We found an argument that references a type parameter in `Self`,
329+
// so we assume that this is the argument that caused the found
330+
// type, which we know already because of `can_eq` above was first
331+
// inferred in this method call.
332+
let arg = &args[i];
333+
let arg_ty = self.node_ty(arg.hir_id);
334+
err.span_label(
335+
arg.span,
336+
&format!(
337+
"this is of type `{arg_ty}`, which causes `{ident}` to be \
338+
inferred as `{ty}`",
339+
),
340+
);
341+
param_args.insert(param_ty, (arg, arg_ty));
342+
}
343+
}
344+
}
345+
346+
// Here we find, for a type param `T`, the type that `T` is in the current
347+
// method call *and* in the original expected type. That way, we can see if we
348+
// can give any structured suggestion for the function argument.
349+
let mut c = CollectAllMismatches {
350+
infcx: &self.infcx,
351+
param_env: self.param_env,
352+
errors: vec![],
353+
};
354+
let _ = c.relate(def_self_ty, ty);
355+
for error in c.errors {
356+
if let TypeError::Sorts(error) = error {
357+
param_found.insert(error.expected, error.found);
358+
}
359+
}
360+
c.errors = vec![];
361+
let _ = c.relate(def_self_ty, expected);
362+
for error in c.errors {
363+
if let TypeError::Sorts(error) = error {
364+
param_expected.insert(error.expected, error.found);
365+
}
366+
}
367+
for (param, (arg, arg_ty)) in param_args.iter() {
368+
let Some(expected) = param_expected.get(param) else { continue; };
369+
let Some(found) = param_found.get(param) else { continue; };
370+
if self.can_eq(self.param_env, *arg_ty, *found).is_err() { continue; }
371+
self.emit_coerce_suggestions(err, arg, *found, *expected, None, None);
372+
}
373+
374+
let ty = eraser.fold_ty(ty);
375+
if ty.references_error() {
376+
break;
377+
}
378+
if ty != prev
379+
&& param_args.is_empty()
380+
&& self.can_eq(self.param_env, ty, found).is_ok()
381+
{
382+
// We only point at the first place where the found type was inferred.
383+
err.span_label(
384+
segment.ident.span,
385+
with_forced_trimmed_paths!(format!(
386+
"here the type of `{ident}` is inferred to be `{ty}`",
387+
)),
388+
);
389+
break;
390+
} else if !param_args.is_empty() {
391+
break;
392+
}
393+
prev = ty;
394+
} else {
395+
let ty = eraser.fold_ty(self.node_ty(binding.hir_id));
396+
if ty.references_error() {
397+
break;
398+
}
399+
if ty != prev
400+
&& let Some(span) = prev_span
401+
&& self.can_eq(self.param_env, ty, found).is_ok()
402+
{
403+
// We only point at the first place where the found type was inferred.
404+
// We use the *previous* span because if the type is known *here* it means
405+
// it was *evaluated earlier*. We don't do this for method calls because we
406+
// evaluate the method's self type eagerly, but not in any other case.
407+
err.span_label(
408+
span,
409+
with_forced_trimmed_paths!(format!(
410+
"here the type of `{ident}` is inferred to be `{ty}`",
411+
)),
412+
);
413+
break;
414+
}
415+
prev = ty;
416+
}
417+
if binding.hir_id == expr.hir_id {
418+
// Do not look at expressions that come after the expression we were originally
419+
// evaluating and had a type error.
420+
break;
421+
}
422+
prev_span = Some(binding.span);
423+
}
424+
true
425+
}
426+
208427
fn annotate_expected_due_to_let_ty(
209428
&self,
210429
err: &mut Diagnostic,

compiler/rustc_hir_typeck/src/expr.rs

+1
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
234234
) => self.check_expr_path(qpath, expr, args),
235235
_ => self.check_expr_kind(expr, expected),
236236
});
237+
let ty = self.resolve_vars_if_possible(ty);
237238

238239
// Warn for non-block expressions with diverging children.
239240
match expr.kind {

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+12
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
801801
full_call_span,
802802
format!("arguments to this {} are incorrect", call_name),
803803
);
804+
if let (Some(callee_ty), hir::ExprKind::MethodCall(_, rcvr, _, _)) =
805+
(callee_ty, &call_expr.kind)
806+
{
807+
// Type that would have accepted this argument if it hadn't been inferred earlier.
808+
// FIXME: We leave an inference variable for now, but it'd be nice to get a more
809+
// specific type to increase the accuracy of the diagnostic.
810+
let expected = self.infcx.next_ty_var(TypeVariableOrigin {
811+
kind: TypeVariableOriginKind::MiscVariable,
812+
span: full_call_span,
813+
});
814+
self.point_at_expr_source_of_inferred_type(&mut err, rcvr, expected, callee_ty);
815+
}
804816
// Call out where the function is defined
805817
self.label_fn_like(
806818
&mut err,

compiler/rustc_infer/src/infer/opaque_types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ impl<'tcx> InferCtxt<'tcx> {
6161
.as_local()
6262
.map_or(false, |def_id| self.opaque_type_origin(def_id, span).is_some())
6363
};
64-
let value = value.fold_with(&mut ty::fold::BottomUpFolder {
64+
let value = value.fold_with(&mut BottomUpFolder {
6565
tcx: self.tcx,
6666
lt_op: |lt| lt,
6767
ct_op: |ct| ct,

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

+9
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ LL | x == 5
6767
error[E0308]: mismatched types
6868
--> $DIR/assignment-in-if.rs:44:18
6969
|
70+
LL | if y = (Foo { foo: x }) {
71+
| - here the type of `x` is inferred to be `usize`
72+
...
7073
LL | if x == x && x = x && x == x {
7174
| ------ ^ expected `bool`, found `usize`
7275
| |
@@ -75,6 +78,9 @@ LL | if x == x && x = x && x == x {
7578
error[E0308]: mismatched types
7679
--> $DIR/assignment-in-if.rs:44:22
7780
|
81+
LL | if y = (Foo { foo: x }) {
82+
| - here the type of `x` is inferred to be `usize`
83+
...
7884
LL | if x == x && x = x && x == x {
7985
| ^ expected `bool`, found `usize`
8086

@@ -92,6 +98,9 @@ LL | if x == x && x == x && x == x {
9298
error[E0308]: mismatched types
9399
--> $DIR/assignment-in-if.rs:51:28
94100
|
101+
LL | if y = (Foo { foo: x }) {
102+
| - here the type of `x` is inferred to be `usize`
103+
...
95104
LL | if x == x && x == x && x = x {
96105
| ---------------- ^ expected `bool`, found `usize`
97106
| |
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+
}

0 commit comments

Comments
 (0)