Skip to content

Commit e5e7021

Browse files
committed
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
1 parent ea65ab6 commit e5e7021

File tree

4 files changed

+124
-97
lines changed

4 files changed

+124
-97
lines changed

src/libsyntax_ext/deriving/mod.rs

+122-94
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,109 @@ pub fn expand_derive(cx: &mut ExtCtxt,
108108
cx.span_err(mitem.span, "unexpected value in `derive`");
109109
}
110110

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

116+
// First, weed out malformed #[derive]
117+
traits.retain(|titem| {
118+
if titem.word().is_none() {
119+
cx.span_err(titem.span, "malformed `derive` entry");
120+
false
121+
} else {
122+
true
123+
}
124+
});
125+
126+
// Next, check for old-style #[derive(Foo)]
127+
//
128+
// These all get expanded to `#[derive_Foo]` and will get expanded first. If
129+
// we actually add any attributes here then we return to get those expanded
130+
// and then eventually we'll come back to finish off the other derive modes.
131+
let mut new_attributes = Vec::new();
132+
traits.retain(|titem| {
133+
let tword = titem.word().unwrap();
134+
let tname = tword.name();
135+
136+
let derive_mode = ast::Ident::with_empty_ctxt(intern(&tname));
137+
let derive_mode = cx.resolver.resolve_derive_mode(derive_mode);
138+
if is_builtin_trait(&tname) || derive_mode.is_some() {
139+
return true
140+
}
141+
142+
if !cx.ecfg.enable_custom_derive() {
143+
feature_gate::emit_feature_err(&cx.parse_sess,
144+
"custom_derive",
145+
titem.span,
146+
feature_gate::GateIssue::Language,
147+
feature_gate::EXPLAIN_CUSTOM_DERIVE);
148+
} else {
149+
let name = intern_and_get_ident(&format!("derive_{}", tname));
150+
let mitem = cx.meta_word(titem.span, name);
151+
new_attributes.push(cx.attribute(mitem.span, mitem));
152+
}
153+
false
154+
});
155+
if new_attributes.len() > 0 {
156+
item = item.map(|mut i| {
157+
let list = cx.meta_list(mitem.span,
158+
intern_and_get_ident("derive"),
159+
traits);
160+
i.attrs.extend(new_attributes);
161+
i.attrs.push(cx.attribute(mitem.span, list));
162+
i
163+
});
164+
return vec![Annotatable::Item(item)]
165+
}
166+
167+
// Now check for macros-1.1 style custom #[derive].
168+
//
169+
// Expand each of them in order given, but *before* we expand any built-in
170+
// derive modes. The logic here is to:
171+
//
172+
// 1. Collect the remaining `#[derive]` annotations into a list. If
173+
// there are any left, attach a `#[derive]` attribute to the item
174+
// that we're currently expanding with the remaining derive modes.
175+
// 2. Manufacture a `#[derive(Foo)]` attribute to pass to the expander.
176+
// 3. Expand the current item we're expanding, getting back a list of
177+
// items that replace it.
178+
// 4. Extend the returned list with the current list of items we've
179+
// collected so far.
180+
// 5. Return everything!
181+
//
182+
// If custom derive extensions end up threading through the `#[derive]`
183+
// attribute, we'll get called again later on to continue expanding
184+
// those modes.
185+
let macros_11_derive = traits.iter()
186+
.cloned()
187+
.enumerate()
188+
.filter(|&(_, ref name)| !is_builtin_trait(&name.name().unwrap()))
189+
.next();
190+
if let Some((i, titem)) = macros_11_derive {
191+
let tname = ast::Ident::with_empty_ctxt(intern(&titem.name().unwrap()));
192+
let ext = cx.resolver.resolve_derive_mode(tname).unwrap();
193+
traits.remove(i);
194+
if traits.len() > 0 {
195+
item = item.map(|mut i| {
196+
let list = cx.meta_list(mitem.span,
197+
intern_and_get_ident("derive"),
198+
traits);
199+
i.attrs.push(cx.attribute(mitem.span, list));
200+
i
201+
});
202+
}
203+
let titem = cx.meta_list_item_word(titem.span, titem.name().unwrap());
204+
let mitem = cx.meta_list(titem.span,
205+
intern_and_get_ident("derive"),
206+
vec![titem]);
207+
let item = Annotatable::Item(item);
208+
return ext.expand(cx, mitem.span, &mitem, item)
209+
}
210+
211+
// Ok, at this point we know that there are no old-style `#[derive_Foo]` nor
212+
// any macros-1.1 style `#[derive(Foo)]`. Expand all built-in traits here.
213+
116214
// RFC #1445. `#[derive(PartialEq, Eq)]` adds a (trusted)
117215
// `#[structural_match]` attribute.
118216
if traits.iter().filter_map(|t| t.name()).any(|t| t == "PartialEq") &&
@@ -141,103 +239,33 @@ pub fn expand_derive(cx: &mut ExtCtxt,
141239
});
142240
}
143241

144-
let mut other_items = Vec::new();
145-
146-
let mut iter = traits.iter();
147-
while let Some(titem) = iter.next() {
148-
149-
let tword = match titem.word() {
150-
Some(name) => name,
151-
None => {
152-
cx.span_err(titem.span, "malformed `derive` entry");
153-
continue
154-
}
242+
let mut items = Vec::new();
243+
for titem in traits.iter() {
244+
let tname = titem.word().unwrap().name();
245+
let name = intern_and_get_ident(&format!("derive({})", tname));
246+
let mitem = cx.meta_word(titem.span, name);
247+
248+
let span = Span {
249+
expn_id: cx.codemap().record_expansion(codemap::ExpnInfo {
250+
call_site: titem.span,
251+
callee: codemap::NameAndSpan {
252+
format: codemap::MacroAttribute(intern(&format!("derive({})", tname))),
253+
span: Some(titem.span),
254+
allow_internal_unstable: true,
255+
},
256+
}),
257+
..titem.span
155258
};
156-
let tname = tword.name();
157259

158-
// If this is a built-in derive mode, then we expand it immediately
159-
// here.
160-
if is_builtin_trait(&tname) {
161-
let name = intern_and_get_ident(&format!("derive({})", tname));
162-
let mitem = cx.meta_word(titem.span, name);
163-
164-
let span = Span {
165-
expn_id: cx.codemap().record_expansion(codemap::ExpnInfo {
166-
call_site: titem.span,
167-
callee: codemap::NameAndSpan {
168-
format: codemap::MacroAttribute(intern(&format!("derive({})", tname))),
169-
span: Some(titem.span),
170-
allow_internal_unstable: true,
171-
},
172-
}),
173-
..titem.span
174-
};
175-
176-
let my_item = Annotatable::Item(item);
177-
expand_builtin(&tname, cx, span, &mitem, &my_item, &mut |a| {
178-
other_items.push(a);
179-
});
180-
item = my_item.expect_item();
181-
182-
// Otherwise if this is a `rustc_macro`-style derive mode, we process it
183-
// here. The logic here is to:
184-
//
185-
// 1. Collect the remaining `#[derive]` annotations into a list. If
186-
// there are any left, attach a `#[derive]` attribute to the item
187-
// that we're currently expanding with the remaining derive modes.
188-
// 2. Manufacture a `#[derive(Foo)]` attribute to pass to the expander.
189-
// 3. Expand the current item we're expanding, getting back a list of
190-
// items that replace it.
191-
// 4. Extend the returned list with the current list of items we've
192-
// collected so far.
193-
// 5. Return everything!
194-
//
195-
// If custom derive extensions end up threading through the `#[derive]`
196-
// attribute, we'll get called again later on to continue expanding
197-
// those modes.
198-
} else if let Some(ext) =
199-
cx.resolver.resolve_derive_mode(ast::Ident::with_empty_ctxt(intern(&tname))) {
200-
let remaining_derives = iter.cloned().collect::<Vec<_>>();
201-
if remaining_derives.len() > 0 {
202-
let list = cx.meta_list(titem.span,
203-
intern_and_get_ident("derive"),
204-
remaining_derives);
205-
let attr = cx.attribute(titem.span, list);
206-
item = item.map(|mut i| {
207-
i.attrs.push(attr);
208-
i
209-
});
210-
}
211-
let titem = cx.meta_list_item_word(titem.span, tname.clone());
212-
let mitem = cx.meta_list(titem.span,
213-
intern_and_get_ident("derive"),
214-
vec![titem]);
215-
let item = Annotatable::Item(item);
216-
let mut items = ext.expand(cx, mitem.span, &mitem, item);
217-
items.extend(other_items);
218-
return items
219-
220-
// If we've gotten this far then it means that we're in the territory of
221-
// the old custom derive mechanism. If the feature isn't enabled, we
222-
// issue an error, otherwise manufacture the `derive_Foo` attribute.
223-
} else if !cx.ecfg.enable_custom_derive() {
224-
feature_gate::emit_feature_err(&cx.parse_sess,
225-
"custom_derive",
226-
titem.span,
227-
feature_gate::GateIssue::Language,
228-
feature_gate::EXPLAIN_CUSTOM_DERIVE);
229-
} else {
230-
let name = intern_and_get_ident(&format!("derive_{}", tname));
231-
let mitem = cx.meta_word(titem.span, name);
232-
item = item.map(|mut i| {
233-
i.attrs.push(cx.attribute(mitem.span, mitem));
234-
i
235-
});
236-
}
260+
let my_item = Annotatable::Item(item);
261+
expand_builtin(&tname, cx, span, &mitem, &my_item, &mut |a| {
262+
items.push(a);
263+
});
264+
item = my_item.expect_item();
237265
}
238266

239-
other_items.insert(0, Annotatable::Item(item));
240-
return other_items
267+
items.insert(0, Annotatable::Item(item));
268+
return items
241269
}
242270

243271
macro_rules! derive_traits {

src/test/compile-fail-fulldeps/rustc-macro/append-impl.rs renamed to src/test/run-pass-fulldeps/rustc-macro/append-impl.rs

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ trait Append {
2424
Append,
2525
Eq)]
2626
struct A {
27-
//~^ ERROR: the semantics of constant patterns is not yet settled
2827
inner: u32,
2928
}
3029

src/test/run-pass-fulldeps/rustc-macro/auxiliary/derive-a.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ use rustc_macro::TokenStream;
2222
pub fn derive(input: TokenStream) -> TokenStream {
2323
let input = input.to_string();
2424
assert!(input.contains("struct A;"));
25-
assert!(input.contains("#[derive(Eq, Copy, Clone)]"));
26-
"#[derive(Eq, Copy, Clone)] struct A;".parse().unwrap()
25+
assert!(input.contains("#[derive(Debug, PartialEq, Eq, Copy, Clone)]"));
26+
"#[derive(Debug, PartialEq, Eq, Copy, Clone)] struct A;".parse().unwrap()
2727
}

0 commit comments

Comments
 (0)