diff --git a/src/librustc_plugin/registry.rs b/src/librustc_plugin/registry.rs index dc5a38bb7647e..b1fa9cc274095 100644 --- a/src/librustc_plugin/registry.rs +++ b/src/librustc_plugin/registry.rs @@ -16,7 +16,7 @@ use rustc::session::Session; use rustc::mir::transform::MirMapPass; use syntax::ext::base::{SyntaxExtension, NamedSyntaxExtension, NormalTT}; -use syntax::ext::base::{IdentTT, MultiModifier, MultiDecorator}; +use syntax::ext::base::{IdentTT, MultiModifier, MultiDecorator, Renovator}; use syntax::ext::base::{MacroExpanderFn, MacroRulesTT}; use syntax::codemap::Span; use syntax::parse::token; @@ -112,6 +112,7 @@ impl<'a> Registry<'a> { } MultiDecorator(ext) => MultiDecorator(ext), MultiModifier(ext) => MultiModifier(ext), + Renovator(ext) => Renovator(ext), MacroRulesTT => { self.sess.err("plugin tried to register a new MacroRulesTT"); return; diff --git a/src/libsyntax/ext/base.rs b/src/libsyntax/ext/base.rs index 303187aeba87d..1d1d52a4eb810 100644 --- a/src/libsyntax/ext/base.rs +++ b/src/libsyntax/ext/base.rs @@ -65,6 +65,14 @@ impl Annotatable { } } + pub fn node_id(&self) -> ast::NodeId { + match *self { + Annotatable::Item(ref i) => i.id, + Annotatable::TraitItem(ref i) => i.id, + Annotatable::ImplItem(ref i) => i.id, + } + } + pub fn expect_item(self) -> P<ast::Item> { match self { Annotatable::Item(i) => i, @@ -149,6 +157,32 @@ impl<F> MultiItemModifier for F } } +pub trait ItemRenovator { + fn expand(&self, + ecx: &mut ExtCtxt, + span: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + push: &mut FnMut(Annotatable)); +} + +impl<F> ItemRenovator for F + where F : Fn(&mut ExtCtxt, + Span, + &ast::MetaItem, + Annotatable, + &mut FnMut(Annotatable)) +{ + fn expand(&self, + ecx: &mut ExtCtxt, + sp: Span, + meta_item: &ast::MetaItem, + item: Annotatable, + push: &mut FnMut(Annotatable)) { + (*self)(ecx, sp, meta_item, item, push) + } +} + /// Represents a thing that maps token trees to Macro Results pub trait TTMacroExpander { fn expand<'cx>(&self, @@ -419,12 +453,24 @@ pub enum SyntaxExtension { /// in-place. More flexible version than Modifier. MultiModifier(Box<MultiItemModifier + 'static>), + /// A syntax extension that is attached to an item, modifying it in + /// place *and* creating new items based upon it. + /// + /// It can be thought of as the union of `MultiDecorator` and `MultiModifier`. + /// + /// Renovators must push *all* of the items they wish to preserve + /// and create. This includes the original item they are given. + /// + /// This allows renovators to remove the original item as well as + /// create new items if it is desired. + Renovator(Box<ItemRenovator + 'static>), + /// A normal, function-like syntax extension. /// /// `bytes!` is a `NormalTT`. /// /// The `bool` dictates whether the contents of the macro can - /// directly use `#[unstable]` things (true == yes). + /// directly use `#[unstable]` things (`true` == yes). NormalTT(Box<TTMacroExpander + 'static>, Option<Span>, bool), /// A function-like syntax extension that has an extra ident before diff --git a/src/libsyntax/ext/expand.rs b/src/libsyntax/ext/expand.rs index d3f5a573218fd..beee7750f58d9 100644 --- a/src/libsyntax/ext/expand.rs +++ b/src/libsyntax/ext/expand.rs @@ -302,7 +302,7 @@ fn expand_mac_invoc<T>(mac: ast::Mac, ident: Option<Ident>, attrs: Vec<ast::Attr None } - MultiDecorator(..) | MultiModifier(..) => { + Renovator(..) | MultiDecorator(..) | MultiModifier(..) => { fld.cx.span_err(path.span, &format!("`{}` can only be used in attributes", extname)); None @@ -718,10 +718,19 @@ impl<'a> Folder for PatIdentRenamer<'a> { } } -fn expand_annotatable(a: Annotatable, +fn expand_annotatable(original_item: Annotatable, fld: &mut MacroExpander) -> SmallVector<Annotatable> { - let a = expand_item_multi_modifier(a, fld); + let a = expand_item_multi_modifier(original_item, fld); + + let mut renovated_items = expand_renovators(a.clone(), fld); + + // Take the original item out (if any). + let a = if let Some(index) = renovated_items.iter().position(|i| is_original_item(i, &a)) { + renovated_items.remove(index) + } else { + return SmallVector::zero(); // The renovator ate the item. + }; let mut decorator_items = SmallVector::zero(); let mut new_attrs = Vec::new(); @@ -789,7 +798,9 @@ fn expand_annotatable(a: Annotatable, } }; + new_items.extend(renovated_items.into_iter()); new_items.push_all(decorator_items); + new_items } @@ -859,6 +870,90 @@ fn expand_decorators(a: Annotatable, } } +fn is_original_item(item: &Annotatable, original: &Annotatable) -> bool { + item.node_id() == original.node_id() +} + +fn expand_renovators(original_item: Annotatable, + fld: &mut MacroExpander) -> Vec<Annotatable> +{ + let mut items = Vec::new(); + items.push(original_item.clone()); + + let mut processed_attributes: Vec<ast::Attribute> = Vec::new(); + + 'main_loop: loop { + let item_idx = items.iter().position(|i| is_original_item(i, &original_item)); + + let item = if let Some(idx) = item_idx { + items.remove(idx) + } else { + break 'main_loop; + }; + + // Find the first unprocessed attribute. + let attr = if let Some(attr) = item.attrs().iter().cloned(). + find(|i| !processed_attributes.iter().any(|pi| i == pi)) { + attr + } else { + items.push(item); // put the item back. + break 'main_loop; + }; + + processed_attributes.push(attr.clone()); + + let macro_name = intern(&attr.name()); + + match fld.cx.syntax_env.find(macro_name) { + Some(rc) => match *rc { + Renovator(ref dec) => { + attr::mark_used(&attr); + + fld.cx.bt_push(ExpnInfo { + call_site: attr.span, + callee: NameAndSpan { + format: MacroAttribute(macro_name), + span: Some(attr.span), + // attributes can do whatever they like, + // for now. + allow_internal_unstable: true, + } + }); + + let mut new_items: SmallVector<Annotatable> = SmallVector::zero(); + dec.expand(fld.cx, + attr.span, + &attr.node.value, + item, + &mut |ann| new_items.push(ann)); + + let new_items = new_items.into_iter().map(|item| { + // Remove all attributes that are already processed. + let attrs: Vec<_> = item.attrs().iter().cloned(). + filter(|a| processed_attributes.iter().any(|pa| pa == a)).collect(); + + item.fold_attrs(attrs) + }); + + for new_item in new_items { + if is_original_item(&new_item, &original_item) { + items.push(new_item); + } else { + items.extend(expand_annotatable(new_item, fld).into_iter()); + } + } + + fld.cx.bt_pop(); + }, + _ => items.push(item.clone()), + }, + _ => items.push(item.clone()), + } + } + + items +} + fn expand_item_multi_modifier(mut it: Annotatable, fld: &mut MacroExpander) -> Annotatable { diff --git a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs index 3516f566e8a1f..9793a58fc71d7 100644 --- a/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs +++ b/src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs @@ -39,6 +39,14 @@ pub fn plugin_registrar(reg: &mut Registry) { token::intern("duplicate"), // FIXME (#22405): Replace `Box::new` with `box` here when/if possible. MultiDecorator(Box::new(expand_duplicate))); + reg.register_syntax_extension( + token::intern("remove"), + // FIXME (#22405): Replace `Box::new` with `box` here when/if possible. + Renovator(Box::new(expand_remove))); + reg.register_syntax_extension( + token::intern("spawn"), + // FIXME (#22405): Replace `Box::new` with `box` here when/if possible. + Renovator(Box::new(expand_spawn))); } fn expand_make_a_1(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) @@ -138,4 +146,46 @@ fn expand_duplicate(cx: &mut ExtCtxt, } } +fn expand_remove(cx: &mut ExtCtxt, + sp: Span, + meta_item: &MetaItem, + mut item: Annotatable, + push: &mut FnMut(Annotatable)) +{ + // eat the item +} + +fn expand_spawn(cx: &mut ExtCtxt, + sp: Span, + meta_item: &MetaItem, + mut item: Annotatable, + push: &mut FnMut(Annotatable)) +{ + // Keep original item around + push(item.clone()); + + match item { + Annotatable::Item(item) => { + let base_name = item.ident.name.as_str().to_owned(); + + for i in 0..5 { + let new_name = format!("{}{}", base_name, i); + + let new_ident = ast::Ident { + name: token::intern(&new_name), + ..item.ident + }; + + // Duplicate the item but with a number on the end of the name. + push(Annotatable::Item(P(Item { + ident: new_ident, + ..(*item).clone() + }))); + } + }, + _ => unimplemented!(), + } +} + pub fn foo() {} +pub fn main() { } diff --git a/src/test/run-pass-fulldeps/macro-crate.rs b/src/test/run-pass-fulldeps/macro-crate.rs index 3a38196366915..98b310ac72871 100644 --- a/src/test/run-pass-fulldeps/macro-crate.rs +++ b/src/test/run-pass-fulldeps/macro-crate.rs @@ -33,6 +33,25 @@ impl Qux for i32 { impl Qux for u8 {} +#[remove] +struct TypeToBeRemoved +{ + foo: NonexistentType, + bar: Baz, +} + +#[spawn] +struct Baz { + n: u32, +} + +struct Bar; + +impl Bar +{ + fn new() -> Self { Bar } +} + pub fn main() { assert_eq!(1, make_a_1!()); assert_eq!(2, exported_macro!()); @@ -44,6 +63,9 @@ pub fn main() { assert_eq!(x.foo(), 42); let x = 10u8; assert_eq!(x.foo(), 0); + + let a = Baz4 { n: 15 }; + assert_eq!(a.n, 15); } fn test<T: PartialEq+Clone>(_: Option<T>) {}