rust-lang / rust-lang/rust

a path fragment cannot begin a struct expression or macro call expression

Open
#143,221 3 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-macros A-parser C-bug T-compiler T-lang
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I tried this code:
macro_rules! m {
    ($p:path) => {
        ($p {})
    }
}

struct T {}

fn main() {
    m!(T);
}
I expected to see this happen:

The code should expand to a main() function that constructs a T {} in a parenthesized expression and then ignores the value.
The code should be valid, and result in a program that does absolutely nothing.

Instead, this happened:

I get two compiler errors:

error: expected one of `)`, `,`, `.`, `?`, or an operator, found `{`
  --> src/main.rs:3:13
   |
3  |         ($p {})
   |            -^ expected one of `)`, `,`, `.`, `?`, or an operator
   |            |
   |            help: missing `,`
...
10 |     m!(T);
   |     ----- in this macro invocation
   |
   = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0423]: expected value, found struct `T`
  --> src/main.rs:10:8
   |
7  | struct T {}
   | ----------- `T` defined here
...
10 |     m!(T);
   |        ^ help: use struct literal syntax instead: `T {}`
$p!() macro call expressions

The exact same bug affects macro call expressions. ($p!()) complains about what it thinks is an infix ! operator.

Internals

The problem is that in the expression parser, we special case "path" metavar expansions, and just interpret them as path expressions. This is wrong, because an expression that starts with a path may also be a macro invocation or a struct literal, so we should not special case it.

The "obvious fix" is to simply delete these lines of code:

https://github.com/rust-lang/rust/blob/35f6036521777bdc0dcea1f980be4c192962a168/compiler/rustc_parse/src/parser/expr.rs#L1430-L1434

It will fall through to the branch that normally handles expressions that start with a path, where parsing a path will correctly uninterpolate a path fragment and it may be turned into a macro call or struct expression.

https://github.com/rust-lang/rust/blob/35f6036521777bdc0dcea1f980be4c192962a168/compiler/rustc_parse/src/parser/expr.rs#L1465-L1466

Unfortunately, fixing this bug is a breaking change, because you can detect it in macro_rules!.

macro_rules! check_for_the_bug {

    // setup: ensure that we capture `$p` as a path fragment.
    ($p:path) => {{
        check_for_the_bug!($p {})
    }};
    
    
    // the following branches will *all*:
    //
    // - match a struct expression
    // - expand to an expression equal to that struct expression
    //
    // but the expanded expressions have different side effects.


    // these branches are to prevent `check_for_bug!(T {})` from printing "bug was fixed"
    // (you can bypass this with generics but whatever who cares)
    ($($p:ident)::+ {}) => {{
        println!("doofus, you called it wrong");
        $($p)::+ {}
    }};
    ($(::$p:ident)+ {}) => {{
        println!("doofus, you called it wrong");
        $(::$p)+
    }};
    
    
    // then, the actual disambiguation branches.

    ($e:expr) => {{
        println!("bug was fixed!");
        $e
    }};
    
    ($p:path {}) => {{
        println!("bug is still present :(");
        $p {}
    }};
}

struct T {}

fn main() {
    let x: T = check_for_the_bug!(T);
}

So, uh, $:expr_2024? Probably not, since surely new fragments are not required for "any change to the expression parser"; i'm not sure what the norms for breaking/additive changes are here.

Diagnostics

Let's come back to the diagnostics that the compiler emits.

The first diagnostic is "technically correct", because it explains the state that the parser is in:

error: expected one of `)`, `,`, `.`, `?`, or an operator, found `{`
  --> src/main.rs:3:13
   |
3  |         ($p {})
   |            -^ expected one of `)`, `,`, `.`, `?`, or an operator
   |            |
   |            help: missing `,`
...
10 |     m!(T);
   |     ----- in this macro invocation
   |
   = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info)

But the second diagnostic is kinda wrong:

error[E0423]: expected value, found struct `T`
  --> src/main.rs:10:8
   |
7  | struct T {}
   | ----------- `T` defined here
...
10 |     m!(T);
   |        ^ help: use struct literal syntax instead: `T {}`

The diagnostic is "technically relevant" based on what was parsed; it detects that $p (the path expression) resolves to a struct, and suggests using a struct expression instead.

But the suggested fix is not correct here; most notably because it points at the macro callsite where an expression is not even allowed (only accepts a path fragment). See #143220 which tracks that diagnostic pointing at the wrong location.

Assuming this parsing bug remains, the compiler should emit a more helpful diagnostic explaining that you can work around the bug by wrapping it in a block.

error: cannot make struct expression beginning with a path fragment here
  --> src/main.rs:3:10
   |
3  |         ($p {})
   |          ^^^^^ this looks like a struct expression because `$p` is a path fragment
...
3  |         ($p {})
   |         -^^ ^^-
   |         |     |
   |         \--------- path fragments are eagerly parsed as path expressions
   |               |
   |               \--- and the braces are never allowed afterwards
   |
   = note: this is a bug in the expression parser
   = help: if you intended to construct a `$p`, you can wrap it in a block:
3  |         ({$p {}})
   |          +-----+
   |          help: add braces here to make it an expression statement

Wrapping it in a block works, because it never special-cases a path expansion group. It just falls through to the normal "statement starts with a path" branch:

https://github.com/rust-lang/rust/blob/35f6036521777bdc0dcea1f980be4c192962a168/compiler/rustc_parse/src/parser/stmt.rs#L195-L213

Meta

rustc --version --verbose:

rustc 1.90.0-nightly (11ad40bb8 2025-06-28)
binary: rustc
commit-hash: 11ad40bb839ca16f74784b4ab72596ad85587298
commit-date: 2025-06-28
host: x86_64-unknown-linux-gnu
release: 1.90.0-nightly
LLVM version: 20.1.7

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the examples first, then read compiler/rustc_parse/src/parser/expr.rs around lines 1430-1434 and 1465-1466, plus the path-handling branch in stmt.rs around lines 195-213. Check how path fragments are parsed for struct expressions and macro calls, and add regression coverage for the reported behavior and diagnostics. Done means the selected behavior is implemented and the relevant compiler tests pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.