blob: 24e1c6b1c087389b0f0e5805b84b034c3b3a9934 [file] [log] [blame]
use super::{Attribute, AttrStyle, Body, Crate, Ident, Item, ItemKind, MacroInput, MetaItem,
NestedMetaItem};
use quote::Tokens;
use std::collections::BTreeMap as Map;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;
/// Implementation of a custom derive. Custom derives take a struct or enum and
/// expand it into zero or more items, typically `impl` items.
pub trait CustomDerive {
/// Expand the given struct or enum. If this custom derive modifies the
/// input item or preserves it unmodified, it must be returned back in the
/// `original` field of Expanded. The custom derive may discard the input
/// item by setting `original` to None.
fn expand(&self, input: MacroInput) -> Result<Expanded, String>;
}
/// Produced by expanding a custom derive.
pub struct Expanded {
/// The items (typically `impl` items) constructed by the custom derive.
pub new_items: Vec<Item>,
/// The input to the custom derive, whether modified or unmodified. If the
/// custom derive discards the input item it may do so by setting `original`
/// to None.
pub original: Option<MacroInput>,
}
/// Registry of custom derives. Callers add custom derives to a registry, then
/// use the registry to expand those derives in a source file.
#[derive(Default)]
pub struct Registry<'a> {
derives: Map<String, Box<CustomDerive + 'a>>,
}
impl<T> CustomDerive for T
where T: Fn(MacroInput) -> Result<Expanded, String>
{
fn expand(&self, input: MacroInput) -> Result<Expanded, String> {
self(input)
}
}
impl<'a> Registry<'a> {
pub fn new() -> Self {
Default::default()
}
/// Register a custom derive. A `fn(MacroInput) -> Result<Expanded, String>`
/// may be used as a custom derive.
///
/// ```ignore
/// registry.add_derive("Serialize", expand_serialize);
/// ```
pub fn add_derive<T>(&mut self, name: &str, derive: T)
where T: CustomDerive + 'a
{
self.derives.insert(name.into(), Box::new(derive));
}
/// Read Rust source code from the `src` file, expand the custom derives
/// that have been registered, and write the result to the `dst` file.
pub fn expand_file<S, D>(&self, src: S, dst: D) -> Result<(), String>
where S: AsRef<Path>,
D: AsRef<Path>
{
// Open the src file
let mut src = match File::open(src) {
Ok(open) => open,
Err(err) => return Err(err.to_string()),
};
// Read the contents of the src file to a String
let mut content = String::new();
if let Err(err) = src.read_to_string(&mut content) {
return Err(err.to_string());
}
// Parse the contents
let krate = try!(super::parse_crate(&content));
// Expand
let expanded = try!(expand_crate(self, krate));
// Print the expanded code to a String
let out = try!(pretty(quote!(#expanded)));
// Create or truncate the dst file, opening in write-only mode
let mut dst = match File::create(dst) {
Ok(create) => create,
Err(err) => return Err(err.to_string()),
};
// Write expanded code to the dst file
if let Err(err) = dst.write_all(out.as_bytes()) {
return Err(err.to_string());
}
Ok(())
}
}
fn expand_crate(reg: &Registry, krate: Crate) -> Result<Crate, String> {
let mut items = Vec::new();
for item in krate.items {
try!(expand_item(reg, item, Vec::new(), &mut items));
}
Ok(Crate { items: items, ..krate })
}
fn expand_item(reg: &Registry,
mut item: Item,
cfg: Vec<NestedMetaItem>,
out: &mut Vec<Item>)
-> Result<(), String> {
let (body, generics) = match item.node {
ItemKind::Enum(variants, generics) => (Body::Enum(variants), generics),
ItemKind::Struct(variant_data, generics) => (Body::Struct(variant_data), generics),
_ => {
// Custom derives cannot apply to this item, preserve it unmodified
item.attrs.extend(combine_cfgs(cfg));
out.push(item);
return Ok(());
}
};
let macro_input = MacroInput {
ident: item.ident,
vis: item.vis,
attrs: item.attrs,
generics: generics,
body: body,
};
expand_macro_input(reg, macro_input, cfg, out)
}
fn expand_macro_input(reg: &Registry,
mut input: MacroInput,
inherited_cfg: Vec<NestedMetaItem>,
out: &mut Vec<Item>)
-> Result<(), String> {
let mut derives = Vec::new();
let mut all_cfg = inherited_cfg;
// Find custom derives on this item, removing them from the input
input.attrs = input.attrs
.into_iter()
.flat_map(|attr| {
let (new_derives, cfg, attr) = parse_attr(reg, attr);
derives.extend(new_derives);
all_cfg.extend(cfg);
attr
})
.collect();
// Expand each custom derive
for derive in derives {
let expanded = try!(reg.derives[derive.name.as_ref()].expand(input));
for new_item in expanded.new_items {
let mut extended_cfg = all_cfg.clone();
extended_cfg.extend(derive.cfg.clone());
try!(expand_item(reg, new_item, extended_cfg, out));
}
input = match expanded.original {
Some(input) => input,
None => return Ok(()),
};
}
input.attrs.extend(combine_cfgs(all_cfg));
out.push(input.into());
Ok(())
}
struct Derive {
name: Ident,
/// If the custom derive was behind a cfg_attr
cfg: Option<NestedMetaItem>,
}
/// Pull custom derives and cfgs out of the given Attribute.
fn parse_attr(reg: &Registry,
attr: Attribute)
-> (Vec<Derive>, Vec<NestedMetaItem>, Option<Attribute>) {
if attr.style != AttrStyle::Outer || attr.is_sugared_doc {
return (Vec::new(), Vec::new(), Some(attr));
}
let (name, nested) = match attr.value {
MetaItem::List(name, nested) => (name, nested),
_ => return (Vec::new(), Vec::new(), Some(attr)),
};
match name.as_ref() {
"derive" => {
let (derives, attr) = parse_derive_attr(reg, nested);
let derives = derives.into_iter()
.map(|d| {
Derive {
name: d,
cfg: None,
}
})
.collect();
(derives, Vec::new(), attr)
}
"cfg_attr" => {
let (derives, attr) = parse_cfg_attr(reg, nested);
(derives, Vec::new(), attr)
}
"cfg" => (Vec::new(), nested, None),
_ => {
// Rebuild the original attribute because it was destructured above
let attr = Attribute {
style: AttrStyle::Outer,
value: MetaItem::List(name, nested),
is_sugared_doc: false,
};
(Vec::new(), Vec::new(), Some(attr))
}
}
}
/// Assuming the given nested meta-items came from a #[derive(...)] attribute,
/// pull out the ones that are custom derives.
fn parse_derive_attr(reg: &Registry,
nested: Vec<NestedMetaItem>)
-> (Vec<Ident>, Option<Attribute>) {
let mut derives = Vec::new();
let remaining: Vec<_> = nested.into_iter()
.flat_map(|meta| {
let word = match meta {
NestedMetaItem::MetaItem(MetaItem::Word(word)) => word,
_ => return Some(meta),
};
if reg.derives.contains_key(word.as_ref()) {
derives.push(word);
None
} else {
Some(NestedMetaItem::MetaItem(MetaItem::Word(word)))
}
})
.collect();
let attr = if remaining.is_empty() {
// Elide an empty #[derive()]
None
} else {
Some(Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("derive".into(), remaining),
is_sugared_doc: false,
})
};
(derives, attr)
}
/// Assuming the given nested meta-items came from a #[cfg_attr(...)] attribute,
/// pull out any custom derives contained within.
fn parse_cfg_attr(reg: &Registry, nested: Vec<NestedMetaItem>) -> (Vec<Derive>, Option<Attribute>) {
if nested.len() != 2 {
let attr = Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg_attr".into(), nested),
is_sugared_doc: false,
};
return (Vec::new(), Some(attr));
}
let mut iter = nested.into_iter();
let cfg = iter.next().unwrap();
let arg = iter.next().unwrap();
let (name, nested) = match arg {
NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
_ => {
let attr = Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg_attr".into(), vec![cfg, arg]),
is_sugared_doc: false,
};
return (Vec::new(), Some(attr));
}
};
if name == "derive" {
let (derives, attr) = parse_derive_attr(reg, nested);
let derives = derives.into_iter()
.map(|d| {
Derive {
name: d,
cfg: Some(cfg.clone()),
}
})
.collect();
let attr = attr.map(|attr| {
Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg_attr".into(),
vec![cfg, NestedMetaItem::MetaItem(attr.value)]),
is_sugared_doc: false,
}
});
(derives, attr)
} else {
let attr = Attribute {
style: AttrStyle::Outer,
value:
MetaItem::List("cfg_attr".into(),
vec![cfg, NestedMetaItem::MetaItem(MetaItem::List(name, nested))]),
is_sugared_doc: false,
};
(Vec::new(), Some(attr))
}
}
/// Combine a list of cfg expressions into an attribute like `#[cfg(a)]` or
/// `#[cfg(all(a, b, c))]`, or nothing if there are no cfg expressions.
fn combine_cfgs(cfg: Vec<NestedMetaItem>) -> Option<Attribute> {
// Flatten `all` cfgs so we don't nest `all` inside of `all`.
let cfg: Vec<_> = cfg.into_iter()
.flat_map(|cfg| {
let (name, nested) = match cfg {
NestedMetaItem::MetaItem(MetaItem::List(name, nested)) => (name, nested),
_ => return vec![cfg],
};
if name == "all" {
nested
} else {
vec![NestedMetaItem::MetaItem(MetaItem::List(name, nested))]
}
})
.collect();
let value = match cfg.len() {
0 => return None,
1 => cfg,
_ => vec![NestedMetaItem::MetaItem(MetaItem::List("all".into(), cfg))],
};
Some(Attribute {
style: AttrStyle::Outer,
value: MetaItem::List("cfg".into(), value),
is_sugared_doc: false,
})
}
#[cfg(not(feature = "pretty"))]
fn pretty(tokens: Tokens) -> Result<String, String> {
Ok(tokens.to_string())
}
#[cfg(feature = "pretty")]
fn pretty(tokens: Tokens) -> Result<String, String> {
use syntax::parse::{self, ParseSess};
use syntax::print::pprust;
let name = "syn".to_string();
let source = tokens.to_string();
let sess = ParseSess::new();
let krate = match parse::parse_crate_from_source_str(name, source, &sess) {
Ok(krate) => krate,
Err(mut err) => {
err.emit();
return Err("pretty printer failed to parse expanded code".into());
}
};
if sess.span_diagnostic.has_errors() {
return Err("pretty printer failed to parse expanded code".into());
}
let mut reader = &tokens.to_string().into_bytes()[..];
let mut writer = Vec::new();
let ann = pprust::NoAnn;
try!(pprust::print_crate(
sess.codemap(),
&sess.span_diagnostic,
&krate,
"".to_string(),
&mut reader,
Box::new(&mut writer),
&ann,
false).map_err(|err| err.to_string()));
String::from_utf8(writer).map_err(|err| err.to_string())
}