forked from rust-lang/rust
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request rust-lang#3484 from topecongiro/config_derive
Add config_proc_macro
- Loading branch information
Showing
14 changed files
with
548 additions
and
187 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
target/ |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "config_proc_macro" | ||
version = "0.1.0" | ||
authors = ["topecongiro <seuchida@gmail.com>"] | ||
edition = "2018" | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
proc-macro2 = "0.4" | ||
quote = "0.6" | ||
syn = { version = "0.15", features = ["full", "visit"] } | ||
|
||
[dev-dependencies] | ||
serde = { version = "1.0", features = ["derive"] } | ||
|
||
[features] | ||
default = [] | ||
debug-with-rustfmt = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
//! This module provides utilities for handling attributes on variants | ||
//! of `config_type` enum. Currently there are two types of attributes | ||
//! that could appear on the variants of `config_type` enum: `doc_hint` | ||
//! and `value`. Both comes in the form of name-value pair whose value | ||
//! is string literal. | ||
|
||
/// Returns the value of the first `doc_hint` attribute in the given slice or | ||
/// `None` if `doc_hint` attribute is not available. | ||
pub fn find_doc_hint(attrs: &[syn::Attribute]) -> Option<String> { | ||
attrs.iter().filter_map(doc_hint).next() | ||
} | ||
|
||
/// Returns `true` if the given attribute is a `doc_hint` attribute. | ||
pub fn is_doc_hint(attr: &syn::Attribute) -> bool { | ||
is_attr_name_value(attr, "doc_hint") | ||
} | ||
|
||
/// Returns a string literal value if the given attribute is `doc_hint` | ||
/// attribute or `None` otherwise. | ||
pub fn doc_hint(attr: &syn::Attribute) -> Option<String> { | ||
get_name_value_str_lit(attr, "doc_hint") | ||
} | ||
|
||
/// Returns the value of the first `value` attribute in the given slice or | ||
/// `None` if `value` attribute is not available. | ||
pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> { | ||
attrs.iter().filter_map(config_value).next() | ||
} | ||
|
||
/// Returns a string literal value if the given attribute is `value` | ||
/// attribute or `None` otherwise. | ||
pub fn config_value(attr: &syn::Attribute) -> Option<String> { | ||
get_name_value_str_lit(attr, "value") | ||
} | ||
|
||
/// Returns `true` if the given attribute is a `value` attribute. | ||
pub fn is_config_value(attr: &syn::Attribute) -> bool { | ||
is_attr_name_value(attr, "value") | ||
} | ||
|
||
fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool { | ||
attr.interpret_meta().map_or(false, |meta| match meta { | ||
syn::Meta::NameValue(syn::MetaNameValue { ref ident, .. }) if ident == name => true, | ||
_ => false, | ||
}) | ||
} | ||
|
||
fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> { | ||
attr.interpret_meta().and_then(|meta| match meta { | ||
syn::Meta::NameValue(syn::MetaNameValue { | ||
ref ident, | ||
lit: syn::Lit::Str(ref lit_str), | ||
.. | ||
}) if ident == name => Some(lit_str.value()), | ||
_ => None, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use proc_macro2::TokenStream; | ||
|
||
use crate::item_enum::define_config_type_on_enum; | ||
use crate::item_struct::define_config_type_on_struct; | ||
|
||
/// Defines `config_type` on enum or struct. | ||
// FIXME: Implement this on struct. | ||
pub fn define_config_type(input: &syn::Item) -> TokenStream { | ||
match input { | ||
syn::Item::Struct(st) => define_config_type_on_struct(st), | ||
syn::Item::Enum(en) => define_config_type_on_enum(en), | ||
_ => panic!("Expected enum or struct"), | ||
} | ||
.unwrap() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
use proc_macro2::TokenStream; | ||
use quote::quote; | ||
|
||
use crate::attrs::*; | ||
use crate::utils::*; | ||
|
||
type Variants = syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>; | ||
|
||
/// Defines and implements `config_type` enum. | ||
pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result<TokenStream> { | ||
let syn::ItemEnum { | ||
vis, | ||
enum_token, | ||
ident, | ||
generics, | ||
variants, | ||
.. | ||
} = em; | ||
|
||
let mod_name_str = format!("__define_config_type_on_enum_{}", ident); | ||
let mod_name = syn::Ident::new(&mod_name_str, ident.span()); | ||
let variants = fold_quote(variants.iter().map(process_variant), |meta| quote!(#meta,)); | ||
|
||
let impl_doc_hint = impl_doc_hint(&em.ident, &em.variants); | ||
let impl_from_str = impl_from_str(&em.ident, &em.variants); | ||
let impl_serde = impl_serde(&em.ident, &em.variants); | ||
let impl_deserialize = impl_deserialize(&em.ident, &em.variants); | ||
|
||
Ok(quote! { | ||
#[allow(non_snake_case)] | ||
mod #mod_name { | ||
#[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
pub #enum_token #ident #generics { #variants } | ||
#impl_doc_hint | ||
#impl_from_str | ||
#impl_serde | ||
#impl_deserialize | ||
} | ||
#vis use #mod_name::#ident; | ||
}) | ||
} | ||
|
||
/// Remove attributes specific to `config_proc_macro` from enum variant fields. | ||
fn process_variant(variant: &syn::Variant) -> TokenStream { | ||
let metas = variant | ||
.attrs | ||
.iter() | ||
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr)); | ||
let attrs = fold_quote(metas, |meta| quote!(#meta)); | ||
let syn::Variant { ident, fields, .. } = variant; | ||
quote!(#attrs #ident #fields) | ||
} | ||
|
||
fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream { | ||
let doc_hint = variants | ||
.iter() | ||
.map(doc_hint_of_variant) | ||
.collect::<Vec<_>>() | ||
.join("|"); | ||
let doc_hint = format!("[{}]", doc_hint); | ||
quote! { | ||
use crate::config::ConfigType; | ||
impl ConfigType for #ident { | ||
fn doc_hint() -> String { | ||
#doc_hint.to_owned() | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream { | ||
let vs = variants | ||
.iter() | ||
.filter(|v| is_unit(v)) | ||
.map(|v| (config_value_of_variant(v), &v.ident)); | ||
let if_patterns = fold_quote(vs, |(s, v)| { | ||
quote! { | ||
if #s.eq_ignore_ascii_case(s) { | ||
return Ok(#ident::#v); | ||
} | ||
} | ||
}); | ||
quote! { | ||
impl ::std::str::FromStr for #ident { | ||
type Err = &'static str; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
#if_patterns | ||
return Err("Bad variant"); | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn doc_hint_of_variant(variant: &syn::Variant) -> String { | ||
find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string()) | ||
} | ||
|
||
fn config_value_of_variant(variant: &syn::Variant) -> String { | ||
find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string()) | ||
} | ||
|
||
fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream { | ||
let arms = fold_quote(variants.iter(), |v| { | ||
let v_ident = &v.ident; | ||
let pattern = match v.fields { | ||
syn::Fields::Named(..) => quote!(#ident::v_ident{..}), | ||
syn::Fields::Unnamed(..) => quote!(#ident::#v_ident(..)), | ||
syn::Fields::Unit => quote!(#ident::#v_ident), | ||
}; | ||
let option_value = config_value_of_variant(v); | ||
quote! { | ||
#pattern => serializer.serialize_str(&#option_value), | ||
} | ||
}); | ||
|
||
quote! { | ||
impl ::serde::ser::Serialize for #ident { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: ::serde::ser::Serializer, | ||
{ | ||
use serde::ser::Error; | ||
match self { | ||
#arms | ||
_ => Err(S::Error::custom(format!("Cannot serialize {:?}", self))), | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
// Currently only unit variants are supported. | ||
fn impl_deserialize(ident: &syn::Ident, variants: &Variants) -> TokenStream { | ||
let supported_vs = variants.iter().filter(|v| is_unit(v)); | ||
let if_patterns = fold_quote(supported_vs, |v| { | ||
let config_value = config_value_of_variant(v); | ||
let variant_ident = &v.ident; | ||
quote! { | ||
if #config_value.eq_ignore_ascii_case(s) { | ||
return Ok(#ident::#variant_ident); | ||
} | ||
} | ||
}); | ||
|
||
let supported_vs = variants.iter().filter(|v| is_unit(v)); | ||
let allowed = fold_quote(supported_vs.map(config_value_of_variant), |s| quote!(#s,)); | ||
|
||
quote! { | ||
impl<'de> serde::de::Deserialize<'de> for #ident { | ||
fn deserialize<D>(d: D) -> Result<Self, D::Error> | ||
where | ||
D: serde::Deserializer<'de>, | ||
{ | ||
use serde::de::{Error, Visitor}; | ||
use std::marker::PhantomData; | ||
use std::fmt; | ||
struct StringOnly<T>(PhantomData<T>); | ||
impl<'de, T> Visitor<'de> for StringOnly<T> | ||
where T: serde::Deserializer<'de> { | ||
type Value = String; | ||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
formatter.write_str("string") | ||
} | ||
fn visit_str<E>(self, value: &str) -> Result<String, E> { | ||
Ok(String::from(value)) | ||
} | ||
} | ||
let s = &d.deserialize_string(StringOnly::<D>(PhantomData))?; | ||
|
||
#if_patterns | ||
|
||
static ALLOWED: &'static[&str] = &[#allowed]; | ||
Err(D::Error::unknown_variant(&s, ALLOWED)) | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
use proc_macro2::TokenStream; | ||
|
||
pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result<TokenStream> { | ||
unimplemented!() | ||
} |
Oops, something went wrong.