Skip to content

Commit

Permalink
feat(syntex): Squash #[derive_X]#[derive_Y] into #[derive(X,Y)]
Browse files Browse the repository at this point in the history
  • Loading branch information
erickt committed Apr 4, 2016
1 parent ed1a9ad commit 5b4d758
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 1 deletion.
2 changes: 1 addition & 1 deletion syntex/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "syntex"
version = "0.30.0"
version = "0.30.1"
authors = [ "erick.tryzelaar@gmail.com" ]
license = "MIT/Apache-2.0"
description = "A library that enables compile time syntax extension expansion"
Expand Down
3 changes: 3 additions & 0 deletions syntex/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
extern crate syntex_syntax;

mod squash_derive;

use std::fs::File;
use std::io::{self, Write};
use std::path::Path;
Expand Down Expand Up @@ -147,6 +149,7 @@ impl Registry {
let ecx = ExtCtxt::new(&sess, cfg, ecfg, &mut gated_cfgs);

let (krate, _) = expand::expand_crate(ecx, self.macros, self.syntax_exts, krate);
let krate = squash_derive::squash_derive(krate);

let krate = self.post_expansion_passes.iter()
.fold(krate, |krate, f| (f)(krate));
Expand Down
219 changes: 219 additions & 0 deletions syntex/src/squash_derive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/// This crate exposes a simple folder that squashes:
///
/// ```rust,ignore
/// #[derive_Foo]
/// #[derive_Bar]
/// struct Baz;
/// ```
///
/// Into:
///
/// ```rust,ignore
/// #[derive(Foo, Bar)]
/// struct Baz;
/// ```

use syntex_syntax::ast;
use syntex_syntax::codemap::{Span, Spanned};
use syntex_syntax::fold::{self, Folder};
use syntex_syntax::parse::token;
use syntex_syntax::ptr::P;
use syntex_syntax::util::move_map::MoveMap;

/// Squash all the `#[derive_*]` into `#[derive(*)]` together.
pub fn squash_derive(krate: ast::Crate) -> ast::Crate {
SquashDeriveFolder.fold_crate(krate)
}

struct SquashDeriveFolder;

impl Folder for SquashDeriveFolder {
fn fold_item_simple(&mut self, mut item: ast::Item) -> ast::Item {
let mut attr_folder = SquashDeriveAttrFolder { derive_attr: None };
item.attrs = item.attrs.move_flat_map(|x| attr_folder.fold_attribute(x));

if let Some(derive_attr) = attr_folder.into_attr() {
item.attrs.push(derive_attr);
}

fold::noop_fold_item_simple(item, self)
}

fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
fold::noop_fold_mac(mac, self)
}
}

struct SquashDeriveAttrFolder {
derive_attr: Option<DeriveAttr>,
}

impl SquashDeriveAttrFolder {
fn into_attr(self) -> Option<ast::Attribute> {
match self.derive_attr {
Some(derive_attr) => {
let meta_item = ast::MetaItemKind::List(
token::intern_and_get_ident("derive"),
derive_attr.meta_items,
);

let meta_item = Spanned {
node: meta_item,
span: derive_attr.span,
};

Some(Spanned {
node: ast::Attribute_ {
id: derive_attr.id,
style: derive_attr.style,
value: P(meta_item),
is_sugared_doc: derive_attr.is_sugared_doc,
},
span: derive_attr.span,
})
}
None => None,
}
}
}

struct DeriveAttr {
id: ast::AttrId,
style: ast::AttrStyle,
is_sugared_doc: bool,
meta_items: Vec<P<ast::MetaItem>>,
span: Span,
}

impl Folder for SquashDeriveAttrFolder {
fn fold_attribute(&mut self,
Spanned {
node: ast::Attribute_ { id, style, value, is_sugared_doc },
span,
}: ast::Attribute) -> Option<ast::Attribute> {
match value.node {
ast::MetaItemKind::Word(ref name) if name.starts_with("derive_") => {
let (_, derive_name) = name.split_at("derive_".len());
let derive_name = token::intern_and_get_ident(derive_name);

let meta_word = P(Spanned {
node: ast::MetaItemKind::Word(derive_name),
span: value.span,
});

match self.derive_attr {
Some(ref mut derive_attr) => {
derive_attr.meta_items.push(meta_word);
}
None => {
self.derive_attr = Some(DeriveAttr {
id: id,
style: style,
is_sugared_doc: is_sugared_doc,
meta_items: vec![meta_word],
span: value.span,
});
}
}

return None;
}
_ => { }
}

Some(Spanned {
node: ast::Attribute_ {
id: id,
style: style,
value: value,
is_sugared_doc: is_sugared_doc,
},
span: span,
})
}
}

#[cfg(test)]
mod tests {
use syntex_syntax::ast;
use syntex_syntax::codemap::{DUMMY_SP, Spanned};
use syntex_syntax::fold::Folder;
use syntex_syntax::parse::token;
use syntex_syntax::ptr::P;

fn mk_meta_word(name: &str) -> P<ast::MetaItem> {
let name = token::intern_and_get_ident(name);

P(Spanned {
node: ast::MetaItemKind::Word(name),
span: DUMMY_SP,
})
}

fn mk_meta_list(name: &str,
meta_items: Vec<P<ast::MetaItem>>) -> P<ast::MetaItem> {
let name = token::intern_and_get_ident(name);

P(Spanned {
node: ast::MetaItemKind::List(name, meta_items),
span: DUMMY_SP,
})
}

fn mk_attr(meta_item: P<ast::MetaItem>) -> ast::Attribute {
Spanned {
node: ast::Attribute_ {
id: ast::AttrId(0),
style: ast::AttrStyle::Outer,
value: meta_item,
is_sugared_doc: false,
},
span: DUMMY_SP,
}
}

#[test]
fn test_squash() {
let variant_data = ast::VariantData::Unit(ast::DUMMY_NODE_ID);

let generics = ast::Generics {
lifetimes: vec![],
ty_params: P::empty(),
where_clause: ast::WhereClause {
id: ast::DUMMY_NODE_ID,
predicates: vec![],
},
};

let item_kind = ast::ItemKind::Struct(variant_data, generics);

let item = ast::Item {
id: ast::DUMMY_NODE_ID,
ident: token::str_to_ident("Foo"),
attrs: vec![
mk_attr(mk_meta_word("derive_A")),
mk_attr(mk_meta_word("derive_B")),
],
node: item_kind.clone(),
vis: ast::Visibility::Inherited,
span: DUMMY_SP,
};

assert_eq!(
super::SquashDeriveFolder.fold_item_simple(item.clone()),
ast::Item {
id: ast::DUMMY_NODE_ID,
ident: token::str_to_ident("Foo"),
attrs: vec![
mk_attr(mk_meta_list(
"derive",
vec![mk_meta_word("A"), mk_meta_word("B")],
)),
],
node: item_kind,
vis: ast::Visibility::Inherited,
span: DUMMY_SP,
}
);
}
}

0 comments on commit 5b4d758

Please sign in to comment.