From 19343860aa44d1c31a7802df22349f055ed9da16 Mon Sep 17 00:00:00 2001 From: Christopher Chambers Date: Tue, 7 Apr 2015 08:21:18 -0500 Subject: [PATCH 1/5] 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 --- src/libsyntax/ext/base.rs | 30 +++++---- src/libsyntax/ext/expand.rs | 67 ++++++++++++------- src/libsyntax/ext/tt/macro_rules.rs | 22 ++++-- .../compile-fail/macro-incomplete-parse.rs | 3 +- src/test/run-pass/macro-nested_stmt_macros.rs | 32 +++++++++ .../macro-stmt_macro_in_expr_macro.rs | 29 ++++++++ 6 files changed, 138 insertions(+), 45 deletions(-) create mode 100644 src/test/run-pass/macro-nested_stmt_macros.rs create mode 100644 src/test/run-pass/macro-stmt_macro_in_expr_macro.rs diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 80ee92608a5..346fb3580e1 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -208,10 +208,11 @@ impl IdentMacroExpander for F } // 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.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 } - /// Create a statement. + /// Create zero or more statements. /// /// By default this attempts to create an expression statement, /// returning None if that fails. - fn make_stmt(self: Box) -> Option> { - make_stmt_default!(self) + fn make_stmts(self: Box) -> Option>> { + make_stmts_default!(self) } } @@ -276,7 +277,7 @@ make_MacEager! { pat: P, items: SmallVector>, impl_items: SmallVector>, - stmt: P, + stmts: SmallVector>, } impl MacResult for MacEager { @@ -292,10 +293,10 @@ impl MacResult for MacEager { self.impl_items } - fn make_stmt(self: Box) -> Option> { - match self.stmt { - None => make_stmt_default!(self), - s => s, + fn make_stmts(self: Box) -> Option>> { + match self.stmts.as_ref().map_or(0, |s| s.len()) { + 0 => make_stmts_default!(self), + _ => self.stmts, } } @@ -384,10 +385,11 @@ impl MacResult for DummyResult { Some(SmallVector::zero()) } } - fn make_stmt(self: Box) -> Option> { - Some(P(codemap::respan(self.span, - ast::StmtExpr(DummyResult::raw_expr(self.span), - ast::DUMMY_NODE_ID)))) + fn make_stmts(self: Box) -> Option>> { + Some(SmallVector::one(P( + codemap::respan(self.span, + ast::StmtExpr(DummyResult::raw_expr(self.span), + ast::DUMMY_NODE_ID))))) } } diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index f13047d3725..b6d1810a3f7 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -745,34 +745,49 @@ pub fn expand_item_mac(it: P, } /// Expand a stmt -fn expand_stmt(s: Stmt, fld: &mut MacroExpander) -> SmallVector> { - let (mac, style) = match s.node { +fn expand_stmt(stmt: P, fld: &mut MacroExpander) -> SmallVector> { + let stmt = stmt.and_then(|stmt| stmt); + let (mac, style) = match stmt.node { 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(), - mark_stmt, fld) { - Some(stmt) => stmt, - None => { - return SmallVector::zero(); + + let maybe_new_items = + expand_mac_invoc(mac.and_then(|m| m), stmt.span, + |r| r.make_stmts(), + |stmts, mark| stmts.move_map(|m| mark_stmt(m, mark)), + 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. - let fully_expanded = fld.fold_stmt(expanded_stmt); - fld.cx.bt_pop(); - - if style == MacStmtWithSemicolon { - fully_expanded.into_iter().map(|s| s.map(|Spanned {node, span}| { - Spanned { - node: match node { - StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id), - _ => node /* might already have a semi */ - }, - span: span - } - })).collect() + // If this is a macro invocation with a semicolon, then apply that + // semicolon to the final statement produced by expansion. + if style == MacStmtWithSemicolon && fully_expanded.len() > 0 { + let last_index = fully_expanded.len() - 1; + fully_expanded.into_iter().enumerate().map(|(i, stmt)| + if i == last_index { + stmt.map(|Spanned {node, span}| { + Spanned { + node: match node { + StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id), + _ => node /* might already have a semi */ + }, + span: span + } + }) + } else { + stmt + }).collect() } else { fully_expanded } @@ -1389,7 +1404,7 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> { } fn fold_stmt(&mut self, stmt: P) -> SmallVector> { - stmt.and_then(|stmt| expand_stmt(stmt, self)) + expand_stmt(stmt, self) } fn fold_block(&mut self, block: P) -> P { @@ -1541,8 +1556,8 @@ fn mark_pat(pat: P, m: Mrk) -> P { } // apply a given mark to the given stmt. Used following the expansion of a macro. -fn mark_stmt(expr: P, m: Mrk) -> P { - Marker{mark:m}.fold_stmt(expr) +fn mark_stmt(stmt: P, m: Mrk) -> P { + Marker{mark:m}.fold_stmt(stmt) .expect_one("marking a stmt didn't return exactly one stmt") } diff --git a/src/libsyntax/ext/tt/macro_rules.rs b/src/libsyntax/ext/tt/macro_rules.rs index 250ba0442ba..8a331bfac0b 100644 --- a/src/libsyntax/ext/tt/macro_rules.rs +++ b/src/libsyntax/ext/tt/macro_rules.rs @@ -88,10 +88,24 @@ impl<'a> MacResult for ParserAnyMacro<'a> { Some(ret) } - fn make_stmt(self: Box>) -> Option> { - let ret = self.parser.borrow_mut().parse_stmt(); - self.ensure_complete_parse(true); - ret + fn make_stmts(self: Box>) + -> Option>> { + let mut ret = SmallVector::zero(); + 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) } } diff --git a/src/test/compile-fail/macro-incomplete-parse.rs b/src/test/compile-fail/macro-incomplete-parse.rs index 53b29ccb0c0..137a3ab42bd 100644 --- a/src/test/compile-fail/macro-incomplete-parse.rs +++ b/src/test/compile-fail/macro-incomplete-parse.rs @@ -17,7 +17,8 @@ macro_rules! ignored_item { } 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 { diff --git a/src/test/run-pass/macro-nested_stmt_macros.rs b/src/test/run-pass/macro-nested_stmt_macros.rs new file mode 100644 index 00000000000..5997a4f18e7 --- /dev/null +++ b/src/test/run-pass/macro-nested_stmt_macros.rs @@ -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 or the MIT license +// , 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() { } + +fn main() { + grault!(); + static_assert_exists::(); + static_assert_exists::(); + static_assert_exists::(); +} diff --git a/src/test/run-pass/macro-stmt_macro_in_expr_macro.rs b/src/test/run-pass/macro-stmt_macro_in_expr_macro.rs new file mode 100644 index 00000000000..c5badd78a63 --- /dev/null +++ b/src/test/run-pass/macro-stmt_macro_in_expr_macro.rs @@ -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 or the MIT license +// , 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); +} From b16cfacbccee272ed8a30aec1f5c25e9f845d1a8 Mon Sep 17 00:00:00 2001 From: Christopher Chambers Date: Fri, 10 Apr 2015 21:50:23 -0500 Subject: [PATCH 2/5] Improves semicolon expansion efficiency, corrects bt_pop placement. Implements pop() on SmallVector, and uses it to expand the final semicolon in a statement macro expansion more efficiently. Corrects the placement of the call to fld.cx.bt_pop(). It must run unconditionally to reverse the corresponding push. --- src/libsyntax/ext/expand.rs | 31 +++++++++++++------------ src/libsyntax/util/small_vector.rs | 36 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index b6d1810a3f7..9c1b32aadbd 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -758,25 +758,23 @@ fn expand_stmt(stmt: P, fld: &mut MacroExpander) -> SmallVector> { |stmts, mark| stmts.move_map(|m| mark_stmt(m, mark)), fld); - let fully_expanded = match maybe_new_items { + let mut fully_expanded = match maybe_new_items { Some(stmts) => { // Keep going, outside-in. - let new_items = stmts.into_iter().flat_map(|s| { + stmts.into_iter().flat_map(|s| { fld.fold_stmt(s).into_iter() - }).collect(); - fld.cx.bt_pop(); - new_items + }).collect() } None => SmallVector::zero() }; + fld.cx.bt_pop(); // If this is a macro invocation with a semicolon, then apply that // semicolon to the final statement produced by expansion. - if style == MacStmtWithSemicolon && fully_expanded.len() > 0 { - let last_index = fully_expanded.len() - 1; - fully_expanded.into_iter().enumerate().map(|(i, stmt)| - if i == last_index { - stmt.map(|Spanned {node, span}| { + if style == MacStmtWithSemicolon && !fully_expanded.is_empty() { + match fully_expanded.pop() { + Some(stmt) => { + let new_stmt = stmt.map(|Spanned {node, span}| { Spanned { node: match node { StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id), @@ -784,13 +782,14 @@ fn expand_stmt(stmt: P, fld: &mut MacroExpander) -> SmallVector> { }, span: span } - }) - } else { - stmt - }).collect() - } else { - fully_expanded + }); + fully_expanded.push(new_stmt); + } + None => (), + } } + + fully_expanded } // expand a non-macro stmt. this is essentially the fallthrough for diff --git a/src/libsyntax/util/small_vector.rs b/src/libsyntax/util/small_vector.rs index 1649934f4b1..c4b096d656f 100644 --- a/src/libsyntax/util/small_vector.rs +++ b/src/libsyntax/util/small_vector.rs @@ -69,6 +69,42 @@ impl SmallVector { } } + pub fn pop(&mut self) -> Option { + match self.repr { + Zero => None, + One(..) => { + let one = mem::replace(&mut self.repr, Zero); + match one { + One(v1) => Some(v1), + _ => unreachable!() + } + } + Many(..) => { + let mut many = mem::replace(&mut self.repr, Zero); + let item = + match many { + Many(ref mut vs) if vs.len() == 1 => { + // self.repr is already Zero + vs.pop() + }, + Many(ref mut vs) if vs.len() == 2 => { + let item = vs.pop(); + mem::replace(&mut self.repr, One(vs.pop().unwrap())); + item + }, + Many(ref mut vs) if vs.len() > 2 => { + let item = vs.pop(); + let rest = mem::replace(vs, vec!()); + mem::replace(&mut self.repr, Many(rest)); + item + }, + _ => unreachable!() + }; + item + } + } + } + pub fn push(&mut self, v: T) { match self.repr { Zero => self.repr = One(v), From fae29e497c08fcdde8a429a7e2b656e7a8b6cc17 Mon Sep 17 00:00:00 2001 From: Christopher Chambers Date: Fri, 10 Apr 2015 22:20:01 -0500 Subject: [PATCH 3/5] Eliminates a pointless is_empty test. --- src/libsyntax/ext/expand.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 9c1b32aadbd..4a6c45b5be5 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -771,7 +771,7 @@ fn expand_stmt(stmt: P, fld: &mut MacroExpander) -> SmallVector> { // If this is a macro invocation with a semicolon, then apply that // semicolon to the final statement produced by expansion. - if style == MacStmtWithSemicolon && !fully_expanded.is_empty() { + if style == MacStmtWithSemicolon { match fully_expanded.pop() { Some(stmt) => { let new_stmt = stmt.map(|Spanned {node, span}| { From 22eb3193a62fa01e97bf53d5f5aa74ca8ea57a67 Mon Sep 17 00:00:00 2001 From: Christopher Chambers Date: Fri, 10 Apr 2015 23:06:34 -0500 Subject: [PATCH 4/5] Simplifications to statement macro handling. SmallVector::pop no longer worries about converting a Many repr downward to One or Zero. expand_stmt makes use of `if let` for style purposes. --- src/libsyntax/ext/expand.rs | 25 +++++++++++-------------- src/libsyntax/util/small_vector.rs | 24 +----------------------- 2 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 4a6c45b5be5..8e6b5f85440 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -772,20 +772,17 @@ fn expand_stmt(stmt: P, fld: &mut MacroExpander) -> SmallVector> { // If this is a macro invocation with a semicolon, then apply that // semicolon to the final statement produced by expansion. if style == MacStmtWithSemicolon { - match fully_expanded.pop() { - Some(stmt) => { - let new_stmt = stmt.map(|Spanned {node, span}| { - Spanned { - node: match node { - StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id), - _ => node /* might already have a semi */ - }, - span: span - } - }); - fully_expanded.push(new_stmt); - } - None => (), + if let Some(stmt) = fully_expanded.pop() { + let new_stmt = stmt.map(|Spanned {node, span}| { + Spanned { + node: match node { + StmtExpr(e, stmt_id) => StmtSemi(e, stmt_id), + _ => node /* might already have a semi */ + }, + span: span + } + }); + fully_expanded.push(new_stmt); } } diff --git a/src/libsyntax/util/small_vector.rs b/src/libsyntax/util/small_vector.rs index c4b096d656f..6b864d52947 100644 --- a/src/libsyntax/util/small_vector.rs +++ b/src/libsyntax/util/small_vector.rs @@ -79,29 +79,7 @@ impl SmallVector { _ => unreachable!() } } - Many(..) => { - let mut many = mem::replace(&mut self.repr, Zero); - let item = - match many { - Many(ref mut vs) if vs.len() == 1 => { - // self.repr is already Zero - vs.pop() - }, - Many(ref mut vs) if vs.len() == 2 => { - let item = vs.pop(); - mem::replace(&mut self.repr, One(vs.pop().unwrap())); - item - }, - Many(ref mut vs) if vs.len() > 2 => { - let item = vs.pop(); - let rest = mem::replace(vs, vec!()); - mem::replace(&mut self.repr, Many(rest)); - item - }, - _ => unreachable!() - }; - item - } + Many(ref mut vs) => vs.pop(), } } From 77627ea8dd3b310d02138647363e72fdd024bb6c Mon Sep 17 00:00:00 2001 From: Christopher Chambers Date: Sat, 11 Apr 2015 00:59:00 -0500 Subject: [PATCH 5/5] Moves expand_stmt's bt_pop so that it balances correctly. --- src/libsyntax/ext/expand.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 8e6b5f85440..8aa6acaeec2 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -761,13 +761,14 @@ fn expand_stmt(stmt: P, fld: &mut MacroExpander) -> SmallVector> { let mut fully_expanded = match maybe_new_items { Some(stmts) => { // Keep going, outside-in. - stmts.into_iter().flat_map(|s| { + let new_items = stmts.into_iter().flat_map(|s| { fld.fold_stmt(s).into_iter() - }).collect() + }).collect(); + fld.cx.bt_pop(); + new_items } None => SmallVector::zero() }; - fld.cx.bt_pop(); // If this is a macro invocation with a semicolon, then apply that // semicolon to the final statement produced by expansion.