rustc: Tweak expansion order of custom derive
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
This commit is contained in:
parent
ea65ab6c7e
commit
e5e7021ca5
4 changed files with 123 additions and 96 deletions
|
|
@ -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") &&
|
||||
|
|
@ -141,103 +239,33 @@ pub fn expand_derive(cx: &mut ExtCtxt,
|
|||
});
|
||||
}
|
||||
|
||||
let mut other_items = Vec::new();
|
||||
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 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 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 {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ trait Append {
|
|||
Append,
|
||||
Eq)]
|
||||
struct A {
|
||||
//~^ ERROR: the semantics of constant patterns is not yet settled
|
||||
inner: u32,
|
||||
}
|
||||
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue