From d70a17a5c8e8634b98efc7d509a573255d68e903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 21 Jul 2026 20:35:57 +0000 Subject: [PATCH 1/3] Add test --- ...suggest-turbofish-parsed-as-comparisons.rs | 23 ++++ ...est-turbofish-parsed-as-comparisons.stderr | 100 ++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs create mode 100644 tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs new file mode 100644 index 0000000000000..131e8c71e32a3 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs @@ -0,0 +1,23 @@ +// Issue #81816. +use std::sync::Arc; +use std::sync::RwLock; +use std::collections::HashMap; + +struct S; +impl S { + fn foo(&self, _: HashMap) {} +} +fn main() { + let _ = Arc::new(RwLock::new(HashMap::default())); + //~^ ERROR expected value, found struct `HashMap` [E0423] + //~| ERROR expected value, found builtin type `i32` [E0423] + //~| ERROR expected value, found builtin type `i64` [E0423] + //~| ERROR cannot find external crate `default` in the crate root [E0425] + //~| ERROR this function takes 1 argument but 2 arguments were supplied [E0061] + let _ = S.foo(HashMap::default()); + //~^ ERROR expected value, found struct `HashMap` [E0423] + //~| ERROR expected value, found builtin type `i32` [E0423] + //~| ERROR expected value, found builtin type `i64` [E0423] + //~| ERROR cannot find external crate `default` in the crate root [E0425] + //~| ERROR this method takes 1 argument but 2 arguments were supplied [E0061] +} diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr new file mode 100644 index 0000000000000..c2561555f7098 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr @@ -0,0 +1,100 @@ +error[E0423]: expected value, found struct `HashMap` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:34 + | +LL | let _ = Arc::new(RwLock::new(HashMap::default())); + | ^^^^^^^ + | + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + ::: $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + | + = note: `HashMap` defined here + +error[E0423]: expected value, found builtin type `i32` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:42 + | +LL | let _ = Arc::new(RwLock::new(HashMap::default())); + | ^^^ not a value + +error[E0423]: expected value, found builtin type `i64` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:47 + | +LL | let _ = Arc::new(RwLock::new(HashMap::default())); + | ^^^ not a value + +error[E0425]: cannot find external crate `default` in the crate root + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:53 + | +LL | let _ = Arc::new(RwLock::new(HashMap::default())); + | ^^^^^^^ not found in the crate root + +error[E0423]: expected value, found struct `HashMap` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:19 + | +LL | let _ = S.foo(HashMap::default()); + | ^^^^^^^ + | + --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + ::: $SRC_DIR/std/src/collections/hash/map.rs:LL:COL + | + = note: `HashMap` defined here + +error[E0423]: expected value, found builtin type `i32` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:27 + | +LL | let _ = S.foo(HashMap::default()); + | ^^^ not a value + +error[E0423]: expected value, found builtin type `i64` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:32 + | +LL | let _ = S.foo(HashMap::default()); + | ^^^ not a value + +error[E0425]: cannot find external crate `default` in the crate root + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:38 + | +LL | let _ = S.foo(HashMap::default()); + | ^^^^^^^ not found in the crate root + +error[E0061]: this function takes 1 argument but 2 arguments were supplied + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:22 + | +LL | let _ = Arc::new(RwLock::new(HashMap::default())); + | ^^^^^^^^^^^ --------------- unexpected argument #2 of type `bool` + | +note: associated function defined here + --> $SRC_DIR/std/src/sync/poison/rwlock.rs:LL:COL +help: remove the extra argument + | +LL - let _ = Arc::new(RwLock::new(HashMap::default())); +LL + let _ = Arc::new(RwLock::new(HashMap $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:15 + | +LL | let _ = S.foo(HashMap::default()); + | ^^^ --------------- unexpected argument #2 of type `bool` + | +note: expected `HashMap`, found `bool` + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:19 + | +LL | let _ = S.foo(HashMap::default()); + | ^^^^^^^^^^^ + = note: expected struct `HashMap` + found type `bool` +note: method defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:8:8 + | +LL | fn foo(&self, _: HashMap) {} + | ^^^ -------------------- +help: remove the extra argument + | +LL - let _ = S.foo(HashMap::default()); +LL + let _ = S.foo(/* HashMap */); + | + +error: aborting due to 10 previous errors + +Some errors have detailed explanations: E0061, E0423, E0425. +For more information about an error, try `rustc --explain E0061`. From 4a35f7724315992c66c48ed40eed146f9300f6b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Tue, 21 Jul 2026 20:47:23 +0000 Subject: [PATCH 2/3] Account for missing turbofish in resolve errors When encountering resolve errors caused by a missing turbofish having parsed as binops of types, emit a single error explaining the mistake clearly and silence redundant resolve errors. ``` error: can't compare two types --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:41 | LL | let _ = Arc::new(RwLock::new(HashMap::default())); | ^ ^ these are parsed as "less than" and "greater than" | help: you likely intended to write type `HashMap` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` | LL | let _ = Arc::new(RwLock::new(HashMap::::default())); | ++ ``` Detect successfully parsed missing turbofish in "incorrect function arguments" error When encountering a call that doesn't have the right number of arguments, see if there are two arguments next to each other that are binops of types. If so, silence the diagnostic as other more targeted diagnostics will have been emitted already. --- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 37 +++ compiler/rustc_resolve/src/late.rs | 8 +- .../rustc_resolve/src/late/diagnostics.rs | 164 ++++++++++ ...suggest-turbofish-parsed-as-comparisons.rs | 51 ++- ...est-turbofish-parsed-as-comparisons.stderr | 303 ++++++++++++++---- 5 files changed, 482 insertions(+), 81 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index f28a99744aa56..88d47cfa9fb58 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -924,6 +924,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let mut err = fn_call_diag_ctxt.initial_final_diagnostic(); fn_call_diag_ctxt.suggest_confusable(&mut err); + fn_call_diag_ctxt.detect_missing_turbofish(&mut err); // As we encounter issues, keep track of what we want to provide for the suggestion. @@ -3227,6 +3228,42 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { self.annotate_alternative_method_deref(err, self.call_expr, None); } + fn detect_missing_turbofish(&self, err: &mut Diag<'_>) { + let mut iter = self.provided_args.iter().peekable(); + while let Some(arg) = iter.next() { + let hir::ExprKind::Binary(lt, lt_left, lt_right) = arg.kind else { + continue; + }; + let hir::BinOpKind::Lt = lt.node else { continue }; + let hir::ExprKind::Path(_) = lt_left.kind else { continue }; + let hir::ExprKind::Path(_) = lt_right.kind else { continue }; + + let Some(mut next) = iter.next() else { break }; + loop { + match next.kind { + hir::ExprKind::Binary(gt, gt_left, gt_right) + if let hir::BinOpKind::Gt = gt.node + && let hir::ExprKind::Path(_) = gt_left.kind + && let hir::ExprKind::Call(..) = gt_right.kind => + { + // We've encountered a likely missing turbofish error, for which we've already + // emitted a more targeted diagnostic during name resolution making this one + // redundant (`LateResolutionVisitor::detect_missing_turbofish`). + err.downgrade_to_delayed_bug(); + } + hir::ExprKind::Path(_) => { + if let Some(n) = iter.next() { + next = n; + continue; + } + } + _ => {} + } + break; + } + } + } + /// A "softer" version of the `demand_compatible`, which checks types without persisting them, /// and treats error types differently /// This will allow us to "probe" for other argument orders that would likely have been correct diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 315ace9c0d4cf..b708fda2b700f 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -5166,6 +5166,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } /// Handles paths that may refer to associated items. + #[tracing::instrument(skip(self, source), level = "debug")] fn resolve_qpath( &mut self, qself: &Option>, @@ -5174,11 +5175,6 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { finalize: Finalize, source: PathSource<'_, 'ast, 'ra>, ) -> Result, Spanned>> { - debug!( - "resolve_qpath(qself={:?}, path={:?}, ns={:?}, finalize={:?})", - qself, path, ns, finalize, - ); - if let Some(qself) = qself { if qself.position == 0 { // This is a case like `::B`, where there is no @@ -5578,6 +5574,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.resolve_expr(arg, None); } self.visit_path_segment(seg); + self.detect_missing_turbofish(&args); } ExprKind::Call(ref callee, ref arguments) => { @@ -5599,6 +5596,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.resolve_expr(argument, None); } } + self.detect_missing_turbofish(&arguments); } ExprKind::FormatArgs(ref args) => { for arg in args.arguments.all_args() { diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 83e5fbef9bcb9..df1155bd3208c 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1768,6 +1768,170 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } } + /// When encountering `Ty < Ty, Ty > call()` in function and method call arguments, suggest + /// using the turbofish and silence redundant resolve errors. + pub(in crate::late) fn detect_missing_turbofish(&mut self, arguments: &'ast [Box]) { + if self.use_injections.is_empty() { + // Avoid doing unnecessary work, we would have had resolve errors by now if this mistake + // had happened. + return; + } + let mut iter = arguments.iter().peekable(); + // Look through every call argument expression, when encountering `a < b`, try to find a + // closing `c > ::d()` that would make the whole thing be syntactically valid as a + // fully-qualified path `a::::d()`. + while let Some(arg) = iter.next() { + // foo(left < right, bar, baz>::qux()) + // ^^^^ ^^^^^ + let ExprKind::Binary(lt, left, right) = &arg.kind else { continue }; + // foo(left < right, bar, baz>::qux()) + // ^ + let ast::BinOpKind::Lt = lt.node else { + continue; + }; + // foo(left < right, bar, baz>::qux()) + // ^^^^ is a path + let ExprKind::Path(_, lt_left) = &left.kind else { continue }; + // foo(a::B < right, bar, baz>::qux()) + // ^ ^ lt_left_segments + let lt_left_segments: Vec<_> = lt_left.segments.iter().map(|seg| seg.into()).collect(); + // `a::B` is a type-like. + let lt_res = if let PathResult::NonModule(res) = + self.resolve_path(<_left_segments, Some(TypeNS), None, PathSource::Expr(None)) + && let Some(lt_res) = res.full_res() + { + lt_res + } else { + continue; + }; + + // Same thing we do for `left`, for `right`: + // foo(left < right, bar, baz>::qux()) + // ^^^^^ is a path + let ExprKind::Path(_, lt_right) = &right.kind else { continue }; + let lt_right_segments: Vec<_> = + lt_right.segments.iter().map(|seg| seg.into()).collect(); + if let PathResult::NonModule(res) = + self.resolve_path(<_right_segments, Some(TypeNS), None, PathSource::Expr(None)) + && let Some(_res) = res.full_res() + { + } else { + continue; + } + + // foo(left < right, bar, baz>::qux()) + // next is ^^^ or ^^^^^^^^^^^ + let Some(mut next) = iter.next() else { + break; + }; + let mut other_path_spans = vec![]; + loop { + match &next.kind { + // foo(left < right, bar, baz>::qux()) + // ^^^^^^^^^ + ExprKind::Binary(gt, gt_left, gt_right) + // foo(left < right, bar, baz>::qux()) + // ^ + if let ast::BinOpKind::Gt = gt.node + // foo(left < right, bar, baz>::qux()) + // ^^^ + && let ExprKind::Path(_, gt_left) = >_left.kind + // foo(left < right, bar, baz>::qux()) + // ^^^^^^^ + && let ExprKind::Call(call_expr, _) = >_right.kind + && let ExprKind::Path(_, call_path) = &call_expr.kind + && let gt_left_segments = gt_left + .segments + .iter() + .map(|seg| seg.into()) + .collect::>() + && let PathResult::NonModule(res) = self.resolve_path( + >_left_segments, + Some(TypeNS), + None, + PathSource::Expr(None), + ) + && let Some(_res) = res.full_res() => + { + // We've encountered an expression where we have `Ty < Ty, Ty > call(...)`. + // A binop between two types is non-sensical in (current?) Rust, and the + // presence of two binops separated by a comma of less-than followed by + // greater-than heavily implies a missing turbofish in an expression. + let mut any = false; + let spans: Vec = lt_left_segments + .iter() + .map(|s| s.ident.span) + .chain(lt_right_segments.iter().map(|s| s.ident.span)) + .chain(gt_left_segments.iter().map(|s| s.ident.span)) + .chain(gt_left_segments.iter().map(|s| s.ident.span)) + .chain(call_path.segments.iter().map(|seg| seg.span())) + .chain(other_path_spans.iter().cloned()) + .collect(); + for injection in self.use_injections.iter_mut() { + // Silence all already constructed resolution errors that are contained + // by the likely missing turbofish. + if injection.path.iter().any(|s| spans.contains(&s.ident.span)) { + any = true; + injection.err.downgrade_to_delayed_bug(); + } + } + if any { + let sp = MultiSpan::from(vec![lt.span, gt.span]); + let mut err = + self.r.tcx.dcx().struct_span_err(sp, "can't compare two types"); + err.span_label( + gt.span, + "these are parsed as \"less than\" and \"greater than\"", + ); + let name = if let Some(def_id) = lt_res.opt_def_id() + && let Some(name) = self.r.tcx.opt_item_name(def_id) + { + name.to_string() + } else { + lt_right_segments + .iter() + .map(|s| s.ident.name.to_string()) + .collect::>() + .join("::") + }; + err.span_suggestion_verbose( + lt.span.shrink_to_lo(), + format!( + "you likely intended to write type `{name}` with type parameters, but \ + type parameters in expression contexts require the use of the \ + \"turbofish\" `::<>`", + ), + "::".to_string(), + Applicability::MachineApplicable, + ); + err.emit(); + } + } + + // foo(left < right, bat, bar, baz>::qux()) + // ^^^ + ExprKind::Path(_, path) => { + // foo(left < right, bat, bar, baz>::qux()) + // ^^^ or ^^^^^^^^^^^ + if let Some(n) = iter.next() { + next = n; + other_path_spans.extend(path.segments.iter().map(|seg| seg.ident.span)); + // Only case where we don't break from the `loop` advancing the parsed + // call arguments , we've encountered a path that is likely one of the + // type parameters for the missing turbofish. + continue; + } + } + + _ => {} + } + // This doesn't look like a turbofish, look for more missing turbofishes on the + // following arguments. + break; + } + } + } + fn suggest_at_operator_in_slice_pat_with_range(&self, err: &mut Diag<'_>, path: &[Segment]) { let Some(pat) = self.diag_metadata.current_pat else { return }; let (bound, side, range) = match &pat.kind { diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs index 131e8c71e32a3..28b8fd8ca1d51 100644 --- a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs @@ -6,18 +6,49 @@ use std::collections::HashMap; struct S; impl S { fn foo(&self, _: HashMap) {} + fn bar(&self, _: Many) {} } +fn bar(_: Many) {} +struct Many { + a: A, + b: B, + c: C, + d: D, +} + +impl Many { + fn new() -> Self { todo!() } +} + fn main() { let _ = Arc::new(RwLock::new(HashMap::default())); - //~^ ERROR expected value, found struct `HashMap` [E0423] - //~| ERROR expected value, found builtin type `i32` [E0423] - //~| ERROR expected value, found builtin type `i64` [E0423] - //~| ERROR cannot find external crate `default` in the crate root [E0425] - //~| ERROR this function takes 1 argument but 2 arguments were supplied [E0061] + //~^ ERROR can't compare two types let _ = S.foo(HashMap::default()); - //~^ ERROR expected value, found struct `HashMap` [E0423] - //~| ERROR expected value, found builtin type `i32` [E0423] - //~| ERROR expected value, found builtin type `i64` [E0423] - //~| ERROR cannot find external crate `default` in the crate root [E0425] - //~| ERROR this method takes 1 argument but 2 arguments were supplied [E0061] + //~^ ERROR can't compare two types + let _ = bar(Many::new()); + //~^ ERROR can't compare two types + let _ = S.bar(Many::new()); + //~^ ERROR can't compare two types + let _ = bar(Many::new(), Many::new()); + //~^ ERROR can't compare two types + //~| ERROR can't compare two types + let _ = S.bar(Many::new(), Many::new()); + //~^ ERROR can't compare two types + //~| ERROR can't compare two types + let _ = bar(1, 2, Many::new()); + //~^ ERROR can't compare two types + let _ = S.bar(1, 2, Many::new()); + //~^ ERROR can't compare two types + let _ = bar(a, b, Many::new(), c, d); + //~^ ERROR can't compare two types + //~| ERROR cannot find value `a` in this scope + //~| ERROR cannot find value `b` in this scope + //~| ERROR cannot find value `c` in this scope + //~| ERROR cannot find value `d` in this scope + let _ = S.bar(a, b, Many::new(), c, d); + //~^ ERROR can't compare two types + //~| ERROR cannot find value `a` in this scope + //~| ERROR cannot find value `b` in this scope + //~| ERROR cannot find value `c` in this scope + //~| ERROR cannot find value `d` in this scope } diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr index c2561555f7098..5ffbc96a02ac2 100644 --- a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr @@ -1,100 +1,271 @@ -error[E0423]: expected value, found struct `HashMap` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:34 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:24:41 | LL | let _ = Arc::new(RwLock::new(HashMap::default())); - | ^^^^^^^ + | ^ ^ these are parsed as "less than" and "greater than" | - --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL - ::: $SRC_DIR/std/src/collections/hash/map.rs:LL:COL +help: you likely intended to write type `HashMap` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` | - = note: `HashMap` defined here +LL | let _ = Arc::new(RwLock::new(HashMap::::default())); + | ++ -error[E0423]: expected value, found builtin type `i32` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:42 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:26:26 | -LL | let _ = Arc::new(RwLock::new(HashMap::default())); - | ^^^ not a value +LL | let _ = S.foo(HashMap::default()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `HashMap` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = S.foo(HashMap::::default()); + | ++ -error[E0423]: expected value, found builtin type `i64` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:47 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:28:21 | -LL | let _ = Arc::new(RwLock::new(HashMap::default())); - | ^^^ not a value +LL | let _ = bar(Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = bar(Many::::new()); + | ++ -error[E0425]: cannot find external crate `default` in the crate root - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:53 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:30:23 | -LL | let _ = Arc::new(RwLock::new(HashMap::default())); - | ^^^^^^^ not found in the crate root +LL | let _ = S.bar(Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = S.bar(Many::::new()); + | ++ -error[E0423]: expected value, found struct `HashMap` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:19 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:32:21 | -LL | let _ = S.foo(HashMap::default()); - | ^^^^^^^ +LL | let _ = bar(Many::new(), Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" | - --> $SRC_DIR/std/src/collections/hash/map.rs:LL:COL - ::: $SRC_DIR/std/src/collections/hash/map.rs:LL:COL +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` | - = note: `HashMap` defined here +LL | let _ = bar(Many::::new(), Many::new()); + | ++ -error[E0423]: expected value, found builtin type `i32` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:27 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:32:54 | -LL | let _ = S.foo(HashMap::default()); - | ^^^ not a value +LL | let _ = bar(Many::new(), Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = bar(Many::new(), Many::::new()); + | ++ -error[E0423]: expected value, found builtin type `i64` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:32 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:35:23 | -LL | let _ = S.foo(HashMap::default()); - | ^^^ not a value +LL | let _ = S.bar(Many::new(), Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = S.bar(Many::::new(), Many::new()); + | ++ -error[E0425]: cannot find external crate `default` in the crate root - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:38 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:35:56 | -LL | let _ = S.foo(HashMap::default()); - | ^^^^^^^ not found in the crate root +LL | let _ = S.bar(Many::new(), Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = S.bar(Many::new(), Many::::new()); + | ++ -error[E0061]: this function takes 1 argument but 2 arguments were supplied - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:11:22 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:38:27 | -LL | let _ = Arc::new(RwLock::new(HashMap::default())); - | ^^^^^^^^^^^ --------------- unexpected argument #2 of type `bool` +LL | let _ = bar(1, 2, Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = bar(1, 2, Many::::new()); + | ++ + +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:40:29 | -note: associated function defined here - --> $SRC_DIR/std/src/sync/poison/rwlock.rs:LL:COL -help: remove the extra argument +LL | let _ = S.bar(1, 2, Many::new()); + | ^ ^ these are parsed as "less than" and "greater than" | -LL - let _ = Arc::new(RwLock::new(HashMap::default())); -LL + let _ = Arc::new(RwLock::new(HashMap` | +LL | let _ = S.bar(1, 2, Many::::new()); + | ++ -error[E0061]: this method takes 1 argument but 2 arguments were supplied - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:15 +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:42:27 | -LL | let _ = S.foo(HashMap::default()); - | ^^^ --------------- unexpected argument #2 of type `bool` +LL | let _ = bar(a, b, Many::new(), c, d); + | ^ ^ these are parsed as "less than" and "greater than" | -note: expected `HashMap`, found `bool` - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:17:19 +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` | -LL | let _ = S.foo(HashMap::default()); - | ^^^^^^^^^^^ - = note: expected struct `HashMap` - found type `bool` -note: method defined here - --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:8:8 +LL | let _ = bar(a, b, Many::::new(), c, d); + | ++ + +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:48:29 + | +LL | let _ = S.bar(a, b, Many::new(), c, d); + | ^ ^ these are parsed as "less than" and "greater than" + | +help: you likely intended to write type `Many` with type parameters, but type parameters in expression contexts require the use of the "turbofish" `::<>` + | +LL | let _ = S.bar(a, b, Many::::new(), c, d); + | ++ + +error[E0425]: cannot find value `a` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:42:17 + | +LL | let _ = bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = bar(a, b, Many::new(), c, d); +LL + let _ = bar(S, b, Many::new(), c, d); + | + +error[E0425]: cannot find value `b` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:42:20 + | +LL | let _ = bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = bar(a, b, Many::new(), c, d); +LL + let _ = bar(a, S, Many::new(), c, d); + | + +error[E0425]: cannot find value `c` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:42:56 + | +LL | let _ = bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = bar(a, b, Many::new(), c, d); +LL + let _ = bar(a, b, Many::new(), S, d); + | + +error[E0425]: cannot find value `d` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:42:59 + | +LL | let _ = bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = bar(a, b, Many::new(), c, d); +LL + let _ = bar(a, b, Many::new(), c, S); + | + +error[E0425]: cannot find value `a` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:48:19 + | +LL | let _ = S.bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = S.bar(a, b, Many::new(), c, d); +LL + let _ = S.bar(S, b, Many::new(), c, d); + | + +error[E0425]: cannot find value `b` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:48:22 + | +LL | let _ = S.bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = S.bar(a, b, Many::new(), c, d); +LL + let _ = S.bar(a, S, Many::new(), c, d); + | + +error[E0425]: cannot find value `c` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:48:58 + | +LL | let _ = S.bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 + | +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists + | +LL - let _ = S.bar(a, b, Many::new(), c, d); +LL + let _ = S.bar(a, b, Many::new(), S, d); + | + +error[E0425]: cannot find value `d` in this scope + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:48:61 + | +LL | let _ = S.bar(a, b, Many::new(), c, d); + | ^ not found in this scope + | +note: similarly named unit struct `S` defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:6:1 | -LL | fn foo(&self, _: HashMap) {} - | ^^^ -------------------- -help: remove the extra argument +LL | struct S; + | ^^^^^^^^^ +help: a unit struct with a similar name exists | -LL - let _ = S.foo(HashMap::default()); -LL + let _ = S.foo(/* HashMap */); +LL - let _ = S.bar(a, b, Many::new(), c, d); +LL + let _ = S.bar(a, b, Many::new(), c, S); | -error: aborting due to 10 previous errors +error: aborting due to 20 previous errors -Some errors have detailed explanations: E0061, E0423, E0425. -For more information about an error, try `rustc --explain E0061`. +For more information about this error, try `rustc --explain E0425`. From c4b10494e34bfbda6ca28d91841e84e58aaec230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Fri, 23 Oct 2026 02:44:28 +0000 Subject: [PATCH 3/3] Avoid downgrading when there are no prior errors (do not ICE) --- .../rustc_hir_typeck/src/fn_ctxt/checks.rs | 3 +++ ...ggest-turbofish-parsed-as-comparisons-2.rs | 11 ++++++++++ ...t-turbofish-parsed-as-comparisons-2.stderr | 20 +++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.rs create mode 100644 tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.stderr diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index 88d47cfa9fb58..2443641efe615 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs @@ -3229,6 +3229,9 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { } fn detect_missing_turbofish(&self, err: &mut Diag<'_>) { + if self.tcx.dcx().has_errors().is_none() { + return; + } let mut iter = self.provided_args.iter().peekable(); while let Some(arg) = iter.next() { let hir::ExprKind::Binary(lt, lt_left, lt_right) = arg.kind else { diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.rs b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.rs new file mode 100644 index 0000000000000..4c2e5e59d3340 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.rs @@ -0,0 +1,11 @@ +fn set_alert(_: bool) {} +fn current_limit() -> i32 { + 100 +} + +fn main() { + let used = 120; + let warning = 90; + set_alert(used < warning, used > current_limit()); + //~^ ERROR: this function takes 1 argument but 2 arguments were supplied +} diff --git a/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.stderr b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.stderr new file mode 100644 index 0000000000000..c0666a75cefcb --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons-2.stderr @@ -0,0 +1,20 @@ +error[E0061]: this function takes 1 argument but 2 arguments were supplied + --> $DIR/suggest-turbofish-parsed-as-comparisons-2.rs:9:5 + | +LL | set_alert(used < warning, used > current_limit()); + | ^^^^^^^^^ ---------------------- unexpected argument #2 of type `bool` + | +note: function defined here + --> $DIR/suggest-turbofish-parsed-as-comparisons-2.rs:1:4 + | +LL | fn set_alert(_: bool) {} + | ^^^^^^^^^ +help: remove the extra argument + | +LL - set_alert(used < warning, used > current_limit()); +LL + set_alert(used < warning); + | + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0061`.