1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use ast::{P, Block, Crate, DeclLocal, ExprMac};
12 use ast::{Local, Ident, MacInvocTT};
13 use ast::{ItemMac, Mrk, Stmt, StmtDecl, StmtMac, StmtExpr, StmtSemi};
17 use ext::build::AstBuilder;
19 use attr::AttrMetaMethods;
21 use codemap::{Span, Spanned, ExpnInfo, NameAndSpan, MacroBang, MacroAttribute};
25 use parse::token::{fresh_mark, fresh_name, intern};
29 use util::small_vector::SmallVector;
32 use std::unstable::dynamic_lib::DynamicLibrary;
36 pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr {
38 // expr_mac should really be expr_ext or something; it's the
39 // entry-point for all syntax extensions.
42 // it would almost certainly be cleaner to pass the whole
43 // macro invocation in, rather than pulling it apart and
44 // marking the tts and the ctxt separately. This also goes
45 // for the other three macro invocation chunks of code
48 MacInvocTT(ref pth, ref tts, _) => {
49 if pth.segments.len() > 1u {
52 format!("expected macro name without module \
54 // let compilation continue
55 return MacResult::raw_dummy_expr(e.span);
57 let extname = pth.segments.get(0).identifier;
58 let extnamestr = token::get_ident(extname);
59 // leaving explicit deref here to highlight unbox op:
60 let marked_after = match fld.extsbox.find(&extname.name) {
64 format!("macro undefined: '{}'",
67 // let compilation continue
68 return MacResult::raw_dummy_expr(e.span);
70 Some(&NormalTT(ref expandfun, exp_span)) => {
71 fld.cx.bt_push(ExpnInfo {
74 name: extnamestr.get().to_str(),
79 let fm = fresh_mark();
81 let marked_before = mark_tts(tts.as_slice(), fm);
83 // The span that we pass to the expanders we want to
84 // be the root of the call stack. That's the most
85 // relevant span and it's the actual invocation of
87 let mac_span = original_span(fld.cx);
89 let expanded = match expandfun.expand(fld.cx,
91 marked_before.as_slice()) {
93 MRAny(any_macro) => any_macro.make_expr(),
98 "non-expr macro in expr pos: {}",
102 return MacResult::raw_dummy_expr(e.span);
107 mark_expr(expanded,fm)
112 format!("'{}' is not a tt-style macro",
115 return MacResult::raw_dummy_expr(e.span);
119 // Keep going, outside-in.
121 // FIXME(pcwalton): Is it necessary to clone the
124 fld.fold_expr(marked_after).node.clone();
128 id: ast::DUMMY_NODE_ID,
129 node: fully_expanded,
136 // Desugar expr_for_loop
137 // From: `['<ident>:] for <src_pat> in <src_expr> <src_loop_block>`
138 // FIXME #6993: change type of opt_ident to Option<Name>
139 ast::ExprForLoop(src_pat, src_expr, src_loop_block, opt_ident) => {
140 // Expand any interior macros etc.
141 // NB: we don't fold pats yet. Curious.
142 let src_expr = fld.fold_expr(src_expr).clone();
143 let (src_loop_block, opt_ident) = expand_loop_block(src_loop_block, opt_ident, fld);
149 // match &mut <src_expr> {
151 // ['<ident>:] loop {
154 // Some(<src_pat>) => <src_loop_block>
160 let local_ident = token::gensym_ident("i");
161 let next_ident = fld.cx.ident_of("next");
162 let none_ident = fld.cx.ident_of("None");
164 let local_path = fld.cx.path_ident(span, local_ident);
165 let some_path = fld.cx.path_ident(span, fld.cx.ident_of("Some"));
167 // `None => break ['<ident>];`
169 let break_expr = fld.cx.expr(span, ast::ExprBreak(opt_ident));
170 let none_pat = fld.cx.pat_ident(span, none_ident);
171 fld.cx.arm(span, vec!(none_pat), break_expr)
174 // `Some(<src_pat>) => <src_loop_block>`
177 vec!(fld.cx.pat_enum(span, some_path, vec!(src_pat))),
178 fld.cx.expr_block(src_loop_block));
180 // `match i.next() { ... }`
183 fld.cx.expr_method_call(span,
184 fld.cx.expr_path(local_path),
188 fld.cx.expr_match(span, next_call_expr, vec!(none_arm, some_arm))
191 // ['ident:] loop { ... }
192 let loop_expr = fld.cx.expr(span,
193 ast::ExprLoop(fld.cx.block_expr(match_expr),
196 // `i => loop { ... }`
198 // `match &mut <src_expr> { i => loop { ... } }`
199 let discrim = fld.cx.expr_mut_addr_of(span, src_expr);
200 let i_pattern = fld.cx.pat_ident(span, local_ident);
201 let arm = fld.cx.arm(span, vec!(i_pattern), loop_expr);
202 fld.cx.expr_match(span, discrim, vec!(arm))
205 ast::ExprLoop(loop_block, opt_ident) => {
206 let (loop_block, opt_ident) = expand_loop_block(loop_block, opt_ident, fld);
207 fld.cx.expr(e.span, ast::ExprLoop(loop_block, opt_ident))
210 _ => noop_fold_expr(e, fld)
214 // Rename loop label and expand its loop body
216 // The renaming procedure for loop is different in the sense that the loop
217 // body is in a block enclosed by loop head so the renaming of loop label
218 // must be propagated to the enclosed context.
219 fn expand_loop_block(loop_block: P<Block>,
220 opt_ident: Option<Ident>,
221 fld: &mut MacroExpander) -> (P<Block>, Option<Ident>) {
224 let new_label = fresh_name(&label);
225 let rename = (label, new_label);
227 // The rename *must not* be added to the pending list of current
228 // syntax context otherwise an unrelated `break` or `continue` in
229 // the same context will pick that up in the deferred renaming pass
230 // and be renamed incorrectly.
231 let mut rename_list = vec!(rename);
232 let mut rename_fld = renames_to_fold(&mut rename_list);
233 let renamed_ident = rename_fld.fold_ident(label);
235 // The rename *must* be added to the enclosed syntax context for
236 // `break` or `continue` to pick up because by definition they are
237 // in a block enclosed by loop head.
238 fld.extsbox.push_frame();
239 fld.extsbox.info().pending_renames.push(rename);
240 let expanded_block = expand_block_elts(loop_block, fld);
241 fld.extsbox.pop_frame();
243 (expanded_block, Some(renamed_ident))
245 None => (fld.fold_block(loop_block), opt_ident)
249 // eval $e with a new exts frame:
250 macro_rules! with_exts_frame (
251 ($extsboxexpr:expr,$macros_escape:expr,$e:expr) =>
252 ({$extsboxexpr.push_frame();
253 $extsboxexpr.info().macros_escape = $macros_escape;
255 $extsboxexpr.pop_frame();
260 // When we enter a module, record it, for the sake of `module!`
261 pub fn expand_item(it: @ast::Item, fld: &mut MacroExpander)
262 -> SmallVector<@ast::Item> {
263 let mut decorator_items: SmallVector<@ast::Item> = SmallVector::zero();
264 for attr in it.attrs.rev_iter() {
265 let mname = attr.name();
267 match fld.extsbox.find(&intern(mname.get())) {
268 Some(&ItemDecorator(dec_fn)) => {
269 fld.cx.bt_push(ExpnInfo {
270 call_site: attr.span,
271 callee: NameAndSpan {
272 name: mname.get().to_str(),
273 format: MacroAttribute,
278 // we'd ideally decorator_items.push_all(expand_item(item, fld)),
279 // but that double-mut-borrows fld
280 let mut items: SmallVector<@ast::Item> = SmallVector::zero();
281 dec_fn(fld.cx, attr.span, attr.node.value, it,
282 |item| items.push(item));
283 decorator_items.extend(&mut items.move_iter()
284 .flat_map(|item| expand_item(item, fld).move_iter()));
292 let mut new_items = match it.node {
293 ast::ItemMac(..) => expand_item_mac(it, fld),
294 ast::ItemMod(_) | ast::ItemForeignMod(_) => {
295 fld.cx.mod_push(it.ident);
296 let macro_escape = contains_macro_escape(it.attrs.as_slice());
297 let result = with_exts_frame!(fld.extsbox,
299 noop_fold_item(it, fld));
303 _ => noop_fold_item(it, fld)
306 new_items.push_all(decorator_items);
310 // does this attribute list contain "macro_escape" ?
311 pub fn contains_macro_escape(attrs: &[ast::Attribute]) -> bool {
312 attr::contains_name(attrs, "macro_escape")
315 // Support for item-position macro invocations, exactly the same
316 // logic as for expression-position macro invocations.
317 pub fn expand_item_mac(it: @ast::Item, fld: &mut MacroExpander)
318 -> SmallVector<@ast::Item> {
319 let (pth, tts) = match it.node {
320 ItemMac(codemap::Spanned {
321 node: MacInvocTT(ref pth, ref tts, _),
324 (pth, (*tts).clone())
326 _ => fld.cx.span_bug(it.span, "invalid item macro invocation")
329 let extname = pth.segments.get(0).identifier;
330 let extnamestr = token::get_ident(extname);
331 let fm = fresh_mark();
332 let expanded = match fld.extsbox.find(&extname.name) {
334 fld.cx.span_err(pth.span,
335 format!("macro undefined: '{}!'",
337 // let compilation continue
338 return SmallVector::zero();
341 Some(&NormalTT(ref expander, span)) => {
342 if it.ident.name != parse::token::special_idents::invalid.name {
343 fld.cx.span_err(pth.span,
344 format!("macro {}! expects no ident argument, \
347 token::get_ident(it.ident)));
348 return SmallVector::zero();
350 fld.cx.bt_push(ExpnInfo {
352 callee: NameAndSpan {
353 name: extnamestr.get().to_str(),
358 // mark before expansion:
359 let marked_before = mark_tts(tts.as_slice(), fm);
360 expander.expand(fld.cx, it.span, marked_before.as_slice())
362 Some(&IdentTT(ref expander, span)) => {
363 if it.ident.name == parse::token::special_idents::invalid.name {
364 fld.cx.span_err(pth.span,
365 format!("macro {}! expects an ident argument",
367 return SmallVector::zero();
369 fld.cx.bt_push(ExpnInfo {
371 callee: NameAndSpan {
372 name: extnamestr.get().to_str(),
377 // mark before expansion:
378 let marked_tts = mark_tts(tts.as_slice(), fm);
379 expander.expand(fld.cx, it.span, it.ident, marked_tts)
382 fld.cx.span_err(it.span,
383 format!("{}! is not legal in item position",
385 return SmallVector::zero();
389 let items = match expanded {
391 mark_item(it,fm).move_iter()
392 .flat_map(|i| fld.fold_item(i).move_iter())
396 fld.cx.span_err(pth.span,
397 format!("expr macro in item position: {}",
399 return SmallVector::zero();
401 MRAny(any_macro) => {
402 any_macro.make_items().move_iter()
403 .flat_map(|i| mark_item(i, fm).move_iter())
404 .flat_map(|i| fld.fold_item(i).move_iter())
407 MRDef(MacroDef { name, ext }) => {
408 // yikes... no idea how to apply the mark to this. I'm afraid
409 // we're going to have to wait-and-see on this one.
410 fld.extsbox.insert(intern(name), ext);
411 if attr::contains_name(it.attrs.as_slice(), "macro_export") {
422 // load macros from syntax-phase crates
423 pub fn expand_view_item(vi: &ast::ViewItem,
424 fld: &mut MacroExpander)
427 ast::ViewItemExternMod(..) => {
428 let should_load = vi.attrs.iter().any(|attr| {
429 attr.name().get() == "phase" &&
430 attr.meta_item_list().map_or(false, |phases| {
431 attr::contains_name(phases, "syntax")
436 load_extern_macros(vi, fld);
439 ast::ViewItemUse(_) => {}
442 noop_fold_view_item(vi, fld)
445 fn load_extern_macros(krate: &ast::ViewItem, fld: &mut MacroExpander) {
446 let MacroCrate { lib, cnum } = fld.cx.ecfg.loader.load_crate(krate);
448 let crate_name = match krate.node {
449 ast::ViewItemExternMod(name, _, _) => name,
452 let name = format!("<{} macros>", token::get_ident(crate_name));
454 let exported_macros = fld.cx.ecfg.loader.get_exported_macros(cnum);
455 for source in exported_macros.iter() {
456 let item = parse::parse_item_from_source_str(name.clone(),
460 .expect("expected a serialized item");
461 expand_item_mac(item, fld);
464 let path = match lib {
468 // Make sure the path contains a / or the linker will search for it.
469 let path = os::make_absolute(&path);
471 let registrar = match fld.cx.ecfg.loader.get_registrar_symbol(cnum) {
472 Some(registrar) => registrar,
476 let lib = match DynamicLibrary::open(Some(&path)) {
478 // this is fatal: there are almost certainly macros we need
479 // inside this crate, so continue would spew "macro undefined"
481 Err(err) => fld.cx.span_fatal(krate.span, err)
485 let registrar: MacroCrateRegistrationFun = match lib.symbol(registrar) {
486 Ok(registrar) => registrar,
487 // again fatal if we can't register macros
488 Err(err) => fld.cx.span_fatal(krate.span, err)
490 registrar(|name, extension| {
491 let extension = match extension {
492 NormalTT(ext, _) => NormalTT(ext, Some(krate.span)),
493 IdentTT(ext, _) => IdentTT(ext, Some(krate.span)),
494 ItemDecorator(ext) => ItemDecorator(ext),
496 fld.extsbox.insert(name, extension);
499 // Intentionally leak the dynamic library. We can't ever unload it
500 // since the library can do things that will outlive the expansion
501 // phase (e.g. make an @-box cycle or launch a task).
507 pub fn expand_stmt(s: &Stmt, fld: &mut MacroExpander) -> SmallVector<@Stmt> {
508 // why the copying here and not in expand_expr?
509 // looks like classic changed-in-only-one-place
510 let (pth, tts, semi) = match s.node {
511 StmtMac(ref mac, semi) => {
513 MacInvocTT(ref pth, ref tts, _) => {
514 (pth, (*tts).clone(), semi)
518 _ => return expand_non_macro_stmt(s, fld)
520 if pth.segments.len() > 1u {
521 fld.cx.span_err(pth.span, "expected macro name without module separators");
522 return SmallVector::zero();
524 let extname = pth.segments.get(0).identifier;
525 let extnamestr = token::get_ident(extname);
526 let marked_after = match fld.extsbox.find(&extname.name) {
528 fld.cx.span_err(pth.span, format!("macro undefined: '{}'", extnamestr));
529 return SmallVector::zero();
532 Some(&NormalTT(ref expandfun, exp_span)) => {
533 fld.cx.bt_push(ExpnInfo {
535 callee: NameAndSpan {
536 name: extnamestr.get().to_str(),
541 let fm = fresh_mark();
542 // mark before expansion:
543 let marked_tts = mark_tts(tts.as_slice(), fm);
545 // See the comment in expand_expr for why we want the original span,
546 // not the current mac.span.
547 let mac_span = original_span(fld.cx);
549 let expanded = match expandfun.expand(fld.cx,
551 marked_tts.as_slice()) {
554 node: StmtExpr(e, ast::DUMMY_NODE_ID),
558 MRAny(any_macro) => any_macro.make_stmt(),
560 fld.cx.span_err(pth.span,
561 format!("non-stmt macro in stmt pos: {}",
563 return SmallVector::zero();
567 mark_stmt(expanded,fm)
571 fld.cx.span_err(pth.span, format!("'{}' is not a tt-style macro",
573 return SmallVector::zero();
577 // Keep going, outside-in.
578 let fully_expanded = fld.fold_stmt(marked_after);
579 if fully_expanded.is_empty() {
580 fld.cx.span_err(pth.span, "macro didn't expand to a statement");
581 return SmallVector::zero();
584 let fully_expanded: SmallVector<@Stmt> = fully_expanded.move_iter()
585 .map(|s| @Spanned { span: s.span, node: s.node.clone() })
588 fully_expanded.move_iter().map(|s| {
590 StmtExpr(e, stmt_id) if semi => {
593 node: StmtSemi(e, stmt_id)
596 _ => s /* might already have a semi */
601 // expand a non-macro stmt. this is essentially the fallthrough for
602 // expand_stmt, above.
603 fn expand_non_macro_stmt(s: &Stmt, fld: &mut MacroExpander)
604 -> SmallVector<@Stmt> {
607 StmtDecl(decl, node_id) => {
610 node: DeclLocal(ref local),
621 // expand the pat (it might contain exprs... #:(o)>
622 let expanded_pat = fld.fold_pat(pat);
623 // find the pat_idents in the pattern:
624 // oh dear heaven... this is going to include the enum
625 // names, as well... but that should be okay, as long as
626 // the new names are gensyms for the old ones.
627 let mut name_finder = new_name_finder(Vec::new());
628 name_finder.visit_pat(expanded_pat,());
629 // generate fresh names, push them to a new pending list
630 let mut new_pending_renames = Vec::new();
631 for ident in name_finder.ident_accumulator.iter() {
632 let new_name = fresh_name(ident);
633 new_pending_renames.push((*ident,new_name));
635 let rewritten_pat = {
637 renames_to_fold(&mut new_pending_renames);
638 // rewrite the pattern using the new names (the old
639 // ones have already been applied):
640 rename_fld.fold_pat(expanded_pat)
642 // add them to the existing pending renames:
643 fld.extsbox.info().pending_renames.push_all_move(new_pending_renames);
644 // also, don't forget to expand the init:
645 let new_init_opt = init.map(|e| fld.fold_expr(e));
646 let rewritten_local =
654 SmallVector::one(@Spanned {
655 node: StmtDecl(@Spanned {
656 node: DeclLocal(rewritten_local),
663 _ => noop_fold_stmt(s, fld),
666 _ => noop_fold_stmt(s, fld),
670 // a visitor that extracts the pat_ident paths
671 // from a given thingy and puts them in a mutable
672 // array (passed in to the traversal)
674 pub struct NewNameFinderContext {
675 ident_accumulator: Vec<ast::Ident> ,
678 impl Visitor<()> for NewNameFinderContext {
679 fn visit_pat(&mut self, pattern: &ast::Pat, _: ()) {
681 // we found a pat_ident!
684 node: ast::PatIdent(_, ref path, ref inner),
688 // a path of length one:
692 segments: ref segments
693 } if segments.len() == 1 => {
694 self.ident_accumulator.push(segments.get(0)
697 // I believe these must be enums...
700 // visit optional subpattern of pat_ident:
701 for subpat in inner.iter() {
702 self.visit_pat(*subpat, ())
705 // use the default traversal for non-pat_idents
706 _ => visit::walk_pat(self, pattern, ())
710 fn visit_ty(&mut self, typ: &ast::Ty, _: ()) {
711 visit::walk_ty(self, typ, ())
716 // return a visitor that extracts the pat_ident paths
717 // from a given thingy and puts them in a mutable
718 // array (passed in to the traversal)
719 pub fn new_name_finder(idents: Vec<ast::Ident> ) -> NewNameFinderContext {
720 NewNameFinderContext {
721 ident_accumulator: idents,
725 // expand a block. pushes a new exts_frame, then calls expand_block_elts
726 pub fn expand_block(blk: &Block, fld: &mut MacroExpander) -> P<Block> {
727 // see note below about treatment of exts table
728 with_exts_frame!(fld.extsbox,false,
729 expand_block_elts(blk, fld))
732 // expand the elements of a block.
733 pub fn expand_block_elts(b: &Block, fld: &mut MacroExpander) -> P<Block> {
734 let new_view_items = b.view_items.map(|x| fld.fold_view_item(x));
736 b.stmts.iter().flat_map(|x| {
738 let pending_renames = &mut fld.extsbox.info().pending_renames;
739 let mut rename_fld = renames_to_fold(pending_renames);
740 rename_fld.fold_stmt(*x).expect_one("rename_fold didn't return one value")
742 fld.fold_stmt(renamed_stmt).move_iter()
744 let new_expr = b.expr.map(|x| {
746 let pending_renames = &mut fld.extsbox.info().pending_renames;
747 let mut rename_fld = renames_to_fold(pending_renames);
748 rename_fld.fold_expr(x)
753 view_items: new_view_items,
756 id: fld.new_id(b.id),
762 pub struct IdentRenamer<'a> {
763 renames: &'a mut RenameList,
766 impl<'a> Folder for IdentRenamer<'a> {
767 fn fold_ident(&mut self, id: Ident) -> Ident {
768 let new_ctxt = self.renames.iter().fold(id.ctxt, |ctxt, &(from, to)| {
769 mtwt::new_rename(from, to, ctxt)
778 // given a mutable list of renames, return a tree-folder that applies those
780 pub fn renames_to_fold<'a>(renames: &'a mut RenameList) -> IdentRenamer<'a> {
786 pub fn new_span(cx: &ExtCtxt, sp: Span) -> Span {
787 /* this discards information in the case of macro-defining macros */
791 expn_info: cx.backtrace(),
795 pub struct MacroExpander<'a> {
797 cx: &'a mut ExtCtxt<'a>,
800 impl<'a> Folder for MacroExpander<'a> {
801 fn fold_expr(&mut self, expr: @ast::Expr) -> @ast::Expr {
802 expand_expr(expr, self)
805 fn fold_item(&mut self, item: @ast::Item) -> SmallVector<@ast::Item> {
806 expand_item(item, self)
809 fn fold_view_item(&mut self, vi: &ast::ViewItem) -> ast::ViewItem {
810 expand_view_item(vi, self)
813 fn fold_stmt(&mut self, stmt: &ast::Stmt) -> SmallVector<@ast::Stmt> {
814 expand_stmt(stmt, self)
817 fn fold_block(&mut self, block: P<Block>) -> P<Block> {
818 expand_block(block, self)
821 fn new_span(&mut self, span: Span) -> Span {
822 new_span(self.cx, span)
826 pub struct ExpansionConfig<'a> {
827 loader: &'a mut CrateLoader,
828 deriving_hash_type_parameter: bool,
831 pub fn expand_crate(parse_sess: @parse::ParseSess,
832 cfg: ExpansionConfig,
834 let mut cx = ExtCtxt::new(parse_sess, c.config.clone(), cfg);
835 let mut expander = MacroExpander {
836 extsbox: syntax_expander_table(),
840 let ret = expander.fold_crate(c);
841 parse_sess.span_diagnostic.handler().abort_if_errors();
845 // HYGIENIC CONTEXT EXTENSION:
846 // all of these functions are for walking over
847 // ASTs and making some change to the context of every
848 // element that has one. a CtxtFn is a trait-ified
849 // version of a closure in (SyntaxContext -> SyntaxContext).
850 // the ones defined here include:
851 // Marker - add a mark to a context
853 // A Marker adds the given mark to the syntax context
854 struct Marker { mark: Mrk }
856 impl Folder for Marker {
857 fn fold_ident(&mut self, id: Ident) -> Ident {
860 ctxt: mtwt::new_mark(self.mark, id.ctxt)
863 fn fold_mac(&mut self, m: &ast::Mac) -> ast::Mac {
864 let macro = match m.node {
865 MacInvocTT(ref path, ref tts, ctxt) => {
866 MacInvocTT(self.fold_path(path),
867 fold_tts(tts.as_slice(), self),
868 mtwt::new_mark(self.mark, ctxt))
878 // just a convenience:
879 fn new_mark_folder(m: Mrk) -> Marker {
883 // apply a given mark to the given token trees. Used prior to expansion of a macro.
884 fn mark_tts(tts: &[TokenTree], m: Mrk) -> Vec<TokenTree> {
885 fold_tts(tts, &mut new_mark_folder(m))
888 // apply a given mark to the given expr. Used following the expansion of a macro.
889 fn mark_expr(expr: @ast::Expr, m: Mrk) -> @ast::Expr {
890 new_mark_folder(m).fold_expr(expr)
893 // apply a given mark to the given stmt. Used following the expansion of a macro.
894 fn mark_stmt(expr: &ast::Stmt, m: Mrk) -> @ast::Stmt {
895 new_mark_folder(m).fold_stmt(expr)
896 .expect_one("marking a stmt didn't return a stmt")
899 // apply a given mark to the given item. Used following the expansion of a macro.
900 fn mark_item(expr: @ast::Item, m: Mrk) -> SmallVector<@ast::Item> {
901 new_mark_folder(m).fold_item(expr)
904 fn original_span(cx: &ExtCtxt) -> @codemap::ExpnInfo {
905 let mut relevant_info = cx.backtrace();
906 let mut einfo = relevant_info.unwrap();
908 match relevant_info {
912 relevant_info = einfo.call_site.expn_info;
923 use ast::{Attribute_, AttrOuter, MetaWord};
925 use codemap::Spanned;
926 use ext::base::{CrateLoader, MacroCrate};
930 use util::parser_testing::{string_to_crate_and_sess};
931 use util::parser_testing::{string_to_pat, strs_to_idents};
935 use std::vec_ng::Vec;
937 // a visitor that extracts the paths
938 // from a given thingy and puts them in a mutable
939 // array (passed in to the traversal)
941 struct NewPathExprFinderContext {
942 path_accumulator: Vec<ast::Path> ,
945 impl Visitor<()> for NewPathExprFinderContext {
947 fn visit_expr(&mut self, expr: &ast::Expr, _: ()) {
949 ast::Expr{id:_,span:_,node:ast::ExprPath(ref p)} => {
950 self.path_accumulator.push(p.clone());
951 // not calling visit_path, should be fine.
953 _ => visit::walk_expr(self,expr,())
957 fn visit_ty(&mut self, typ: &ast::Ty, _: ()) {
958 visit::walk_ty(self, typ, ())
963 // return a visitor that extracts the paths
964 // from a given pattern and puts them in a mutable
965 // array (passed in to the traversal)
966 pub fn new_path_finder(paths: Vec<ast::Path> ) -> NewPathExprFinderContext {
967 NewPathExprFinderContext {
968 path_accumulator: paths
974 impl CrateLoader for ErrLoader {
975 fn load_crate(&mut self, _: &ast::ViewItem) -> MacroCrate {
979 fn get_exported_macros(&mut self, _: ast::CrateNum) -> Vec<~str> {
983 fn get_registrar_symbol(&mut self, _: ast::CrateNum) -> Option<~str> {
988 // these following tests are quite fragile, in that they don't test what
989 // *kind* of failure occurs.
991 // make sure that macros can leave scope
993 #[test] fn macros_cant_escape_fns_test () {
994 let src = ~"fn bogus() {macro_rules! z (() => (3+4))}\
995 fn inty() -> int { z!() }";
996 let sess = parse::new_parse_sess();
997 let crate_ast = parse::parse_crate_from_source_str(
1002 let mut loader = ErrLoader;
1003 let cfg = ::syntax::ext::expand::ExpansionConfig {
1004 loader: &mut loader,
1005 deriving_hash_type_parameter: false,
1007 expand_crate(sess,cfg,crate_ast);
1010 // make sure that macros can leave scope for modules
1012 #[test] fn macros_cant_escape_mods_test () {
1013 let src = ~"mod foo {macro_rules! z (() => (3+4))}\
1014 fn inty() -> int { z!() }";
1015 let sess = parse::new_parse_sess();
1016 let crate_ast = parse::parse_crate_from_source_str(
1021 let mut loader = ErrLoader;
1022 let cfg = ::syntax::ext::expand::ExpansionConfig {
1023 loader: &mut loader,
1024 deriving_hash_type_parameter: false,
1026 expand_crate(sess,cfg,crate_ast);
1029 // macro_escape modules shouldn't cause macros to leave scope
1030 #[test] fn macros_can_escape_flattened_mods_test () {
1031 let src = ~"#[macro_escape] mod foo {macro_rules! z (() => (3+4))}\
1032 fn inty() -> int { z!() }";
1033 let sess = parse::new_parse_sess();
1034 let crate_ast = parse::parse_crate_from_source_str(
1039 let mut loader = ErrLoader;
1040 let cfg = ::syntax::ext::expand::ExpansionConfig {
1041 loader: &mut loader,
1042 deriving_hash_type_parameter: false,
1044 expand_crate(sess, cfg, crate_ast);
1047 #[test] fn test_contains_flatten (){
1048 let attr1 = make_dummy_attr ("foo");
1049 let attr2 = make_dummy_attr ("bar");
1050 let escape_attr = make_dummy_attr ("macro_escape");
1051 let attrs1 = vec!(attr1, escape_attr, attr2);
1052 assert_eq!(contains_macro_escape(attrs1.as_slice()),true);
1053 let attrs2 = vec!(attr1,attr2);
1054 assert_eq!(contains_macro_escape(attrs2.as_slice()),false);
1057 // make a MetaWord outer attribute with the given name
1058 fn make_dummy_attr(s: &str) -> ast::Attribute {
1060 span:codemap::DUMMY_SP,
1064 node: MetaWord(token::intern_and_get_ident(s)),
1065 span: codemap::DUMMY_SP,
1067 is_sugared_doc: false,
1072 //fn fake_print_crate(krate: &ast::Crate) {
1073 // let mut out = ~std::io::stderr() as ~std::io::Writer;
1074 // let mut s = pprust::rust_printer(out, get_ident_interner());
1075 // pprust::print_crate_(&mut s, krate);
1078 fn expand_crate_str(crate_str: ~str) -> ast::Crate {
1079 let (crate_ast,ps) = string_to_crate_and_sess(crate_str);
1080 // the cfg argument actually does matter, here...
1081 let mut loader = ErrLoader;
1082 let cfg = ::syntax::ext::expand::ExpansionConfig {
1083 loader: &mut loader,
1084 deriving_hash_type_parameter: false,
1086 expand_crate(ps,cfg,crate_ast)
1089 //fn expand_and_resolve(crate_str: @str) -> ast::crate {
1090 //let expanded_ast = expand_crate_str(crate_str);
1091 // println!("expanded: {:?}\n",expanded_ast);
1092 //mtwt_resolve_crate(expanded_ast)
1094 //fn expand_and_resolve_and_pretty_print (crate_str: @str) -> ~str {
1095 //let resolved_ast = expand_and_resolve(crate_str);
1096 //pprust::to_str(&resolved_ast,fake_print_crate,get_ident_interner())
1099 #[test] fn macro_tokens_should_match(){
1100 expand_crate_str(~"macro_rules! m((a)=>(13)) fn main(){m!(a);}");
1103 // renaming tests expand a crate and then check that the bindings match
1104 // the right varrefs. The specification of the test case includes the
1105 // text of the crate, and also an array of arrays. Each element in the
1106 // outer array corresponds to a binding in the traversal of the AST
1107 // induced by visit. Each of these arrays contains a list of indexes,
1108 // interpreted as the varrefs in the varref traversal that this binding
1109 // should match. So, for instance, in a program with two bindings and
1110 // three varrefs, the array ~[~[1,2],~[0]] would indicate that the first
1111 // binding should match the second two varrefs, and the second binding
1112 // should match the first varref.
1114 // The comparisons are done post-mtwt-resolve, so we're comparing renamed
1115 // names; differences in marks don't matter any more.
1117 // oog... I also want tests that check "binding-identifier-=?". That is,
1118 // not just "do these have the same name", but "do they have the same
1119 // name *and* the same marks"? Understanding this is really pretty painful.
1120 // in principle, you might want to control this boolean on a per-varref basis,
1121 // but that would make things even harder to understand, and might not be
1122 // necessary for thorough testing.
1123 type RenamingTest = (&'static str, Vec<Vec<uint>>, bool);
1126 fn automatic_renaming () {
1127 let tests: Vec<RenamingTest> =
1128 vec!(// b & c should get new names throughout, in the expr too:
1129 ("fn a() -> int { let b = 13; let c = b; b+c }",
1130 vec!(vec!(0,1),vec!(2)), false),
1131 // both x's should be renamed (how is this causing a bug?)
1132 ("fn main () {let x: int = 13;x;}",
1133 vec!(vec!(0)), false),
1134 // the use of b after the + should be renamed, the other one not:
1135 ("macro_rules! f (($x:ident) => (b + $x)) fn a() -> int { let b = 13; f!(b)}",
1136 vec!(vec!(1)), false),
1137 // the b before the plus should not be renamed (requires marks)
1138 ("macro_rules! f (($x:ident) => ({let b=9; ($x + b)})) fn a() -> int { f!(b)}",
1139 vec!(vec!(1)), false),
1140 // the marks going in and out of letty should cancel, allowing that $x to
1141 // capture the one following the semicolon.
1142 // this was an awesome test case, and caught a *lot* of bugs.
1143 ("macro_rules! letty(($x:ident) => (let $x = 15;))
1144 macro_rules! user(($x:ident) => ({letty!($x); $x}))
1145 fn main() -> int {user!(z)}",
1146 vec!(vec!(0)), false));
1147 for (idx,s) in tests.iter().enumerate() {
1148 run_renaming_test(s,idx);
1152 // run one of the renaming tests
1153 fn run_renaming_test(t: &RenamingTest, test_idx: uint) {
1154 let invalid_name = token::special_idents::invalid.name;
1155 let (teststr, bound_connections, bound_ident_check) = match *t {
1156 (ref str,ref conns, bic) => (str.to_owned(), conns.clone(), bic)
1158 let cr = expand_crate_str(teststr.to_owned());
1159 // find the bindings:
1160 let mut name_finder = new_name_finder(Vec::new());
1161 visit::walk_crate(&mut name_finder,&cr,());
1162 let bindings = name_finder.ident_accumulator;
1164 // find the varrefs:
1165 let mut path_finder = new_path_finder(Vec::new());
1166 visit::walk_crate(&mut path_finder,&cr,());
1167 let varrefs = path_finder.path_accumulator;
1169 // must be one check clause for each binding:
1170 assert_eq!(bindings.len(),bound_connections.len());
1171 for (binding_idx,shouldmatch) in bound_connections.iter().enumerate() {
1172 let binding_name = mtwt::resolve(*bindings.get(binding_idx));
1173 let binding_marks = mtwt::marksof(bindings.get(binding_idx).ctxt, invalid_name);
1174 // shouldmatch can't name varrefs that don't exist:
1175 assert!((shouldmatch.len() == 0) ||
1176 (varrefs.len() > *shouldmatch.iter().max().unwrap()));
1177 for (idx,varref) in varrefs.iter().enumerate() {
1178 if shouldmatch.contains(&idx) {
1179 // it should be a path of length 1, and it should
1180 // be free-identifier=? or bound-identifier=? to the given binding
1181 assert_eq!(varref.segments.len(),1);
1182 let varref_name = mtwt::resolve(varref.segments
1185 let varref_marks = mtwt::marksof(varref.segments
1190 if !(varref_name==binding_name) {
1191 println!("uh oh, should match but doesn't:");
1192 println!("varref: {:?}",varref);
1193 println!("binding: {:?}", *bindings.get(binding_idx));
1194 mtwt::with_sctable(|x| mtwt::display_sctable(x));
1196 assert_eq!(varref_name,binding_name);
1197 if bound_ident_check {
1198 // we're checking bound-identifier=?, and the marks
1199 // should be the same, too:
1200 assert_eq!(varref_marks,binding_marks.clone());
1203 let fail = (varref.segments.len() == 1)
1204 && (mtwt::resolve(varref.segments.get(0).identifier)
1208 println!("failure on test {}",test_idx);
1209 println!("text of test case: \"{}\"", teststr);
1211 println!("uh oh, matches but shouldn't:");
1212 println!("varref: {:?}",varref);
1213 // good lord, you can't make a path with 0 segments, can you?
1214 let string = token::get_ident(varref.segments
1217 println!("varref's first segment's uint: {}, and string: \"{}\"",
1218 varref.segments.get(0).identifier.name,
1220 println!("binding: {:?}", *bindings.get(binding_idx));
1221 mtwt::with_sctable(|x| mtwt::display_sctable(x));
1229 #[test] fn fmt_in_macro_used_inside_module_macro() {
1230 let crate_str = ~"macro_rules! fmt_wrap(($b:expr)=>($b.to_str()))
1231 macro_rules! foo_module (() => (mod generated { fn a() { let xx = 147; fmt_wrap!(xx);}}))
1234 let cr = expand_crate_str(crate_str);
1235 // find the xx binding
1236 let mut name_finder = new_name_finder(Vec::new());
1237 visit::walk_crate(&mut name_finder, &cr, ());
1238 let bindings = name_finder.ident_accumulator;
1240 let cxbinds: Vec<&ast::Ident> =
1241 bindings.iter().filter(|b| {
1242 let ident = token::get_ident(**b);
1243 let string = ident.get();
1246 let cxbinds: &[&ast::Ident] = cxbinds.as_slice();
1247 let cxbind = match cxbinds {
1249 _ => fail!("expected just one binding for ext_cx")
1251 let resolved_binding = mtwt::resolve(*cxbind);
1252 // find all the xx varrefs:
1253 let mut path_finder = new_path_finder(Vec::new());
1254 visit::walk_crate(&mut path_finder, &cr, ());
1255 let varrefs = path_finder.path_accumulator;
1257 // the xx binding should bind all of the xx varrefs:
1258 for (idx,v) in varrefs.iter().filter(|p| {
1259 p.segments.len() == 1
1260 && "xx" == token::get_ident(p.segments.get(0).identifier).get()
1262 if mtwt::resolve(v.segments.get(0).identifier) != resolved_binding {
1263 println!("uh oh, xx binding didn't match xx varref:");
1264 println!("this is xx varref \\# {:?}",idx);
1265 println!("binding: {:?}",cxbind);
1266 println!("resolves to: {:?}",resolved_binding);
1267 println!("varref: {:?}",v.segments.get(0).identifier);
1268 println!("resolves to: {:?}",
1269 mtwt::resolve(v.segments.get(0).identifier));
1270 mtwt::with_sctable(|x| mtwt::display_sctable(x));
1272 assert_eq!(mtwt::resolve(v.segments.get(0).identifier),
1279 let pat = string_to_pat(~"(a,Foo{x:c @ (b,9),y:Bar(4,d)})");
1280 let mut pat_idents = new_name_finder(Vec::new());
1281 pat_idents.visit_pat(pat, ());
1282 assert_eq!(pat_idents.ident_accumulator,
1283 strs_to_idents(vec!("a","c","b","d")));