Improves handling of statement macros.

Statement macros are now treated somewhat like item macros, in that a
statement macro can now expand into a series of statements, rather than
just a single statement.

This allows statement macros to be nested inside other kinds of macros and
expand properly, where previously the expansion would only work when no
nesting was present.

See: src/test/run-pass/macro-stmt_macro_in_expr_macro.rs
     src/test/run-pass/macro-nested_stmt_macro.rs

This changes the interface of the MacResult trait.  make_stmt has become
make_stmts and now returns a vector, rather than a single item.  Plugin
writers who were implementing MacResult will have breakage, as well as
anyone using MacEager::stmt.

See: src/libsyntax/ext/base.rs

This also causes a minor difference in behavior to the diagnostics
produced by certain malformed macros.

See: src/test/compile-fail/macro-incomplete-parse.rs
This commit is contained in:
Christopher Chambers
2015-04-07 08:21:18 -05:00
parent de51bbec15
commit 19343860aa
6 changed files with 138 additions and 45 deletions

View File

@@ -208,10 +208,11 @@ impl<F> IdentMacroExpander for F
} }
// Use a macro because forwarding to a simple function has type system issues // Use a macro because forwarding to a simple function has type system issues
macro_rules! make_stmt_default { macro_rules! make_stmts_default {
($me:expr) => { ($me:expr) => {
$me.make_expr().map(|e| { $me.make_expr().map(|e| {
P(codemap::respan(e.span, ast::StmtExpr(e, ast::DUMMY_NODE_ID))) SmallVector::one(P(codemap::respan(
e.span, ast::StmtExpr(e, ast::DUMMY_NODE_ID))))
}) })
} }
} }
@@ -238,12 +239,12 @@ pub trait MacResult {
None None
} }
/// Create a statement. /// Create zero or more statements.
/// ///
/// By default this attempts to create an expression statement, /// By default this attempts to create an expression statement,
/// returning None if that fails. /// returning None if that fails.
fn make_stmt(self: Box<Self>) -> Option<P<ast::Stmt>> { fn make_stmts(self: Box<Self>) -> Option<SmallVector<P<ast::Stmt>>> {
make_stmt_default!(self) make_stmts_default!(self)
} }
} }
@@ -276,7 +277,7 @@ make_MacEager! {
pat: P<ast::Pat>, pat: P<ast::Pat>,
items: SmallVector<P<ast::Item>>, items: SmallVector<P<ast::Item>>,
impl_items: SmallVector<P<ast::ImplItem>>, impl_items: SmallVector<P<ast::ImplItem>>,
stmt: P<ast::Stmt>, stmts: SmallVector<P<ast::Stmt>>,
} }
impl MacResult for MacEager { impl MacResult for MacEager {
@@ -292,10 +293,10 @@ impl MacResult for MacEager {
self.impl_items self.impl_items
} }
fn make_stmt(self: Box<Self>) -> Option<P<ast::Stmt>> { fn make_stmts(self: Box<Self>) -> Option<SmallVector<P<ast::Stmt>>> {
match self.stmt { match self.stmts.as_ref().map_or(0, |s| s.len()) {
None => make_stmt_default!(self), 0 => make_stmts_default!(self),
s => s, _ => self.stmts,
} }
} }
@@ -384,10 +385,11 @@ impl MacResult for DummyResult {
Some(SmallVector::zero()) Some(SmallVector::zero())
} }
} }
fn make_stmt(self: Box<DummyResult>) -> Option<P<ast::Stmt>> { fn make_stmts(self: Box<DummyResult>) -> Option<SmallVector<P<ast::Stmt>>> {
Some(P(codemap::respan(self.span, Some(SmallVector::one(P(
ast::StmtExpr(DummyResult::raw_expr(self.span), codemap::respan(self.span,
ast::DUMMY_NODE_ID)))) ast::StmtExpr(DummyResult::raw_expr(self.span),
ast::DUMMY_NODE_ID)))))
} }
} }

View File

@@ -745,34 +745,49 @@ pub fn expand_item_mac(it: P<ast::Item>,
} }
/// Expand a stmt /// Expand a stmt
fn expand_stmt(s: Stmt, fld: &mut MacroExpander) -> SmallVector<P<Stmt>> { fn expand_stmt(stmt: P<Stmt>, fld: &mut MacroExpander) -> SmallVector<P<Stmt>> {
let (mac, style) = match s.node { let stmt = stmt.and_then(|stmt| stmt);
let (mac, style) = match stmt.node {
StmtMac(mac, style) => (mac, style), StmtMac(mac, style) => (mac, style),
_ => return expand_non_macro_stmt(s, fld) _ => return expand_non_macro_stmt(stmt, fld)
}; };
let expanded_stmt = match expand_mac_invoc(mac.and_then(|m| m), s.span,
|r| r.make_stmt(), let maybe_new_items =
mark_stmt, fld) { expand_mac_invoc(mac.and_then(|m| m), stmt.span,
Some(stmt) => stmt, |r| r.make_stmts(),
None => { |stmts, mark| stmts.move_map(|m| mark_stmt(m, mark)),
return SmallVector::zero(); fld);
let fully_expanded = match maybe_new_items {
Some(stmts) => {
// Keep going, outside-in.
let new_items = stmts.into_iter().flat_map(|s| {
fld.fold_stmt(s).into_iter()
}).collect();
fld.cx.bt_pop();
new_items
} }
None => SmallVector::zero()
}; };
// Keep going, outside-in. // If this is a macro invocation with a semicolon, then apply that
let fully_expanded = fld.fold_stmt(expanded_stmt); // semicolon to the final statement produced by expansion.
fld.cx.bt_pop(); if style == MacStmtWithSemicolon && fully_expanded.len() > 0 {
let last_index = fully_expanded.len() - 1;
if style == MacStmtWithSemicolon { fully_expanded.into_iter().enumerate().map(|(i, stmt)|
fully_expanded.into_iter().map(|s| s.map(|Spanned {node, span}| { if i == last_index {
Spanned { stmt.map(|Spanned {node, span}| {
node: match node { Spanned {
StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id), node: match node {
_ => node /* might already have a semi */ StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id),
}, _ => node /* might already have a semi */
span: span },
} span: span
})).collect() }
})
} else {
stmt
}).collect()
} else { } else {
fully_expanded fully_expanded
} }
@@ -1389,7 +1404,7 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> {
} }
fn fold_stmt(&mut self, stmt: P<ast::Stmt>) -> SmallVector<P<ast::Stmt>> { fn fold_stmt(&mut self, stmt: P<ast::Stmt>) -> SmallVector<P<ast::Stmt>> {
stmt.and_then(|stmt| expand_stmt(stmt, self)) expand_stmt(stmt, self)
} }
fn fold_block(&mut self, block: P<Block>) -> P<Block> { fn fold_block(&mut self, block: P<Block>) -> P<Block> {
@@ -1541,8 +1556,8 @@ fn mark_pat(pat: P<ast::Pat>, m: Mrk) -> P<ast::Pat> {
} }
// apply a given mark to the given stmt. Used following the expansion of a macro. // apply a given mark to the given stmt. Used following the expansion of a macro.
fn mark_stmt(expr: P<ast::Stmt>, m: Mrk) -> P<ast::Stmt> { fn mark_stmt(stmt: P<ast::Stmt>, m: Mrk) -> P<ast::Stmt> {
Marker{mark:m}.fold_stmt(expr) Marker{mark:m}.fold_stmt(stmt)
.expect_one("marking a stmt didn't return exactly one stmt") .expect_one("marking a stmt didn't return exactly one stmt")
} }

View File

@@ -88,10 +88,24 @@ impl<'a> MacResult for ParserAnyMacro<'a> {
Some(ret) Some(ret)
} }
fn make_stmt(self: Box<ParserAnyMacro<'a>>) -> Option<P<ast::Stmt>> { fn make_stmts(self: Box<ParserAnyMacro<'a>>)
let ret = self.parser.borrow_mut().parse_stmt(); -> Option<SmallVector<P<ast::Stmt>>> {
self.ensure_complete_parse(true); let mut ret = SmallVector::zero();
ret loop {
let mut parser = self.parser.borrow_mut();
match parser.token {
token::Eof => break,
_ => match parser.parse_stmt_nopanic() {
Ok(maybe_stmt) => match maybe_stmt {
Some(stmt) => ret.push(stmt),
None => (),
},
Err(_) => break,
}
}
}
self.ensure_complete_parse(false);
Some(ret)
} }
} }

View File

@@ -17,7 +17,8 @@ macro_rules! ignored_item {
} }
macro_rules! ignored_expr { macro_rules! ignored_expr {
() => ( 1, 2 ) //~ ERROR macro expansion ignores token `,` () => ( 1, //~ ERROR unexpected token: `,`
2 ) //~ ERROR macro expansion ignores token `2`
} }
macro_rules! ignored_pat { macro_rules! ignored_pat {

View File

@@ -0,0 +1,32 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
macro_rules! foo {
() => {
struct Bar;
struct Baz;
}
}
macro_rules! grault {
() => {
foo!();
struct Xyzzy;
}
}
fn static_assert_exists<T>() { }
fn main() {
grault!();
static_assert_exists::<Bar>();
static_assert_exists::<Baz>();
static_assert_exists::<Xyzzy>();
}

View File

@@ -0,0 +1,29 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
macro_rules! foo {
() => {
struct Bar;
struct Baz;
}
}
macro_rules! grault {
() => {{
foo!();
struct Xyzzy;
0
}}
}
fn main() {
let x = grault!();
assert_eq!(x, 0);
}