blob: 24e1c6b1c087389b0f0e5805b84b034c3b3a9934 [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 Tolnayc1fea502016-10-30 17:54:02 -0700184fn parse_attr(reg: &Registry,
185 attr: Attribute)
186 -> (Vec<Derive>, Vec<NestedMetaItem>, Option<Attribute>) {
David Tolnay453cfd12016-10-23 11:00:14 -0700187 if attr.style != AttrStyle::Outer || attr.is_sugared_doc {
188 return (Vec::new(), Vec::new(), Some(attr));
189 }
190
191 let (name, nested) = match attr.value {
192 MetaItem::List(name, nested) => (name, nested),
193 _ => return (Vec::new(), Vec::new(), Some(attr)),
194 };
195
196 match name.as_ref() {
197 "derive" => {
198 let (derives, attr) = parse_derive_attr(reg, nested);
199 let derives = derives.into_iter()
200 .map(|d| {
201 Derive {
202 name: d,
203 cfg: None,
204 }
205 })
206 .collect();
207 (derives, Vec::new(), attr)
208 }
209 "cfg_attr" => {
210 let (derives, attr) = parse_cfg_attr(reg, nested);
211 (derives, Vec::new(), attr)
212 }
213 "cfg" => (Vec::new(), nested, None),
214 _ => {
215 // Rebuild the original attribute because it was destructured above
216 let attr = Attribute {
217 style: AttrStyle::Outer,
218 value: MetaItem::List(name, nested),
219 is_sugared_doc: false,
220 };
221 (Vec::new(), Vec::new(), Some(attr))
222 }
223 }
224}
225
226/// Assuming the given nested meta-items came from a #[derive(...)] attribute,
227/// pull out the ones that are custom derives.
David Tolnayc1fea502016-10-30 17:54:02 -0700228fn parse_derive_attr(reg: &Registry,
229 nested: Vec<NestedMetaItem>)
230 -> (Vec<Ident>, Option<Attribute>) {
David Tolnay453cfd12016-10-23 11:00:14 -0700231 let mut derives = Vec::new();
232
233 let remaining: Vec<_> = nested.into_iter()
234 .flat_map(|meta| {
235 let word = match meta {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700236 NestedMetaItem::MetaItem(MetaItem::Word(word)) => word,
David Tolnay453cfd12016-10-23 11:00:14 -0700237 _ => return Some(meta),
238 };
239 if reg.derives.contains_key(word.as_ref()) {
240 derives.push(word);
241 None
242 } else {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700243 Some(NestedMetaItem::MetaItem(MetaItem::Word(word)))
David Tolnay453cfd12016-10-23 11:00:14 -0700244 }
245 })
246 .collect();
247
248 let attr = if remaining.is_empty() {
249 // Elide an empty #[derive()]
250 None
251 } else {
252 Some(Attribute {
253 style: AttrStyle::Outer,
254 value: MetaItem::List("derive".into(), remaining),
255 is_sugared_doc: false,
256 })
257 };
258
259 (derives, attr)
260}
261
262/// Assuming the given nested meta-items came from a #[cfg_attr(...)] attribute,
263/// pull out any custom derives contained within.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700264fn parse_cfg_attr(reg: &Registry, nested: Vec<NestedMetaItem>) -> (Vec<Derive>, Option<Attribute>) {
David Tolnay453cfd12016-10-23 11:00:14 -0700265 if nested.len() != 2 {
266 let attr = Attribute {
267 style: AttrStyle::Outer,
268 value: MetaItem::List("cfg_attr".into(), nested),
269 is_sugared_doc: false,
270 };
271 return (Vec::new(), Some(attr));
272 }
273
274 let mut iter = nested.into_iter();
275 let cfg = iter.next().unwrap();
276 let arg = iter.next().unwrap();
277
278 let (name, nested) = match arg {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700279 NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
David Tolnay453cfd12016-10-23 11:00:14 -0700280 _ => {
281 let attr = Attribute {
282 style: AttrStyle::Outer,
283 value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]),
284 is_sugared_doc: false,
285 };
286 return (Vec::new(), Some(attr));
287 }
288 };
289
290 if name == "derive" {
291 let (derives, attr) = parse_derive_attr(reg, nested);
292 let derives = derives.into_iter()
293 .map(|d| {
294 Derive {
295 name: d,
296 cfg: Some(cfg.clone()),
297 }
298 })
299 .collect();
300 let attr = attr.map(|attr| {
301 Attribute {
302 style: AttrStyle::Outer,
David Tolnayc1fea502016-10-30 17:54:02 -0700303 value: MetaItem::List("cfg_attr".into(),
304 vec![cfg, NestedMetaItem::MetaItem(attr.value)]),
David Tolnay453cfd12016-10-23 11:00:14 -0700305 is_sugared_doc: false,
306 }
307 });
308 (derives, attr)
309 } else {
310 let attr = Attribute {
311 style: AttrStyle::Outer,
David Tolnayc1fea502016-10-30 17:54:02 -0700312 value:
313 MetaItem::List("cfg_attr".into(),
314 vec![cfg, NestedMetaItem::MetaItem(MetaItem::List(name, nested))]),
David Tolnay453cfd12016-10-23 11:00:14 -0700315 is_sugared_doc: false,
316 };
317 (Vec::new(), Some(attr))
318 }
319}
320
321/// Combine a list of cfg expressions into an attribute like `#[cfg(a)]` or
322/// `#[cfg(all(a, b, c))]`, or nothing if there are no cfg expressions.
David Tolnayb7fa2b62016-10-30 10:50:47 -0700323fn combine_cfgs(cfg: Vec<NestedMetaItem>) -> Option<Attribute> {
David Tolnay453cfd12016-10-23 11:00:14 -0700324 // Flatten `all` cfgs so we don't nest `all` inside of `all`.
David Tolnaye8cb5f42016-10-23 22:26:20 -0700325 let cfg: Vec<_> = cfg.into_iter()
326 .flat_map(|cfg| {
327 let (name, nested) = match cfg {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700328 NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
David Tolnaye8cb5f42016-10-23 22:26:20 -0700329 _ => return vec![cfg],
330 };
331 if name == "all" {
332 nested
333 } else {
David Tolnayb7fa2b62016-10-30 10:50:47 -0700334 vec![NestedMetaItem::MetaItem(MetaItem::List(name, nested))]
David Tolnaye8cb5f42016-10-23 22:26:20 -0700335 }
336 })
337 .collect();
David Tolnay453cfd12016-10-23 11:00:14 -0700338
339 let value = match cfg.len() {
340 0 => return None,
341 1 => cfg,
David Tolnayb7fa2b62016-10-30 10:50:47 -0700342 _ => vec![NestedMetaItem::MetaItem(MetaItem::List("all".into(), cfg))],
David Tolnay453cfd12016-10-23 11:00:14 -0700343 };
344
345 Some(Attribute {
346 style: AttrStyle::Outer,
347 value: MetaItem::List("cfg".into(), value),
348 is_sugared_doc: false,
349 })
350}
David Tolnay9e24d4d2016-10-23 22:17:27 -0700351
352#[cfg(not(feature = "pretty"))]
353fn pretty(tokens: Tokens) -> Result<String, String> {
354 Ok(tokens.to_string())
355}
356
357#[cfg(feature = "pretty")]
358fn pretty(tokens: Tokens) -> Result<String, String> {
359 use syntax::parse::{self, ParseSess};
360 use syntax::print::pprust;
361
362 let name = "syn".to_string();
363 let source = tokens.to_string();
David Tolnay9e24d4d2016-10-23 22:17:27 -0700364 let sess = ParseSess::new();
David Tolnaydb84cb22016-11-20 21:34:58 -0800365 let krate = match parse::parse_crate_from_source_str(name, source, &sess) {
David Tolnay9e24d4d2016-10-23 22:17:27 -0700366 Ok(krate) => krate,
367 Err(mut err) => {
368 err.emit();
369 return Err("pretty printer failed to parse expanded code".into());
370 }
371 };
372
373 if sess.span_diagnostic.has_errors() {
374 return Err("pretty printer failed to parse expanded code".into());
375 }
376
377 let mut reader = &tokens.to_string().into_bytes()[..];
378 let mut writer = Vec::new();
379 let ann = pprust::NoAnn;
380
381 try!(pprust::print_crate(
382 sess.codemap(),
383 &sess.span_diagnostic,
384 &krate,
385 "".to_string(),
386 &mut reader,
387 Box::new(&mut writer),
388 &ann,
389 false).map_err(|err| err.to_string()));
390
391 String::from_utf8(writer).map_err(|err| err.to_string())
392}