blob: 8a6ce7bd7ee78d3be6f2a47b94a137075390d1be [file] [log] [blame]
David Tolnay453cfd12016-10-23 11:00:14 -07001use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem};
2
3use std::collections::BTreeMap as Map;
4use std::fs::File;
5use std::io::{Read, Write};
6use std::path::Path;
7
8/// Implementation of a custom derive. Custom derives take a struct or enum and
9/// expand it into zero or more items, typically `impl` items.
10pub trait CustomDerive {
11 /// Expand the given struct or enum. If this custom derive modifies the
12 /// input item or preserves it unmodified, it must be returned back in the
13 /// `original` field of Expanded. The custom derive may discard the input
14 /// item by setting `original` to None.
15 fn expand(&self, input: MacroInput) -> Result<Expanded, String>;
16}
17
18/// Produced by expanding a custom derive.
19pub struct Expanded {
20 /// The items (typically `impl` items) constructed by the custom derive.
21 pub new_items: Vec<Item>,
22 /// The input to the custom derive, whether modified or unmodified. If the
23 /// custom derive discards the input item it may do so by setting `original`
24 /// to None.
25 pub original: Option<MacroInput>,
26}
27
28/// Registry of custom derives. Callers add custom derives to a registry, then
29/// use the registry to expand those derives in a source file.
30#[derive(Default)]
31pub struct Registry<'a> {
32 derives: Map<String, Box<CustomDerive + 'a>>,
33}
34
35impl<T> CustomDerive for T
36 where T: Fn(MacroInput) -> Result<Expanded, String>
37{
38 fn expand(&self, input: MacroInput) -> Result<Expanded, String> {
39 self(input)
40 }
41}
42
43impl<'a> Registry<'a> {
44 pub fn new() -> Self {
45 Default::default()
46 }
47
48 /// Register a custom derive. A `fn(MacroInput) -> Result<Expanded, String>`
49 /// may be used as a custom derive.
50 ///
51 /// ```ignore
52 /// registry.add_derive("Serialize", expand_serialize);
53 /// ```
54 pub fn add_derive<T>(&mut self, name: &str, derive: T)
55 where T: CustomDerive + 'a
56 {
57 self.derives.insert(name.into(), Box::new(derive));
58 }
59
60 /// Read Rust source code from the `src` file, expand the custom derives
61 /// that have been registered, and write the result to the `dst` file.
62 pub fn expand_file<S, D>(&self, src: S, dst: D) -> Result<(), String>
63 where S: AsRef<Path>,
64 D: AsRef<Path>
65 {
66 // Open the src file
67 let mut src = match File::open(src) {
68 Ok(open) => open,
69 Err(err) => return Err(err.to_string()),
70 };
71
72 // Read the contents of the src file to a String
73 let mut content = String::new();
74 if let Err(err) = src.read_to_string(&mut content) {
75 return Err(err.to_string());
76 }
77
78 // Parse the contents
79 let krate = try!(super::parse_crate(&content));
80
81 // Expand
82 let expanded = try!(expand_crate(self, krate));
83
84 // Print the expanded code to a String
85 let out = quote!(#expanded).to_string();
86
87 // Create or truncate the dst file, opening in write-only mode
88 let mut dst = match File::create(dst) {
89 Ok(create) => create,
90 Err(err) => return Err(err.to_string()),
91 };
92
93 // Write expanded code to the dst file
94 if let Err(err) = dst.write_all(out.as_bytes()) {
95 return Err(err.to_string());
96 }
97
98 Ok(())
99 }
100}
101
102fn expand_crate(reg: &Registry, krate: Crate) -> Result<Crate, String> {
103 let mut items = Vec::new();
104 for item in krate.items {
105 try!(expand_item(reg, item, Vec::new(), &mut items));
106 }
107 Ok(Crate { items: items, ..krate })
108}
109
110fn expand_item(reg: &Registry, mut item: Item, cfg: Vec<MetaItem>, out: &mut Vec<Item>) -> Result<(), String> {
111 let (body, generics) = match item.node {
112 ItemKind::Enum(variants, generics) => (Body::Enum(variants), generics),
113 ItemKind::Struct(variant_data, generics) => (Body::Struct(variant_data), generics),
114 _ => {
115 // Custom derives cannot apply to this item, preserve it unmodified
116 item.attrs.extend(combine_cfgs(cfg));
117 out.push(item);
118 return Ok(());
119 }
120 };
121 let macro_input = MacroInput {
122 ident: item.ident,
123 vis: item.vis,
124 attrs: item.attrs,
125 generics: generics,
126 body: body,
127 };
128 expand_macro_input(reg, macro_input, cfg, out)
129}
130
131fn expand_macro_input(reg: &Registry,
132 mut input: MacroInput,
133 inherited_cfg: Vec<MetaItem>,
134 out: &mut Vec<Item>)
135 -> Result<(), String> {
136 let mut derives = Vec::new();
137 let mut all_cfg = inherited_cfg;
138
139 // Find custom derives on this item, removing them from the input
140 input.attrs = input.attrs
141 .into_iter()
142 .flat_map(|attr| {
143 let (new_derives, cfg, attr) = parse_attr(reg, attr);
144 derives.extend(new_derives);
145 all_cfg.extend(cfg);
146 attr
147 })
148 .collect();
149
150 // Expand each custom derive
151 for derive in derives {
152 let expanded = try!(reg.derives[derive.name.as_ref()].expand(input));
153
154 for new_item in expanded.new_items {
155 let mut extended_cfg = all_cfg.clone();
156 extended_cfg.extend(derive.cfg.clone());
157 try!(expand_item(reg, new_item, extended_cfg, out));
158 }
159
160 input = match expanded.original {
161 Some(input) => input,
162 None => return Ok(()),
163 };
164 }
165
166 input.attrs.extend(combine_cfgs(all_cfg));
167 out.push(input.into());
168 Ok(())
169}
170
171struct Derive {
172 name: Ident,
173 /// If the custom derive was behind a cfg_attr
174 cfg: Option<MetaItem>,
175}
176
177/// Pull custom derives and cfgs out of the given Attribute.
178fn parse_attr(reg: &Registry, attr: Attribute) -> (Vec<Derive>, Vec<MetaItem>, Option<Attribute>) {
179 if attr.style != AttrStyle::Outer || attr.is_sugared_doc {
180 return (Vec::new(), Vec::new(), Some(attr));
181 }
182
183 let (name, nested) = match attr.value {
184 MetaItem::List(name, nested) => (name, nested),
185 _ => return (Vec::new(), Vec::new(), Some(attr)),
186 };
187
188 match name.as_ref() {
189 "derive" => {
190 let (derives, attr) = parse_derive_attr(reg, nested);
191 let derives = derives.into_iter()
192 .map(|d| {
193 Derive {
194 name: d,
195 cfg: None,
196 }
197 })
198 .collect();
199 (derives, Vec::new(), attr)
200 }
201 "cfg_attr" => {
202 let (derives, attr) = parse_cfg_attr(reg, nested);
203 (derives, Vec::new(), attr)
204 }
205 "cfg" => (Vec::new(), nested, None),
206 _ => {
207 // Rebuild the original attribute because it was destructured above
208 let attr = Attribute {
209 style: AttrStyle::Outer,
210 value: MetaItem::List(name, nested),
211 is_sugared_doc: false,
212 };
213 (Vec::new(), Vec::new(), Some(attr))
214 }
215 }
216}
217
218/// Assuming the given nested meta-items came from a #[derive(...)] attribute,
219/// pull out the ones that are custom derives.
220fn parse_derive_attr(reg: &Registry, nested: Vec<MetaItem>) -> (Vec<Ident>, Option<Attribute>) {
221 let mut derives = Vec::new();
222
223 let remaining: Vec<_> = nested.into_iter()
224 .flat_map(|meta| {
225 let word = match meta {
226 MetaItem::Word(word) => word,
227 _ => return Some(meta),
228 };
229 if reg.derives.contains_key(word.as_ref()) {
230 derives.push(word);
231 None
232 } else {
233 Some(MetaItem::Word(word))
234 }
235 })
236 .collect();
237
238 let attr = if remaining.is_empty() {
239 // Elide an empty #[derive()]
240 None
241 } else {
242 Some(Attribute {
243 style: AttrStyle::Outer,
244 value: MetaItem::List("derive".into(), remaining),
245 is_sugared_doc: false,
246 })
247 };
248
249 (derives, attr)
250}
251
252/// Assuming the given nested meta-items came from a #[cfg_attr(...)] attribute,
253/// pull out any custom derives contained within.
254fn parse_cfg_attr(reg: &Registry, nested: Vec<MetaItem>) -> (Vec<Derive>, Option<Attribute>) {
255 if nested.len() != 2 {
256 let attr = Attribute {
257 style: AttrStyle::Outer,
258 value: MetaItem::List("cfg_attr".into(), nested),
259 is_sugared_doc: false,
260 };
261 return (Vec::new(), Some(attr));
262 }
263
264 let mut iter = nested.into_iter();
265 let cfg = iter.next().unwrap();
266 let arg = iter.next().unwrap();
267
268 let (name, nested) = match arg {
269 MetaItem::List(name, nested) => (name, nested),
270 _ => {
271 let attr = Attribute {
272 style: AttrStyle::Outer,
273 value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]),
274 is_sugared_doc: false,
275 };
276 return (Vec::new(), Some(attr));
277 }
278 };
279
280 if name == "derive" {
281 let (derives, attr) = parse_derive_attr(reg, nested);
282 let derives = derives.into_iter()
283 .map(|d| {
284 Derive {
285 name: d,
286 cfg: Some(cfg.clone()),
287 }
288 })
289 .collect();
290 let attr = attr.map(|attr| {
291 Attribute {
292 style: AttrStyle::Outer,
293 value: MetaItem::List("cfg_attr".into(), vec![cfg, attr.value]),
294 is_sugared_doc: false,
295 }
296 });
297 (derives, attr)
298 } else {
299 let attr = Attribute {
300 style: AttrStyle::Outer,
301 value: MetaItem::List("cfg_attr".into(), vec![cfg, MetaItem::List(name, nested)]),
302 is_sugared_doc: false,
303 };
304 (Vec::new(), Some(attr))
305 }
306}
307
308/// Combine a list of cfg expressions into an attribute like `#[cfg(a)]` or
309/// `#[cfg(all(a, b, c))]`, or nothing if there are no cfg expressions.
310fn combine_cfgs(cfg: Vec<MetaItem>) -> Option<Attribute> {
311 // Flatten `all` cfgs so we don't nest `all` inside of `all`.
312 let cfg: Vec<_> = cfg.into_iter().flat_map(|cfg| {
313 let (name, nested) = match cfg {
314 MetaItem::List(name, nested) => (name, nested),
315 _ => return vec![cfg],
316 };
317 if name == "all" {
318 nested
319 } else {
320 vec![MetaItem::List(name, nested)]
321 }
322 }).collect();
323
324 let value = match cfg.len() {
325 0 => return None,
326 1 => cfg,
327 _ => vec![MetaItem::List("all".into(), cfg)],
328 };
329
330 Some(Attribute {
331 style: AttrStyle::Outer,
332 value: MetaItem::List("cfg".into(), value),
333 is_sugared_doc: false,
334 })
335}