Skip to content

Suggest turbofish for multi-param generics used as call arguments - #160104

Open
martonmoro wants to merge 2 commits into
rust-lang:mainfrom
martonmoro:fix-missing-turbofish-diagnostic
Open

Suggest turbofish for multi-param generics used as call arguments#160104
martonmoro wants to merge 2 commits into
rust-lang:mainfrom
martonmoro:fix-missing-turbofish-diagnostic

Conversation

@martonmoro

@martonmoro martonmoro commented Jul 28, 2026

Copy link
Copy Markdown

Fixes #159745

The issue occurred because when parsing bar(Many<i32, Many<(), i32, S, S>, i32, i32>::new()) it parsed < as a Binary(Lt) and treated the comma as the end of argument 1 then, when it gets to > it tries parsing it again as an operator, attempting to parse a RHS which in this case seems like a bare comma that cannot start an expression. After that, it parses the rest of the "comma-separated arguments" incorrectly, which is why the error was expected expression, found ','. Some other methods seemed relevant here, like check_no_chained_comparison (only when the very next token is also a comparison) and check_mistyped_turbofish_with_multiple_type_params (only called from parse_full_stmt after the parser already hit an error expecting ;, and comma is an error at stmt level but not at expr level), but they were not called during parsing. (There is probably some refactoring potential in check_mistyped_turbofish_with_multiple_type_params and my function tho).

My first attempt was to recover whenever a call argument parsed as Binary(Lt) followed by a comma, re-parsing the rest with parse_generic_arg. That turned out to be wrong, because it can't tell the broken case apart from a valid one: f(a < b, c > (d)) is two arguments and looks identical to the parser at that point, so it started rejecting code that compiles today.

So instead of deciding per-argument, I let parse_expr_paren_seq parse the whole argument list as expressions first, and only if that fails do I reparse the list with the turbofish recovery enabled. If the retry also fails, I cancel it and return the original error, restoring the parser to where the first attempt died so the reported span doesn't move.

I scoped this to call-argument parsing, not to the shared comma-separated list parser.

@rustbot

rustbot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

The parser was modified, potentially altering the grammar of (stable) Rust
which would be a breaking change.

cc @fmease

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 28, 2026
@rustbot

rustbot commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome! The Rust Project is excited to review your changes, and you should hear from @hanna-kruppe (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue
Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler, parser
  • compiler, parser expanded to 74 candidates
  • Random selection from 18 candidates

@hanna-kruppe

Copy link
Copy Markdown
Contributor

I’m not familiar enough with the parser code to review this, and won’t have time to get familiar with it, so: @rustbot reroll

@rustbot rustbot assigned chenyukang and unassigned hanna-kruppe Aug 2, 2026
@@ -0,0 +1,24 @@
struct Many<A, B, C, D> {

@chenyukang chenyukang Aug 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use run-rustfix for this test?

View changes since the review

let failed = self.create_snapshot_for_diagnostic();
self.restore_snapshot(start);
match self.parse_paren_comma_seq(|p| {
let expr = p.parse_expr_catch_underscore(Restrictions::empty())?;

@chenyukang chenyukang Aug 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback here reparses the entire argument list and applies recovery to every Binary(Lt). If a later argument causes the initial parse failure, earlier valid comparisons are reconsidered.

For example this code:

struct S;
struct Many<A, B, C, D>(A, B, C, D);

impl<A, B, C, D> Many<A, B, C, D> {
    fn new() -> Self {
        todo!()
    }
}

fn take_three(_: bool, _: bool, _: Many<i32, Many<(), i32, S, S>, i32, i32>) {}

fn main() {
    let (a, b, c, d) = (1, 2, 3, 4);
    take_three(a < b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new());
}

the current output is:

  --> /tmp/now.rs:14:16
   |
14 |     take_three(a < b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new());
   |                ^^^^^
   |
help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
   |
14 |     take_three(a ::< b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new());
   |                  ++

error: generic args in this position require the turbofish syntax
  --> /tmp/now.rs:14:32
   |
14 |     take_three(a < b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new());
   |                                ^^^^^^^^
   |
help: use `::<...>` instead of `<...>` to specify lifetime, type, or const arguments
   |
14 |     take_three(a < b, c > (d), Many::<i32, Many<(), i32, S, S>, i32, i32>::new());
   |                                    ++

error[E0061]: this function takes 3 arguments but 2 arguments were supplied
  --> /tmp/now.rs:14:5
   |
14 |     take_three(a < b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new());
   |     ^^^^^^^^^^----------------------------------------------------------------- argument #3 of type `Many<i32, Many<(), i32, S, S>, i32, i32>` is missing
   |
note: function defined here
  --> /tmp/now.rs:10:4
   |
10 | fn take_three(_: bool, _: bool, _: Many<i32, Many<(), i32, S, S>, i32, i32>) {}
   |    ^^^^^^^^^^                   -------------------------------------------
help: provide the argument
   |
14 |     take_three(a < b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new(), /* Many<i32, Many<(), i32, S, S>, i32, i32> */);
   |                                                                               ++++++++++++++++++++++++++++++++++++++++++++++++

which is wrong.

The nightly output for this code is :

error: expected expression, found `,`
  --> src/main.rs:14:61
   |
14 |     take_three(a < b, c > (d), Many<i32, Many<(), i32, S, S>, i32, i32>::new());
   |                                                             ^ expected expression

which points out the rootcause.

View changes since the review

// The attempt above can emit diagnostics before it fails (like in
// `tests/ui/parser/attribute/attr-stmt-expr-attr-bad.rs`), and parsing again the same
// tokens would emit them a second time.
if self.dcx().err_count() != err_count {

@chenyukang chenyukang Aug 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a hack here.

View changes since the review

let failed = self.create_snapshot_for_diagnostic();
self.restore_snapshot(start);
match self.parse_paren_comma_seq(|p| {
let expr = p.parse_expr_catch_underscore(Restrictions::empty())?;

@chenyukang chenyukang Aug 7, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally, i don't like the way of repeat the same parsing here, can we do in the way of stop where error happened and try to speculatively detect a missing turbofish.

View changes since the review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Missing turbofish in nested type params has terse diagnostic

4 participants