David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 1 | use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem, |
| 2 | NestedMetaItem}; |
David Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 3 | use quote::Tokens; |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 4 | |
| 5 | use std::collections::BTreeMap as Map; |
| 6 | use std::fs::File; |
| 7 | use std::io::{Read, Write}; |
| 8 | use 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. |
| 12 | pub 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. |
| 21 | pub 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)] |
| 33 | pub struct Registry<'a> { |
| 34 | derives: Map<String, Box<CustomDerive + 'a>>, |
| 35 | } |
| 36 | |
| 37 | impl<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 | |
| 45 | impl<'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 Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 87 | let out = try!(pretty(quote!(#expanded))); |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 88 | |
| 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 | |
| 104 | fn 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 Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 112 | fn expand_item(reg: &Registry, |
| 113 | mut item: Item, |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 114 | cfg: Vec<NestedMetaItem>, |
David Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 115 | out: &mut Vec<Item>) |
| 116 | -> Result<(), String> { |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 117 | 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 | |
| 137 | fn expand_macro_input(reg: &Registry, |
| 138 | mut input: MacroInput, |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 139 | inherited_cfg: Vec<NestedMetaItem>, |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 140 | 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 | |
| 177 | struct Derive { |
| 178 | name: Ident, |
| 179 | /// If the custom derive was behind a cfg_attr |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 180 | cfg: Option<NestedMetaItem>, |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 181 | } |
| 182 | |
| 183 | /// Pull custom derives and cfgs out of the given Attribute. |
David Tolnay | c1fea50 | 2016-10-30 17:54:02 -0700 | [diff] [blame] | 184 | fn parse_attr(reg: &Registry, |
| 185 | attr: Attribute) |
| 186 | -> (Vec<Derive>, Vec<NestedMetaItem>, Option<Attribute>) { |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 187 | 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 Tolnay | c1fea50 | 2016-10-30 17:54:02 -0700 | [diff] [blame] | 228 | fn parse_derive_attr(reg: &Registry, |
| 229 | nested: Vec<NestedMetaItem>) |
| 230 | -> (Vec<Ident>, Option<Attribute>) { |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 231 | let mut derives = Vec::new(); |
| 232 | |
| 233 | let remaining: Vec<_> = nested.into_iter() |
| 234 | .flat_map(|meta| { |
| 235 | let word = match meta { |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 236 | NestedMetaItem::MetaItem(MetaItem::Word(word)) => word, |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 237 | _ => return Some(meta), |
| 238 | }; |
| 239 | if reg.derives.contains_key(word.as_ref()) { |
| 240 | derives.push(word); |
| 241 | None |
| 242 | } else { |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 243 | Some(NestedMetaItem::MetaItem(MetaItem::Word(word))) |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 244 | } |
| 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 Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 264 | fn parse_cfg_attr(reg: &Registry, nested: Vec<NestedMetaItem>) -> (Vec<Derive>, Option<Attribute>) { |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 265 | 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 Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 279 | NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested), |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 280 | _ => { |
| 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 Tolnay | c1fea50 | 2016-10-30 17:54:02 -0700 | [diff] [blame] | 303 | value: MetaItem::List("cfg_attr".into(), |
| 304 | vec![cfg, NestedMetaItem::MetaItem(attr.value)]), |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 305 | is_sugared_doc: false, |
| 306 | } |
| 307 | }); |
| 308 | (derives, attr) |
| 309 | } else { |
| 310 | let attr = Attribute { |
| 311 | style: AttrStyle::Outer, |
David Tolnay | c1fea50 | 2016-10-30 17:54:02 -0700 | [diff] [blame] | 312 | value: |
| 313 | MetaItem::List("cfg_attr".into(), |
| 314 | vec![cfg, NestedMetaItem::MetaItem(MetaItem::List(name, nested))]), |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 315 | 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 Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 323 | fn combine_cfgs(cfg: Vec<NestedMetaItem>) -> Option<Attribute> { |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 324 | // Flatten `all` cfgs so we don't nest `all` inside of `all`. |
David Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 325 | let cfg: Vec<_> = cfg.into_iter() |
| 326 | .flat_map(|cfg| { |
| 327 | let (name, nested) = match cfg { |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 328 | NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested), |
David Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 329 | _ => return vec![cfg], |
| 330 | }; |
| 331 | if name == "all" { |
| 332 | nested |
| 333 | } else { |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 334 | vec![NestedMetaItem::MetaItem(MetaItem::List(name, nested))] |
David Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 335 | } |
| 336 | }) |
| 337 | .collect(); |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 338 | |
| 339 | let value = match cfg.len() { |
| 340 | 0 => return None, |
| 341 | 1 => cfg, |
David Tolnay | b7fa2b6 | 2016-10-30 10:50:47 -0700 | [diff] [blame] | 342 | _ => vec![NestedMetaItem::MetaItem(MetaItem::List("all".into(), cfg))], |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 343 | }; |
| 344 | |
| 345 | Some(Attribute { |
| 346 | style: AttrStyle::Outer, |
| 347 | value: MetaItem::List("cfg".into(), value), |
| 348 | is_sugared_doc: false, |
| 349 | }) |
| 350 | } |
David Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 351 | |
| 352 | #[cfg(not(feature = "pretty"))] |
| 353 | fn pretty(tokens: Tokens) -> Result<String, String> { |
| 354 | Ok(tokens.to_string()) |
| 355 | } |
| 356 | |
| 357 | #[cfg(feature = "pretty")] |
| 358 | fn 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 Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 364 | let sess = ParseSess::new(); |
David Tolnay | db84cb2 | 2016-11-20 21:34:58 -0800 | [diff] [blame] | 365 | let krate = match parse::parse_crate_from_source_str(name, source, &sess) { |
David Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 366 | 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 | } |