diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs index f28a99744aa56..2443641efe615 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,45 @@ impl<'a, 'tcx> ArgMatchingCtxt<'a, 'tcx> { self.annotate_alternative_method_deref(err, self.call_expr, None); } + 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 { + 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-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`. 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..28b8fd8ca1d51 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.rs @@ -0,0 +1,54 @@ +// Issue #81816. +use std::sync::Arc; +use std::sync::RwLock; +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 can't compare two types + let _ = S.foo(HashMap::default()); + //~^ 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 new file mode 100644 index 0000000000000..5ffbc96a02ac2 --- /dev/null +++ b/tests/ui/suggestions/suggest-turbofish-parsed-as-comparisons.stderr @@ -0,0 +1,271 @@ +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" + | +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())); + | ++ + +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:26:26 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:28:21 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:30:23 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:32:21 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:32:54 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:35:23 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:35:56 + | +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: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:38:27 + | +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 + | +LL | let _ = S.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 _ = S.bar(1, 2, Many::::new()); + | ++ + +error: can't compare two types + --> $DIR/suggest-turbofish-parsed-as-comparisons.rs:42:27 + | +LL | let _ = 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 _ = 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 | 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(), c, S); + | + +error: aborting due to 20 previous errors + +For more information about this error, try `rustc --explain E0425`.