From 2302ce903d8e5a3b9fad345a3942c1a9c191cf17 Mon Sep 17 00:00:00 2001 From: Edward Wang Date: Tue, 25 Feb 2014 04:47:19 +0800 Subject: [PATCH] Refactor and fix FIXME's in mtwt hygiene code - Moves mtwt hygiene code into its own file - Fixes FIXME's which leads to ~2x speed gain in expansion pass - It is now @-free --- src/librustc/middle/resolve.rs | 13 +- src/libsyntax/ast.rs | 41 +-- src/libsyntax/ast_util.rs | 458 +------------------------------ src/libsyntax/ext/base.rs | 6 +- src/libsyntax/ext/expand.rs | 112 ++++---- src/libsyntax/ext/mtwt.rs | 482 +++++++++++++++++++++++++++++++++ src/libsyntax/lib.rs | 1 + src/libsyntax/parse/token.rs | 7 +- 8 files changed, 558 insertions(+), 562 deletions(-) create mode 100644 src/libsyntax/ext/mtwt.rs diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs index 07d0758c555c5..0d3586266e869 100644 --- a/src/librustc/middle/resolve.rs +++ b/src/librustc/middle/resolve.rs @@ -19,8 +19,9 @@ use middle::pat_util::pat_bindings; use syntax::ast::*; use syntax::ast; -use syntax::ast_util::{def_id_of_def, local_def, mtwt_resolve}; +use syntax::ast_util::{def_id_of_def, local_def}; use syntax::ast_util::{path_to_ident, walk_pat, trait_method_to_ty_method}; +use syntax::ext::mtwt; use syntax::parse::token::special_idents; use syntax::parse::token; use syntax::print::pprust::path_to_str; @@ -4176,7 +4177,7 @@ impl Resolver { fn binding_mode_map(&mut self, pat: @Pat) -> BindingMap { let mut result = HashMap::new(); pat_bindings(self.def_map, pat, |binding_mode, _id, sp, path| { - let name = mtwt_resolve(path_to_ident(path)); + let name = mtwt::resolve(path_to_ident(path)); result.insert(name, binding_info {span: sp, binding_mode: binding_mode}); @@ -4411,7 +4412,7 @@ impl Resolver { // what you want). let ident = path.segments.get(0).identifier; - let renamed = mtwt_resolve(ident); + let renamed = mtwt::resolve(ident); match self.resolve_bare_identifier_pattern(ident) { FoundStructOrEnumVariant(def, lp) @@ -4965,7 +4966,7 @@ impl Resolver { let search_result; match namespace { ValueNS => { - let renamed = mtwt_resolve(ident); + let renamed = mtwt::resolve(ident); let mut value_ribs = self.value_ribs.borrow_mut(); search_result = self.search_ribs(value_ribs.get(), renamed, @@ -5213,7 +5214,7 @@ impl Resolver { let rib = label_ribs.get()[label_ribs.get().len() - 1]; let mut bindings = rib.bindings.borrow_mut(); - let renamed = mtwt_resolve(label); + let renamed = mtwt::resolve(label); bindings.get().insert(renamed, def_like); } @@ -5225,7 +5226,7 @@ impl Resolver { ExprBreak(Some(label)) | ExprAgain(Some(label)) => { let mut label_ribs = self.label_ribs.borrow_mut(); - let renamed = mtwt_resolve(label); + let renamed = mtwt::resolve(label); match self.search_ribs(label_ribs.get(), renamed, expr.span) { None => self.resolve_error(expr.span, diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 7efae9305e408..544d9f2d66935 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -19,8 +19,6 @@ use parse::token; use std::fmt; use std::fmt::Show; -use std::cell::RefCell; -use collections::HashMap; use std::option::Option; use std::rc::Rc; use std::vec_ng::Vec; @@ -42,7 +40,10 @@ pub fn P(value: T) -> P { // macro expansion per Flatt et al., "Macros // That Work Together" #[deriving(Clone, Hash, TotalEq, TotalOrd, Show)] -pub struct Ident { name: Name, ctxt: SyntaxContext } +pub struct Ident { + name: Name, + ctxt: SyntaxContext +} impl Ident { /// Construct an identifier with the given name and an empty context: @@ -88,43 +89,9 @@ impl Eq for Ident { // this uint is a reference to a table stored in thread-local // storage. pub type SyntaxContext = u32; - -// the SCTable contains a table of SyntaxContext_'s. It -// represents a flattened tree structure, to avoid having -// managed pointers everywhere (that caused an ICE). -// the mark_memo and rename_memo fields are side-tables -// that ensure that adding the same mark to the same context -// gives you back the same context as before. This shouldn't -// change the semantics--everything here is immutable--but -// it should cut down on memory use *a lot*; applying a mark -// to a tree containing 50 identifiers would otherwise generate -pub struct SCTable { - table: RefCell >, - mark_memo: RefCell>, - rename_memo: RefCell>, -} - -// NB: these must be placed in any SCTable... pub static EMPTY_CTXT : SyntaxContext = 0; pub static ILLEGAL_CTXT : SyntaxContext = 1; -#[deriving(Eq, Encodable, Decodable, Hash)] -pub enum SyntaxContext_ { - EmptyCtxt, - Mark (Mrk,SyntaxContext), - // flattening the name and syntaxcontext into the rename... - // HIDDEN INVARIANTS: - // 1) the first name in a Rename node - // can only be a programmer-supplied name. - // 2) Every Rename node with a given Name in the - // "to" slot must have the same name and context - // in the "from" slot. In essence, they're all - // pointers to a single "rename" event node. - Rename (Ident,Name,SyntaxContext), - // actually, IllegalCtxt may not be necessary. - IllegalCtxt -} - /// A name is a part of an identifier, representing a string or gensym. It's /// the result of interning. pub type Name = u32; diff --git a/src/libsyntax/ast_util.rs b/src/libsyntax/ast_util.rs index db9ea480e9620..3899731b25008 100644 --- a/src/libsyntax/ast_util.rs +++ b/src/libsyntax/ast_util.rs @@ -18,11 +18,9 @@ use print::pprust; use visit::Visitor; use visit; -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::cmp; -use collections::HashMap; use std::u32; -use std::local_data; use std::vec_ng::Vec; pub fn path_name_i(idents: &[Ident]) -> ~str { @@ -651,251 +649,6 @@ pub fn pat_is_ident(pat: @ast::Pat) -> bool { } } -// HYGIENE FUNCTIONS - -/// Extend a syntax context with a given mark -pub fn new_mark(m:Mrk, tail:SyntaxContext) -> SyntaxContext { - new_mark_internal(m,tail,get_sctable()) -} - -// Extend a syntax context with a given mark and table -// FIXME #8215 : currently pub to allow testing -pub fn new_mark_internal(m: Mrk, tail: SyntaxContext, table: &SCTable) - -> SyntaxContext { - let key = (tail,m); - // FIXME #5074 : can't use more natural style because we're missing - // flow-sensitivity. Results in two lookups on a hash table hit. - // also applies to new_rename_internal, below. - // let try_lookup = table.mark_memo.find(&key); - let mut mark_memo = table.mark_memo.borrow_mut(); - match mark_memo.get().contains_key(&key) { - false => { - let new_idx = { - let mut table = table.table.borrow_mut(); - idx_push(table.get(), Mark(m,tail)) - }; - mark_memo.get().insert(key,new_idx); - new_idx - } - true => { - match mark_memo.get().find(&key) { - None => fail!("internal error: key disappeared 2013042901"), - Some(idxptr) => {*idxptr} - } - } - } -} - -/// Extend a syntax context with a given rename -pub fn new_rename(id:Ident, to:Name, tail:SyntaxContext) -> SyntaxContext { - new_rename_internal(id, to, tail, get_sctable()) -} - -// Extend a syntax context with a given rename and sctable -// FIXME #8215 : currently pub to allow testing -pub fn new_rename_internal(id: Ident, - to: Name, - tail: SyntaxContext, - table: &SCTable) - -> SyntaxContext { - let key = (tail,id,to); - // FIXME #5074 - //let try_lookup = table.rename_memo.find(&key); - let mut rename_memo = table.rename_memo.borrow_mut(); - match rename_memo.get().contains_key(&key) { - false => { - let new_idx = { - let mut table = table.table.borrow_mut(); - idx_push(table.get(), Rename(id,to,tail)) - }; - rename_memo.get().insert(key,new_idx); - new_idx - } - true => { - match rename_memo.get().find(&key) { - None => fail!("internal error: key disappeared 2013042902"), - Some(idxptr) => {*idxptr} - } - } - } -} - -/// Make a fresh syntax context table with EmptyCtxt in slot zero -/// and IllegalCtxt in slot one. -// FIXME #8215 : currently pub to allow testing -pub fn new_sctable_internal() -> SCTable { - SCTable { - table: RefCell::new(vec!(EmptyCtxt,IllegalCtxt)), - mark_memo: RefCell::new(HashMap::new()), - rename_memo: RefCell::new(HashMap::new()), - } -} - -// fetch the SCTable from TLS, create one if it doesn't yet exist. -pub fn get_sctable() -> @SCTable { - local_data_key!(sctable_key: @@SCTable) - match local_data::get(sctable_key, |k| k.map(|k| *k)) { - None => { - let new_table = @@new_sctable_internal(); - local_data::set(sctable_key,new_table); - *new_table - }, - Some(intr) => *intr - } -} - -/// print out an SCTable for debugging -pub fn display_sctable(table : &SCTable) { - error!("SC table:"); - let table = table.table.borrow(); - for (idx,val) in table.get().iter().enumerate() { - error!("{:4u} : {:?}",idx,val); - } -} - - -/// Add a value to the end of a vec, return its index -fn idx_push(vec: &mut Vec , val: T) -> u32 { - vec.push(val); - (vec.len() - 1) as u32 -} - -/// Resolve a syntax object to a name, per MTWT. -pub fn mtwt_resolve(id : Ident) -> Name { - let resolve_table = get_resolve_table(); - let mut resolve_table = resolve_table.borrow_mut(); - resolve_internal(id, get_sctable(), resolve_table.get()) -} - -// FIXME #8215: must be pub for testing -pub type ResolveTable = HashMap<(Name,SyntaxContext),Name>; - -// okay, I admit, putting this in TLS is not so nice: -// fetch the SCTable from TLS, create one if it doesn't yet exist. -pub fn get_resolve_table() -> @RefCell { - local_data_key!(resolve_table_key: @@RefCell) - match local_data::get(resolve_table_key, |k| k.map(|k| *k)) { - None => { - let new_table = @@RefCell::new(HashMap::new()); - local_data::set(resolve_table_key, new_table); - *new_table - }, - Some(intr) => *intr - } -} - -// Resolve a syntax object to a name, per MTWT. -// adding memoization to possibly resolve 500+ seconds in resolve for librustc (!) -// FIXME #8215 : currently pub to allow testing -pub fn resolve_internal(id : Ident, - table : &SCTable, - resolve_table : &mut ResolveTable) -> Name { - let key = (id.name,id.ctxt); - match resolve_table.contains_key(&key) { - false => { - let resolved = { - let result = { - let table = table.table.borrow(); - *table.get().get(id.ctxt as uint) - }; - match result { - EmptyCtxt => id.name, - // ignore marks here: - Mark(_,subctxt) => - resolve_internal(Ident{name:id.name, ctxt: subctxt},table,resolve_table), - // do the rename if necessary: - Rename(Ident{name,ctxt},toname,subctxt) => { - let resolvedfrom = - resolve_internal(Ident{name:name,ctxt:ctxt},table,resolve_table); - let resolvedthis = - resolve_internal(Ident{name:id.name,ctxt:subctxt},table,resolve_table); - if (resolvedthis == resolvedfrom) - && (marksof(ctxt,resolvedthis,table) - == marksof(subctxt,resolvedthis,table)) { - toname - } else { - resolvedthis - } - } - IllegalCtxt() => fail!("expected resolvable context, got IllegalCtxt") - } - }; - resolve_table.insert(key,resolved); - resolved - } - true => { - // it's guaranteed to be there, because we just checked that it was - // there and we never remove anything from the table: - *(resolve_table.find(&key).unwrap()) - } - } -} - -/// Compute the marks associated with a syntax context. -pub fn mtwt_marksof(ctxt: SyntaxContext, stopname: Name) -> Vec { - marksof(ctxt, stopname, get_sctable()) -} - -// the internal function for computing marks -// it's not clear to me whether it's better to use a [] mutable -// vector or a cons-list for this. -pub fn marksof(ctxt: SyntaxContext, stopname: Name, table: &SCTable) -> Vec { - let mut result = Vec::new(); - let mut loopvar = ctxt; - loop { - let table_entry = { - let table = table.table.borrow(); - *table.get().get(loopvar as uint) - }; - match table_entry { - EmptyCtxt => { - return result; - }, - Mark(mark, tl) => { - xorPush(&mut result, mark); - loopvar = tl; - }, - Rename(_,name,tl) => { - // see MTWT for details on the purpose of the stopname. - // short version: it prevents duplication of effort. - if name == stopname { - return result; - } else { - loopvar = tl; - } - } - IllegalCtxt => fail!("expected resolvable context, got IllegalCtxt") - } - } -} - -/// Return the outer mark for a context with a mark at the outside. -/// FAILS when outside is not a mark. -pub fn mtwt_outer_mark(ctxt: SyntaxContext) -> Mrk { - let sctable = get_sctable(); - let table = sctable.table.borrow(); - match *table.get().get(ctxt as uint) { - ast::Mark(mrk,_) => mrk, - _ => fail!("can't retrieve outer mark when outside is not a mark") - } -} - -/// Push a name... unless it matches the one on top, in which -/// case pop and discard (so two of the same marks cancel) -pub fn xorPush(marks: &mut Vec , mark: Mrk) { - if (marks.len() > 0) && (getLast(marks) == mark) { - marks.pop().unwrap(); - } else { - marks.push(mark); - } -} - -// get the last element of a mutable array. -// FIXME #4903: , must be a separate procedure for now. -pub fn getLast(arr: &Vec ) -> Mrk { - *arr.last().unwrap() -} - // are two paths equal when compared unhygienically? // since I'm using this to replace ==, it seems appropriate // to compare the span, global, etc. fields as well. @@ -937,9 +690,6 @@ mod test { use ast::*; use super::*; use opt_vec; - use collections::HashMap; - - use std::vec_ng::Vec; fn ident_to_segment(id : &Ident) -> PathSegment { PathSegment {identifier:id.clone(), @@ -957,210 +707,4 @@ mod test { [Ident{name:3,ctxt:104}, Ident{name:77,ctxt:182}].map(ident_to_segment))); } - - #[test] fn xorpush_test () { - let mut s = Vec::new(); - xorPush(&mut s, 14); - assert_eq!(s.clone(), vec!(14)); - xorPush(&mut s, 14); - assert_eq!(s.clone(), Vec::new()); - xorPush(&mut s, 14); - assert_eq!(s.clone(), vec!(14)); - xorPush(&mut s, 15); - assert_eq!(s.clone(), vec!(14, 15)); - xorPush(&mut s, 16); - assert_eq!(s.clone(), vec!(14, 15, 16)); - xorPush(&mut s, 16); - assert_eq!(s.clone(), vec!(14, 15)); - xorPush(&mut s, 15); - assert_eq!(s.clone(), vec!(14)); - } - - fn id(n: Name, s: SyntaxContext) -> Ident { - Ident {name: n, ctxt: s} - } - - // because of the SCTable, I now need a tidy way of - // creating syntax objects. Sigh. - #[deriving(Clone, Eq, Show)] - enum TestSC { - M(Mrk), - R(Ident,Name) - } - - // unfold a vector of TestSC values into a SCTable, - // returning the resulting index - fn unfold_test_sc(tscs : Vec , tail: SyntaxContext, table: &SCTable) - -> SyntaxContext { - tscs.rev_iter().fold(tail, |tail : SyntaxContext, tsc : &TestSC| - {match *tsc { - M(mrk) => new_mark_internal(mrk,tail,table), - R(ident,name) => new_rename_internal(ident,name,tail,table)}}) - } - - // gather a SyntaxContext back into a vector of TestSCs - fn refold_test_sc(mut sc: SyntaxContext, table : &SCTable) -> Vec { - let mut result = Vec::new(); - loop { - let table = table.table.borrow(); - match *table.get().get(sc as uint) { - EmptyCtxt => {return result;}, - Mark(mrk,tail) => { - result.push(M(mrk)); - sc = tail; - continue; - }, - Rename(id,name,tail) => { - result.push(R(id,name)); - sc = tail; - continue; - } - IllegalCtxt => fail!("expected resolvable context, got IllegalCtxt") - } - } - } - - #[test] fn test_unfold_refold(){ - let mut t = new_sctable_internal(); - - let test_sc = vec!(M(3),R(id(101,0),14),M(9)); - assert_eq!(unfold_test_sc(test_sc.clone(),EMPTY_CTXT,&mut t),4); - { - let table = t.table.borrow(); - assert!(*table.get().get(2) == Mark(9,0)); - assert!(*table.get().get(3) == Rename(id(101,0),14,2)); - assert!(*table.get().get(4) == Mark(3,3)); - } - assert_eq!(refold_test_sc(4,&t),test_sc); - } - - // extend a syntax context with a sequence of marks given - // in a vector. v[0] will be the outermost mark. - fn unfold_marks(mrks: Vec , tail: SyntaxContext, table: &SCTable) - -> SyntaxContext { - mrks.rev_iter().fold(tail, |tail:SyntaxContext, mrk:&Mrk| - {new_mark_internal(*mrk,tail,table)}) - } - - #[test] fn unfold_marks_test() { - let mut t = new_sctable_internal(); - - assert_eq!(unfold_marks(vec!(3,7),EMPTY_CTXT,&mut t),3); - { - let table = t.table.borrow(); - assert!(*table.get().get(2) == Mark(7,0)); - assert!(*table.get().get(3) == Mark(3,2)); - } - } - - #[test] fn test_marksof () { - let stopname = 242; - let name1 = 243; - let mut t = new_sctable_internal(); - assert_eq!(marksof (EMPTY_CTXT,stopname,&t),Vec::new()); - // FIXME #5074: ANF'd to dodge nested calls - { let ans = unfold_marks(vec!(4,98),EMPTY_CTXT,&mut t); - assert_eq! (marksof (ans,stopname,&t),vec!(4,98));} - // does xoring work? - { let ans = unfold_marks(vec!(5,5,16),EMPTY_CTXT,&mut t); - assert_eq! (marksof (ans,stopname,&t), vec!(16));} - // does nested xoring work? - { let ans = unfold_marks(vec!(5,10,10,5,16),EMPTY_CTXT,&mut t); - assert_eq! (marksof (ans, stopname,&t), vec!(16));} - // rename where stop doesn't match: - { let chain = vec!(M(9), - R(id(name1, - new_mark_internal (4, EMPTY_CTXT,&mut t)), - 100101102), - M(14)); - let ans = unfold_test_sc(chain,EMPTY_CTXT,&mut t); - assert_eq! (marksof (ans, stopname, &t), vec!(9,14));} - // rename where stop does match - { let name1sc = new_mark_internal(4, EMPTY_CTXT, &mut t); - let chain = vec!(M(9), - R(id(name1, name1sc), - stopname), - M(14)); - let ans = unfold_test_sc(chain,EMPTY_CTXT,&mut t); - assert_eq! (marksof (ans, stopname, &t), vec!(9)); } - } - - - #[test] fn resolve_tests () { - let a = 40; - let mut t = new_sctable_internal(); - let mut rt = HashMap::new(); - // - ctxt is MT - assert_eq!(resolve_internal(id(a,EMPTY_CTXT),&mut t, &mut rt),a); - // - simple ignored marks - { let sc = unfold_marks(vec!(1,2,3),EMPTY_CTXT,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),a);} - // - orthogonal rename where names don't match - { let sc = unfold_test_sc(vec!(R(id(50,EMPTY_CTXT),51),M(12)),EMPTY_CTXT,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),a);} - // - rename where names do match, but marks don't - { let sc1 = new_mark_internal(1,EMPTY_CTXT,&mut t); - let sc = unfold_test_sc(vec!(R(id(a,sc1),50), - M(1), - M(2)), - EMPTY_CTXT,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), a);} - // - rename where names and marks match - { let sc1 = unfold_test_sc(vec!(M(1),M(2)),EMPTY_CTXT,&mut t); - let sc = unfold_test_sc(vec!(R(id(a,sc1),50),M(1),M(2)),EMPTY_CTXT,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), 50); } - // - rename where names and marks match by literal sharing - { let sc1 = unfold_test_sc(vec!(M(1),M(2)),EMPTY_CTXT,&mut t); - let sc = unfold_test_sc(vec!(R(id(a,sc1),50)),sc1,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), 50); } - // - two renames of the same var.. can only happen if you use - // local-expand to prevent the inner binding from being renamed - // during the rename-pass caused by the first: - println!("about to run bad test"); - { let sc = unfold_test_sc(vec!(R(id(a,EMPTY_CTXT),50), - R(id(a,EMPTY_CTXT),51)), - EMPTY_CTXT,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), 51); } - // the simplest double-rename: - { let a_to_a50 = new_rename_internal(id(a,EMPTY_CTXT),50,EMPTY_CTXT,&mut t); - let a50_to_a51 = new_rename_internal(id(a,a_to_a50),51,a_to_a50,&mut t); - assert_eq!(resolve_internal(id(a,a50_to_a51),&mut t, &mut rt),51); - // mark on the outside doesn't stop rename: - let sc = new_mark_internal(9,a50_to_a51,&mut t); - assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),51); - // but mark on the inside does: - let a50_to_a51_b = unfold_test_sc(vec!(R(id(a,a_to_a50),51), - M(9)), - a_to_a50, - &mut t); - assert_eq!(resolve_internal(id(a,a50_to_a51_b),&mut t, &mut rt),50);} - } - - #[test] fn mtwt_resolve_test(){ - let a = 40; - assert_eq!(mtwt_resolve(id(a,EMPTY_CTXT)),a); - } - - - #[test] fn hashing_tests () { - let mut t = new_sctable_internal(); - assert_eq!(new_mark_internal(12,EMPTY_CTXT,&mut t),2); - assert_eq!(new_mark_internal(13,EMPTY_CTXT,&mut t),3); - // using the same one again should result in the same index: - assert_eq!(new_mark_internal(12,EMPTY_CTXT,&mut t),2); - // I'm assuming that the rename table will behave the same.... - } - - #[test] fn resolve_table_hashing_tests() { - let mut t = new_sctable_internal(); - let mut rt = HashMap::new(); - assert_eq!(rt.len(),0); - resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt); - assert_eq!(rt.len(),1); - resolve_internal(id(39,EMPTY_CTXT),&mut t, &mut rt); - assert_eq!(rt.len(),2); - resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt); - assert_eq!(rt.len(),2); - } - } diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 5cb09ec823221..79068d4046965 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -146,9 +146,9 @@ pub enum SyntaxExtension { pub struct BlockInfo { // should macros escape from this scope? - macros_escape : bool, + macros_escape: bool, // what are the pending renames? - pending_renames : RenameList, + pending_renames: RenameList, } impl BlockInfo { @@ -161,7 +161,7 @@ impl BlockInfo { } // a list of ident->name renamings -pub type RenameList = Vec<(ast::Ident,Name)> ; +pub type RenameList = Vec<(ast::Ident, Name)>; // The base map of methods for expanding syntax extension // AST nodes into full ASTs diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 13c340cb7747b..8b23de235b804 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -11,9 +11,9 @@ use ast::{P, Block, Crate, DeclLocal, ExprMac}; use ast::{Local, Ident, MacInvocTT}; use ast::{ItemMac, Mrk, Stmt, StmtDecl, StmtMac, StmtExpr, StmtSemi}; -use ast::{TokenTree}; +use ast::TokenTree; use ast; -use ast_util::{new_rename, new_mark}; +use ext::mtwt; use ext::build::AstBuilder; use attr; use attr::AttrMetaMethods; @@ -140,9 +140,7 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr { // Expand any interior macros etc. // NB: we don't fold pats yet. Curious. let src_expr = fld.fold_expr(src_expr).clone(); - // Rename label before expansion. - let (opt_ident, src_loop_block) = rename_loop_label(opt_ident, src_loop_block, fld); - let src_loop_block = fld.fold_block(src_loop_block); + let (src_loop_block, opt_ident) = expand_loop_block(src_loop_block, opt_ident, fld); let span = e.span; @@ -205,9 +203,7 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr { } ast::ExprLoop(loop_block, opt_ident) => { - let (opt_ident, loop_block) = - rename_loop_label(opt_ident, loop_block, fld); - let loop_block = fld.fold_block(loop_block); + let (loop_block, opt_ident) = expand_loop_block(loop_block, opt_ident, fld); fld.cx.expr(e.span, ast::ExprLoop(loop_block, opt_ident)) } @@ -215,22 +211,38 @@ pub fn expand_expr(e: @ast::Expr, fld: &mut MacroExpander) -> @ast::Expr { } } -// Rename loop label and its all occurrences inside the loop body -fn rename_loop_label(opt_ident: Option, - loop_block: P, - fld: &mut MacroExpander) -> (Option, P) { +// Rename loop label and expand its loop body +// +// The renaming procedure for loop is different in the sense that the loop +// body is in a block enclosed by loop head so the renaming of loop label +// must be propagated to the enclosed context. +fn expand_loop_block(loop_block: P, + opt_ident: Option, + fld: &mut MacroExpander) -> (P, Option) { match opt_ident { Some(label) => { - // Generate fresh label and add to the existing pending renames let new_label = fresh_name(&label); let rename = (label, new_label); + + // The rename *must not* be added to the pending list of current + // syntax context otherwise an unrelated `break` or `continue` in + // the same context will pick that up in the deferred renaming pass + // and be renamed incorrectly. + let mut rename_list = vec!(rename); + let mut rename_fld = renames_to_fold(&mut rename_list); + let renamed_ident = rename_fld.fold_ident(label); + + // 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); - let mut pending_renames = vec!(rename); - let mut rename_fld = renames_to_fold(&mut pending_renames); - (Some(rename_fld.fold_ident(label)), - rename_fld.fold_block(loop_block)) + let expanded_block = expand_block_elts(loop_block, fld); + fld.extsbox.pop_frame(); + + (expanded_block, Some(renamed_ident)) } - None => (None, loop_block) + None => (fld.fold_block(loop_block), opt_ident) } } @@ -628,9 +640,7 @@ fn expand_non_macro_stmt(s: &Stmt, fld: &mut MacroExpander) rename_fld.fold_pat(expanded_pat) }; // add them to the existing pending renames: - for pr in new_pending_renames.iter() { - fld.extsbox.info().pending_renames.push(*pr) - } + fld.extsbox.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 = @@ -754,11 +764,11 @@ pub struct IdentRenamer<'a> { } impl<'a> Folder for IdentRenamer<'a> { - fn fold_ident(&mut self, id: ast::Ident) -> ast::Ident { + fn fold_ident(&mut self, id: Ident) -> Ident { let new_ctxt = self.renames.iter().fold(id.ctxt, |ctxt, &(from, to)| { - new_rename(from, to, ctxt) + mtwt::new_rename(from, to, ctxt) }); - ast::Ident { + Ident { name: id.name, ctxt: new_ctxt, } @@ -839,10 +849,10 @@ pub fn expand_crate(parse_sess: @parse::ParseSess, struct Marker { mark: Mrk } impl Folder for Marker { - fn fold_ident(&mut self, id: ast::Ident) -> ast::Ident { + fn fold_ident(&mut self, id: Ident) -> Ident { ast::Ident { name: id.name, - ctxt: new_mark(self.mark, id.ctxt) + ctxt: mtwt::new_mark(self.mark, id.ctxt) } } fn fold_mac(&mut self, m: &ast::Mac) -> ast::Mac { @@ -850,7 +860,7 @@ impl Folder for Marker { MacInvocTT(ref path, ref tts, ctxt) => { MacInvocTT(self.fold_path(path), fold_tts(tts.as_slice(), self), - new_mark(self.mark, ctxt)) + mtwt::new_mark(self.mark, ctxt)) } }; Spanned { @@ -906,11 +916,10 @@ mod test { use super::*; use ast; use ast::{Attribute_, AttrOuter, MetaWord}; - use ast_util::{get_sctable, mtwt_marksof, mtwt_resolve}; - use ast_util; use codemap; use codemap::Spanned; use ext::base::{CrateLoader, MacroCrate}; + use ext::mtwt; use parse; use parse::token; use util::parser_testing::{string_to_crate_and_sess}; @@ -1139,8 +1148,8 @@ mod test { // must be one check clause for each binding: assert_eq!(bindings.len(),bound_connections.len()); for (binding_idx,shouldmatch) in bound_connections.iter().enumerate() { - let binding_name = mtwt_resolve(*bindings.get(binding_idx)); - let binding_marks = mtwt_marksof(bindings.get(binding_idx).ctxt,invalid_name); + let binding_name = mtwt::resolve(*bindings.get(binding_idx)); + let binding_marks = mtwt::marksof(bindings.get(binding_idx).ctxt, invalid_name); // shouldmatch can't name varrefs that don't exist: assert!((shouldmatch.len() == 0) || (varrefs.len() > *shouldmatch.iter().max().unwrap())); @@ -1149,19 +1158,19 @@ mod test { // it should be a path of length 1, and it should // be free-identifier=? or bound-identifier=? to the given binding assert_eq!(varref.segments.len(),1); - let varref_name = mtwt_resolve(varref.segments - .get(0) - .identifier); - let varref_marks = mtwt_marksof(varref.segments + let varref_name = mtwt::resolve(varref.segments .get(0) - .identifier - .ctxt, - invalid_name); + .identifier); + let varref_marks = mtwt::marksof(varref.segments + .get(0) + .identifier + .ctxt, + invalid_name); if !(varref_name==binding_name) { println!("uh oh, should match but doesn't:"); println!("varref: {:?}",varref); println!("binding: {:?}", *bindings.get(binding_idx)); - ast_util::display_sctable(get_sctable()); + mtwt::with_sctable(|x| mtwt::display_sctable(x)); } assert_eq!(varref_name,binding_name); if bound_ident_check { @@ -1171,8 +1180,8 @@ mod test { } } else { let fail = (varref.segments.len() == 1) - && (mtwt_resolve(varref.segments.get(0).identifier) == - binding_name); + && (mtwt::resolve(varref.segments.get(0).identifier) + == binding_name); // temp debugging: if fail { println!("failure on test {}",test_idx); @@ -1188,7 +1197,7 @@ mod test { varref.segments.get(0).identifier.name, string.get()); println!("binding: {:?}", *bindings.get(binding_idx)); - ast_util::display_sctable(get_sctable()); + mtwt::with_sctable(|x| mtwt::display_sctable(x)); } assert!(!fail); } @@ -1218,7 +1227,7 @@ foo_module!() [b] => b, _ => fail!("expected just one binding for ext_cx") }; - let resolved_binding = mtwt_resolve(*cxbind); + let resolved_binding = mtwt::resolve(*cxbind); // find all the xx varrefs: let mut path_finder = new_path_finder(Vec::new()); visit::walk_crate(&mut path_finder, &cr, ()); @@ -1229,26 +1238,17 @@ foo_module!() p.segments.len() == 1 && "xx" == token::get_ident(p.segments.get(0).identifier).get() }).enumerate() { - if mtwt_resolve(v.segments.get(0).identifier) != - resolved_binding { + if mtwt::resolve(v.segments.get(0).identifier) != resolved_binding { println!("uh oh, xx binding didn't match xx varref:"); println!("this is xx varref \\# {:?}",idx); println!("binding: {:?}",cxbind); println!("resolves to: {:?}",resolved_binding); println!("varref: {:?}",v.segments.get(0).identifier); println!("resolves to: {:?}", - mtwt_resolve(v.segments.get(0).identifier)); - let table = get_sctable(); - println!("SC table:"); - - { - let table = table.table.borrow(); - for (idx,val) in table.get().iter().enumerate() { - println!("{:4u}: {:?}",idx,val); - } - } + mtwt::resolve(v.segments.get(0).identifier)); + mtwt::with_sctable(|x| mtwt::display_sctable(x)); } - assert_eq!(mtwt_resolve(v.segments.get(0).identifier), + assert_eq!(mtwt::resolve(v.segments.get(0).identifier), resolved_binding); }; } diff --git a/src/libsyntax/ext/mtwt.rs b/src/libsyntax/ext/mtwt.rs new file mode 100644 index 0000000000000..b0ed215f3e103 --- /dev/null +++ b/src/libsyntax/ext/mtwt.rs @@ -0,0 +1,482 @@ +// Copyright 2012-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. + +//! Machinery for hygienic macros, as described in the MTWT[1] paper. +//! +//! [1] Matthew Flatt, Ryan Culpepper, David Darais, and Robert Bruce Findler. +//! 2012. *Macros that work together: Compile-time bindings, partial expansion, +//! and definition contexts*. J. Funct. Program. 22, 2 (March 2012), 181-216. +//! DOI=10.1017/S0956796812000093 http://dx.doi.org/10.1017/S0956796812000093 + +use ast::{Ident, Mrk, Name, SyntaxContext}; + +use std::cell::RefCell; +use std::local_data; +use std::rc::Rc; +use std::vec_ng::Vec; + +use collections::HashMap; + +// the SCTable contains a table of SyntaxContext_'s. It +// represents a flattened tree structure, to avoid having +// managed pointers everywhere (that caused an ICE). +// the mark_memo and rename_memo fields are side-tables +// that ensure that adding the same mark to the same context +// gives you back the same context as before. This shouldn't +// change the semantics--everything here is immutable--but +// it should cut down on memory use *a lot*; applying a mark +// to a tree containing 50 identifiers would otherwise generate +pub struct SCTable { + table: RefCell>, + mark_memo: RefCell>, + rename_memo: RefCell>, +} + +#[deriving(Eq, Encodable, Decodable, Hash)] +pub enum SyntaxContext_ { + EmptyCtxt, + Mark (Mrk,SyntaxContext), + // flattening the name and syntaxcontext into the rename... + // HIDDEN INVARIANTS: + // 1) the first name in a Rename node + // can only be a programmer-supplied name. + // 2) Every Rename node with a given Name in the + // "to" slot must have the same name and context + // in the "from" slot. In essence, they're all + // pointers to a single "rename" event node. + Rename (Ident,Name,SyntaxContext), + // actually, IllegalCtxt may not be necessary. + IllegalCtxt +} + +/// Extend a syntax context with a given mark +pub fn new_mark(m: Mrk, tail: SyntaxContext) -> SyntaxContext { + with_sctable(|table| new_mark_internal(m, tail, table)) +} + +// Extend a syntax context with a given mark and table +fn new_mark_internal(m: Mrk, tail: SyntaxContext, table: &SCTable) -> SyntaxContext { + let key = (tail, m); + let mut mark_memo = table.mark_memo.borrow_mut(); + let new_ctxt = |_: &(SyntaxContext, Mrk)| + idx_push(table.table.borrow_mut().get(), Mark(m, tail)); + + *mark_memo.get().find_or_insert_with(key, new_ctxt) +} + +/// Extend a syntax context with a given rename +pub fn new_rename(id: Ident, to:Name, + tail: SyntaxContext) -> SyntaxContext { + with_sctable(|table| new_rename_internal(id, to, tail, table)) +} + +// Extend a syntax context with a given rename and sctable +fn new_rename_internal(id: Ident, + to: Name, + tail: SyntaxContext, + table: &SCTable) -> SyntaxContext { + let key = (tail,id,to); + let mut rename_memo = table.rename_memo.borrow_mut(); + let new_ctxt = |_: &(SyntaxContext, Ident, Mrk)| + idx_push(table.table.borrow_mut().get(), Rename(id, to, tail)); + + *rename_memo.get().find_or_insert_with(key, new_ctxt) +} + +/// Fetch the SCTable from TLS, create one if it doesn't yet exist. +pub fn with_sctable(op: |&SCTable| -> T) -> T { + local_data_key!(sctable_key: Rc) + + local_data::get(sctable_key, |opt_ts| { + let table = match opt_ts { + None => { + let ts = Rc::new(new_sctable_internal()); + local_data::set(sctable_key, ts.clone()); + ts + } + Some(ts) => ts.clone() + }; + op(table.borrow()) + }) +} + +// Make a fresh syntax context table with EmptyCtxt in slot zero +// and IllegalCtxt in slot one. +fn new_sctable_internal() -> SCTable { + SCTable { + table: RefCell::new(vec!(EmptyCtxt, IllegalCtxt)), + mark_memo: RefCell::new(HashMap::new()), + rename_memo: RefCell::new(HashMap::new()), + } +} + +/// Print out an SCTable for debugging +pub fn display_sctable(table: &SCTable) { + error!("SC table:"); + let table = table.table.borrow(); + for (idx,val) in table.get().iter().enumerate() { + error!("{:4u} : {:?}",idx,val); + } +} + + +// Add a value to the end of a vec, return its index +fn idx_push(vec: &mut Vec , val: T) -> u32 { + vec.push(val); + (vec.len() - 1) as u32 +} + +/// Resolve a syntax object to a name, per MTWT. +pub fn resolve(id: Ident) -> Name { + with_sctable(|sctable| { + with_resolve_table_mut(|resolve_table| { + resolve_internal(id, sctable, resolve_table) + }) + }) +} + +type ResolveTable = HashMap<(Name,SyntaxContext),Name>; + +// okay, I admit, putting this in TLS is not so nice: +// fetch the SCTable from TLS, create one if it doesn't yet exist. +fn with_resolve_table_mut(op: |&mut ResolveTable| -> T) -> T { + local_data_key!(resolve_table_key: Rc>) + + local_data::get(resolve_table_key, |opt_ts| { + let table = match opt_ts { + None => { + let ts = Rc::new(RefCell::new(HashMap::new())); + local_data::set(resolve_table_key, ts.clone()); + ts + } + Some(ts) => ts.clone() + }; + op(table.borrow().borrow_mut().get()) + }) +} + +// Resolve a syntax object to a name, per MTWT. +// adding memorization to possibly resolve 500+ seconds in resolve for librustc (!) +fn resolve_internal(id: Ident, + table: &SCTable, + resolve_table: &mut ResolveTable) -> Name { + let key = (id.name, id.ctxt); + + match resolve_table.find(&key) { + Some(&name) => return name, + None => {} + } + + let resolved = { + let result = *table.table.borrow().get().get(id.ctxt as uint); + match result { + EmptyCtxt => id.name, + // ignore marks here: + Mark(_,subctxt) => + resolve_internal(Ident{name:id.name, ctxt: subctxt}, + table, resolve_table), + // do the rename if necessary: + Rename(Ident{name, ctxt}, toname, subctxt) => { + let resolvedfrom = + resolve_internal(Ident{name:name, ctxt:ctxt}, + table, resolve_table); + let resolvedthis = + resolve_internal(Ident{name:id.name, ctxt:subctxt}, + table, resolve_table); + if (resolvedthis == resolvedfrom) + && (marksof_internal(ctxt, resolvedthis, table) + == marksof_internal(subctxt, resolvedthis, table)) { + toname + } else { + resolvedthis + } + } + IllegalCtxt() => fail!("expected resolvable context, got IllegalCtxt") + } + }; + resolve_table.insert(key, resolved); + resolved +} + +/// Compute the marks associated with a syntax context. +pub fn marksof(ctxt: SyntaxContext, stopname: Name) -> Vec { + with_sctable(|table| marksof_internal(ctxt, stopname, table)) +} + +// the internal function for computing marks +// it's not clear to me whether it's better to use a [] mutable +// vector or a cons-list for this. +fn marksof_internal(ctxt: SyntaxContext, + stopname: Name, + table: &SCTable) -> Vec { + let mut result = Vec::new(); + let mut loopvar = ctxt; + loop { + let table_entry = { + let table = table.table.borrow(); + *table.get().get(loopvar as uint) + }; + match table_entry { + EmptyCtxt => { + return result; + }, + Mark(mark, tl) => { + xorPush(&mut result, mark); + loopvar = tl; + }, + Rename(_,name,tl) => { + // see MTWT for details on the purpose of the stopname. + // short version: it prevents duplication of effort. + if name == stopname { + return result; + } else { + loopvar = tl; + } + } + IllegalCtxt => fail!("expected resolvable context, got IllegalCtxt") + } + } +} + +/// Return the outer mark for a context with a mark at the outside. +/// FAILS when outside is not a mark. +pub fn outer_mark(ctxt: SyntaxContext) -> Mrk { + with_sctable(|sctable| { + match *sctable.table.borrow().get().get(ctxt as uint) { + Mark(mrk, _) => mrk, + _ => fail!("can't retrieve outer mark when outside is not a mark") + } + }) +} + +// Push a name... unless it matches the one on top, in which +// case pop and discard (so two of the same marks cancel) +fn xorPush(marks: &mut Vec, mark: Mrk) { + if (marks.len() > 0) && (*marks.last().unwrap() == mark) { + marks.pop().unwrap(); + } else { + marks.push(mark); + } +} + +#[cfg(test)] +mod tests { + use ast::*; + use super::{resolve, xorPush, new_mark_internal, new_sctable_internal}; + use super::{new_rename_internal, marksof_internal, resolve_internal}; + use super::{SCTable, EmptyCtxt, Mark, Rename, IllegalCtxt}; + use std::vec_ng::Vec; + use collections::HashMap; + + #[test] fn xorpush_test () { + let mut s = Vec::new(); + xorPush(&mut s, 14); + assert_eq!(s.clone(), vec!(14)); + xorPush(&mut s, 14); + assert_eq!(s.clone(), Vec::new()); + xorPush(&mut s, 14); + assert_eq!(s.clone(), vec!(14)); + xorPush(&mut s, 15); + assert_eq!(s.clone(), vec!(14, 15)); + xorPush(&mut s, 16); + assert_eq!(s.clone(), vec!(14, 15, 16)); + xorPush(&mut s, 16); + assert_eq!(s.clone(), vec!(14, 15)); + xorPush(&mut s, 15); + assert_eq!(s.clone(), vec!(14)); + } + + fn id(n: Name, s: SyntaxContext) -> Ident { + Ident {name: n, ctxt: s} + } + + // because of the SCTable, I now need a tidy way of + // creating syntax objects. Sigh. + #[deriving(Clone, Eq, Show)] + enum TestSC { + M(Mrk), + R(Ident,Name) + } + + // unfold a vector of TestSC values into a SCTable, + // returning the resulting index + fn unfold_test_sc(tscs : Vec , tail: SyntaxContext, table: &SCTable) + -> SyntaxContext { + tscs.rev_iter().fold(tail, |tail : SyntaxContext, tsc : &TestSC| + {match *tsc { + M(mrk) => new_mark_internal(mrk,tail,table), + R(ident,name) => new_rename_internal(ident,name,tail,table)}}) + } + + // gather a SyntaxContext back into a vector of TestSCs + fn refold_test_sc(mut sc: SyntaxContext, table : &SCTable) -> Vec { + let mut result = Vec::new(); + loop { + let table = table.table.borrow(); + match *table.get().get(sc as uint) { + EmptyCtxt => {return result;}, + Mark(mrk,tail) => { + result.push(M(mrk)); + sc = tail; + continue; + }, + Rename(id,name,tail) => { + result.push(R(id,name)); + sc = tail; + continue; + } + IllegalCtxt => fail!("expected resolvable context, got IllegalCtxt") + } + } + } + + #[test] fn test_unfold_refold(){ + let mut t = new_sctable_internal(); + + let test_sc = vec!(M(3),R(id(101,0),14),M(9)); + assert_eq!(unfold_test_sc(test_sc.clone(),EMPTY_CTXT,&mut t),4); + { + let table = t.table.borrow(); + assert!(*table.get().get(2) == Mark(9,0)); + assert!(*table.get().get(3) == Rename(id(101,0),14,2)); + assert!(*table.get().get(4) == Mark(3,3)); + } + assert_eq!(refold_test_sc(4,&t),test_sc); + } + + // extend a syntax context with a sequence of marks given + // in a vector. v[0] will be the outermost mark. + fn unfold_marks(mrks: Vec , tail: SyntaxContext, table: &SCTable) + -> SyntaxContext { + mrks.rev_iter().fold(tail, |tail:SyntaxContext, mrk:&Mrk| + {new_mark_internal(*mrk,tail,table)}) + } + + #[test] fn unfold_marks_test() { + let mut t = new_sctable_internal(); + + assert_eq!(unfold_marks(vec!(3,7),EMPTY_CTXT,&mut t),3); + { + let table = t.table.borrow(); + assert!(*table.get().get(2) == Mark(7,0)); + assert!(*table.get().get(3) == Mark(3,2)); + } + } + + #[test] fn test_marksof () { + let stopname = 242; + let name1 = 243; + let mut t = new_sctable_internal(); + assert_eq!(marksof_internal (EMPTY_CTXT,stopname,&t),Vec::new()); + // FIXME #5074: ANF'd to dodge nested calls + { let ans = unfold_marks(vec!(4,98),EMPTY_CTXT,&mut t); + assert_eq! (marksof_internal (ans,stopname,&t),vec!(4,98));} + // does xoring work? + { let ans = unfold_marks(vec!(5,5,16),EMPTY_CTXT,&mut t); + assert_eq! (marksof_internal (ans,stopname,&t), vec!(16));} + // does nested xoring work? + { let ans = unfold_marks(vec!(5,10,10,5,16),EMPTY_CTXT,&mut t); + assert_eq! (marksof_internal (ans, stopname,&t), vec!(16));} + // rename where stop doesn't match: + { let chain = vec!(M(9), + R(id(name1, + new_mark_internal (4, EMPTY_CTXT,&mut t)), + 100101102), + M(14)); + let ans = unfold_test_sc(chain,EMPTY_CTXT,&mut t); + assert_eq! (marksof_internal (ans, stopname, &t), vec!(9,14));} + // rename where stop does match + { let name1sc = new_mark_internal(4, EMPTY_CTXT, &mut t); + let chain = vec!(M(9), + R(id(name1, name1sc), + stopname), + M(14)); + let ans = unfold_test_sc(chain,EMPTY_CTXT,&mut t); + assert_eq! (marksof_internal (ans, stopname, &t), vec!(9)); } + } + + + #[test] fn resolve_tests () { + let a = 40; + let mut t = new_sctable_internal(); + let mut rt = HashMap::new(); + // - ctxt is MT + assert_eq!(resolve_internal(id(a,EMPTY_CTXT),&mut t, &mut rt),a); + // - simple ignored marks + { let sc = unfold_marks(vec!(1,2,3),EMPTY_CTXT,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),a);} + // - orthogonal rename where names don't match + { let sc = unfold_test_sc(vec!(R(id(50,EMPTY_CTXT),51),M(12)),EMPTY_CTXT,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),a);} + // - rename where names do match, but marks don't + { let sc1 = new_mark_internal(1,EMPTY_CTXT,&mut t); + let sc = unfold_test_sc(vec!(R(id(a,sc1),50), + M(1), + M(2)), + EMPTY_CTXT,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), a);} + // - rename where names and marks match + { let sc1 = unfold_test_sc(vec!(M(1),M(2)),EMPTY_CTXT,&mut t); + let sc = unfold_test_sc(vec!(R(id(a,sc1),50),M(1),M(2)),EMPTY_CTXT,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), 50); } + // - rename where names and marks match by literal sharing + { let sc1 = unfold_test_sc(vec!(M(1),M(2)),EMPTY_CTXT,&mut t); + let sc = unfold_test_sc(vec!(R(id(a,sc1),50)),sc1,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), 50); } + // - two renames of the same var.. can only happen if you use + // local-expand to prevent the inner binding from being renamed + // during the rename-pass caused by the first: + println!("about to run bad test"); + { let sc = unfold_test_sc(vec!(R(id(a,EMPTY_CTXT),50), + R(id(a,EMPTY_CTXT),51)), + EMPTY_CTXT,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt), 51); } + // the simplest double-rename: + { let a_to_a50 = new_rename_internal(id(a,EMPTY_CTXT),50,EMPTY_CTXT,&mut t); + let a50_to_a51 = new_rename_internal(id(a,a_to_a50),51,a_to_a50,&mut t); + assert_eq!(resolve_internal(id(a,a50_to_a51),&mut t, &mut rt),51); + // mark on the outside doesn't stop rename: + let sc = new_mark_internal(9,a50_to_a51,&mut t); + assert_eq!(resolve_internal(id(a,sc),&mut t, &mut rt),51); + // but mark on the inside does: + let a50_to_a51_b = unfold_test_sc(vec!(R(id(a,a_to_a50),51), + M(9)), + a_to_a50, + &mut t); + assert_eq!(resolve_internal(id(a,a50_to_a51_b),&mut t, &mut rt),50);} + } + + #[test] fn mtwt_resolve_test(){ + let a = 40; + assert_eq!(resolve(id(a,EMPTY_CTXT)),a); + } + + + #[test] fn hashing_tests () { + let mut t = new_sctable_internal(); + assert_eq!(new_mark_internal(12,EMPTY_CTXT,&mut t),2); + assert_eq!(new_mark_internal(13,EMPTY_CTXT,&mut t),3); + // using the same one again should result in the same index: + assert_eq!(new_mark_internal(12,EMPTY_CTXT,&mut t),2); + // I'm assuming that the rename table will behave the same.... + } + + #[test] fn resolve_table_hashing_tests() { + let mut t = new_sctable_internal(); + let mut rt = HashMap::new(); + assert_eq!(rt.len(),0); + resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt); + assert_eq!(rt.len(),1); + resolve_internal(id(39,EMPTY_CTXT),&mut t, &mut rt); + assert_eq!(rt.len(),2); + resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt); + assert_eq!(rt.len(),2); + } +} diff --git a/src/libsyntax/lib.rs b/src/libsyntax/lib.rs index 9d2810d8344ef..0d7e54bb69d36 100644 --- a/src/libsyntax/lib.rs +++ b/src/libsyntax/lib.rs @@ -87,6 +87,7 @@ pub mod ext { pub mod macro_rules; } + pub mod mtwt; pub mod cfg; pub mod fmt; diff --git a/src/libsyntax/parse/token.rs b/src/libsyntax/parse/token.rs index 1499a1b4c19be..71ee32b4aade7 100644 --- a/src/libsyntax/parse/token.rs +++ b/src/libsyntax/parse/token.rs @@ -11,6 +11,7 @@ use ast; use ast::{P, Ident, Name, Mrk}; use ast_util; +use ext::mtwt; use parse::token; use util::interner::{RcStr, StrInterner}; use util::interner; @@ -722,7 +723,7 @@ pub fn is_reserved_keyword(tok: &Token) -> bool { pub fn mtwt_token_eq(t1 : &Token, t2 : &Token) -> bool { match (t1,t2) { (&IDENT(id1,_),&IDENT(id2,_)) | (&LIFETIME(id1),&LIFETIME(id2)) => - ast_util::mtwt_resolve(id1) == ast_util::mtwt_resolve(id2), + mtwt::resolve(id1) == mtwt::resolve(id2), _ => *t1 == *t2 } } @@ -732,10 +733,10 @@ pub fn mtwt_token_eq(t1 : &Token, t2 : &Token) -> bool { mod test { use super::*; use ast; - use ast_util; + use ext::mtwt; fn mark_ident(id : ast::Ident, m : ast::Mrk) -> ast::Ident { - ast::Ident{name:id.name,ctxt:ast_util::new_mark(m,id.ctxt)} + ast::Ident{name:id.name,ctxt:mtwt::new_mark(m,id.ctxt)} } #[test] fn mtwt_token_eq_test() {