auto merge of #15425 : jbclements/rust/hygiene-for-3-kinds-of-args, r=cmr

This pull request adds hygiene for 3 kinds of argument bindings:
- arguments to item fns,
- arguments to `ExprFnBlock`s, and
- arguments to `ExprProc`s

It also adds a bunch of unit tests, fixes a few macro uses to be non-capturing, and has a few cleanup items.

local `make check` succeeds.
This commit is contained in:
bors 2014-07-05 03:16:50 +00:00
commit 29d6a8ecc6
8 changed files with 297 additions and 123 deletions

View file

@ -781,9 +781,9 @@ fn extract_vec_elems<'a>(
// matches should fit that sort of pattern or NONE (however, some of the
// matches may be wildcards like _ or identifiers).
macro_rules! any_pat (
($m:expr, $pattern:pat) => (
($m:expr, $col:expr, $pattern:pat) => (
($m).iter().any(|br| {
match br.pats.get(col).node {
match br.pats.get($col).node {
$pattern => true,
_ => false
}
@ -792,11 +792,11 @@ macro_rules! any_pat (
)
fn any_uniq_pat(m: &[Match], col: uint) -> bool {
any_pat!(m, ast::PatBox(_))
any_pat!(m, col, ast::PatBox(_))
}
fn any_region_pat(m: &[Match], col: uint) -> bool {
any_pat!(m, ast::PatRegion(_))
any_pat!(m, col, ast::PatRegion(_))
}
fn any_irrefutable_adt_pat(bcx: &Block, m: &[Match], col: uint) -> bool {

View file

@ -508,14 +508,15 @@ mod bench {
use prelude::*;
use self::test::Bencher;
// why is this a macro? wouldn't an inlined function work just as well?
macro_rules! u64_from_be_bytes_bench_impl(
($size:expr, $stride:expr, $start_index:expr) =>
($b:expr, $size:expr, $stride:expr, $start_index:expr) =>
({
use super::u64_from_be_bytes;
let data = Vec::from_fn($stride*100+$start_index, |i| i as u8);
let mut sum = 0u64;
b.iter(|| {
$b.iter(|| {
let mut i = $start_index;
while i < data.len() {
sum += u64_from_be_bytes(data.as_slice(), i, $size);
@ -527,31 +528,31 @@ mod bench {
#[bench]
fn u64_from_be_bytes_4_aligned(b: &mut Bencher) {
u64_from_be_bytes_bench_impl!(4, 4, 0);
u64_from_be_bytes_bench_impl!(b, 4, 4, 0);
}
#[bench]
fn u64_from_be_bytes_4_unaligned(b: &mut Bencher) {
u64_from_be_bytes_bench_impl!(4, 4, 1);
u64_from_be_bytes_bench_impl!(b, 4, 4, 1);
}
#[bench]
fn u64_from_be_bytes_7_aligned(b: &mut Bencher) {
u64_from_be_bytes_bench_impl!(7, 8, 0);
u64_from_be_bytes_bench_impl!(b, 7, 8, 0);
}
#[bench]
fn u64_from_be_bytes_7_unaligned(b: &mut Bencher) {
u64_from_be_bytes_bench_impl!(7, 8, 1);
u64_from_be_bytes_bench_impl!(b, 7, 8, 1);
}
#[bench]
fn u64_from_be_bytes_8_aligned(b: &mut Bencher) {
u64_from_be_bytes_bench_impl!(8, 8, 0);
u64_from_be_bytes_bench_impl!(b, 8, 8, 0);
}
#[bench]
fn u64_from_be_bytes_8_unaligned(b: &mut Bencher) {
u64_from_be_bytes_bench_impl!(8, 8, 1);
u64_from_be_bytes_bench_impl!(b, 8, 8, 1);
}
}

View file

@ -190,6 +190,8 @@ pub struct TyParam {
pub span: Span
}
/// Represents lifetimes and type parameters attached to a declaration
/// of a function, enum, trait, etc.
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash)]
pub struct Generics {
pub lifetimes: Vec<Lifetime>,
@ -288,7 +290,7 @@ pub enum Pat_ {
PatWild,
PatWildMulti,
// A PatIdent may either be a new bound variable,
// or a nullary enum (in which case the second field
// or a nullary enum (in which case the third field
// is None).
// In the nullary enum case, the parser can't determine
// which it is. The resolver determines this, and
@ -453,10 +455,10 @@ pub enum Expr_ {
ExprCast(Gc<Expr>, P<Ty>),
ExprIf(Gc<Expr>, P<Block>, Option<Gc<Expr>>),
ExprWhile(Gc<Expr>, P<Block>),
// FIXME #6993: change to Option<Name>
// FIXME #6993: change to Option<Name> ... or not, if these are hygienic.
ExprForLoop(Gc<Pat>, Gc<Expr>, P<Block>, Option<Ident>),
// Conditionless loop (can be exited with break, cont, or ret)
// FIXME #6993: change to Option<Name>
// FIXME #6993: change to Option<Name> ... or not, if these are hygienic.
ExprLoop(P<Block>, Option<Ident>),
ExprMatch(Gc<Expr>, Vec<Arm>),
ExprFnBlock(P<FnDecl>, P<Block>),
@ -468,9 +470,8 @@ pub enum Expr_ {
ExprField(Gc<Expr>, SpannedIdent, Vec<P<Ty>>),
ExprIndex(Gc<Expr>, Gc<Expr>),
/// Expression that looks like a "name". For example,
/// `std::slice::from_elem::<uint>` is an ExprPath that's the "name" part
/// of a function call.
/// Variable reference, possibly containing `::` and/or
/// type parameters, e.g. foo::bar::<baz>
ExprPath(Path),
ExprAddrOf(Mutability, Gc<Expr>),
@ -643,6 +644,8 @@ pub struct TypeField {
pub span: Span,
}
/// Represents a required method in a trait declaration,
/// one without a default implementation
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash)]
pub struct TypeMethod {
pub ident: Ident,
@ -656,6 +659,8 @@ pub struct TypeMethod {
pub vis: Visibility,
}
/// Represents a method declaration in a trait declaration, possibly
/// including a default implementation
// A trait method is either required (meaning it doesn't have an
// implementation, just a signature) or provided (meaning it has a default
// implementation).
@ -741,6 +746,7 @@ impl fmt::Show for Onceness {
}
}
/// Represents the type of a closure
#[deriving(PartialEq, Eq, Encodable, Decodable, Hash)]
pub struct ClosureTy {
pub lifetimes: Vec<Lifetime>,
@ -809,6 +815,7 @@ pub struct InlineAsm {
pub dialect: AsmDialect
}
/// represents an argument in a function header
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash)]
pub struct Arg {
pub ty: P<Ty>,
@ -836,7 +843,7 @@ impl Arg {
}
}
// represents the header (not the body) of a function declaration
/// represents the header (not the body) of a function declaration
#[deriving(Clone, PartialEq, Eq, Encodable, Decodable, Hash)]
pub struct FnDecl {
pub inputs: Vec<Arg>,
@ -1107,6 +1114,7 @@ pub enum Item_ {
ItemTy(P<Ty>, Generics),
ItemEnum(EnumDef, Generics),
ItemStruct(Gc<StructDef>, Generics),
/// Represents a Trait Declaration
ItemTrait(Generics, Sized, Vec<TraitRef> , Vec<TraitMethod> ),
ItemImpl(Generics,
Option<TraitRef>, // (optional) trait this impl implements

View file

@ -19,6 +19,7 @@ use parse::parser;
use parse::token;
use parse::token::{InternedString, intern, str_to_ident};
use util::small_vector::SmallVector;
use ext::mtwt;
use std::collections::HashMap;
use std::gc::{Gc, GC};
@ -273,7 +274,7 @@ pub struct BlockInfo {
// should macros escape from this scope?
pub macros_escape: bool,
// what are the pending renames?
pub pending_renames: RenameList,
pub pending_renames: mtwt::RenameList,
}
impl BlockInfo {
@ -285,9 +286,6 @@ impl BlockInfo {
}
}
// a list of ident->name renamings
pub type RenameList = Vec<(ast::Ident, Name)>;
// The base map of methods for expanding syntax extension
// AST nodes into full ASTs
pub fn syntax_expander_table() -> SyntaxEnv {

View file

@ -21,6 +21,7 @@ use codemap;
use codemap::{Span, Spanned, ExpnInfo, NameAndSpan, MacroBang, MacroAttribute};
use crateid::CrateId;
use ext::base::*;
use fold;
use fold::*;
use parse;
use parse::token::{fresh_mark, fresh_name, intern};
@ -228,6 +229,20 @@ pub fn expand_expr(e: Gc<ast::Expr>, fld: &mut MacroExpander) -> Gc<ast::Expr> {
fld.cx.expr(e.span, ast::ExprLoop(loop_block, opt_ident))
}
ast::ExprFnBlock(fn_decl, block) => {
let (rewritten_fn_decl, rewritten_block)
= expand_and_rename_fn_decl_and_block(fn_decl, block, fld);
let new_node = ast::ExprFnBlock(rewritten_fn_decl, rewritten_block);
box(GC) ast::Expr{id:e.id, node: new_node, span: fld.new_span(e.span)}
}
ast::ExprProc(fn_decl, block) => {
let (rewritten_fn_decl, rewritten_block)
= expand_and_rename_fn_decl_and_block(fn_decl, block, fld);
let new_node = ast::ExprProc(rewritten_fn_decl, rewritten_block);
box(GC) ast::Expr{id:e.id, node: new_node, span: fld.new_span(e.span)}
}
_ => noop_fold_expr(e, fld)
}
}
@ -267,7 +282,8 @@ fn expand_loop_block(loop_block: P<Block>,
}
}
// eval $e with a new exts frame:
// eval $e with a new exts frame.
// must be a macro so that $e isn't evaluated too early.
macro_rules! with_exts_frame (
($extsboxexpr:expr,$macros_escape:expr,$e:expr) =>
({$extsboxexpr.push_frame();
@ -342,15 +358,16 @@ fn expand_item(it: Gc<ast::Item>, fld: &mut MacroExpander)
fn expand_item_modifiers(mut it: Gc<ast::Item>, fld: &mut MacroExpander)
-> Gc<ast::Item> {
let (modifiers, attrs) = it.attrs.partitioned(|attr| {
// 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,
_ => false
}
});
// update the attrs, leave everything else alone. Is this mutation really a good idea?
it = box(GC) ast::Item {
attrs: attrs,
attrs: other_attrs,
..(*it).clone()
};
@ -383,6 +400,19 @@ fn expand_item_modifiers(mut it: Gc<ast::Item>, fld: &mut MacroExpander)
expand_item_modifiers(it, fld)
}
/// Expand item_underscore
fn expand_item_underscore(item: &ast::Item_, fld: &mut MacroExpander) -> ast::Item_ {
match *item {
ast::ItemFn(decl, fn_style, abi, ref generics, body) => {
let (rewritten_fn_decl, rewritten_body)
= expand_and_rename_fn_decl_and_block(decl,body,fld);
let expanded_generics = fold::fold_generics(generics,fld);
ast::ItemFn(rewritten_fn_decl, fn_style, abi, expanded_generics, rewritten_body)
}
_ => noop_fold_item_underscore(&*item, fld)
}
}
// does this attribute list contain "macro_escape" ?
fn contains_macro_escape(attrs: &[ast::Attribute]) -> bool {
attr::contains_name(attrs, "macro_escape")
@ -609,7 +639,7 @@ fn expand_non_macro_stmt(s: &Stmt, fld: &mut MacroExpander)
} = **local;
// expand the pat (it might contain macro uses):
let expanded_pat = fld.fold_pat(pat);
// find the pat_idents in the pattern:
// find the PatIdents in the pattern:
// oh dear heaven... this is going to include the enum
// names, as well... but that should be okay, as long as
// the new names are gensyms for the old ones.
@ -653,6 +683,7 @@ fn expand_non_macro_stmt(s: &Stmt, fld: &mut MacroExpander)
}
}
// expand the arm of a 'match', renaming for macro hygiene
fn expand_arm(arm: &ast::Arm, fld: &mut MacroExpander) -> ast::Arm {
// expand pats... they might contain macro uses:
let expanded_pats : Vec<Gc<ast::Pat>> = arm.pats.iter().map(|pat| fld.fold_pat(*pat)).collect();
@ -662,22 +693,15 @@ fn expand_arm(arm: &ast::Arm, fld: &mut MacroExpander) -> ast::Arm {
// all of the pats must have the same set of bindings, so use the
// first one to extract them and generate new names:
let first_pat = expanded_pats.get(0);
// code duplicated from 'let', above. Perhaps this can be lifted
// into a separate function:
let idents = pattern_bindings(*first_pat);
let mut new_pending_renames =
let new_renames =
idents.iter().map(|id| (*id,fresh_name(id))).collect();
// rewrite all of the patterns using the new names (the old
// ones have already been applied). Note that we depend here
// on the guarantee that after expansion, there can't be any
// Path expressions (a.k.a. varrefs) left in the pattern. If
// this were false, we'd need to apply this renaming only to
// the bindings, and not to the varrefs, using a more targeted
// fold-er.
let mut rename_fld = IdentRenamer{renames:&mut new_pending_renames};
// apply the renaming, but only to the PatIdents:
let mut rename_pats_fld = PatIdentRenamer{renames:&new_renames};
let rewritten_pats =
expanded_pats.iter().map(|pat| rename_fld.fold_pat(*pat)).collect();
expanded_pats.iter().map(|pat| rename_pats_fld.fold_pat(*pat)).collect();
// apply renaming and then expansion to the guard and the body:
let mut rename_fld = IdentRenamer{renames:&new_renames};
let rewritten_guard =
arm.guard.map(|g| fld.fold_expr(rename_fld.fold_expr(g)));
let rewritten_body = fld.fold_expr(rename_fld.fold_expr(arm.body));
@ -689,45 +713,47 @@ fn expand_arm(arm: &ast::Arm, fld: &mut MacroExpander) -> ast::Arm {
}
}
// a visitor that extracts the pat_ident (binding) paths
// from a given thingy and puts them in a mutable
// array
/// A visitor that extracts the PatIdent (binding) paths
/// from a given thingy and puts them in a mutable
/// array
#[deriving(Clone)]
struct NameFinderContext {
struct PatIdentFinder {
ident_accumulator: Vec<ast::Ident> ,
}
impl Visitor<()> for NameFinderContext {
impl Visitor<()> for PatIdentFinder {
fn visit_pat(&mut self, pattern: &ast::Pat, _: ()) {
match *pattern {
// we found a pat_ident!
ast::Pat {
id: _,
node: ast::PatIdent(_, ref path1, ref inner),
span: _
} => {
ast::Pat { id: _, node: ast::PatIdent(_, ref path1, ref inner), span: _ } => {
self.ident_accumulator.push(path1.node);
// visit optional subpattern of pat_ident:
// visit optional subpattern of PatIdent:
for subpat in inner.iter() {
self.visit_pat(&**subpat, ())
}
}
// use the default traversal for non-pat_idents
// use the default traversal for non-PatIdents
_ => visit::walk_pat(self, pattern, ())
}
}
}
// find the pat_ident paths in a pattern
/// find the PatIdent paths in a pattern
fn pattern_bindings(pat : &ast::Pat) -> Vec<ast::Ident> {
let mut name_finder = NameFinderContext{ident_accumulator:Vec::new()};
let mut name_finder = PatIdentFinder{ident_accumulator:Vec::new()};
name_finder.visit_pat(pat,());
name_finder.ident_accumulator
}
/// find the PatIdent paths in a
fn fn_decl_arg_bindings(fn_decl: &ast::FnDecl) -> Vec<ast::Ident> {
let mut pat_idents = PatIdentFinder{ident_accumulator:Vec::new()};
for arg in fn_decl.inputs.iter() {
pat_idents.visit_pat(arg.pat,());
}
pat_idents.ident_accumulator
}
// expand a block. pushes a new exts_frame, then calls expand_block_elts
fn expand_block(blk: &Block, fld: &mut MacroExpander) -> P<Block> {
// see note below about treatment of exts table
@ -844,34 +870,71 @@ fn expand_pat(p: Gc<ast::Pat>, fld: &mut MacroExpander) -> Gc<ast::Pat> {
}
}
// a tree-folder that applies every rename in its (mutable) list
// to every identifier, including both bindings and varrefs
// (and lots of things that will turn out to be neither)
/// A tree-folder that applies every rename in its (mutable) list
/// to every identifier, including both bindings and varrefs
/// (and lots of things that will turn out to be neither)
pub struct IdentRenamer<'a> {
renames: &'a mut RenameList,
renames: &'a mtwt::RenameList,
}
impl<'a> Folder for IdentRenamer<'a> {
fn fold_ident(&mut self, id: Ident) -> Ident {
let new_ctxt = self.renames.iter().fold(id.ctxt, |ctxt, &(from, to)| {
mtwt::new_rename(from, to, ctxt)
});
Ident {
name: id.name,
ctxt: new_ctxt,
ctxt: mtwt::apply_renames(self.renames, id.ctxt),
}
}
}
fn new_span(cx: &ExtCtxt, sp: Span) -> Span {
/* this discards information in the case of macro-defining macros */
Span {
lo: sp.lo,
hi: sp.hi,
expn_info: cx.backtrace(),
/// A tree-folder that applies every rename in its list to
/// the idents that are in PatIdent patterns. This is more narrowly
/// focused than IdentRenamer, and is needed for FnDecl,
/// where we want to rename the args but not the fn name or the generics etc.
pub struct PatIdentRenamer<'a> {
renames: &'a mtwt::RenameList,
}
impl<'a> Folder for PatIdentRenamer<'a> {
fn fold_pat(&mut self, pat: Gc<ast::Pat>) -> Gc<ast::Pat> {
match pat.node {
ast::PatIdent(binding_mode, Spanned{span: ref sp, node: id}, ref sub) => {
let new_ident = Ident{name: id.name,
ctxt: mtwt::apply_renames(self.renames, id.ctxt)};
let new_node =
ast::PatIdent(binding_mode,
Spanned{span: self.new_span(*sp), node: new_ident},
sub.map(|p| self.fold_pat(p)));
box(GC) ast::Pat {
id: pat.id,
span: self.new_span(pat.span),
node: new_node,
}
},
_ => noop_fold_pat(pat, self)
}
}
}
/// Given a fn_decl and a block and a MacroExpander, expand the fn_decl, then use the
/// PatIdents in its arguments to perform renaming in the FnDecl and
/// the block, returning both the new FnDecl and the new Block.
fn expand_and_rename_fn_decl_and_block(fn_decl: &ast::FnDecl, block: Gc<ast::Block>,
fld: &mut MacroExpander)
-> (Gc<ast::FnDecl>, Gc<ast::Block>) {
let expanded_decl = fld.fold_fn_decl(fn_decl);
let idents = fn_decl_arg_bindings(expanded_decl);
let renames =
idents.iter().map(|id : &ast::Ident| (*id,fresh_name(id))).collect();
// first, a renamer for the PatIdents, for the fn_decl:
let mut rename_pat_fld = PatIdentRenamer{renames: &renames};
let rewritten_fn_decl = rename_pat_fld.fold_fn_decl(expanded_decl);
// now, a renamer for *all* idents, for the body:
let mut rename_fld = IdentRenamer{renames: &renames};
let rewritten_body = fld.fold_block(rename_fld.fold_block(block));
(rewritten_fn_decl,rewritten_body)
}
/// A tree-folder that performs macro expansion
pub struct MacroExpander<'a, 'b> {
pub extsbox: SyntaxEnv,
pub cx: &'a mut ExtCtxt<'b>,
@ -890,6 +953,10 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> {
expand_item(item, self)
}
fn fold_item_underscore(&mut self, item: &ast::Item_) -> ast::Item_ {
expand_item_underscore(item, self)
}
fn fold_stmt(&mut self, stmt: &ast::Stmt) -> SmallVector<Gc<ast::Stmt>> {
expand_stmt(stmt, self)
}
@ -907,6 +974,15 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> {
}
}
fn new_span(cx: &ExtCtxt, sp: Span) -> Span {
/* this discards information in the case of macro-defining macros */
Span {
lo: sp.lo,
hi: sp.hi,
expn_info: cx.backtrace(),
}
}
pub struct ExpansionConfig {
pub deriving_hash_type_parameter: bool,
pub crate_id: CrateId,
@ -966,7 +1042,7 @@ impl Folder for Marker {
fn fold_ident(&mut self, id: Ident) -> Ident {
ast::Ident {
name: id.name,
ctxt: mtwt::new_mark(self.mark, id.ctxt)
ctxt: mtwt::apply_mark(self.mark, id.ctxt)
}
}
fn fold_mac(&mut self, m: &ast::Mac) -> ast::Mac {
@ -974,7 +1050,7 @@ impl Folder for Marker {
MacInvocTT(ref path, ref tts, ctxt) => {
MacInvocTT(self.fold_path(path),
fold_tts(tts.as_slice(), self),
mtwt::new_mark(self.mark, ctxt))
mtwt::apply_mark(self.mark, ctxt))
}
};
Spanned {
@ -1028,13 +1104,14 @@ fn original_span(cx: &ExtCtxt) -> Gc<codemap::ExpnInfo> {
#[cfg(test)]
mod test {
use super::{pattern_bindings, expand_crate, contains_macro_escape};
use super::{NameFinderContext};
use super::{PatIdentFinder, IdentRenamer, PatIdentRenamer};
use ast;
use ast::{Attribute_, AttrOuter, MetaWord};
use attr;
use codemap;
use codemap::Spanned;
use ext::mtwt;
use fold::Folder;
use parse;
use parse::token;
use util::parser_testing::{string_to_parser};
@ -1072,7 +1149,24 @@ mod test {
path_finder.path_accumulator
}
/// A Visitor that extracts the identifiers from a thingy.
// as a side note, I'm starting to want to abstract over these....
struct IdentFinder{
ident_accumulator: Vec<ast::Ident>
}
impl Visitor<()> for IdentFinder {
fn visit_ident(&mut self, _: codemap::Span, id: ast::Ident, _: ()){
self.ident_accumulator.push(id);
}
}
/// Find the idents in a crate
fn crate_idents(the_crate: &ast::Crate) -> Vec<ast::Ident> {
let mut ident_finder = IdentFinder{ident_accumulator: Vec::new()};
visit::walk_crate(&mut ident_finder, the_crate, ());
ident_finder.ident_accumulator
}
// these following tests are quite fragile, in that they don't test what
// *kind* of failure occurs.
@ -1167,12 +1261,11 @@ mod test {
// find the pat_ident paths in a crate
fn crate_bindings(the_crate : &ast::Crate) -> Vec<ast::Ident> {
let mut name_finder = NameFinderContext{ident_accumulator:Vec::new()};
let mut name_finder = PatIdentFinder{ident_accumulator:Vec::new()};
visit::walk_crate(&mut name_finder, the_crate, ());
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);
@ -1298,18 +1391,37 @@ mod test {
// but *shouldn't* bind because it was inserted by a different macro....
// can't write this test case until we have macro-generating macros.
// FIXME #9383 : lambda var hygiene
// interesting... can't even write this test, yet, because the name-finder
// only finds pattern vars. Time to upgrade test framework.
/*#[test]
fn issue_9383(){
// item fn hygiene
// expands to fn q(x_1:int){fn g(x_2:int){x_2 + x_1};}
#[test] fn issue_9383(){
run_renaming_test(
&("macro_rules! bad_macro (($ex:expr) => ({(|_x| { $ex }) (9) }))
fn takes_x(_x : int) { assert_eq!(bad_macro!(_x),8); }
fn main() { takes_x(8); }",
vec!(vec!()),false),
&("macro_rules! bad_macro (($ex:expr) => (fn g(x:int){ x + $ex }))
fn q(x:int) { bad_macro!(x); }",
vec!(vec!(1),vec!(0)),true),
0)
}*/
}
// closure arg hygiene (ExprFnBlock)
// expands to fn f(){(|x_1 : int| {(x_2 + x_1)})(3);}
#[test] fn closure_arg_hygiene(){
run_renaming_test(
&("macro_rules! inject_x (()=>(x))
fn f(){(|x : int| {(inject_x!() + x)})(3);}",
vec!(vec!(1)),
true),
0)
}
// closure arg hygiene (ExprProc)
// expands to fn f(){(proc(x_1 : int) {(x_2 + x_1)})(3);}
#[test] fn closure_arg_hygiene_2(){
run_renaming_test(
&("macro_rules! inject_x (()=>(x))
fn f(){ (proc(x : int){(inject_x!() + x)})(3); }",
vec!(vec!(1)),
true),
0)
}
// run one of the renaming tests
fn run_renaming_test(t: &RenamingTest, test_idx: uint) {
@ -1359,9 +1471,9 @@ mod test {
assert_eq!(varref_marks,binding_marks.clone());
}
} else {
let varref_name = mtwt::resolve(varref.segments.get(0).identifier);
let fail = (varref.segments.len() == 1)
&& (mtwt::resolve(varref.segments.get(0).identifier)
== binding_name);
&& (varref_name == binding_name);
// temp debugging:
if fail {
let varref_idents : Vec<ast::Ident>
@ -1372,7 +1484,8 @@ mod test {
println!("text of test case: \"{}\"", teststr);
println!("");
println!("uh oh, matches but shouldn't:");
println!("varref: {}",varref_idents);
println!("varref #{}: {}, resolves to {}",idx, varref_idents,
varref_name);
// good lord, you can't make a path with 0 segments, can you?
let string = token::get_ident(varref.segments
.get(0)
@ -1380,7 +1493,9 @@ mod test {
println!("varref's first segment's uint: {}, and string: \"{}\"",
varref.segments.get(0).identifier.name,
string.get());
println!("binding: {}", *bindings.get(binding_idx));
println!("binding #{}: {}, resolves to {}",
binding_idx, *bindings.get(binding_idx),
binding_name);
mtwt::with_sctable(|x| mtwt::display_sctable(x));
}
assert!(!fail);
@ -1443,13 +1558,43 @@ foo_module!()
// 'None' is listed as an identifier pattern because we don't yet know that
// it's the name of a 0-ary variant, and that 'i' appears twice in succession.
#[test]
fn crate_idents(){
fn crate_bindings_test(){
let the_crate = string_to_crate("fn main (a : int) -> int {|b| {
match 34 {None => 3, Some(i) | i => j, Foo{k:z,l:y} => \"banana\"}} }".to_string());
let idents = crate_bindings(&the_crate);
assert_eq!(idents, strs_to_idents(vec!("a","b","None","i","i","z","y")));
}
//
// test the IdentRenamer directly
#[test]
fn ident_renamer_test () {
let the_crate = string_to_crate("fn f(x : int){let x = x; x}".to_string());
let f_ident = token::str_to_ident("f");
let x_ident = token::str_to_ident("x");
let int_ident = token::str_to_ident("int");
let renames = vec!((x_ident,16));
let mut renamer = IdentRenamer{renames: &renames};
let renamed_crate = renamer.fold_crate(the_crate);
let idents = crate_idents(&renamed_crate);
let resolved : Vec<ast::Name> = idents.iter().map(|id| mtwt::resolve(*id)).collect();
assert_eq!(resolved,vec!(f_ident.name,16,int_ident.name,16,16,16));
}
// test the PatIdentRenamer; only PatIdents get renamed
#[test]
fn pat_ident_renamer_test () {
let the_crate = string_to_crate("fn f(x : int){let x = x; x}".to_string());
let f_ident = token::str_to_ident("f");
let x_ident = token::str_to_ident("x");
let int_ident = token::str_to_ident("int");
let renames = vec!((x_ident,16));
let mut renamer = PatIdentRenamer{renames: &renames};
let renamed_crate = renamer.fold_crate(the_crate);
let idents = crate_idents(&renamed_crate);
let resolved : Vec<ast::Name> = idents.iter().map(|id| mtwt::resolve(*id)).collect();
let x_name = x_ident.name;
assert_eq!(resolved,vec!(f_ident.name,16,int_ident.name,16,x_name,x_name));
}
}

View file

@ -54,38 +54,51 @@ pub enum SyntaxContext_ {
IllegalCtxt
}
/// A list of ident->name renamings
pub type RenameList = Vec<(Ident, Name)>;
/// 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))
pub fn apply_mark(m: Mrk, ctxt: SyntaxContext) -> SyntaxContext {
with_sctable(|table| apply_mark_internal(m, ctxt, 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);
// Extend a syntax context with a given mark and sctable (explicit memoization)
fn apply_mark_internal(m: Mrk, ctxt: SyntaxContext, table: &SCTable) -> SyntaxContext {
let key = (ctxt, m);
let new_ctxt = |_: &(SyntaxContext, Mrk)|
idx_push(&mut *table.table.borrow_mut(), Mark(m, tail));
idx_push(&mut *table.table.borrow_mut(), Mark(m, ctxt));
*table.mark_memo.borrow_mut().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))
pub fn apply_rename(id: Ident, to:Name,
ctxt: SyntaxContext) -> SyntaxContext {
with_sctable(|table| apply_rename_internal(id, to, ctxt, table))
}
// Extend a syntax context with a given rename and sctable
fn new_rename_internal(id: Ident,
// Extend a syntax context with a given rename and sctable (explicit memoization)
fn apply_rename_internal(id: Ident,
to: Name,
tail: SyntaxContext,
ctxt: SyntaxContext,
table: &SCTable) -> SyntaxContext {
let key = (tail,id,to);
let key = (ctxt,id,to);
let new_ctxt = |_: &(SyntaxContext, Ident, Mrk)|
idx_push(&mut *table.table.borrow_mut(), Rename(id, to, tail));
idx_push(&mut *table.table.borrow_mut(), Rename(id, to, ctxt));
*table.rename_memo.borrow_mut().find_or_insert_with(key, new_ctxt)
}
/// Apply a list of renamings to a context
// if these rename lists get long, it would make sense
// to consider memoizing this fold. This may come up
// when we add hygiene to item names.
pub fn apply_renames(renames: &RenameList, ctxt: SyntaxContext) -> SyntaxContext {
renames.iter().fold(ctxt, |ctxt, &(from, to)| {
apply_rename(from, to, ctxt)
})
}
/// Fetch the SCTable from TLS, create one if it doesn't yet exist.
pub fn with_sctable<T>(op: |&SCTable| -> T) -> T {
local_data_key!(sctable_key: Rc<SCTable>)
@ -263,9 +276,9 @@ fn xor_push(marks: &mut Vec<Mrk>, mark: Mrk) {
#[cfg(test)]
mod tests {
use ast::*;
use super::{resolve, xor_push, new_mark_internal, new_sctable_internal};
use super::{new_rename_internal, marksof_internal, resolve_internal};
use ast::{EMPTY_CTXT, Ident, Mrk, Name, SyntaxContext};
use super::{resolve, xor_push, apply_mark_internal, new_sctable_internal};
use super::{apply_rename_internal, apply_renames, marksof_internal, resolve_internal};
use super::{SCTable, EmptyCtxt, Mark, Rename, IllegalCtxt};
use std::collections::HashMap;
@ -306,8 +319,8 @@ mod tests {
-> SyntaxContext {
tscs.iter().rev().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)}})
M(mrk) => apply_mark_internal(mrk,tail,table),
R(ident,name) => apply_rename_internal(ident,name,tail,table)}})
}
// gather a SyntaxContext back into a vector of TestSCs
@ -352,7 +365,7 @@ mod tests {
fn unfold_marks(mrks: Vec<Mrk> , tail: SyntaxContext, table: &SCTable)
-> SyntaxContext {
mrks.iter().rev().fold(tail, |tail:SyntaxContext, mrk:&Mrk|
{new_mark_internal(*mrk,tail,table)})
{apply_mark_internal(*mrk,tail,table)})
}
#[test] fn unfold_marks_test() {
@ -384,13 +397,13 @@ mod tests {
// rename where stop doesn't match:
{ let chain = vec!(M(9),
R(id(name1,
new_mark_internal (4, EMPTY_CTXT,&mut t)),
apply_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 name1sc = apply_mark_internal(4, EMPTY_CTXT, &mut t);
let chain = vec!(M(9),
R(id(name1, name1sc),
stopname),
@ -414,7 +427,7 @@ mod tests {
{ 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 sc1 = apply_mark_internal(1,EMPTY_CTXT,&mut t);
let sc = unfold_test_sc(vec!(R(id(a,sc1),50),
M(1),
M(2)),
@ -437,11 +450,11 @@ mod tests {
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);
{ let a_to_a50 = apply_rename_internal(id(a,EMPTY_CTXT),50,EMPTY_CTXT,&mut t);
let a50_to_a51 = apply_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);
let sc = apply_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),
@ -461,10 +474,10 @@ mod tests {
#[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);
assert_eq!(apply_mark_internal(12,EMPTY_CTXT,&mut t),2);
assert_eq!(apply_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);
assert_eq!(apply_mark_internal(12,EMPTY_CTXT,&mut t),2);
// I'm assuming that the rename table will behave the same....
}
@ -480,4 +493,13 @@ mod tests {
resolve_internal(id(30,EMPTY_CTXT),&mut t, &mut rt);
assert_eq!(rt.len(),2);
}
#[test]
fn new_resolves_test() {
let renames = vec!((Ident{name:23,ctxt:EMPTY_CTXT},24),
(Ident{name:29,ctxt:EMPTY_CTXT},29));
let new_ctxt1 = apply_renames(&renames,EMPTY_CTXT);
assert_eq!(resolve(Ident{name:23,ctxt:new_ctxt1}),24);
assert_eq!(resolve(Ident{name:29,ctxt:new_ctxt1}),29);
}
}

View file

@ -794,7 +794,7 @@ pub fn noop_fold_pat<T: Folder>(p: Gc<Pat>, folder: &mut T) -> Gc<Pat> {
PatIdent(binding_mode, ref pth1, ref sub) => {
PatIdent(binding_mode,
Spanned{span: folder.new_span(pth1.span),
node: folder.fold_ident(pth1.node)},
node: folder.fold_ident(pth1.node)},
sub.map(|x| folder.fold_pat(x)))
}
PatLit(e) => PatLit(folder.fold_expr(e)),

View file

@ -765,7 +765,7 @@ mod test {
use ext::mtwt;
fn mark_ident(id : ast::Ident, m : ast::Mrk) -> ast::Ident {
ast::Ident{name:id.name,ctxt:mtwt::new_mark(m,id.ctxt)}
ast::Ident{name:id.name,ctxt:mtwt::apply_mark(m,id.ctxt)}
}
#[test] fn mtwt_token_eq_test() {