Closed
Description
Consider the following macro and all the ways to invoke it:
macro_rules! make_item {
($a:ident) => { struct $a; }
}
make_item!(A); // ok (semicolon required)
make_item! { B } // ok (semicolon not required)
fn c() { make_item!(C); } // ok
fn d() { make_item!(D) } // bad (expected: missing semicolon)
fn e() { make_item! { E } } // bad (BUG: semicolon should not be required)
fn f() { make_item! { F }; } // ok (workaround)
fn gh() { make_item! { G } make_item!(H); } // ok (further evidence case E is a bug)
I believe that case E should be accepted, however it gives the error that the macro is invalid in expression position. My theory (before investigating) is that the tail of the function is considered as expression position because the macro expander forgets the possibility that a macro may generate items.
Activity
[-]cannot end function with item-genering macro invoked with curly braces[/-][+]cannot end function with item-generating macro invoked with curly braces[/+]durka commentedon Jun 23, 2016
I'm working on a fix.
jseyfried commentedon Jun 23, 2016
cc me
durka commentedon Jun 23, 2016
OK, a little harder to fix than we thought. By the time we get to expanding macros in a block, it's already an
ast::Block
which consists of statements and an optional expression. I think the decision of whether the last thing in a block is the last statement, or the expression, is purely syntactical -- whether it ends in a semicolon. We can change the expansion to be.fold_stmt()
instead of.fold_expr()
, but I believe that will breakfn foo() -> Vec<i32> { vec! { 1, 2 } }
becauseStmtKind::Expr
must have unit type. So I don't know how to do what I want, which is:We could change
expand_block
to always expand the trailing expr as a stmt, and if it happens to expand to aStmtKind::Expr
, then keep it as a trailing expr, otherwise move it to the stmt list. The docs forStmtKind
say thatStmtKind::Expr
must have unit type (thus my assumption that this strategy would break the aboveVec
example), but it was pointed out that types aren't known at expansion time, so maybe it's okay to blatantly disregard this documented invariant. I won't really be able to try this until next week.jseyfried commentedon Jun 23, 2016
@durka
I like this idea.
durka commentedon Jun 23, 2016
Why does
Block
even have theOption<P<Expr>>
, one wonders, whenStmtKind::Expr
exists?jseyfried commentedon Jun 23, 2016
Good question...
durka commentedon Jun 23, 2016
fold_stmt
can return a number of stmts, so really it's: expand the trailing expr as statements, if the last one isStmtKind::Expr
then peel it off as the trailing expr, append the rest to the stmt list.jseyfried commentedon Jun 23, 2016
Exactly!
Ideally we'll refactor away the
Option<P<Expr>>
from the block so it's all statements.jseyfried commentedon Jun 23, 2016
I'm going to try to remove the field
expr
ofBlock
in the next breaking batch, which should land in a day or two.