Drop the expander registry

This commit is contained in:
David Tolnay 2017-01-23 00:33:04 -08:00
parent a6ac181ae7
commit 48088d2a0e
No known key found for this signature in database
GPG Key ID: F9BA143B95FF6D82
5 changed files with 0 additions and 597 deletions

View File

@ -11,10 +11,8 @@ include = ["Cargo.toml", "src/**/*.rs"]
[features]
default = ["parsing", "printing"]
aster = []
expand = ["full", "parsing", "printing"]
full = ["type-macros"]
parsing = ["unicode-xid"]
pretty = ["syntex_syntax"]
printing = ["quote"]
type-macros = []
visit = []
@ -22,7 +20,6 @@ visit = []
[dependencies]
clippy = { version = "0.*", optional = true }
quote = { version = "0.3.0", optional = true }
syntex_syntax = { version = "0.52.0", optional = true }
unicode-xid = { version = "0.0.3", optional = true }
[dev-dependencies]

View File

@ -182,62 +182,6 @@ full | 2 sec | The data structures representing the full AST of all possible Rus
full, parsing | 7 sec | Parsing any valid Rust source code to an AST.
full, printing | 4 sec | Turning an AST into Rust source code.
full, parsing, printing | 8 sec | Parsing and printing any Rust syntax.
full, parsing, printing, expand | 9 sec | Expansion of custom derives in a file of Rust code. This is typically what you want for expanding custom derives on stable Rust using a build script.
full, parsing, printing, expand, pretty | 60 sec | Expansion of custom derives with pretty-printed output. This is what you want when iterating on or debugging a custom derive, but the pretty printing should be disabled once you get everything working.
## Custom derives on stable Rust
Syn supports a way of expanding custom derives from a build script, similar to
what [Serde is able to do with serde_codegen](https://serde.rs/codegen-stable.html).
The advantage of using Syn for this purpose rather than Syntex is much faster
compile time.
Continuing with the `NumFields` example from above, it can be extended to
support stable Rust like this. One or more custom derives are added to a
[`Registry`](https://dtolnay.github.io/syn/syn/struct.Registry.html), which is
then able to expand those derives in a source file at a particular path and
write the expanded output to a different path. A custom derive is represented by
the [`CustomDerive`](https://dtolnay.github.io/syn/syn/trait.CustomDerive.html)
trait which takes a [`MacroInput`](https://dtolnay.github.io/syn/syn/struct.MacroInput.html)
(either a struct or an enum) and [expands it](https://dtolnay.github.io/syn/syn/struct.Expanded.html)
into zero or more new items and maybe a modified or unmodified instance of the
original input.
```rust
pub fn expand_file<S, D>(src: S, dst: D) -> Result<(), String>
where S: AsRef<Path>,
D: AsRef<Path>
{
let mut registry = syn::Registry::new();
registry.add_derive("NumFields", |input| {
let tokens = expand_num_fields(&input);
let items = syn::parse_items(&tokens.to_string()).unwrap();
Ok(syn::Expanded {
new_items: items,
original: Some(input),
})
});
registry.expand_file(src, dst)
}
```
The codegen can be invoked from a build script as follows.
```rust
extern crate your_codegen;
use std::env;
use std::path::Path;
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap();
let src = Path::new("src/codegen_types.in.rs");
let dst = Path::new(&out_dir).join("codegen_types.rs");
your_codegen::expand_file(&src, &dst).unwrap();
}
```
## License

View File

@ -5,9 +5,6 @@
#[macro_use]
extern crate quote;
#[cfg(feature = "pretty")]
extern crate syntex_syntax as syntax;
#[cfg(feature = "parsing")]
extern crate unicode_xid;
@ -76,11 +73,6 @@ pub use macro_input::{Body, MacroInput};
mod op;
pub use op::{BinOp, UnOp};
#[cfg(feature = "expand")]
mod registry;
#[cfg(feature = "expand")]
pub use registry::{CustomDerive, Expanded, Registry};
#[cfg(feature = "parsing")]
mod space;

View File

@ -1,392 +0,0 @@
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())
}

View File

@ -1,138 +0,0 @@
#![cfg(feature = "expand")]
extern crate syn;
use syn::*;
#[macro_use]
extern crate quote;
use quote::Tokens;
extern crate tempdir;
use tempdir::TempDir;
use std::fs::File;
use std::io::{Read, Write};
#[test]
fn test_cfg() {
let original = quote! {
use super::*;
#[derive(A)]
struct P;
#[cfg_attr(feature = "q", derive(A))]
struct Q;
#[derive(A)]
#[cfg(feature = "r")]
struct R;
#[cfg(feature = "s1")]
#[cfg(all(feature = "s2", feature = "s3"))]
#[cfg_attr(feature = "s4", derive(A))]
struct S;
};
let expected = quote! {
// Unmodified from the input
use super::*;
type P = ();
#[cfg(feature = "q")]
type Q = ();
#[cfg(feature = "r")]
type R = ();
#[cfg(all(feature = "s1", feature = "s2", feature = "s3", feature = "s4"))]
type S = ();
};
test_expand(original, expected);
}
#[test]
fn test_recursive() {
let original = quote! {
#[d]
#[cfg_attr(feature = "f", derive(Copy, B, Clone))]
#[e]
#[cfg(feature = "e")]
struct T;
};
let expected = quote! {
// From #[derive(A)] on struct S produced by #[derive(B)]
#[cfg(all(feature = "e", feature = "f", feature = "g"))]
type S = ();
// From #[derive(B)] on struct T
#[cfg(all(feature = "e", feature = "f"))]
impl B for T {}
// From the input
#[d]
#[cfg_attr(feature = "f", derive(Copy, Clone))]
#[e]
#[cfg(feature = "e")]
struct T;
};
test_expand(original, expected);
}
fn test_expand(original: Tokens, expected: Tokens) {
let dir = TempDir::new("syn").expect("create temp dir");
let src_path = dir.path().join("expand.in.rs");
let dst_path = dir.path().join("expand.rs");
// Write the src file
let mut src_file = File::create(&src_path).expect("create temp file");
src_file.write_all(original.to_string().as_bytes()).expect("write temp file");
// Run expansion
let mut registry = Registry::new();
registry.add_derive("A", expand_a);
registry.add_derive("B", expand_b);
registry.expand_file(&src_path, &dst_path).unwrap();
// Read the dst file
let mut expanded = String::new();
let mut dst_file = File::open(&dst_path).expect("open output file");
dst_file.read_to_string(&mut expanded).expect("read output file");
let krate = parse_crate(&expanded).expect("parse output file");
assert_eq!(quote!(#krate), expected);
}
fn expand_a(input: MacroInput) -> Result<Expanded, String> {
let name = &input.ident;
let out = quote! {
type #name = ();
};
Ok(Expanded {
new_items: parse_items(&out.to_string()).unwrap(),
original: None,
})
}
fn expand_b(input: MacroInput) -> Result<Expanded, String> {
assert_eq!(quote!(#input), quote! {
#[d]
#[cfg_attr(feature = "f", derive(Copy, Clone))]
#[e]
struct T;
});
let out = quote! {
#[cfg_attr(feature = "g", derive(A))]
struct S;
impl B for T {}
};
Ok(Expanded {
new_items: parse_items(&out.to_string()).unwrap(),
original: Some(input),
})
}