Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow syntax extensions which modify and decorate #33738

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/librustc_plugin/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
48 changes: 47 additions & 1 deletion src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
101 changes: 98 additions & 3 deletions src/libsyntax/ext/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -789,7 +798,9 @@ fn expand_annotatable(a: Annotatable,
}
};

new_items.extend(renovated_items.into_iter());
new_items.push_all(decorator_items);

new_items
}

Expand Down Expand Up @@ -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 {
Expand Down
50 changes: 50 additions & 0 deletions src/test/run-pass-fulldeps/auxiliary/macro_crate_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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() { }
22 changes: 22 additions & 0 deletions src/test/run-pass-fulldeps/macro-crate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!());
Expand All @@ -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>) {}