diff --git a/src/librustc/plugin/registry.rs b/src/librustc/plugin/registry.rs index feec97f02da..f6fb1c2d419 100644 --- a/src/librustc/plugin/registry.rs +++ b/src/librustc/plugin/registry.rs @@ -14,7 +14,7 @@ use lint::{LintPassObject, LintId, Lint}; use session::Session; use syntax::ext::base::{SyntaxExtension, NamedSyntaxExtension, NormalTT}; -use syntax::ext::base::{IdentTT, Decorator, Modifier, MacroRulesTT}; +use syntax::ext::base::{IdentTT, Decorator, Modifier, MultiModifier, MacroRulesTT}; use syntax::ext::base::{MacroExpanderFn}; use syntax::codemap::Span; use syntax::parse::token; @@ -82,7 +82,7 @@ impl<'a> Registry<'a> { IdentTT(ext, _) => IdentTT(ext, Some(self.krate_span)), Decorator(ext) => Decorator(ext), Modifier(ext) => Modifier(ext), - + MultiModifier(ext) => MultiModifier(ext), MacroRulesTT => { self.sess.err("plugin tried to register a new MacroRulesTT"); return; diff --git a/src/libsyntax/ast.rs b/src/libsyntax/ast.rs index 61bc1865517..0ea429116b0 100644 --- a/src/libsyntax/ast.rs +++ b/src/libsyntax/ast.rs @@ -957,7 +957,7 @@ pub type Mac = Spanned; pub enum Mac_ { // NB: the additional ident for a macro_rules-style macro is actually // stored in the enclosing item. Oog. - MacInvocTT(Path, Vec , SyntaxContext), // new macro-invocation + MacInvocTT(Path, Vec, SyntaxContext), // new macro-invocation } #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Show, Copy)] diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 9b9d8a9ceb3..f2498abfa6a 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -73,6 +73,108 @@ impl ItemModifier for F } } +#[derive(Show,Clone)] +pub enum Annotatable { + Item(P), + TraitItem(ast::TraitItem), + ImplItem(ast::ImplItem), +} + +impl Annotatable { + pub fn attrs(&self) -> &[ast::Attribute] { + match *self { + Annotatable::Item(ref i) => &i.attrs[], + Annotatable::TraitItem(ref i) => match *i { + ast::TraitItem::RequiredMethod(ref tm) => &tm.attrs[], + ast::TraitItem::ProvidedMethod(ref m) => &m.attrs[], + ast::TraitItem::TypeTraitItem(ref at) => &at.attrs[], + }, + Annotatable::ImplItem(ref i) => match *i { + ast::ImplItem::MethodImplItem(ref m) => &m.attrs[], + ast::ImplItem::TypeImplItem(ref t) => &t.attrs[], + } + } + } + + pub fn fold_attrs(self, attrs: Vec) -> Annotatable { + match self { + Annotatable::Item(i) => Annotatable::Item(P(ast::Item { + attrs: attrs, + ..(*i).clone() + })), + Annotatable::TraitItem(i) => match i { + ast::TraitItem::RequiredMethod(tm) => Annotatable::TraitItem( + ast::TraitItem::RequiredMethod( + ast::TypeMethod { attrs: attrs, ..tm })), + ast::TraitItem::ProvidedMethod(m) => Annotatable::TraitItem( + ast::TraitItem::ProvidedMethod(P( + ast::Method { attrs: attrs, ..(*m).clone() }))), + ast::TraitItem::TypeTraitItem(at) => Annotatable::TraitItem( + ast::TraitItem::TypeTraitItem(P( + ast::AssociatedType { attrs: attrs, ..(*at).clone() }))), + }, + Annotatable::ImplItem(i) => match i { + ast::ImplItem::MethodImplItem(m) => Annotatable::ImplItem( + ast::ImplItem::MethodImplItem(P( + ast::Method { attrs: attrs, ..(*m).clone() }))), + ast::ImplItem::TypeImplItem(t) => Annotatable::ImplItem( + ast::ImplItem::TypeImplItem(P( + ast::Typedef { attrs: attrs, ..(*t).clone() }))), + } + } + } + + pub fn expect_item(self) -> P { + match self { + Annotatable::Item(i) => i, + _ => panic!("expected Item") + } + } + + pub fn expect_trait_item(self) -> ast::TraitItem { + match self { + Annotatable::TraitItem(i) => i, + _ => panic!("expected Item") + } + } + + pub fn expect_impl_item(self) -> ast::ImplItem { + match self { + Annotatable::ImplItem(i) => i, + _ => panic!("expected Item") + } + } +} + +// A more flexible ItemModifier (ItemModifier should go away, eventually, FIXME). +// meta_item is the annotation, item is the item being modified, parent_item +// is the impl or trait item is declared in if item is part of such a thing. +// FIXME Decorators should follow the same pattern too. +pub trait MultiItemModifier { + fn expand(&self, + ecx: &mut ExtCtxt, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable) + -> Annotatable; +} + +impl MultiItemModifier for F + where F: Fn(&mut ExtCtxt, + Span, + &ast::MetaItem, + Annotatable) -> Annotatable +{ + fn expand(&self, + ecx: &mut ExtCtxt, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable) + -> Annotatable { + (*self)(ecx, span, meta_item, item) + } +} + /// Represents a thing that maps token trees to Macro Results pub trait TTMacroExpander { fn expand<'cx>(&self, @@ -299,6 +401,10 @@ pub enum SyntaxExtension { /// in-place. Modifier(Box), + /// A syntax extension that is attached to an item and modifies it + /// in-place. More flexible version than Modifier. + MultiModifier(Box), + /// A normal, function-like syntax extension. /// /// `bytes!` is a `NormalTT`. diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index 9ef996ac317..c95bdeefd45 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -395,81 +395,15 @@ pub fn expand_item(it: P, fld: &mut MacroExpander) -> SmallVector> { let it = expand_item_modifiers(it, fld); - let mut decorator_items = SmallVector::zero(); - let mut new_attrs = Vec::new(); - for attr in it.attrs.iter() { - let mname = attr.name(); - - match fld.cx.syntax_env.find(&intern(mname.get())) { - Some(rc) => match *rc { - Decorator(ref dec) => { - attr::mark_used(attr); - - fld.cx.bt_push(ExpnInfo { - call_site: attr.span, - callee: NameAndSpan { - name: mname.get().to_string(), - format: MacroAttribute, - span: None - } - }); - - // we'd ideally decorator_items.push_all(expand_item(item, fld)), - // but that double-mut-borrows fld - let mut items: SmallVector> = SmallVector::zero(); - dec.expand(fld.cx, attr.span, &*attr.node.value, &*it, - box |&mut : item| items.push(item)); - decorator_items.extend(items.into_iter() - .flat_map(|item| expand_item(item, fld).into_iter())); - - fld.cx.bt_pop(); - } - _ => new_attrs.push((*attr).clone()), - }, - _ => new_attrs.push((*attr).clone()), - } - } - - let mut new_items = match it.node { - ast::ItemMac(..) => expand_item_mac(it, fld), - ast::ItemMod(_) | ast::ItemForeignMod(_) => { - let valid_ident = - it.ident.name != parse::token::special_idents::invalid.name; - - if valid_ident { - fld.cx.mod_push(it.ident); - } - let macro_use = contains_macro_use(fld, &new_attrs[]); - let result = with_exts_frame!(fld.cx.syntax_env, - macro_use, - noop_fold_item(it, fld)); - if valid_ident { - fld.cx.mod_pop(); - } - result - }, - _ => { - let it = P(ast::Item { - attrs: new_attrs, - ..(*it).clone() - }); - noop_fold_item(it, fld) - } - }; - - new_items.push_all(decorator_items); - new_items + expand_annotatable(Annotatable::Item(it), fld) + .into_iter().map(|i| i.expect_item()).collect() } fn expand_item_modifiers(mut it: P, fld: &mut MacroExpander) -> P { // partition the attributes into ItemModifiers and others - let (modifiers, other_attrs): (Vec<_>, _) = it.attrs.iter().cloned().partition(|attr| { - match fld.cx.syntax_env.find(&intern(attr.name().get())) { - Some(rc) => match *rc { Modifier(_) => true, _ => false }, - _ => false - } - }); + let (modifiers, other_attrs) = modifiers(&it.attrs, fld); + // update the attrs, leave everything else alone. Is this mutation really a good idea? it = P(ast::Item { attrs: other_attrs, @@ -477,7 +411,8 @@ fn expand_item_modifiers(mut it: P, fld: &mut MacroExpander) }); if modifiers.is_empty() { - return it; + let it = expand_item_multi_modifier(Annotatable::Item(it), fld); + return it.expect_item(); } for attr in modifiers.iter() { @@ -504,7 +439,12 @@ fn expand_item_modifiers(mut it: P, fld: &mut MacroExpander) } } - // expansion may have added new ItemModifiers + // Expansion may have added new ItemModifiers. + // It is possible, that an item modifier could expand to a multi-modifier or + // vice versa. In this case we will expand all modifiers before multi-modifiers, + // which might give an odd ordering. However, I think it is unlikely that the + // two kinds will be mixed, and I old-style multi-modifiers should be deprecated + // anyway. expand_item_modifiers(it, fld) } @@ -1029,6 +969,196 @@ impl<'a> Folder for PatIdentRenamer<'a> { } } +fn expand_annotatable(a: Annotatable, + fld: &mut MacroExpander) + -> SmallVector { + let a = expand_item_multi_modifier(a, fld); + + let mut decorator_items = SmallVector::zero(); + let mut new_attrs = Vec::new(); + for attr in a.attrs().iter() { + let mname = attr.name(); + + match fld.cx.syntax_env.find(&intern(mname.get())) { + Some(rc) => match *rc { + Decorator(ref dec) => { + let it = match a { + Annotatable::Item(ref it) => it, + // ItemDecorators are only implemented for Items. + _ => break, + }; + + attr::mark_used(attr); + + fld.cx.bt_push(ExpnInfo { + call_site: attr.span, + callee: NameAndSpan { + name: mname.get().to_string(), + format: MacroAttribute, + span: None + } + }); + + // we'd ideally decorator_items.push_all(expand_item(item, fld)), + // but that double-mut-borrows fld + let mut items: SmallVector> = SmallVector::zero(); + dec.expand(fld.cx, attr.span, &*attr.node.value, &**it, + box |&mut: item| items.push(item)); + decorator_items.extend(items.into_iter() + .flat_map(|item| expand_item(item, fld).into_iter())); + + fld.cx.bt_pop(); + } + _ => new_attrs.push((*attr).clone()), + }, + _ => new_attrs.push((*attr).clone()), + } + } + + let mut new_items: SmallVector = match a { + Annotatable::Item(it) => match it.node { + ast::ItemMac(..) => { + expand_item_mac(it, fld).into_iter().map(|i| Annotatable::Item(i)).collect() + } + ast::ItemMod(_) | ast::ItemForeignMod(_) => { + let valid_ident = + it.ident.name != parse::token::special_idents::invalid.name; + + if valid_ident { + fld.cx.mod_push(it.ident); + } + let macro_use = contains_macro_use(fld, &new_attrs[]); + let result = with_exts_frame!(fld.cx.syntax_env, + macro_use, + noop_fold_item(it, fld)); + if valid_ident { + fld.cx.mod_pop(); + } + result.into_iter().map(|i| Annotatable::Item(i)).collect() + }, + _ => { + let it = P(ast::Item { + attrs: new_attrs, + ..(*it).clone() + }); + noop_fold_item(it, fld).into_iter().map(|i| Annotatable::Item(i)).collect() + } + }, + Annotatable::TraitItem(it) => match it { + ast::TraitItem::ProvidedMethod(m) => { + expand_method(m, fld).into_iter().map(|m| + Annotatable::TraitItem(ast::TraitItem::ProvidedMethod(m))).collect() + } + ast::TraitItem::RequiredMethod(m) => { + SmallVector::one(Annotatable::TraitItem( + ast::TraitItem::RequiredMethod(fld.fold_type_method(m)))) + } + ast::TraitItem::TypeTraitItem(t) => { + SmallVector::one(Annotatable::TraitItem( + ast::TraitItem::TypeTraitItem(P(fld.fold_associated_type((*t).clone()))))) + } + }, + Annotatable::ImplItem(it) => match it { + ast::ImplItem::MethodImplItem(m) => { + expand_method(m, fld).into_iter().map(|m| + Annotatable::ImplItem(ast::ImplItem::MethodImplItem(m))).collect() + } + ast::ImplItem::TypeImplItem(t) => { + SmallVector::one(Annotatable::ImplItem( + ast::ImplItem::TypeImplItem(P(fld.fold_typedef((*t).clone()))))) + } + } + }; + + new_items.push_all(decorator_items.into_iter().map(|i| Annotatable::Item(i)).collect()); + new_items +} + +fn expand_trait_item(i: ast::TraitItem, + fld: &mut MacroExpander) + -> SmallVector { + expand_annotatable(Annotatable::TraitItem(i), fld) + .into_iter().map(|i| i.expect_trait_item()).collect() + +} + +fn expand_impl_item(i: ast::ImplItem, + fld: &mut MacroExpander) + -> SmallVector { + expand_annotatable(Annotatable::ImplItem(i), fld) + .into_iter().map(|i| i.expect_impl_item()).collect() +} + +// partition the attributes into ItemModifiers and others +fn modifiers(attrs: &Vec, + fld: &MacroExpander) + -> (Vec, Vec) { + attrs.iter().cloned().partition(|attr| { + match fld.cx.syntax_env.find(&intern(attr.name().get())) { + Some(rc) => match *rc { + Modifier(_) => true, + _ => false + }, + _ => false + } + }) +} + +// partition the attributes into MultiModifiers and others +fn multi_modifiers(attrs: &[ast::Attribute], + fld: &MacroExpander) + -> (Vec, Vec) { + attrs.iter().cloned().partition(|attr| { + match fld.cx.syntax_env.find(&intern(attr.name().get())) { + Some(rc) => match *rc { + MultiModifier(_) => true, + _ => false + }, + _ => false + } + }) +} + +fn expand_item_multi_modifier(mut it: Annotatable, + fld: &mut MacroExpander) + -> Annotatable { + let (modifiers, other_attrs) = multi_modifiers(it.attrs(), fld); + + // Update the attrs, leave everything else alone. Is this mutation really a good idea? + it = it.fold_attrs(other_attrs); + + if modifiers.is_empty() { + return it + } + + for attr in modifiers.iter() { + let mname = attr.name(); + + match fld.cx.syntax_env.find(&intern(mname.get())) { + Some(rc) => match *rc { + MultiModifier(ref mac) => { + attr::mark_used(attr); + fld.cx.bt_push(ExpnInfo { + call_site: attr.span, + callee: NameAndSpan { + name: mname.get().to_string(), + format: MacroAttribute, + span: None, + } + }); + it = mac.expand(fld.cx, attr.span, &*attr.node.value, it); + fld.cx.bt_pop(); + } + _ => unreachable!() + }, + _ => unreachable!() + } + } + + // Expansion may have added new ItemModifiers. + expand_item_multi_modifier(it, fld) +} + // expand a method fn expand_method(m: P, fld: &mut MacroExpander) -> SmallVector> { m.and_then(|m| match m.node { @@ -1042,7 +1172,7 @@ fn expand_method(m: P, fld: &mut MacroExpander) -> SmallVector { let id = fld.new_id(m.id); let (rewritten_fn_decl, rewritten_body) - = expand_and_rename_fn_decl_and_block(decl,body,fld); + = expand_and_rename_fn_decl_and_block(decl, body, fld); SmallVector::one(P(ast::Method { attrs: m.attrs.move_map(|a| fld.fold_attribute(a)), id: id, @@ -1147,6 +1277,14 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> { expand_arm(arm, self) } + fn fold_trait_item(&mut self, i: ast::TraitItem) -> SmallVector { + expand_trait_item(i, self) + } + + fn fold_impl_item(&mut self, i: ast::ImplItem) -> SmallVector { + expand_impl_item(i, self) + } + fn fold_method(&mut self, method: P) -> SmallVector> { expand_method(method, self) } diff --git a/src/libsyntax/fold.rs b/src/libsyntax/fold.rs index 2a704349295..16c29c9b5eb 100644 --- a/src/libsyntax/fold.rs +++ b/src/libsyntax/fold.rs @@ -102,6 +102,14 @@ pub trait Folder : Sized { noop_fold_item_underscore(i, self) } + fn fold_trait_item(&mut self, i: TraitItem) -> SmallVector { + noop_fold_trait_item(i, self) + } + + fn fold_impl_item(&mut self, i: ImplItem) -> SmallVector { + noop_fold_impl_item(i, self) + } + fn fold_fn_decl(&mut self, d: P) -> P { noop_fold_fn_decl(d, self) } @@ -1007,21 +1015,9 @@ pub fn noop_fold_item_underscore(i: Item_, folder: &mut T) -> Item_ { ItemStruct(struct_def, folder.fold_generics(generics)) } ItemImpl(unsafety, polarity, generics, ifce, ty, impl_items) => { - let mut new_impl_items = Vec::new(); - for impl_item in impl_items.iter() { - match *impl_item { - MethodImplItem(ref x) => { - for method in folder.fold_method((*x).clone()) - .into_iter() { - new_impl_items.push(MethodImplItem(method)) - } - } - TypeImplItem(ref t) => { - new_impl_items.push(TypeImplItem( - P(folder.fold_typedef((**t).clone())))); - } - } - } + let new_impl_items = impl_items.into_iter().flat_map(|item| { + folder.fold_impl_item(item).into_iter() + }).collect(); let ifce = match ifce { None => None, Some(ref trait_ref) => { @@ -1035,43 +1031,50 @@ pub fn noop_fold_item_underscore(i: Item_, folder: &mut T) -> Item_ { folder.fold_ty(ty), new_impl_items) } - ItemTrait(unsafety, generics, bounds, methods) => { + ItemTrait(unsafety, generics, bounds, items) => { let bounds = folder.fold_bounds(bounds); - let methods = methods.into_iter().flat_map(|method| { - let r = match method { - RequiredMethod(m) => { - SmallVector::one(RequiredMethod( - folder.fold_type_method(m))) - .into_iter() - } - ProvidedMethod(method) => { - // the awkward collect/iter idiom here is because - // even though an iter and a map satisfy the same - // trait bound, they're not actually the same type, so - // the method arms don't unify. - let methods: SmallVector = - folder.fold_method(method).into_iter() - .map(|m| ProvidedMethod(m)).collect(); - methods.into_iter() - } - TypeTraitItem(at) => { - SmallVector::one(TypeTraitItem(P( - folder.fold_associated_type( - (*at).clone())))) - .into_iter() - } - }; - r + let items = items.into_iter().flat_map(|item| { + folder.fold_trait_item(item).into_iter() }).collect(); ItemTrait(unsafety, folder.fold_generics(generics), bounds, - methods) + items) } ItemMac(m) => ItemMac(folder.fold_mac(m)), } } +pub fn noop_fold_trait_item(i: TraitItem, folder: &mut T) -> SmallVector { + match i { + RequiredMethod(m) => { + SmallVector::one(RequiredMethod( + folder.fold_type_method(m))) + } + ProvidedMethod(method) => { + folder.fold_method(method).into_iter() + .map(|m| ProvidedMethod(m)).collect() + } + TypeTraitItem(at) => { + SmallVector::one(TypeTraitItem(P( + folder.fold_associated_type( + (*at).clone())))) + } + } +} + +pub fn noop_fold_impl_item(i: ImplItem, folder: &mut T) -> SmallVector { + match i { + MethodImplItem(ref x) => { + folder.fold_method((*x).clone()).into_iter().map(|m| MethodImplItem(m)).collect() + } + TypeImplItem(ref t) => { + SmallVector::one(TypeImplItem( + P(folder.fold_typedef((**t).clone())))) + } + } +} + pub fn noop_fold_type_method(m: TypeMethod, fld: &mut T) -> TypeMethod { let TypeMethod { id, diff --git a/src/test/auxiliary/macro_crate_test.rs b/src/test/auxiliary/macro_crate_test.rs index 9eeb7ee8857..e3e91e05f55 100644 --- a/src/test/auxiliary/macro_crate_test.rs +++ b/src/test/auxiliary/macro_crate_test.rs @@ -1,4 +1,4 @@ -// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -16,7 +16,7 @@ extern crate syntax; extern crate rustc; -use syntax::ast::{TokenTree, Item, MetaItem}; +use syntax::ast::{TokenTree, Item, MetaItem, ImplItem, TraitItem, Method}; use syntax::codemap::Span; use syntax::ext::base::*; use syntax::parse::token; @@ -37,6 +37,9 @@ pub fn plugin_registrar(reg: &mut Registry) { reg.register_syntax_extension( token::intern("into_foo"), Modifier(box expand_into_foo)); + reg.register_syntax_extension( + token::intern("into_multi_foo"), + MultiModifier(box expand_into_foo_multi)); } fn expand_make_a_1(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) @@ -65,6 +68,30 @@ fn expand_into_foo(cx: &mut ExtCtxt, sp: Span, attr: &MetaItem, it: P) }) } +fn expand_into_foo_multi(cx: &mut ExtCtxt, + sp: Span, + attr: &MetaItem, + it: Annotatable) -> Annotatable { + match it { + Annotatable::Item(it) => { + Annotatable::Item(P(Item { + attrs: it.attrs.clone(), + ..(*quote_item!(cx, enum Foo2 { Bar2, Baz2 }).unwrap()).clone() + })) + } + Annotatable::ImplItem(it) => { + Annotatable::ImplItem(ImplItem::MethodImplItem( + quote_method!(cx, fn foo(&self) -> i32 { 42 }) + )) + } + Annotatable::TraitItem(it) => { + Annotatable::TraitItem(TraitItem::ProvidedMethod( + quote_method!(cx, fn foo(&self) -> i32 { 0 }) + )) + } + } +} + fn expand_forged_ident(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box { use syntax::ext::quote::rt::*; diff --git a/src/test/run-pass-fulldeps/macro-crate.rs b/src/test/run-pass-fulldeps/macro-crate.rs index 4ffb8a3f74d..5236b35d4d2 100644 --- a/src/test/run-pass-fulldeps/macro-crate.rs +++ b/src/test/run-pass-fulldeps/macro-crate.rs @@ -1,4 +1,4 @@ -// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// Copyright 2013-2015 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // @@ -20,12 +20,36 @@ extern crate macro_crate_test; #[derive(PartialEq, Clone, Show)] fn foo() -> AFakeTypeThatHadBetterGoAway {} +#[into_multi_foo] +#[derive(PartialEq, Clone, Show)] +fn foo() -> AnotherFakeTypeThatHadBetterGoAway {} + +trait Qux { + #[into_multi_foo] + fn bar(); +} + +impl Qux for i32 { + #[into_multi_foo] + fn bar() {} +} + +impl Qux for u8 {} + pub fn main() { assert_eq!(1, make_a_1!()); assert_eq!(2, exported_macro!()); assert_eq!(Foo::Bar, Foo::Bar); test(None::); + + assert_eq!(Foo2::Bar2, Foo2::Bar2); + test(None::); + + let x = 10i32; + assert_eq!(x.foo(), 42); + let x = 10u8; + assert_eq!(x.foo(), 0); } fn test(_: Option) {}