diff --git a/src/libregex_macros/lib.rs b/src/libregex_macros/lib.rs index 7163dfa3b16ab..07539a11113f5 100644 --- a/src/libregex_macros/lib.rs +++ b/src/libregex_macros/lib.rs @@ -34,6 +34,7 @@ use syntax::ext::build::AstBuilder; use syntax::ext::base::{ExtCtxt, MacResult, MacExpr, DummyResult}; use syntax::parse::token; use syntax::print::pprust; +use syntax::fold::Folder; use rustc::plugin::Registry; @@ -615,7 +616,7 @@ fn exec<'t>(which: ::regex::native::MatchKind, input: &'t str, /// Otherwise, logs an error with cx.span_err and returns None. fn parse(cx: &mut ExtCtxt, tts: &[ast::TokenTree]) -> Option { let mut parser = cx.new_parser_from_tts(tts); - let entry = cx.expand_expr(parser.parse_expr()); + let entry = cx.expander().fold_expr(parser.parse_expr()); let regex = match entry.node { ast::ExprLit(lit) => { match lit.node { diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 5341f0c2d61b2..a66d6839ab074 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -20,9 +20,11 @@ use parse::token; use parse::token::{InternedString, intern, str_to_ident}; use util::small_vector::SmallVector; use ext::mtwt; +use fold::Folder; use std::collections::HashMap; use std::gc::{Gc, GC}; +use std::rc::Rc; // new-style macro! tt code: // @@ -104,9 +106,9 @@ pub type IdentMacroExpanderFn = /// just into the compiler's internal macro table, for `make_def`). pub trait MacResult { /// Define a new macro. - // this particular flavor should go away; the idea that a macro might - // expand into either a macro definition or an expression, depending - // on what the context wants, is kind of silly. + // this should go away; the idea that a macro might expand into + // either a macro definition or an expression, depending on what + // the context wants, is kind of silly. fn make_def(&self) -> Option { None } @@ -314,7 +316,7 @@ impl BlockInfo { /// The base map of methods for expanding syntax extension /// AST nodes into full ASTs -pub fn syntax_expander_table() -> SyntaxEnv { +fn initial_syntax_expander_table() -> SyntaxEnv { // utility function to simplify creating NormalTT syntax extensions fn builtin_normal_expander(f: MacroExpanderFn) -> SyntaxExtension { NormalTT(box BasicMacroExpander { @@ -431,7 +433,9 @@ pub struct ExtCtxt<'a> { pub mod_path: Vec , pub trace_mac: bool, - pub exported_macros: Vec> + pub exported_macros: Vec>, + + pub syntax_env: SyntaxEnv, } impl<'a> ExtCtxt<'a> { @@ -445,22 +449,18 @@ impl<'a> ExtCtxt<'a> { ecfg: ecfg, trace_mac: false, exported_macros: Vec::new(), + syntax_env: initial_syntax_expander_table(), } } - pub fn expand_expr(&mut self, mut e: Gc) -> Gc { - loop { - match e.node { - ast::ExprMac(..) => { - let mut expander = expand::MacroExpander { - extsbox: syntax_expander_table(), - cx: self, - }; - e = expand::expand_expr(e, &mut expander); - } - _ => return e - } - } + #[deprecated = "Replaced with `expander().fold_expr()`"] + pub fn expand_expr(&mut self, e: Gc) -> Gc { + self.expander().fold_expr(e) + } + + /// Returns a `Folder` for deeply expanding all macros in a AST node. + pub fn expander<'b>(&'b mut self) -> expand::MacroExpander<'b, 'a> { + expand::MacroExpander { cx: self } } pub fn new_parser_from_tts(&self, tts: &[ast::TokenTree]) @@ -570,7 +570,7 @@ impl<'a> ExtCtxt<'a> { pub fn expr_to_string(cx: &mut ExtCtxt, expr: Gc, err_msg: &str) -> Option<(InternedString, ast::StrStyle)> { // we want to be able to handle e.g. concat("foo", "bar") - let expr = cx.expand_expr(expr); + let expr = cx.expander().fold_expr(expr); match expr.node { ast::ExprLit(l) => match l.node { ast::LitStr(ref s, style) => return Some(((*s).clone(), style)), @@ -627,7 +627,7 @@ pub fn get_exprs_from_tts(cx: &mut ExtCtxt, let mut p = cx.new_parser_from_tts(tts); let mut es = Vec::new(); while p.token != token::EOF { - es.push(cx.expand_expr(p.parse_expr())); + es.push(cx.expander().fold_expr(p.parse_expr())); if p.eat(&token::COMMA) { continue; } @@ -642,10 +642,13 @@ pub fn get_exprs_from_tts(cx: &mut ExtCtxt, /// In order to have some notion of scoping for macros, /// we want to implement the notion of a transformation /// environment. - +/// /// This environment maps Names to SyntaxExtensions. +pub struct SyntaxEnv { + chain: Vec , +} -//impl question: how to implement it? Initially, the +// impl question: how to implement it? Initially, the // env will contain only macros, so it might be painful // to add an empty frame for every context. Let's just // get it working, first.... @@ -657,15 +660,11 @@ pub fn get_exprs_from_tts(cx: &mut ExtCtxt, struct MapChainFrame { info: BlockInfo, - map: HashMap, -} - -pub struct SyntaxEnv { - chain: Vec , + map: HashMap>, } impl SyntaxEnv { - pub fn new() -> SyntaxEnv { + fn new() -> SyntaxEnv { let mut map = SyntaxEnv { chain: Vec::new() }; map.push_frame(); map @@ -692,10 +691,10 @@ impl SyntaxEnv { unreachable!() } - pub fn find<'a>(&'a self, k: &Name) -> Option<&'a SyntaxExtension> { + pub fn find(&self, k: &Name) -> Option> { for frame in self.chain.iter().rev() { match frame.map.find(k) { - Some(v) => return Some(v), + Some(v) => return Some(v.clone()), None => {} } } @@ -703,7 +702,7 @@ impl SyntaxEnv { } pub fn insert(&mut self, k: Name, v: SyntaxExtension) { - self.find_escape_frame().map.insert(k, v); + self.find_escape_frame().map.insert(k, Rc::new(v)); } pub fn info<'a>(&'a mut self) -> &'a mut BlockInfo { diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index c10f3ce07749b..123dcf366f440 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -32,7 +32,7 @@ use util::small_vector::SmallVector; use std::gc::{Gc, GC}; -pub fn expand_expr(e: Gc, fld: &mut MacroExpander) -> Gc { +fn expand_expr(e: Gc, fld: &mut MacroExpander) -> Gc { match e.node { // expr_mac should really be expr_ext or something; it's the // entry-point for all syntax extensions. @@ -208,7 +208,7 @@ fn expand_mac_invoc(mac: &ast::Mac, span: &codemap::Span, } let extname = pth.segments.get(0).identifier; let extnamestr = token::get_ident(extname); - match fld.extsbox.find(&extname.name) { + match fld.cx.syntax_env.find(&extname.name) { None => { fld.cx.span_err( pth.span, @@ -218,46 +218,48 @@ fn expand_mac_invoc(mac: &ast::Mac, span: &codemap::Span, // let compilation continue None } - Some(&NormalTT(ref expandfun, exp_span)) => { - fld.cx.bt_push(ExpnInfo { - call_site: *span, - callee: NameAndSpan { - name: extnamestr.get().to_string(), - format: MacroBang, - span: exp_span, - }, - }); - let fm = fresh_mark(); - let marked_before = mark_tts(tts.as_slice(), fm); - - // The span that we pass to the expanders we want to - // be the root of the call stack. That's the most - // relevant span and it's the actual invocation of - // the macro. - let mac_span = original_span(fld.cx); - - let expanded = expandfun.expand(fld.cx, - mac_span.call_site, - marked_before.as_slice()); - let parsed = match parse_thunk(expanded) { - Some(e) => e, - None => { - fld.cx.span_err( - pth.span, - format!("non-expression macro in expression position: {}", - extnamestr.get().as_slice() - ).as_slice()); - return None; - } - }; - Some(mark_thunk(parsed,fm)) - } - _ => { - fld.cx.span_err( - pth.span, - format!("'{}' is not a tt-style macro", - extnamestr.get()).as_slice()); - None + Some(rc) => match *rc { + NormalTT(ref expandfun, exp_span) => { + fld.cx.bt_push(ExpnInfo { + call_site: *span, + callee: NameAndSpan { + name: extnamestr.get().to_string(), + format: MacroBang, + span: exp_span, + }, + }); + let fm = fresh_mark(); + let marked_before = mark_tts(tts.as_slice(), fm); + + // The span that we pass to the expanders we want to + // be the root of the call stack. That's the most + // relevant span and it's the actual invocation of + // the macro. + let mac_span = original_span(fld.cx); + + let expanded = expandfun.expand(fld.cx, + mac_span.call_site, + marked_before.as_slice()); + let parsed = match parse_thunk(expanded) { + Some(e) => e, + None => { + fld.cx.span_err( + pth.span, + format!("non-expression macro in expression position: {}", + extnamestr.get().as_slice() + ).as_slice()); + return None; + } + }; + Some(mark_thunk(parsed,fm)) + } + _ => { + fld.cx.span_err( + pth.span, + format!("'{}' is not a tt-style macro", + extnamestr.get()).as_slice()); + None + } } } } @@ -288,10 +290,10 @@ fn expand_loop_block(loop_block: P, // The rename *must* be added to the enclosed syntax context for // `break` or `continue` to pick up because by definition they are // in a block enclosed by loop head. - fld.extsbox.push_frame(); - fld.extsbox.info().pending_renames.push(rename); + fld.cx.syntax_env.push_frame(); + fld.cx.syntax_env.info().pending_renames.push(rename); let expanded_block = expand_block_elts(&*loop_block, fld); - fld.extsbox.pop_frame(); + fld.cx.syntax_env.pop_frame(); (expanded_block, Some(renamed_ident)) } @@ -321,29 +323,32 @@ fn expand_item(it: Gc, fld: &mut MacroExpander) for attr in it.attrs.iter() { let mname = attr.name(); - match fld.extsbox.find(&intern(mname.get())) { - Some(&ItemDecorator(dec_fn)) => { - attr::mark_used(attr); + match fld.cx.syntax_env.find(&intern(mname.get())) { + Some(rc) => match *rc { + ItemDecorator(dec_fn) => { + attr::mark_used(attr); - fld.cx.bt_push(ExpnInfo { - call_site: attr.span, - callee: NameAndSpan { - name: mname.get().to_string(), - format: MacroAttribute, - span: None - } - }); + fld.cx.bt_push(ExpnInfo { + call_site: attr.span, + callee: NameAndSpan { + name: mname.get().to_string(), + format: MacroAttribute, + span: None + } + }); - // we'd ideally decorator_items.push_all(expand_item(item, fld)), - // but that double-mut-borrows fld - let mut items: SmallVector> = SmallVector::zero(); - dec_fn(fld.cx, attr.span, attr.node.value, it, - |item| items.push(item)); - decorator_items.extend(items.move_iter() - .flat_map(|item| expand_item(item, fld).move_iter())); + // we'd ideally decorator_items.push_all(expand_item(item, fld)), + // but that double-mut-borrows fld + let mut items: SmallVector> = SmallVector::zero(); + dec_fn(fld.cx, attr.span, attr.node.value, it, + |item| items.push(item)); + decorator_items.extend(items.move_iter() + .flat_map(|item| expand_item(item, fld).move_iter())); - fld.cx.bt_pop(); - } + fld.cx.bt_pop(); + } + _ => new_attrs.push((*attr).clone()), + }, _ => new_attrs.push((*attr).clone()), } } @@ -353,7 +358,7 @@ fn expand_item(it: Gc, fld: &mut MacroExpander) ast::ItemMod(_) | ast::ItemForeignMod(_) => { fld.cx.mod_push(it.ident); let macro_escape = contains_macro_escape(new_attrs.as_slice()); - let result = with_exts_frame!(fld.extsbox, + let result = with_exts_frame!(fld.cx.syntax_env, macro_escape, noop_fold_item(&*it, fld)); fld.cx.mod_pop(); @@ -377,8 +382,8 @@ fn expand_item_modifiers(mut it: Gc, fld: &mut MacroExpander) -> Gc { // partition the attributes into ItemModifiers and others let (modifiers, other_attrs) = it.attrs.partitioned(|attr| { - match fld.extsbox.find(&intern(attr.name().get())) { - Some(&ItemModifier(_)) => true, + match fld.cx.syntax_env.find(&intern(attr.name().get())) { + Some(rc) => match *rc { ItemModifier(_) => true, _ => false }, _ => false } }); @@ -395,20 +400,23 @@ fn expand_item_modifiers(mut it: Gc, fld: &mut MacroExpander) for attr in modifiers.iter() { let mname = attr.name(); - match fld.extsbox.find(&intern(mname.get())) { - Some(&ItemModifier(dec_fn)) => { - attr::mark_used(attr); - fld.cx.bt_push(ExpnInfo { - call_site: attr.span, - callee: NameAndSpan { - name: mname.get().to_string(), - format: MacroAttribute, - span: None, - } - }); - it = dec_fn(fld.cx, attr.span, attr.node.value, it); - fld.cx.bt_pop(); - } + match fld.cx.syntax_env.find(&intern(mname.get())) { + Some(rc) => match *rc { + ItemModifier(dec_fn) => { + attr::mark_used(attr); + fld.cx.bt_push(ExpnInfo { + call_site: attr.span, + callee: NameAndSpan { + name: mname.get().to_string(), + format: MacroAttribute, + span: None, + } + }); + it = dec_fn(fld.cx, attr.span, attr.node.value, it); + fld.cx.bt_pop(); + } + _ => unreachable!() + }, _ => unreachable!() } } @@ -452,7 +460,7 @@ fn expand_item_mac(it: Gc, fld: &mut MacroExpander) let extname = pth.segments.get(0).identifier; let extnamestr = token::get_ident(extname); let fm = fresh_mark(); - let expanded = match fld.extsbox.find(&extname.name) { + let expanded = match fld.cx.syntax_env.find(&extname.name) { None => { fld.cx.span_err(pth.span, format!("macro undefined: '{}!'", @@ -461,70 +469,72 @@ fn expand_item_mac(it: Gc, fld: &mut MacroExpander) return SmallVector::zero(); } - Some(&NormalTT(ref expander, span)) => { - if it.ident.name != parse::token::special_idents::invalid.name { - fld.cx - .span_err(pth.span, - format!("macro {}! expects no ident argument, \ - given '{}'", - extnamestr, - token::get_ident(it.ident)).as_slice()); - return SmallVector::zero(); + Some(rc) => match *rc { + NormalTT(ref expander, span) => { + if it.ident.name != parse::token::special_idents::invalid.name { + fld.cx + .span_err(pth.span, + format!("macro {}! expects no ident argument, \ + given '{}'", + extnamestr, + token::get_ident(it.ident)).as_slice()); + return SmallVector::zero(); + } + fld.cx.bt_push(ExpnInfo { + call_site: it.span, + callee: NameAndSpan { + name: extnamestr.get().to_string(), + format: MacroBang, + span: span + } + }); + // mark before expansion: + let marked_before = mark_tts(tts.as_slice(), fm); + expander.expand(fld.cx, it.span, marked_before.as_slice()) } - fld.cx.bt_push(ExpnInfo { - call_site: it.span, - callee: NameAndSpan { - name: extnamestr.get().to_string(), - format: MacroBang, - span: span + IdentTT(ref expander, span) => { + if it.ident.name == parse::token::special_idents::invalid.name { + fld.cx.span_err(pth.span, + format!("macro {}! expects an ident argument", + extnamestr.get()).as_slice()); + return SmallVector::zero(); } - }); - // mark before expansion: - let marked_before = mark_tts(tts.as_slice(), fm); - expander.expand(fld.cx, it.span, marked_before.as_slice()) - } - Some(&IdentTT(ref expander, span)) => { - if it.ident.name == parse::token::special_idents::invalid.name { - fld.cx.span_err(pth.span, - format!("macro {}! expects an ident argument", - extnamestr.get()).as_slice()); - return SmallVector::zero(); + fld.cx.bt_push(ExpnInfo { + call_site: it.span, + callee: NameAndSpan { + name: extnamestr.get().to_string(), + format: MacroBang, + span: span + } + }); + // mark before expansion: + let marked_tts = mark_tts(tts.as_slice(), fm); + expander.expand(fld.cx, it.span, it.ident, marked_tts) } - fld.cx.bt_push(ExpnInfo { - call_site: it.span, - callee: NameAndSpan { - name: extnamestr.get().to_string(), - format: MacroBang, - span: span + LetSyntaxTT(ref expander, span) => { + if it.ident.name == parse::token::special_idents::invalid.name { + fld.cx.span_err(pth.span, + format!("macro {}! expects an ident argument", + extnamestr.get()).as_slice()); + return SmallVector::zero(); } - }); - // mark before expansion: - let marked_tts = mark_tts(tts.as_slice(), fm); - expander.expand(fld.cx, it.span, it.ident, marked_tts) - } - Some(&LetSyntaxTT(ref expander, span)) => { - if it.ident.name == parse::token::special_idents::invalid.name { - fld.cx.span_err(pth.span, - format!("macro {}! expects an ident argument", + fld.cx.bt_push(ExpnInfo { + call_site: it.span, + callee: NameAndSpan { + name: extnamestr.get().to_string(), + format: MacroBang, + span: span + } + }); + // DON'T mark before expansion: + expander.expand(fld.cx, it.span, it.ident, tts) + } + _ => { + fld.cx.span_err(it.span, + format!("{}! is not legal in item position", extnamestr.get()).as_slice()); return SmallVector::zero(); } - fld.cx.bt_push(ExpnInfo { - call_site: it.span, - callee: NameAndSpan { - name: extnamestr.get().to_string(), - format: MacroBang, - span: span - } - }); - // DON'T mark before expansion: - expander.expand(fld.cx, it.span, it.ident, tts) - } - _ => { - fld.cx.span_err(it.span, - format!("{}! is not legal in item position", - extnamestr.get()).as_slice()); - return SmallVector::zero(); } }; @@ -534,7 +544,7 @@ fn expand_item_mac(it: Gc, fld: &mut MacroExpander) // result of expanding a LetSyntaxTT, and thus doesn't // need to be marked. Not that it could be marked anyway. // create issue to recommend refactoring here? - fld.extsbox.insert(intern(name.as_slice()), ext); + fld.cx.syntax_env.insert(intern(name.as_slice()), ext); if attr::contains_name(it.attrs.as_slice(), "macro_export") { fld.cx.exported_macros.push(it); } @@ -641,7 +651,7 @@ fn expand_non_macro_stmt(s: &Stmt, fld: &mut MacroExpander) rename_fld.fold_pat(expanded_pat) }; // add them to the existing pending renames: - fld.extsbox.info().pending_renames.push_all_move(new_pending_renames); + fld.cx.syntax_env.info().pending_renames.push_all_move(new_pending_renames); // also, don't forget to expand the init: let new_init_opt = init.map(|e| fld.fold_expr(e)); let rewritten_local = @@ -742,7 +752,7 @@ fn fn_decl_arg_bindings(fn_decl: &ast::FnDecl) -> Vec { // expand a block. pushes a new exts_frame, then calls expand_block_elts fn expand_block(blk: &Block, fld: &mut MacroExpander) -> P { // see note below about treatment of exts table - with_exts_frame!(fld.extsbox,false, + with_exts_frame!(fld.cx.syntax_env,false, expand_block_elts(blk, fld)) } @@ -753,7 +763,7 @@ fn expand_block_elts(b: &Block, fld: &mut MacroExpander) -> P { b.stmts.iter().flat_map(|x| { // perform all pending renames let renamed_stmt = { - let pending_renames = &mut fld.extsbox.info().pending_renames; + let pending_renames = &mut fld.cx.syntax_env.info().pending_renames; let mut rename_fld = IdentRenamer{renames:pending_renames}; rename_fld.fold_stmt(&**x).expect_one("rename_fold didn't return one value") }; @@ -762,7 +772,7 @@ fn expand_block_elts(b: &Block, fld: &mut MacroExpander) -> P { }).collect(); let new_expr = b.expr.map(|x| { let expr = { - let pending_renames = &mut fld.extsbox.info().pending_renames; + let pending_renames = &mut fld.cx.syntax_env.info().pending_renames; let mut rename_fld = IdentRenamer{renames:pending_renames}; rename_fld.fold_expr(x) }; @@ -795,7 +805,7 @@ fn expand_pat(p: Gc, fld: &mut MacroExpander) -> Gc { } let extname = pth.segments.get(0).identifier; let extnamestr = token::get_ident(extname); - let marked_after = match fld.extsbox.find(&extname.name) { + let marked_after = match fld.cx.syntax_env.find(&extname.name) { None => { fld.cx.span_err(pth.span, format!("macro undefined: '{}!'", @@ -804,43 +814,45 @@ fn expand_pat(p: Gc, fld: &mut MacroExpander) -> Gc { return DummyResult::raw_pat(p.span); } - Some(&NormalTT(ref expander, span)) => { - fld.cx.bt_push(ExpnInfo { - call_site: p.span, - callee: NameAndSpan { - name: extnamestr.get().to_string(), - format: MacroBang, - span: span - } - }); - - let fm = fresh_mark(); - let marked_before = mark_tts(tts.as_slice(), fm); - let mac_span = original_span(fld.cx); - let expanded = match expander.expand(fld.cx, - mac_span.call_site, - marked_before.as_slice()).make_pat() { - Some(e) => e, - None => { - fld.cx.span_err( - pth.span, - format!( - "non-pattern macro in pattern position: {}", - extnamestr.get() - ).as_slice() - ); - return DummyResult::raw_pat(p.span); - } - }; + Some(rc) => match *rc { + NormalTT(ref expander, span) => { + fld.cx.bt_push(ExpnInfo { + call_site: p.span, + callee: NameAndSpan { + name: extnamestr.get().to_string(), + format: MacroBang, + span: span + } + }); - // mark after: - mark_pat(expanded,fm) - } - _ => { - fld.cx.span_err(p.span, - format!("{}! is not legal in pattern position", - extnamestr.get()).as_slice()); - return DummyResult::raw_pat(p.span); + let fm = fresh_mark(); + let marked_before = mark_tts(tts.as_slice(), fm); + let mac_span = original_span(fld.cx); + let expanded = match expander.expand(fld.cx, + mac_span.call_site, + marked_before.as_slice()).make_pat() { + Some(e) => e, + None => { + fld.cx.span_err( + pth.span, + format!( + "non-pattern macro in pattern position: {}", + extnamestr.get() + ).as_slice() + ); + return DummyResult::raw_pat(p.span); + } + }; + + // mark after: + mark_pat(expanded,fm) + } + _ => { + fld.cx.span_err(p.span, + format!("{}! is not legal in pattern position", + extnamestr.get()).as_slice()); + return DummyResult::raw_pat(p.span); + } } }; @@ -975,7 +987,6 @@ fn expand_and_rename_fn_decl_and_block(fn_decl: &ast::FnDecl, block: Gc { - pub extsbox: SyntaxEnv, pub cx: &'a mut ExtCtxt<'b>, } @@ -1044,7 +1055,6 @@ pub fn expand_crate(parse_sess: &parse::ParseSess, c: Crate) -> Crate { let mut cx = ExtCtxt::new(parse_sess, c.config.clone(), cfg); let mut expander = MacroExpander { - extsbox: syntax_expander_table(), cx: &mut cx, }; @@ -1063,7 +1073,7 @@ pub fn expand_crate(parse_sess: &parse::ParseSess, } for (name, extension) in user_exts.move_iter() { - expander.extsbox.insert(name, extension); + expander.cx.syntax_env.insert(name, extension); } let mut ret = expander.fold_crate(c); @@ -1337,16 +1347,6 @@ mod test { name_finder.ident_accumulator } - //fn expand_and_resolve(crate_str: @str) -> ast::crate { - //let expanded_ast = expand_crate_str(crate_str); - // println!("expanded: {:?}\n",expanded_ast); - //mtwt_resolve_crate(expanded_ast) - //} - //fn expand_and_resolve_and_pretty_print (crate_str: @str) -> String { - //let resolved_ast = expand_and_resolve(crate_str); - //pprust::to_string(&resolved_ast,fake_print_crate,get_ident_interner()) - //} - #[test] fn macro_tokens_should_match(){ expand_crate_str( "macro_rules! m((a)=>(13)) fn main(){m!(a);}".to_string()); diff --git a/src/test/run-pass/macro-deep_expansion.rs b/src/test/run-pass/macro-deep_expansion.rs new file mode 100644 index 0000000000000..f85c6f1fc9349 --- /dev/null +++ b/src/test/run-pass/macro-deep_expansion.rs @@ -0,0 +1,27 @@ +// Copyright 2014 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. + +#![feature(macro_rules)] + +macro_rules! foo2 { + () => { + "foo" + } +} + +macro_rules! foo { + () => { + foo2!() + } +} + +fn main() { + assert_eq!(concat!(foo!(), "bar"), "foobar") +}