add hygiene support fns, move them around.

also adds test cases
This commit is contained in:
John Clements 2013-06-25 11:40:51 -07:00
parent 7fd5bdcb9a
commit fa6c981606
5 changed files with 176 additions and 61 deletions

View file

@ -8,9 +8,9 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use ast::{Block, Crate, NodeId, Expr_, ExprMac, Ident, mac_invoc_tt};
use ast::{item_mac, Stmt_, StmtMac, StmtExpr, StmtSemi};
use ast::{ILLEGAL_CTXT};
use ast::{Block, Crate, NodeId, DeclLocal, Expr_, ExprMac, Local, Ident, mac_invoc_tt};
use ast::{item_mac, Mrk, Stmt_, StmtDecl, StmtMac, StmtExpr, StmtSemi};
use ast::{ILLEGAL_CTXT, SCTable, token_tree};
use ast;
use ast_util::{new_rename, new_mark, mtwt_resolve};
use attr;
@ -23,7 +23,7 @@ use opt_vec;
use parse;
use parse::{parse_item_from_source_str};
use parse::token;
use parse::token::{ident_to_str, intern};
use parse::token::{fresh_name, ident_to_str, intern};
use visit;
use visit::Visitor;
@ -521,6 +521,71 @@ pub fn expand_stmt(extsbox: @mut SyntaxEnv,
}
// expand a non-macro stmt. this is essentially the fallthrough for
// expand_stmt, above.
fn expand_non_macro_stmt (exts: SyntaxEnv,
s: &Stmt_,
sp: Span,
fld: @ast_fold,
orig: @fn(&Stmt_, Span, @ast_fold) -> (Option<Stmt_>, Span))
-> (Option<Stmt_>,Span) {
// is it a let?
match *s {
StmtDecl(@Spanned{node: DeclLocal(ref local), span: stmt_span}, node_id) => {
let block_info = get_block_info(exts);
let pending_renames = block_info.pending_renames;
// take it apart:
let @Local{is_mutbl:is_mutbl,
ty:_,
pat:pat,
init:init,
id:id,
span:span
} = *local;
// types can't be copied automatically because of the owned ptr in ty_tup...
let ty = local.ty.clone();
// expand the pat (it might contain exprs... #:(o)>
let expanded_pat = fld.fold_pat(pat);
// find the pat_idents in the pattern:
// oh dear heaven... this is going to include the enum names, as well....
let idents = @mut ~[];
let name_finder = new_name_finder(idents);
name_finder.visit_pat(expanded_pat,());
// generate fresh names, push them to a new pending list
let new_pending_renames = @mut ~[];
for ident in idents.iter() {
let new_name = fresh_name(ident);
new_pending_renames.push((*ident,new_name));
}
let mut rename_fld = renames_to_fold(new_pending_renames);
// rewrite the pattern using the new names (the old ones
// have already been applied):
let rewritten_pat = rename_fld.fold_pat(expanded_pat);
// add them to the existing pending renames:
for pr in new_pending_renames.iter() {pending_renames.push(*pr)}
// also, don't forget to expand the init:
let new_init_opt = init.map(|e| fld.fold_expr(*e));
let rewritten_local =
@Local{is_mutbl:is_mutbl,
ty:ty,
pat:rewritten_pat,
init:new_init_opt,
id:id,
span:span};
(Some(StmtDecl(@Spanned{node:DeclLocal(rewritten_local),
span: stmt_span},node_id)),
sp)
},
_ => {
orig(s, sp, fld)
}
}
}
// a visitor that extracts the pat_ident paths
// from a given pattern and puts them in a mutable
// array (passed in to the traversal)
#[deriving(Clone)]
struct NewNameFinderContext {
ident_accumulator: @mut ~[ast::Ident],
@ -674,30 +739,10 @@ pub fn new_name_finder(idents: @mut ~[ast::Ident]) -> @mut Visitor<()> {
context as @mut Visitor<()>
}
pub fn expand_block(extsbox: @mut SyntaxEnv,
_cx: @ExtCtxt,
blk: &Block,
fld: @ast_fold,
orig: @fn(&Block, @ast_fold) -> Block)
-> Block {
// see note below about treatment of exts table
with_exts_frame!(extsbox,false,orig(blk,fld))
}
// get the (innermost) BlockInfo from an exts stack
fn get_block_info(exts : SyntaxEnv) -> BlockInfo {
match exts.find_in_topmost_frame(&intern(special_block_name)) {
Some(@BlockInfo(bi)) => bi,
_ => fail!(fmt!("special identifier %? was bound to a non-BlockInfo",
@" block"))
}
}
// given a mutable list of renames, return a tree-folder that applies those
// renames.
fn renames_to_fold(renames : @mut ~[(ast::Ident,ast::Name)]) -> @ast_fold {
// FIXME #4536: currently pub to allow testing
pub fn renames_to_fold(renames : @mut ~[(ast::Ident,ast::Name)]) -> @ast_fold {
let afp = default_ast_fold();
let f_pre = @AstFoldFns {
fold_ident: |id,_| {
@ -713,15 +758,56 @@ fn renames_to_fold(renames : @mut ~[(ast::Ident,ast::Name)]) -> @ast_fold {
make_fold(f_pre)
}
// perform a bunch of renames
fn apply_pending_renames(folder : @ast_fold, stmt : ast::Stmt) -> @ast::Stmt {
match folder.fold_stmt(&stmt) {
Some(s) => s,
None => fail!(fmt!("renaming of stmt produced None"))
}
pub fn expand_block(extsbox: @mut SyntaxEnv,
_cx: @ExtCtxt,
blk: &Block,
fld: @ast_fold,
orig: @fn(&Block, @ast_fold) -> Block)
-> Block {
// see note below about treatment of exts table
with_exts_frame!(extsbox,false,orig(blk,fld))
}
pub fn expand_block_elts(exts: SyntaxEnv, b: &Block, fld: @ast_fold) -> Block {
let block_info = get_block_info(exts);
let pending_renames = block_info.pending_renames;
let mut rename_fld = renames_to_fold(pending_renames);
let new_view_items = b.view_items.map(|x| fld.fold_view_item(x));
let mut new_stmts = ~[];
for x in b.stmts.iter() {
match fld.fold_stmt(mustbesome(rename_fld.fold_stmt(*x))) {
Some(s) => new_stmts.push(s),
None => ()
}
}
let new_expr = b.expr.map(|x| fld.fold_expr(rename_fld.fold_expr(*x)));
Block{
view_items: new_view_items,
stmts: new_stmts,
expr: new_expr,
id: fld.new_id(b.id),
rules: b.rules,
span: b.span,
}
}
// rename_fold should never return "None".
fn mustbesome<T>(val : Option<T>) -> T {
match val {
Some(v) => v,
None => fail!("rename_fold returned None")
}
}
// get the (innermost) BlockInfo from an exts stack
fn get_block_info(exts : SyntaxEnv) -> BlockInfo {
match exts.find_in_topmost_frame(&intern(special_block_name)) {
Some(@BlockInfo(bi)) => bi,
_ => fail!(fmt!("special identifier %? was bound to a non-BlockInfo",
@" block"))
}
}
pub fn new_span(cx: @ExtCtxt, sp: Span) -> Span {
/* this discards information in the case of macro-defining macros */
@ -1228,12 +1314,15 @@ mod test {
use super::*;
use ast;
use ast::{Attribute_, AttrOuter, MetaWord, EMPTY_CTXT};
use ast_util::{get_sctable, new_rename};
use codemap;
use codemap::Spanned;
use parse;
use parse::token::{intern, get_ident_interner};
use parse::token::{gensym, intern, get_ident_interner};
use print::pprust;
use util::parser_testing::{string_to_item, string_to_pat, strs_to_idents};
use std;
use util::parser_testing::{string_to_crate_and_sess, string_to_item, string_to_pat};
use util::parser_testing::{strs_to_idents};
// make sure that fail! is present
#[test] fn fail_exists_test () {
@ -1333,26 +1422,60 @@ mod test {
#[test]
fn renaming () {
let maybe_item_ast = string_to_item(@"fn a() -> int { let b = 13; b }");
let item_ast = match maybe_item_ast {
Some(x) => x,
None => fail!("test case fail")
};
let item_ast = string_to_item(@"fn a() -> int { let b = 13; b }").unwrap();
let a_name = intern("a");
let a2_name = intern("a2");
let a2_name = gensym("a2");
let renamer = new_ident_renamer(ast::Ident{name:a_name,ctxt:EMPTY_CTXT},
a2_name);
let renamed_ast = fun_to_ident_folder(renamer).fold_item(item_ast).unwrap();
let resolver = new_ident_resolver();
let resolved_ast = fun_to_ident_folder(resolver).fold_item(renamed_ast).unwrap();
let resolver_fold = fun_to_ident_folder(resolver);
let resolved_ast = resolver_fold.fold_item(renamed_ast).unwrap();
let resolved_as_str = pprust::item_to_str(resolved_ast,
get_ident_interner());
assert_eq!(resolved_as_str,~"fn a2() -> int { let b = 13; b }");
// try a double-rename, with pending_renames.
let a3_name = gensym("a3");
let ctxt2 = new_rename(ast::Ident::new(a_name),a2_name,EMPTY_CTXT);
let pending_renames = @mut ~[(ast::Ident::new(a_name),a2_name),
(ast::Ident{name:a_name,ctxt:ctxt2},a3_name)];
let double_renamed = renames_to_fold(pending_renames).fold_item(item_ast).unwrap();
let resolved_again = resolver_fold.fold_item(double_renamed).unwrap();
let double_renamed_as_str = pprust::item_to_str(resolved_again,
get_ident_interner());
assert_eq!(double_renamed_as_str,~"fn a3() -> int { let b = 13; b }");
}
// sigh... it looks like I have two different renaming mechanisms, now...
fn fake_print_crate(s: @pprust::ps, crate: &ast::Crate) {
pprust::print_mod(s, &crate.module, crate.attrs);
}
// "fn a() -> int { let b = 13; let c = b; b+c }" --> b & c should get new names, in the expr too.
// "macro_rules! f (($x:ident) => ($x + b)) fn a() -> int { let b = 13; f!(b)}" --> one should
// be renamed, one should not.
fn expand_and_resolve_and_pretty_print (crate_str : @str) -> ~str {
let resolver = new_ident_resolver();
let resolver_fold = fun_to_ident_folder(resolver);
let (crate_ast,ps) = string_to_crate_and_sess(crate_str);
// the cfg argument actually does matter, here...
let expanded_ast = expand_crate(ps,~[],crate_ast);
// std::io::println(fmt!("expanded: %?\n",expanded_ast));
let resolved_ast = resolver_fold.fold_crate(expanded_ast);
pprust::to_str(&resolved_ast,fake_print_crate,get_ident_interner())
}
#[test]
fn automatic_renaming () {
let teststrs =
~[@"fn a() -> int { let b = 13; let c = b; b+c }",
@"macro_rules! f (($x:ident) => ($x + b)) fn a() -> int { let b = 13; f!(b)}"];
for s in teststrs.iter() {
std::io::println(expand_and_resolve_and_pretty_print(*s));
}
}
#[test]
fn pat_idents(){