David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 1 | use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem}; |
David Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 2 | use quote::Tokens; |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 3 | |
| 4 | use std::collections::BTreeMap as Map; |
| 5 | use std::fs::File; |
| 6 | use std::io::{Read, Write}; |
| 7 | use 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. |
| 11 | pub 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. |
| 20 | pub 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)] |
| 32 | pub struct Registry<'a> { |
| 33 | derives: Map<String, Box<CustomDerive + 'a>>, |
| 34 | } |
| 35 | |
| 36 | impl<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 | |
| 44 | impl<'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 Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 86 | let out = try!(pretty(quote!(#expanded))); |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 87 | |
| 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 | |
| 103 | fn 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 | |
David Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 111 | fn expand_item(reg: &Registry, |
| 112 | mut item: Item, |
| 113 | cfg: Vec<MetaItem>, |
| 114 | out: &mut Vec<Item>) |
| 115 | -> Result<(), String> { |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 116 | let (body, generics) = match item.node { |
| 117 | ItemKind::Enum(variants, generics) => (Body::Enum(variants), generics), |
| 118 | ItemKind::Struct(variant_data, generics) => (Body::Struct(variant_data), generics), |
| 119 | _ => { |
| 120 | // Custom derives cannot apply to this item, preserve it unmodified |
| 121 | item.attrs.extend(combine_cfgs(cfg)); |
| 122 | out.push(item); |
| 123 | return Ok(()); |
| 124 | } |
| 125 | }; |
| 126 | let macro_input = MacroInput { |
| 127 | ident: item.ident, |
| 128 | vis: item.vis, |
| 129 | attrs: item.attrs, |
| 130 | generics: generics, |
| 131 | body: body, |
| 132 | }; |
| 133 | expand_macro_input(reg, macro_input, cfg, out) |
| 134 | } |
| 135 | |
| 136 | fn expand_macro_input(reg: &Registry, |
| 137 | mut input: MacroInput, |
| 138 | inherited_cfg: Vec<MetaItem>, |
| 139 | out: &mut Vec<Item>) |
| 140 | -> Result<(), String> { |
| 141 | let mut derives = Vec::new(); |
| 142 | let mut all_cfg = inherited_cfg; |
| 143 | |
| 144 | // Find custom derives on this item, removing them from the input |
| 145 | input.attrs = input.attrs |
| 146 | .into_iter() |
| 147 | .flat_map(|attr| { |
| 148 | let (new_derives, cfg, attr) = parse_attr(reg, attr); |
| 149 | derives.extend(new_derives); |
| 150 | all_cfg.extend(cfg); |
| 151 | attr |
| 152 | }) |
| 153 | .collect(); |
| 154 | |
| 155 | // Expand each custom derive |
| 156 | for derive in derives { |
| 157 | let expanded = try!(reg.derives[derive.name.as_ref()].expand(input)); |
| 158 | |
| 159 | for new_item in expanded.new_items { |
| 160 | let mut extended_cfg = all_cfg.clone(); |
| 161 | extended_cfg.extend(derive.cfg.clone()); |
| 162 | try!(expand_item(reg, new_item, extended_cfg, out)); |
| 163 | } |
| 164 | |
| 165 | input = match expanded.original { |
| 166 | Some(input) => input, |
| 167 | None => return Ok(()), |
| 168 | }; |
| 169 | } |
| 170 | |
| 171 | input.attrs.extend(combine_cfgs(all_cfg)); |
| 172 | out.push(input.into()); |
| 173 | Ok(()) |
| 174 | } |
| 175 | |
| 176 | struct Derive { |
| 177 | name: Ident, |
| 178 | /// If the custom derive was behind a cfg_attr |
| 179 | cfg: Option<MetaItem>, |
| 180 | } |
| 181 | |
| 182 | /// Pull custom derives and cfgs out of the given Attribute. |
| 183 | fn parse_attr(reg: &Registry, attr: Attribute) -> (Vec<Derive>, Vec<MetaItem>, Option<Attribute>) { |
| 184 | if attr.style != AttrStyle::Outer || attr.is_sugared_doc { |
| 185 | return (Vec::new(), Vec::new(), Some(attr)); |
| 186 | } |
| 187 | |
| 188 | let (name, nested) = match attr.value { |
| 189 | MetaItem::List(name, nested) => (name, nested), |
| 190 | _ => return (Vec::new(), Vec::new(), Some(attr)), |
| 191 | }; |
| 192 | |
| 193 | match name.as_ref() { |
| 194 | "derive" => { |
| 195 | let (derives, attr) = parse_derive_attr(reg, nested); |
| 196 | let derives = derives.into_iter() |
| 197 | .map(|d| { |
| 198 | Derive { |
| 199 | name: d, |
| 200 | cfg: None, |
| 201 | } |
| 202 | }) |
| 203 | .collect(); |
| 204 | (derives, Vec::new(), attr) |
| 205 | } |
| 206 | "cfg_attr" => { |
| 207 | let (derives, attr) = parse_cfg_attr(reg, nested); |
| 208 | (derives, Vec::new(), attr) |
| 209 | } |
| 210 | "cfg" => (Vec::new(), nested, None), |
| 211 | _ => { |
| 212 | // Rebuild the original attribute because it was destructured above |
| 213 | let attr = Attribute { |
| 214 | style: AttrStyle::Outer, |
| 215 | value: MetaItem::List(name, nested), |
| 216 | is_sugared_doc: false, |
| 217 | }; |
| 218 | (Vec::new(), Vec::new(), Some(attr)) |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /// Assuming the given nested meta-items came from a #[derive(...)] attribute, |
| 224 | /// pull out the ones that are custom derives. |
| 225 | fn parse_derive_attr(reg: &Registry, nested: Vec<MetaItem>) -> (Vec<Ident>, Option<Attribute>) { |
| 226 | let mut derives = Vec::new(); |
| 227 | |
| 228 | let remaining: Vec<_> = nested.into_iter() |
| 229 | .flat_map(|meta| { |
| 230 | let word = match meta { |
| 231 | MetaItem::Word(word) => word, |
| 232 | _ => return Some(meta), |
| 233 | }; |
| 234 | if reg.derives.contains_key(word.as_ref()) { |
| 235 | derives.push(word); |
| 236 | None |
| 237 | } else { |
| 238 | Some(MetaItem::Word(word)) |
| 239 | } |
| 240 | }) |
| 241 | .collect(); |
| 242 | |
| 243 | let attr = if remaining.is_empty() { |
| 244 | // Elide an empty #[derive()] |
| 245 | None |
| 246 | } else { |
| 247 | Some(Attribute { |
| 248 | style: AttrStyle::Outer, |
| 249 | value: MetaItem::List("derive".into(), remaining), |
| 250 | is_sugared_doc: false, |
| 251 | }) |
| 252 | }; |
| 253 | |
| 254 | (derives, attr) |
| 255 | } |
| 256 | |
| 257 | /// Assuming the given nested meta-items came from a #[cfg_attr(...)] attribute, |
| 258 | /// pull out any custom derives contained within. |
| 259 | fn parse_cfg_attr(reg: &Registry, nested: Vec<MetaItem>) -> (Vec<Derive>, Option<Attribute>) { |
| 260 | if nested.len() != 2 { |
| 261 | let attr = Attribute { |
| 262 | style: AttrStyle::Outer, |
| 263 | value: MetaItem::List("cfg_attr".into(), nested), |
| 264 | is_sugared_doc: false, |
| 265 | }; |
| 266 | return (Vec::new(), Some(attr)); |
| 267 | } |
| 268 | |
| 269 | let mut iter = nested.into_iter(); |
| 270 | let cfg = iter.next().unwrap(); |
| 271 | let arg = iter.next().unwrap(); |
| 272 | |
| 273 | let (name, nested) = match arg { |
| 274 | MetaItem::List(name, nested) => (name, nested), |
| 275 | _ => { |
| 276 | let attr = Attribute { |
| 277 | style: AttrStyle::Outer, |
| 278 | value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]), |
| 279 | is_sugared_doc: false, |
| 280 | }; |
| 281 | return (Vec::new(), Some(attr)); |
| 282 | } |
| 283 | }; |
| 284 | |
| 285 | if name == "derive" { |
| 286 | let (derives, attr) = parse_derive_attr(reg, nested); |
| 287 | let derives = derives.into_iter() |
| 288 | .map(|d| { |
| 289 | Derive { |
| 290 | name: d, |
| 291 | cfg: Some(cfg.clone()), |
| 292 | } |
| 293 | }) |
| 294 | .collect(); |
| 295 | let attr = attr.map(|attr| { |
| 296 | Attribute { |
| 297 | style: AttrStyle::Outer, |
| 298 | value: MetaItem::List("cfg_attr".into(), vec![cfg, attr.value]), |
| 299 | is_sugared_doc: false, |
| 300 | } |
| 301 | }); |
| 302 | (derives, attr) |
| 303 | } else { |
| 304 | let attr = Attribute { |
| 305 | style: AttrStyle::Outer, |
| 306 | value: MetaItem::List("cfg_attr".into(), vec![cfg, MetaItem::List(name, nested)]), |
| 307 | is_sugared_doc: false, |
| 308 | }; |
| 309 | (Vec::new(), Some(attr)) |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | /// Combine a list of cfg expressions into an attribute like `#[cfg(a)]` or |
| 314 | /// `#[cfg(all(a, b, c))]`, or nothing if there are no cfg expressions. |
| 315 | fn combine_cfgs(cfg: Vec<MetaItem>) -> Option<Attribute> { |
| 316 | // Flatten `all` cfgs so we don't nest `all` inside of `all`. |
David Tolnay | e8cb5f4 | 2016-10-23 22:26:20 -0700 | [diff] [blame] | 317 | let cfg: Vec<_> = cfg.into_iter() |
| 318 | .flat_map(|cfg| { |
| 319 | let (name, nested) = match cfg { |
| 320 | MetaItem::List(name, nested) => (name, nested), |
| 321 | _ => return vec![cfg], |
| 322 | }; |
| 323 | if name == "all" { |
| 324 | nested |
| 325 | } else { |
| 326 | vec![MetaItem::List(name, nested)] |
| 327 | } |
| 328 | }) |
| 329 | .collect(); |
David Tolnay | 453cfd1 | 2016-10-23 11:00:14 -0700 | [diff] [blame] | 330 | |
| 331 | let value = match cfg.len() { |
| 332 | 0 => return None, |
| 333 | 1 => cfg, |
| 334 | _ => vec![MetaItem::List("all".into(), cfg)], |
| 335 | }; |
| 336 | |
| 337 | Some(Attribute { |
| 338 | style: AttrStyle::Outer, |
| 339 | value: MetaItem::List("cfg".into(), value), |
| 340 | is_sugared_doc: false, |
| 341 | }) |
| 342 | } |
David Tolnay | 9e24d4d | 2016-10-23 22:17:27 -0700 | [diff] [blame] | 343 | |
| 344 | #[cfg(not(feature = "pretty"))] |
| 345 | fn pretty(tokens: Tokens) -> Result<String, String> { |
| 346 | Ok(tokens.to_string()) |
| 347 | } |
| 348 | |
| 349 | #[cfg(feature = "pretty")] |
| 350 | fn pretty(tokens: Tokens) -> Result<String, String> { |
| 351 | use syntax::parse::{self, ParseSess}; |
| 352 | use syntax::print::pprust; |
| 353 | |
| 354 | let name = "syn".to_string(); |
| 355 | let source = tokens.to_string(); |
| 356 | let cfg = Vec::new(); |
| 357 | let sess = ParseSess::new(); |
| 358 | let krate = match parse::parse_crate_from_source_str(name, source, cfg, &sess) { |
| 359 | Ok(krate) => krate, |
| 360 | Err(mut err) => { |
| 361 | err.emit(); |
| 362 | return Err("pretty printer failed to parse expanded code".into()); |
| 363 | } |
| 364 | }; |
| 365 | |
| 366 | if sess.span_diagnostic.has_errors() { |
| 367 | return Err("pretty printer failed to parse expanded code".into()); |
| 368 | } |
| 369 | |
| 370 | let mut reader = &tokens.to_string().into_bytes()[..]; |
| 371 | let mut writer = Vec::new(); |
| 372 | let ann = pprust::NoAnn; |
| 373 | |
| 374 | try!(pprust::print_crate( |
| 375 | sess.codemap(), |
| 376 | &sess.span_diagnostic, |
| 377 | &krate, |
| 378 | "".to_string(), |
| 379 | &mut reader, |
| 380 | Box::new(&mut writer), |
| 381 | &ann, |
| 382 | false).map_err(|err| err.to_string())); |
| 383 | |
| 384 | String::from_utf8(writer).map_err(|err| err.to_string()) |
| 385 | } |