blob: 2acd759dc9f3af941516eab6c5707a6b100c6877 [file] [log] [blame]
David Tolnayb7fa2b62016-10-30 10:50:47 -07001use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem,
2 NestedMetaItem};
David Tolnay9e24d4d2016-10-23 22:17:27 -07003use quote::Tokens;
David Tolnay453cfd12016-10-23 11:00:14 -07004
5use std::collections::BTreeMap as Map;
6use std::fs::File;
7use std::io::{Read, Write};
8use std::path::Path;
9
10/// Implementation of a custom derive. Custom derives take a struct or enum and
11/// expand it into zero or more items, typically `impl` items.
12pub trait CustomDerive {
13 /// Expand the given struct or enum. If this custom derive modifies the
14 /// input item or preserves it unmodified, it must be returned back in the
15 /// `original` field of Expanded. The custom derive may discard the input
16 /// item by setting `original` to None.
17 fn expand(&self, input: MacroInput) -> Result<Expanded, String>;
18}
19
20/// Produced by expanding a custom derive.
21pub struct Expanded {
22 /// The items (typically `impl` items) constructed by the custom derive.
23 pub new_items: Vec<Item>,
24 /// The input to the custom derive, whether modified or unmodified. If the
25 /// custom derive discards the input item it may do so by setting `original`
26 /// to None.
27 pub original: Option<MacroInput>,
28}
29
30/// Registry of custom derives. Callers add custom derives to a registry, then
31/// use the registry to expand those derives in a source file.
32#[derive(Default)]
33pub struct Registry<'a> {
34 derives: Map<String, Box<CustomDerive + 'a>>,
35}
36
37impl<T> CustomDerive for T
38 where T: Fn(MacroInput) -> Result<Expanded, String>
39{
40 fn expand(&self, input: MacroInput) -> Result<Expanded, String> {
41 self(input)
42 }
43}
44
45impl<'a> Registry<'a> {
46 pub fn new() -> Self {
47 Default::default()
48 }
49
50 /// Register a custom derive. A `fn(MacroInput) -> Result<Expanded, String>`
51 /// may be used as a custom derive.
52 ///
53 /// ```ignore
54 /// registry.add_derive("Serialize", expand_serialize);
55 /// ```
56 pub fn add_derive<T>(&mut self, name: &str, derive: T)
57 where T: CustomDerive + 'a
58 {
59 self.derives.insert(name.into(), Box::new(derive));
60 }
61
62 /// Read Rust source code from the `src` file, expand the custom derives
63 /// that have been registered, and write the result to the `dst` file.
64 pub fn expand_file<S, D>(&self, src: S, dst: D) -> Result<(), String>
65 where S: AsRef<Path>,
66 D: AsRef<Path>
67 {
68 // Open the src file
69 let mut src = match File::open(src) {
70 Ok(open) => open,
71 Err(err) => return Err(err.to_string()),
72 };
73
74 // Read the contents of the src file to a String
75 let mut content = String::new();
76 if let Err(err) = src.read_to_string(&mut content) {
77 return Err(err.to_string());
78 }
79
80 // Parse the contents
81 let krate = try!(super::parse_crate(&content));
82
83 // Expand
84 let expanded = try!(expand_crate(self, krate));
85
86 // Print the expanded code to a String
David Tolnay9e24d4d2016-10-23 22:17:27 -070087 let out = try!(pretty(quote!(#expanded)));
David Tolnay453cfd12016-10-23 11:00:14 -070088
89 // Create or truncate the dst file, opening in write-only mode
90 let mut dst = match File::create(dst) {
91 Ok(create) => create,
92 Err(err) => return Err(err.to_string()),
93 };
94
95 // Write expanded code to the dst file
96 if let Err(err) = dst.write_all(out.as_bytes()) {
97 return Err(err.to_string());
98 }
99
100 Ok(())
101 }
102}
103
104fn expand_crate(reg: &Registry, krate: Crate) -> Result<Crate, String> {
105 let mut items = Vec::new();
106 for item in krate.items {
107 try!(expand_item(reg, item, Vec::new(), &mut items));
108 }
109 Ok(Crate { items: items, ..krate })
110}
111
David Tolnaye8cb5f42016-10-23 22:26:20 -0700112fn expand_item(reg: &Registry,
113 mut item: Item,
David Tolnayb7fa2b62016-10-30 10:50:47 -0700114 cfg: Vec<NestedMetaItem>,
David Tolnaye8cb5f42016-10-23 22:26:20 -0700115 out: &mut Vec<Item>)
116 -> Result<(), String> {
David Tolnay453cfd12016-10-23 11:00:14 -0700117 let (body, generics) = match item.node {
118 ItemKind::Enum(variants, generics) => (Body::Enum(variants), generics),
119 ItemKind::Struct(variant_data, generics) => (Body::Struct(variant_data), generics),
120 _ => {
121 // Custom derives cannot apply to this item, preserve it unmodified
122 item.attrs.extend(combine_cfgs(cfg));
123 out.push(item);
124 return Ok(());
125 }
126 };
127 let macro_input = MacroInput {
128 ident: item.ident,
129 vis: item.vis,
130 attrs: item.attrs,
131 generics: generics,
132 body: body,
133 };
134 expand_macro_input(reg, macro_input, cfg, out)
135}
136
137fn expand_macro_input(reg: &Registry,
138 mut input: MacroInput,
David Tolnayb7fa2b62016-10-30 10:50:47 -0700139 inherited_cfg: Vec<NestedMetaItem>,
David Tolnay453cfd12016-10-23 11:00:14 -0700140 out: &mut Vec<Item>)
141 -> Result<(), String> {
142 let mut derives = Vec::new();
143 let mut all_cfg = inherited_cfg;
144
145 // Find custom derives on this item, removing them from the input
146 input.attrs = input.attrs
147 .into_iter()
148 .flat_map(|attr| {
149 let (new_derives, cfg, attr) = parse_attr(reg, attr);
150 derives.extend(new_derives);
151 all_cfg.extend(cfg);
152 attr
153 })
154 .collect();
155
156 // Expand each custom derive
157 for derive in derives {
158 let expanded = try!(reg.derives[derive.name.as_ref()].expand(input));
159
160 for new_item in expanded.new_items {
161 let mut extended_cfg = all_cfg.clone();
162 extended_cfg.extend(derive.cfg.clone());
163 try!(expand_item(reg, new_item, extended_cfg, out));
164 }
165
166 input = match expanded.original {
167 Some(input) => input,
168 None => return Ok(()),
169 };
170 }
171
172 input.attrs.extend(combine_cfgs(all_cfg));
173 out.push(input.into());
174 Ok(())
175}
176
177struct Derive {
178 name: Ident,
179 /// If the custom derive was behind a cfg_attr
David Tolnayb7fa2b62016-10-30 10:50:47 -0700180 cfg: Option<NestedMetaItem>,
David Tolnay453cfd12016-10-23 11:00:14 -0700181}
182
183/// Pull custom derives and cfgs out of the given Attribute.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700184fn parse_attr(reg: &Registry, attr: Attribute) -> (Vec<Derive>, Vec<NestedMetaItem>, Option<Attribute>) {
David Tolnay453cfd12016-10-23 11:00:14 -0700185 if attr.style != AttrStyle::Outer || attr.is_sugared_doc {
186 return (Vec::new(), Vec::new(), Some(attr));
187 }
188
189 let (name, nested) = match attr.value {
190 MetaItem::List(name, nested) => (name, nested),
191 _ => return (Vec::new(), Vec::new(), Some(attr)),
192 };
193
194 match name.as_ref() {
195 "derive" => {
196 let (derives, attr) = parse_derive_attr(reg, nested);
197 let derives = derives.into_iter()
198 .map(|d| {
199 Derive {
200 name: d,
201 cfg: None,
202 }
203 })
204 .collect();
205 (derives, Vec::new(), attr)
206 }
207 "cfg_attr" => {
208 let (derives, attr) = parse_cfg_attr(reg, nested);
209 (derives, Vec::new(), attr)
210 }
211 "cfg" => (Vec::new(), nested, None),
212 _ => {
213 // Rebuild the original attribute because it was destructured above
214 let attr = Attribute {
215 style: AttrStyle::Outer,
216 value: MetaItem::List(name, nested),
217 is_sugared_doc: false,
218 };
219 (Vec::new(), Vec::new(), Some(attr))
220 }
221 }
222}
223
224/// Assuming the given nested meta-items came from a #[derive(...)] attribute,
225/// pull out the ones that are custom derives.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700226fn parse_derive_attr(reg: &Registry, nested: Vec<NestedMetaItem>) -> (Vec<Ident>, Option<Attribute>) {
David Tolnay453cfd12016-10-23 11:00:14 -0700227 let mut derives = Vec::new();
228
229 let remaining: Vec<_> = nested.into_iter()
230 .flat_map(|meta| {
231 let word = match meta {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700232 NestedMetaItem::MetaItem(MetaItem::Word(word)) => word,
David Tolnay453cfd12016-10-23 11:00:14 -0700233 _ => return Some(meta),
234 };
235 if reg.derives.contains_key(word.as_ref()) {
236 derives.push(word);
237 None
238 } else {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700239 Some(NestedMetaItem::MetaItem(MetaItem::Word(word)))
David Tolnay453cfd12016-10-23 11:00:14 -0700240 }
241 })
242 .collect();
243
244 let attr = if remaining.is_empty() {
245 // Elide an empty #[derive()]
246 None
247 } else {
248 Some(Attribute {
249 style: AttrStyle::Outer,
250 value: MetaItem::List("derive".into(), remaining),
251 is_sugared_doc: false,
252 })
253 };
254
255 (derives, attr)
256}
257
258/// Assuming the given nested meta-items came from a #[cfg_attr(...)] attribute,
259/// pull out any custom derives contained within.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700260fn parse_cfg_attr(reg: &Registry, nested: Vec<NestedMetaItem>) -> (Vec<Derive>, Option<Attribute>) {
David Tolnay453cfd12016-10-23 11:00:14 -0700261 if nested.len() != 2 {
262 let attr = Attribute {
263 style: AttrStyle::Outer,
264 value: MetaItem::List("cfg_attr".into(), nested),
265 is_sugared_doc: false,
266 };
267 return (Vec::new(), Some(attr));
268 }
269
270 let mut iter = nested.into_iter();
271 let cfg = iter.next().unwrap();
272 let arg = iter.next().unwrap();
273
274 let (name, nested) = match arg {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700275 NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
David Tolnay453cfd12016-10-23 11:00:14 -0700276 _ => {
277 let attr = Attribute {
278 style: AttrStyle::Outer,
279 value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]),
280 is_sugared_doc: false,
281 };
282 return (Vec::new(), Some(attr));
283 }
284 };
285
286 if name == "derive" {
287 let (derives, attr) = parse_derive_attr(reg, nested);
288 let derives = derives.into_iter()
289 .map(|d| {
290 Derive {
291 name: d,
292 cfg: Some(cfg.clone()),
293 }
294 })
295 .collect();
296 let attr = attr.map(|attr| {
297 Attribute {
298 style: AttrStyle::Outer,
David Tolnayb7fa2b62016-10-30 10:50:47 -0700299 value: MetaItem::List("cfg_attr".into(), vec![cfg, NestedMetaItem::MetaItem(attr.value)]),
David Tolnay453cfd12016-10-23 11:00:14 -0700300 is_sugared_doc: false,
301 }
302 });
303 (derives, attr)
304 } else {
305 let attr = Attribute {
306 style: AttrStyle::Outer,
David Tolnayb7fa2b62016-10-30 10:50:47 -0700307 value: MetaItem::List("cfg_attr".into(), vec![cfg, NestedMetaItem::MetaItem(MetaItem::List(name, nested))]),
David Tolnay453cfd12016-10-23 11:00:14 -0700308 is_sugared_doc: false,
309 };
310 (Vec::new(), Some(attr))
311 }
312}
313
314/// Combine a list of cfg expressions into an attribute like `#[cfg(a)]` or
315/// `#[cfg(all(a, b, c))]`, or nothing if there are no cfg expressions.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700316fn combine_cfgs(cfg: Vec<NestedMetaItem>) -> Option<Attribute> {
David Tolnay453cfd12016-10-23 11:00:14 -0700317 // Flatten `all` cfgs so we don't nest `all` inside of `all`.
David Tolnaye8cb5f42016-10-23 22:26:20 -0700318 let cfg: Vec<_> = cfg.into_iter()
319 .flat_map(|cfg| {
320 let (name, nested) = match cfg {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700321 NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
David Tolnaye8cb5f42016-10-23 22:26:20 -0700322 _ => return vec![cfg],
323 };
324 if name == "all" {
325 nested
326 } else {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700327 vec![NestedMetaItem::MetaItem(MetaItem::List(name, nested))]
David Tolnaye8cb5f42016-10-23 22:26:20 -0700328 }
329 })
330 .collect();
David Tolnay453cfd12016-10-23 11:00:14 -0700331
332 let value = match cfg.len() {
333 0 => return None,
334 1 => cfg,
David Tolnayb7fa2b62016-10-30 10:50:47 -0700335 _ => vec![NestedMetaItem::MetaItem(MetaItem::List("all".into(), cfg))],
David Tolnay453cfd12016-10-23 11:00:14 -0700336 };
337
338 Some(Attribute {
339 style: AttrStyle::Outer,
340 value: MetaItem::List("cfg".into(), value),
341 is_sugared_doc: false,
342 })
343}
David Tolnay9e24d4d2016-10-23 22:17:27 -0700344
345#[cfg(not(feature = "pretty"))]
346fn pretty(tokens: Tokens) -> Result<String, String> {
347 Ok(tokens.to_string())
348}
349
350#[cfg(feature = "pretty")]
351fn pretty(tokens: Tokens) -> Result<String, String> {
352 use syntax::parse::{self, ParseSess};
353 use syntax::print::pprust;
354
355 let name = "syn".to_string();
356 let source = tokens.to_string();
357 let cfg = Vec::new();
358 let sess = ParseSess::new();
359 let krate = match parse::parse_crate_from_source_str(name, source, cfg, &sess) {
360 Ok(krate) => krate,
361 Err(mut err) => {
362 err.emit();
363 return Err("pretty printer failed to parse expanded code".into());
364 }
365 };
366
367 if sess.span_diagnostic.has_errors() {
368 return Err("pretty printer failed to parse expanded code".into());
369 }
370
371 let mut reader = &tokens.to_string().into_bytes()[..];
372 let mut writer = Vec::new();
373 let ann = pprust::NoAnn;
374
375 try!(pprust::print_crate(
376 sess.codemap(),
377 &sess.span_diagnostic,
378 &krate,
379 "".to_string(),
380 &mut reader,
381 Box::new(&mut writer),
382 &ann,
383 false).map_err(|err| err.to_string()));
384
385 String::from_utf8(writer).map_err(|err| err.to_string())
386}