Braced macros are inconsistent with other braced items

d15f97a
Opened by Sergio Benitez at 2024-07-26 01:17:46

Consider the following macro:

macro_rules! test {
    () => ( "hello" )
}

The following function compiles as expected if the macro is invoked with parenthesis:

fn string_len() -> usize {
    test!().len()
}

However, if I invoke the macro with curly braces instead, Rust emits an error:

fn string_len() -> usize {
    test!{ }.len()
}

rustc 1.15.1 (021bd294c 2017-02-08)
error: expected expression, found `.`
 --> <anon>:2:13
  |
2 |     test!{ }.len()
  |             ^

From discussions with @eddyb, this occurs because the macro is being treated as expanding into a statement as opposed to an expression. This is inconsistent with how other curly-braced constructs are treated. For instance, the following functions as expected:

fn string_len() -> usize {
    { "hello" }.len()
}

It seems that the expansion site is not correctly being identified as an expression or statement. In this case, the . suffix to the macro invocation clearly determines that the macro should expand to an expression.

  1. cc @nrc @jseyfried

    Eduard-Mihai Burtescu at 2017-02-25 22:55:40

  2. Triage: Still an issue. Error message:

    error: expected expression, found `.`
     --> src/main.rs:6:12
      |
    6 |     test!{}.len()
      |            ^ expected expression
    
    error[E0308]: mismatched types
     --> src/main.rs:2:13
      |
    2 |     () => ( "hello" )
      |             ^^^^^^^ expected usize, found reference
    ...
    5 | fn string_len() -> usize {
      |                    ----- expected `usize` because of return type
    6 |     test!{}.len()
      |     ------- in this macro invocation
      |
      = note: expected type `usize`
                 found type `&'static str`
    

    Phlosioneer at 2018-03-07 03:55:09

  3. I just rediscovered this issue for myself, but unfortunately it strikes me as impractical to try and change this behavior. Consider this code:

    fn f() -> [usize; 1] {
        mac! {}
        [1]
    }
    

    In order to tell whether this parses as mac!{}[1] or mac! {}; [1], we would have to know whether mac!{} expands to an item or an expression. If it expands to fn g() {...}, the function returns [1]; if it expands to, e.g., [[0], [12]], the function returns [12]. This leaves the door open to writing some obscure C-like macro code that I don't think anybody wants to have to read. It would probably also make the language grammar harder to define, in case that matters to anyone.

    It might be possible to allow this in cases where the next token cannot start an expression, which would make the test!{}.len() example work, but curly brace macros would still be gimped compared to the other two paren types. I suspect this is simply an inherent drawback in the original decision to let curly brace macros be used this way.

    I might be belaboring the point, but basically I'm just saying I personally don't see this as a bug and I wouldn't change anything except to improve the error message.

    Matthew McAllister at 2020-07-17 03:57:03

  4. Compiles on rustc 1.80.0: https://rust.godbolt.org/z/xsxrGPfrs

    Veera at 2024-07-26 01:17:46