Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit d3ec460

Browse files
authored
Merge pull request rust-lang#3484 from topecongiro/config_derive
Add config_proc_macro
2 parents aa53d2d + 3dec18a commit d3ec460

File tree

14 files changed

+548
-187
lines changed

14 files changed

+548
-187
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ dirs = "1.0.4"
6060
ignore = "0.4.6"
6161
annotate-snippets = { version = "0.5.0", features = ["ansi_term"] }
6262

63+
config_proc_macro = { path = "config_proc_macro" }
64+
6365
# A noop dependency that changes in the Rust repository, it's a bit of a hack.
6466
# See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust`
6567
# for more information.

config_proc_macro/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target/

config_proc_macro/Cargo.lock

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config_proc_macro/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "config_proc_macro"
3+
version = "0.1.0"
4+
authors = ["topecongiro <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
proc-macro = true
9+
10+
[dependencies]
11+
proc-macro2 = "0.4"
12+
quote = "0.6"
13+
syn = { version = "0.15", features = ["full", "visit"] }
14+
15+
[dev-dependencies]
16+
serde = { version = "1.0", features = ["derive"] }
17+
18+
[features]
19+
default = []
20+
debug-with-rustfmt = []

config_proc_macro/src/attrs.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//! This module provides utilities for handling attributes on variants
2+
//! of `config_type` enum. Currently there are two types of attributes
3+
//! that could appear on the variants of `config_type` enum: `doc_hint`
4+
//! and `value`. Both comes in the form of name-value pair whose value
5+
//! is string literal.
6+
7+
/// Returns the value of the first `doc_hint` attribute in the given slice or
8+
/// `None` if `doc_hint` attribute is not available.
9+
pub fn find_doc_hint(attrs: &[syn::Attribute]) -> Option<String> {
10+
attrs.iter().filter_map(doc_hint).next()
11+
}
12+
13+
/// Returns `true` if the given attribute is a `doc_hint` attribute.
14+
pub fn is_doc_hint(attr: &syn::Attribute) -> bool {
15+
is_attr_name_value(attr, "doc_hint")
16+
}
17+
18+
/// Returns a string literal value if the given attribute is `doc_hint`
19+
/// attribute or `None` otherwise.
20+
pub fn doc_hint(attr: &syn::Attribute) -> Option<String> {
21+
get_name_value_str_lit(attr, "doc_hint")
22+
}
23+
24+
/// Returns the value of the first `value` attribute in the given slice or
25+
/// `None` if `value` attribute is not available.
26+
pub fn find_config_value(attrs: &[syn::Attribute]) -> Option<String> {
27+
attrs.iter().filter_map(config_value).next()
28+
}
29+
30+
/// Returns a string literal value if the given attribute is `value`
31+
/// attribute or `None` otherwise.
32+
pub fn config_value(attr: &syn::Attribute) -> Option<String> {
33+
get_name_value_str_lit(attr, "value")
34+
}
35+
36+
/// Returns `true` if the given attribute is a `value` attribute.
37+
pub fn is_config_value(attr: &syn::Attribute) -> bool {
38+
is_attr_name_value(attr, "value")
39+
}
40+
41+
fn is_attr_name_value(attr: &syn::Attribute, name: &str) -> bool {
42+
attr.interpret_meta().map_or(false, |meta| match meta {
43+
syn::Meta::NameValue(syn::MetaNameValue { ref ident, .. }) if ident == name => true,
44+
_ => false,
45+
})
46+
}
47+
48+
fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> {
49+
attr.interpret_meta().and_then(|meta| match meta {
50+
syn::Meta::NameValue(syn::MetaNameValue {
51+
ref ident,
52+
lit: syn::Lit::Str(ref lit_str),
53+
..
54+
}) if ident == name => Some(lit_str.value()),
55+
_ => None,
56+
})
57+
}

config_proc_macro/src/config_type.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use proc_macro2::TokenStream;
2+
3+
use crate::item_enum::define_config_type_on_enum;
4+
use crate::item_struct::define_config_type_on_struct;
5+
6+
/// Defines `config_type` on enum or struct.
7+
// FIXME: Implement this on struct.
8+
pub fn define_config_type(input: &syn::Item) -> TokenStream {
9+
match input {
10+
syn::Item::Struct(st) => define_config_type_on_struct(st),
11+
syn::Item::Enum(en) => define_config_type_on_enum(en),
12+
_ => panic!("Expected enum or struct"),
13+
}
14+
.unwrap()
15+
}

config_proc_macro/src/item_enum.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
use proc_macro2::TokenStream;
2+
use quote::quote;
3+
4+
use crate::attrs::*;
5+
use crate::utils::*;
6+
7+
type Variants = syn::punctuated::Punctuated<syn::Variant, syn::Token![,]>;
8+
9+
/// Defines and implements `config_type` enum.
10+
pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result<TokenStream> {
11+
let syn::ItemEnum {
12+
vis,
13+
enum_token,
14+
ident,
15+
generics,
16+
variants,
17+
..
18+
} = em;
19+
20+
let mod_name_str = format!("__define_config_type_on_enum_{}", ident);
21+
let mod_name = syn::Ident::new(&mod_name_str, ident.span());
22+
let variants = fold_quote(variants.iter().map(process_variant), |meta| quote!(#meta,));
23+
24+
let impl_doc_hint = impl_doc_hint(&em.ident, &em.variants);
25+
let impl_from_str = impl_from_str(&em.ident, &em.variants);
26+
let impl_serde = impl_serde(&em.ident, &em.variants);
27+
let impl_deserialize = impl_deserialize(&em.ident, &em.variants);
28+
29+
Ok(quote! {
30+
#[allow(non_snake_case)]
31+
mod #mod_name {
32+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
33+
pub #enum_token #ident #generics { #variants }
34+
#impl_doc_hint
35+
#impl_from_str
36+
#impl_serde
37+
#impl_deserialize
38+
}
39+
#vis use #mod_name::#ident;
40+
})
41+
}
42+
43+
/// Remove attributes specific to `config_proc_macro` from enum variant fields.
44+
fn process_variant(variant: &syn::Variant) -> TokenStream {
45+
let metas = variant
46+
.attrs
47+
.iter()
48+
.filter(|attr| !is_doc_hint(attr) && !is_config_value(attr));
49+
let attrs = fold_quote(metas, |meta| quote!(#meta));
50+
let syn::Variant { ident, fields, .. } = variant;
51+
quote!(#attrs #ident #fields)
52+
}
53+
54+
fn impl_doc_hint(ident: &syn::Ident, variants: &Variants) -> TokenStream {
55+
let doc_hint = variants
56+
.iter()
57+
.map(doc_hint_of_variant)
58+
.collect::<Vec<_>>()
59+
.join("|");
60+
let doc_hint = format!("[{}]", doc_hint);
61+
quote! {
62+
use crate::config::ConfigType;
63+
impl ConfigType for #ident {
64+
fn doc_hint() -> String {
65+
#doc_hint.to_owned()
66+
}
67+
}
68+
}
69+
}
70+
71+
fn impl_from_str(ident: &syn::Ident, variants: &Variants) -> TokenStream {
72+
let vs = variants
73+
.iter()
74+
.filter(|v| is_unit(v))
75+
.map(|v| (config_value_of_variant(v), &v.ident));
76+
let if_patterns = fold_quote(vs, |(s, v)| {
77+
quote! {
78+
if #s.eq_ignore_ascii_case(s) {
79+
return Ok(#ident::#v);
80+
}
81+
}
82+
});
83+
quote! {
84+
impl ::std::str::FromStr for #ident {
85+
type Err = &'static str;
86+
87+
fn from_str(s: &str) -> Result<Self, Self::Err> {
88+
#if_patterns
89+
return Err("Bad variant");
90+
}
91+
}
92+
}
93+
}
94+
95+
fn doc_hint_of_variant(variant: &syn::Variant) -> String {
96+
find_doc_hint(&variant.attrs).unwrap_or(variant.ident.to_string())
97+
}
98+
99+
fn config_value_of_variant(variant: &syn::Variant) -> String {
100+
find_config_value(&variant.attrs).unwrap_or(variant.ident.to_string())
101+
}
102+
103+
fn impl_serde(ident: &syn::Ident, variants: &Variants) -> TokenStream {
104+
let arms = fold_quote(variants.iter(), |v| {
105+
let v_ident = &v.ident;
106+
let pattern = match v.fields {
107+
syn::Fields::Named(..) => quote!(#ident::v_ident{..}),
108+
syn::Fields::Unnamed(..) => quote!(#ident::#v_ident(..)),
109+
syn::Fields::Unit => quote!(#ident::#v_ident),
110+
};
111+
let option_value = config_value_of_variant(v);
112+
quote! {
113+
#pattern => serializer.serialize_str(&#option_value),
114+
}
115+
});
116+
117+
quote! {
118+
impl ::serde::ser::Serialize for #ident {
119+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120+
where
121+
S: ::serde::ser::Serializer,
122+
{
123+
use serde::ser::Error;
124+
match self {
125+
#arms
126+
_ => Err(S::Error::custom(format!("Cannot serialize {:?}", self))),
127+
}
128+
}
129+
}
130+
}
131+
}
132+
133+
// Currently only unit variants are supported.
134+
fn impl_deserialize(ident: &syn::Ident, variants: &Variants) -> TokenStream {
135+
let supported_vs = variants.iter().filter(|v| is_unit(v));
136+
let if_patterns = fold_quote(supported_vs, |v| {
137+
let config_value = config_value_of_variant(v);
138+
let variant_ident = &v.ident;
139+
quote! {
140+
if #config_value.eq_ignore_ascii_case(s) {
141+
return Ok(#ident::#variant_ident);
142+
}
143+
}
144+
});
145+
146+
let supported_vs = variants.iter().filter(|v| is_unit(v));
147+
let allowed = fold_quote(supported_vs.map(config_value_of_variant), |s| quote!(#s,));
148+
149+
quote! {
150+
impl<'de> serde::de::Deserialize<'de> for #ident {
151+
fn deserialize<D>(d: D) -> Result<Self, D::Error>
152+
where
153+
D: serde::Deserializer<'de>,
154+
{
155+
use serde::de::{Error, Visitor};
156+
use std::marker::PhantomData;
157+
use std::fmt;
158+
struct StringOnly<T>(PhantomData<T>);
159+
impl<'de, T> Visitor<'de> for StringOnly<T>
160+
where T: serde::Deserializer<'de> {
161+
type Value = String;
162+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163+
formatter.write_str("string")
164+
}
165+
fn visit_str<E>(self, value: &str) -> Result<String, E> {
166+
Ok(String::from(value))
167+
}
168+
}
169+
let s = &d.deserialize_string(StringOnly::<D>(PhantomData))?;
170+
171+
#if_patterns
172+
173+
static ALLOWED: &'static[&str] = &[#allowed];
174+
Err(D::Error::unknown_variant(&s, ALLOWED))
175+
}
176+
}
177+
}
178+
}

config_proc_macro/src/item_struct.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
use proc_macro2::TokenStream;
2+
3+
pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result<TokenStream> {
4+
unimplemented!()
5+
}

0 commit comments

Comments
 (0)