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