Skip to content

Attribute macro can replace item with expression. #144716

Open

Description

@theemathas

I tried this code:

bar/src/lib.rs:

use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn my_macro(_: TokenStream, _: TokenStream) -> TokenStream {
    "\"weird\"".parse().unwrap()
}

src/main.rs:

fn main() {
    // prints "weird"
    println!("{}", {
        #[bar::my_macro]
        struct Thing;
    });
}

I expected the code to give a compile error. Instead, it compiles and prints weird.

The proc macro is somehow replacing an item with an expression. The reference says this isn't allowed:

The returned TokenStream replaces the item with an arbitrary number of items.

Initially discovered in #144579.

@rustbot labels +A-macros +A-proc-macros +A-attributes

Meta

rustc --version --verbose:

rustc 1.88.0 (6b00bc388 2025-06-23)
binary: rustc
commit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc
commit-date: 2025-06-23
host: aarch64-apple-darwin
release: 1.88.0
LLVM version: 20.1.5

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    A-attributesArea: Attributes (`#[…]`, `#![…]`)
    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)
    on Jul 31, 2025
  2. danielhenrymantilla commented on Jul 31, 2025

    @danielhenrymantilla
    Contributor

    This might be an issue with the reference rather than a bug in implementation?

  3. Noratrieb commented on Jul 31, 2025

    @Noratrieb
    Member

    @petrochenkov about your opinion

  4. added
    T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jul 31, 2025
  5. petrochenkov commented on Jul 31, 2025

    @petrochenkov
    Contributor

    The macro invocation is in a statement position, so it can produce multiple statements, including expression statements.
    So it's not necessarily an item.

  6. theemathas commented on Jul 31, 2025

    @theemathas
    ContributorAuthor

    Expression statements do not produce values though, right? If the macro expanded to an expression statement (as opposed to a non-statement expression), I would not have been able to print the value "weird".

  7. danielhenrymantilla commented on Jul 31, 2025

    @danielhenrymantilla
    Contributor

    They can when within braces:

    {
        foo! {} // say this emits `42`
        bar! {} // and this, ``
    } // <- evaluates to `42`
  8. theemathas commented on Jul 31, 2025

    @theemathas
    ContributorAuthor

    And..... the reference says that the result of an expression statement is always ignored.

    This function compiles, and returns 42:

    macro_rules! nothing {
        () => {}
    }
    
    fn demo() -> i32 {
        { 42 }
        nothing! {}
    }

    Based on the grammar in the reference, I believe that the { 42 } must be an expression statement. However, the result of it is clearly not ignored.

    Either the implementation or the reference is also wrong here.

  9. danielhenrymantilla commented on Jul 31, 2025

    @danielhenrymantilla
    Contributor

    Yes, I believe the reference ought to mention the special case of the without-semicolon "block statement", whose value is not necessarily always discarded. Do you want to open an issue about it?

  10. kpreid commented on Jul 31, 2025

    @kpreid
    Contributor

    This function compiles, and returns 42:

    IMO, this shouldn't work. It appears to violate the tree structure of the language in a way that macros usually can't (e.g. foo(a, b!(), c) always has 3 arguments), and thus surprisingly reduces the ability to read Rust without knowing everything about every involved macro expansion.

  11. petrochenkov commented on Jul 31, 2025

    @petrochenkov
    Contributor

    (e.g. foo(a, b!(), c) always has 3 arguments

    What about foo(a, #[my_cfg(false)] b, c), how many arguments does it have?

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-attributesArea: Attributes (`#[…]`, `#![…]`)A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)A-proc-macrosArea: Procedural macrosC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions