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