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>) {}