From 48088d2a0ed8d2f927f16eb8987d7f08d4c6c1c7 Mon Sep 17 00:00:00 2001 From: David Tolnay Date: Mon, 23 Jan 2017 00:33:04 -0800 Subject: [PATCH] Drop the expander registry --- Cargo.toml | 3 - README.md | 56 ------- src/lib.rs | 8 - src/registry.rs | 392 ------------------------------------------- tests/test_expand.rs | 138 --------------- 5 files changed, 597 deletions(-) delete mode 100644 src/registry.rs delete mode 100644 tests/test_expand.rs diff --git a/Cargo.toml b/Cargo.toml index 39c18447..1ff5b723 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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] diff --git a/README.md b/README.md index 2a68fdf3..27630648 100644 --- a/README.md +++ b/README.md @@ -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(src: S, dst: D) -> Result<(), String> - where S: AsRef, - D: AsRef -{ - 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 diff --git a/src/lib.rs b/src/lib.rs index 896e9990..80043f46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/registry.rs b/src/registry.rs deleted file mode 100644 index 24e1c6b1..00000000 --- a/src/registry.rs +++ /dev/null @@ -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; -} - -/// Produced by expanding a custom derive. -pub struct Expanded { - /// The items (typically `impl` items) constructed by the custom derive. - pub new_items: Vec, - /// 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, -} - -/// 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>, -} - -impl CustomDerive for T - where T: Fn(MacroInput) -> Result -{ - fn expand(&self, input: MacroInput) -> Result { - self(input) - } -} - -impl<'a> Registry<'a> { - pub fn new() -> Self { - Default::default() - } - - /// Register a custom derive. A `fn(MacroInput) -> Result` - /// may be used as a custom derive. - /// - /// ```ignore - /// registry.add_derive("Serialize", expand_serialize); - /// ``` - pub fn add_derive(&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(&self, src: S, dst: D) -> Result<(), String> - where S: AsRef, - D: AsRef - { - // 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 { - 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, - out: &mut Vec) - -> 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, - out: &mut Vec) - -> 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, -} - -/// Pull custom derives and cfgs out of the given Attribute. -fn parse_attr(reg: &Registry, - attr: Attribute) - -> (Vec, Vec, Option) { - 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) - -> (Vec, Option) { - 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) -> (Vec, Option) { - 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) -> Option { - // 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 { - Ok(tokens.to_string()) -} - -#[cfg(feature = "pretty")] -fn pretty(tokens: Tokens) -> Result { - 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()) -} diff --git a/tests/test_expand.rs b/tests/test_expand.rs deleted file mode 100644 index dc351ef1..00000000 --- a/tests/test_expand.rs +++ /dev/null @@ -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 { - 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 { - 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), - }) -}