Skip to content

rustc_parser incorrectly parses groups with Delimiter::None #67062

Open

Description

@petrochenkov

Such groups can currently be created by proc macros.

Reproduction, proc macro crate:

#[proc_macro]
pub fn add_mul(_: TokenStream) -> TokenStream {
    // ⟪ 2 + 2 ⟫ * 2
    vec![
        TokenTree::from(Group::new(Delimiter::None, "2 + 2".parse().unwrap())),
        TokenTree::from(Punct::new('*', Spacing::Alone)),
        TokenTree::from(Literal::u8_unsuffixed(2)),
    ].into_iter().collect()
}

User crate:

fn main() {
    let x = add_mul!();
    assert_eq!(x, 8); // FAIL: the result is 6 != 8
}

This is not a huge issue right now because proc macros have no reasons to created Delimiter::None delimiters themselves (but they can still get them from input and return them if they return their input partially or fully, see the example below).

However, this will became a huge issue when interpolated tokens like $e: expr migrate from AST pieces to token streams, because in the token stream model they are represented exactly like groups with Delimiter::None delimiters (proc macros already see them in this way).

Activity

  1. added
    T-langRelevant to the language team
    A-parserArea: The lexing & parsing of Rust source code to an AST
    C-bugCategory: This is a bug.
    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)
    on Dec 5, 2019
  2. petrochenkov commented on Dec 5, 2019

    @petrochenkov
    ContributorAuthor

    How to reproduce this without constructing Delimiter::None by yourself.

    Proc macro crate:

    #[proc_macro]
    pub fn add_mul(input: TokenStream) -> TokenStream {
        let mul_2 = vec![
            TokenTree::from(Punct::new('*', Spacing::Alone)),
            TokenTree::from(Literal::u8_unsuffixed(2)),
        ];
        input.into_iter().chain(mul_2.into_iter()).collect()
    }

    User crate:

    macro_rules! mbe {
        ($e: expr) => ( add_mul!($e) )
    }
    
    fn main() {
        let x = mbe!(2 + 2);
        assert_eq!(x, 8); // FAIL: the result is 6 != 8
    }
  3. petrochenkov commented on Dec 5, 2019

    @petrochenkov
    ContributorAuthor

    What it means to parse Delimiter::None correctly is a big language design question in general.

    However, it's clear that in pieces of grammar with expression-like priorities they should behave more or less like parentheses to keep the "operator priority hygiene" property of our macros.

    "How would an interpolated token behave here?" should probably be the primary intuition here.

  4. petrochenkov commented on Dec 6, 2019

    @petrochenkov
    ContributorAuthor

    cc @dtolnay @alexcrichton
    To make you aware of this issue, and perhaps proc-macro2 and syn could attempt hiding this bug somehow.

  5. petrochenkov commented on Jul 4, 2020

    @petrochenkov
    ContributorAuthor

    Encountered in practice in #74036.

  6. added a commit that references this issue on May 11, 2022
  7. added a commit that references this issue on Jun 13, 2022
  8. nnethercote commented on Aug 21, 2023

    @nnethercote
    Contributor

    I have made a serious attempt to fix this issue in #114647. The commits there eliminate Nonterminal and Token::Interpolate, and stop the stripping of invisible delimiters in the parser. This basically works for declarative macros, where pasted code fragments have a known kind, such as expr, stmt, item, etc. But it's harder for proc macros, where the produced code fragments have unknown kinds. A lot of the time the kind is clear from context, but there are certain cases where it's very difficult to know how a proc-macro-produced fragment should be parsed. See this comment for details. This seems hard to work around, and could be a potential showstopper.

  9. added a commit that references this issue on Feb 18, 2024
  10. 8 remaining items

  11. Jules-Bertholet commented on Jul 19, 2026

    @Jules-Bertholet
    Contributor

    Like any miscompilation, I believe this should be considered @rustbot label I-miscompile I-unsound

  12. added
    I-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jul 19, 2026
  13. rustbot commented on Jul 19, 2026

    @rustbot
    Collaborator

    Assigning P-high (discussion on Zulip).

  14. added
    P-highHigh priority
    and removed
    I-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
    on Jul 19, 2026
  15. added
    I-miscompileIssue: Correct Rust code lowers to incorrect machine code
    on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)A-parserArea: The lexing & parsing of Rust source code to an ASTC-bugCategory: This is a bug.I-miscompileIssue: Correct Rust code lowers to incorrect machine codeI-unsoundIssue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/SoundnessP-highHigh priorityT-langRelevant to the language team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions