Skip to content

Commit

Permalink
rustc: Tweak expansion order of custom derive
Browse files Browse the repository at this point in the history
This commit alters the expansion order of custom macros-1.1 style `#[derive]`
modes. Instead of left-to-right the expansion now happens in three categories,
each of which is internally left-to-right:

* Old-style custom derive (`#[derive_Foo]`) is expanded
* New-style custom derive (macros 1.1) is expanded
* Built in derive modes are expanded

This gives built in derive modes maximal knowledge about the struct that's being
expanded and also avoids pesky issues like exposing `#[structural_match]` or
`#[rustc_copy_clone_marker]`.

cc #35900
  • Loading branch information
alexcrichton committed Sep 27, 2016
1 parent ea65ab6 commit e5e7021
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 97 deletions.
216 changes: 122 additions & 94 deletions src/libsyntax_ext/deriving/mod.rs
Expand Up @@ -108,11 +108,109 @@ pub fn expand_derive(cx: &mut ExtCtxt,
cx.span_err(mitem.span, "unexpected value in `derive`");
}

let traits = mitem.meta_item_list().unwrap_or(&[]);
let mut traits = mitem.meta_item_list().unwrap_or(&[]).to_owned();
if traits.is_empty() {
cx.span_warn(mitem.span, "empty trait list in `derive`");
}

// First, weed out malformed #[derive]
traits.retain(|titem| {
if titem.word().is_none() {
cx.span_err(titem.span, "malformed `derive` entry");
false
} else {
true
}
});

// Next, check for old-style #[derive(Foo)]
//
// These all get expanded to `#[derive_Foo]` and will get expanded first. If
// we actually add any attributes here then we return to get those expanded
// and then eventually we'll come back to finish off the other derive modes.
let mut new_attributes = Vec::new();
traits.retain(|titem| {
let tword = titem.word().unwrap();
let tname = tword.name();

let derive_mode = ast::Ident::with_empty_ctxt(intern(&tname));
let derive_mode = cx.resolver.resolve_derive_mode(derive_mode);
if is_builtin_trait(&tname) || derive_mode.is_some() {
return true
}

if !cx.ecfg.enable_custom_derive() {
feature_gate::emit_feature_err(&cx.parse_sess,
"custom_derive",
titem.span,
feature_gate::GateIssue::Language,
feature_gate::EXPLAIN_CUSTOM_DERIVE);
} else {
let name = intern_and_get_ident(&format!("derive_{}", tname));
let mitem = cx.meta_word(titem.span, name);
new_attributes.push(cx.attribute(mitem.span, mitem));
}
false
});
if new_attributes.len() > 0 {
item = item.map(|mut i| {
let list = cx.meta_list(mitem.span,
intern_and_get_ident("derive"),
traits);
i.attrs.extend(new_attributes);
i.attrs.push(cx.attribute(mitem.span, list));
i
});
return vec![Annotatable::Item(item)]
}

// Now check for macros-1.1 style custom #[derive].
//
// Expand each of them in order given, but *before* we expand any built-in
// derive modes. The logic here is to:
//
// 1. Collect the remaining `#[derive]` annotations into a list. If
// there are any left, attach a `#[derive]` attribute to the item
// that we're currently expanding with the remaining derive modes.
// 2. Manufacture a `#[derive(Foo)]` attribute to pass to the expander.
// 3. Expand the current item we're expanding, getting back a list of
// items that replace it.
// 4. Extend the returned list with the current list of items we've
// collected so far.
// 5. Return everything!
//
// If custom derive extensions end up threading through the `#[derive]`
// attribute, we'll get called again later on to continue expanding
// those modes.
let macros_11_derive = traits.iter()
.cloned()
.enumerate()
.filter(|&(_, ref name)| !is_builtin_trait(&name.name().unwrap()))
.next();
if let Some((i, titem)) = macros_11_derive {
let tname = ast::Ident::with_empty_ctxt(intern(&titem.name().unwrap()));
let ext = cx.resolver.resolve_derive_mode(tname).unwrap();
traits.remove(i);
if traits.len() > 0 {
item = item.map(|mut i| {
let list = cx.meta_list(mitem.span,
intern_and_get_ident("derive"),
traits);
i.attrs.push(cx.attribute(mitem.span, list));
i
});
}
let titem = cx.meta_list_item_word(titem.span, titem.name().unwrap());
let mitem = cx.meta_list(titem.span,
intern_and_get_ident("derive"),
vec![titem]);
let item = Annotatable::Item(item);
return ext.expand(cx, mitem.span, &mitem, item)
}

// Ok, at this point we know that there are no old-style `#[derive_Foo]` nor
// any macros-1.1 style `#[derive(Foo)]`. Expand all built-in traits here.

// RFC #1445. `#[derive(PartialEq, Eq)]` adds a (trusted)
// `#[structural_match]` attribute.
if traits.iter().filter_map(|t| t.name()).any(|t| t == "PartialEq") &&
Expand Down Expand Up @@ -141,103 +239,33 @@ pub fn expand_derive(cx: &mut ExtCtxt,
});
}

let mut other_items = Vec::new();

let mut iter = traits.iter();
while let Some(titem) = iter.next() {

let tword = match titem.word() {
Some(name) => name,
None => {
cx.span_err(titem.span, "malformed `derive` entry");
continue
}
let mut items = Vec::new();
for titem in traits.iter() {
let tname = titem.word().unwrap().name();
let name = intern_and_get_ident(&format!("derive({})", tname));
let mitem = cx.meta_word(titem.span, name);

let span = Span {
expn_id: cx.codemap().record_expansion(codemap::ExpnInfo {
call_site: titem.span,
callee: codemap::NameAndSpan {
format: codemap::MacroAttribute(intern(&format!("derive({})", tname))),
span: Some(titem.span),
allow_internal_unstable: true,
},
}),
..titem.span
};
let tname = tword.name();

// If this is a built-in derive mode, then we expand it immediately
// here.
if is_builtin_trait(&tname) {
let name = intern_and_get_ident(&format!("derive({})", tname));
let mitem = cx.meta_word(titem.span, name);

let span = Span {
expn_id: cx.codemap().record_expansion(codemap::ExpnInfo {
call_site: titem.span,
callee: codemap::NameAndSpan {
format: codemap::MacroAttribute(intern(&format!("derive({})", tname))),
span: Some(titem.span),
allow_internal_unstable: true,
},
}),
..titem.span
};

let my_item = Annotatable::Item(item);
expand_builtin(&tname, cx, span, &mitem, &my_item, &mut |a| {
other_items.push(a);
});
item = my_item.expect_item();

// Otherwise if this is a `rustc_macro`-style derive mode, we process it
// here. The logic here is to:
//
// 1. Collect the remaining `#[derive]` annotations into a list. If
// there are any left, attach a `#[derive]` attribute to the item
// that we're currently expanding with the remaining derive modes.
// 2. Manufacture a `#[derive(Foo)]` attribute to pass to the expander.
// 3. Expand the current item we're expanding, getting back a list of
// items that replace it.
// 4. Extend the returned list with the current list of items we've
// collected so far.
// 5. Return everything!
//
// If custom derive extensions end up threading through the `#[derive]`
// attribute, we'll get called again later on to continue expanding
// those modes.
} else if let Some(ext) =
cx.resolver.resolve_derive_mode(ast::Ident::with_empty_ctxt(intern(&tname))) {
let remaining_derives = iter.cloned().collect::<Vec<_>>();
if remaining_derives.len() > 0 {
let list = cx.meta_list(titem.span,
intern_and_get_ident("derive"),
remaining_derives);
let attr = cx.attribute(titem.span, list);
item = item.map(|mut i| {
i.attrs.push(attr);
i
});
}
let titem = cx.meta_list_item_word(titem.span, tname.clone());
let mitem = cx.meta_list(titem.span,
intern_and_get_ident("derive"),
vec![titem]);
let item = Annotatable::Item(item);
let mut items = ext.expand(cx, mitem.span, &mitem, item);
items.extend(other_items);
return items

// If we've gotten this far then it means that we're in the territory of
// the old custom derive mechanism. If the feature isn't enabled, we
// issue an error, otherwise manufacture the `derive_Foo` attribute.
} else if !cx.ecfg.enable_custom_derive() {
feature_gate::emit_feature_err(&cx.parse_sess,
"custom_derive",
titem.span,
feature_gate::GateIssue::Language,
feature_gate::EXPLAIN_CUSTOM_DERIVE);
} else {
let name = intern_and_get_ident(&format!("derive_{}", tname));
let mitem = cx.meta_word(titem.span, name);
item = item.map(|mut i| {
i.attrs.push(cx.attribute(mitem.span, mitem));
i
});
}
let my_item = Annotatable::Item(item);
expand_builtin(&tname, cx, span, &mitem, &my_item, &mut |a| {
items.push(a);
});
item = my_item.expect_item();
}

other_items.insert(0, Annotatable::Item(item));
return other_items
items.insert(0, Annotatable::Item(item));
return items
}

macro_rules! derive_traits {
Expand Down
Expand Up @@ -24,7 +24,6 @@ trait Append {
Append,
Eq)]
struct A {
//~^ ERROR: the semantics of constant patterns is not yet settled
inner: u32,
}

Expand Down
4 changes: 2 additions & 2 deletions src/test/run-pass-fulldeps/rustc-macro/auxiliary/derive-a.rs
Expand Up @@ -22,6 +22,6 @@ use rustc_macro::TokenStream;
pub fn derive(input: TokenStream) -> TokenStream {
let input = input.to_string();
assert!(input.contains("struct A;"));
assert!(input.contains("#[derive(Eq, Copy, Clone)]"));
"#[derive(Eq, Copy, Clone)] struct A;".parse().unwrap()
assert!(input.contains("#[derive(Debug, PartialEq, Eq, Copy, Clone)]"));
"#[derive(Debug, PartialEq, Eq, Copy, Clone)] struct A;".parse().unwrap()
}

0 comments on commit e5e7021

Please sign in to comment.