From ee02cdfb0bdde08acf8821242fd68ba4137a8582 Mon Sep 17 00:00:00 2001 From: topecongiro Date: Sun, 31 Mar 2019 18:42:49 +0900 Subject: [PATCH 1/3] Add config_proc_macro --- Cargo.lock | 10 ++ Cargo.toml | 2 + config_proc_macro/.gitignore | 1 + config_proc_macro/Cargo.lock | 68 ++++++++++ config_proc_macro/Cargo.toml | 20 +++ config_proc_macro/src/attrs.rs | 41 +++++++ config_proc_macro/src/config_type.rs | 13 ++ config_proc_macro/src/item_enum.rs | 177 +++++++++++++++++++++++++++ config_proc_macro/src/item_struct.rs | 5 + config_proc_macro/src/lib.rs | 27 ++++ config_proc_macro/src/utils.rs | 45 +++++++ config_proc_macro/tests/smoke.rs | 20 +++ 12 files changed, 429 insertions(+) create mode 100644 config_proc_macro/.gitignore create mode 100644 config_proc_macro/Cargo.lock create mode 100644 config_proc_macro/Cargo.toml create mode 100644 config_proc_macro/src/attrs.rs create mode 100644 config_proc_macro/src/config_type.rs create mode 100644 config_proc_macro/src/item_enum.rs create mode 100644 config_proc_macro/src/item_struct.rs create mode 100644 config_proc_macro/src/lib.rs create mode 100644 config_proc_macro/src/utils.rs create mode 100644 config_proc_macro/tests/smoke.rs diff --git a/Cargo.lock b/Cargo.lock index 8e5509c33291a..0ed465c66fe90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,6 +143,15 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "config_proc_macro" +version = "0.1.0" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "constant_time_eq" version = "0.1.3" @@ -772,6 +781,7 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "bytecount 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "cargo_metadata 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "config_proc_macro 0.1.0", "derive-new 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index e1106a03d28ee..ba888db72abcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,8 @@ dirs = "1.0.4" ignore = "0.4.6" annotate-snippets = { version = "0.5.0", features = ["ansi_term"] } +config_proc_macro = { path = "config_proc_macro" } + # A noop dependency that changes in the Rust repository, it's a bit of a hack. # See the `src/tools/rustc-workspace-hack/README.md` file in `rust-lang/rust` # for more information. diff --git a/config_proc_macro/.gitignore b/config_proc_macro/.gitignore new file mode 100644 index 0000000000000..9f970225adb6a --- /dev/null +++ b/config_proc_macro/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/config_proc_macro/Cargo.lock b/config_proc_macro/Cargo.lock new file mode 100644 index 0000000000000..6ee8e34b13fc5 --- /dev/null +++ b/config_proc_macro/Cargo.lock @@ -0,0 +1,68 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "config_proc_macro" +version = "0.1.0" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro2" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.15.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[metadata] +"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" +"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" +"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" +"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" diff --git a/config_proc_macro/Cargo.toml b/config_proc_macro/Cargo.toml new file mode 100644 index 0000000000000..45f80bf095635 --- /dev/null +++ b/config_proc_macro/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "config_proc_macro" +version = "0.1.0" +authors = ["topecongiro "] +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 = [] diff --git a/config_proc_macro/src/attrs.rs b/config_proc_macro/src/attrs.rs new file mode 100644 index 0000000000000..cd0a701479b09 --- /dev/null +++ b/config_proc_macro/src/attrs.rs @@ -0,0 +1,41 @@ +pub fn find_doc_hint(attrs: &[syn::Attribute]) -> Option { + attrs.iter().filter_map(doc_hint).next() +} + +pub fn is_doc_hint(attr: &syn::Attribute) -> bool { + is_attr_name_value(attr, "doc_hint") +} + +pub fn doc_hint(attr: &syn::Attribute) -> Option { + get_name_value_str_lit(attr, "doc_hint") +} + +pub fn find_config_value(attrs: &[syn::Attribute]) -> Option { + attrs.iter().filter_map(config_value).next() +} + +pub fn config_value(attr: &syn::Attribute) -> Option { + get_name_value_str_lit(attr, "value") +} + +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 { + 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, + }) +} diff --git a/config_proc_macro/src/config_type.rs b/config_proc_macro/src/config_type.rs new file mode 100644 index 0000000000000..daff3f5c61cfe --- /dev/null +++ b/config_proc_macro/src/config_type.rs @@ -0,0 +1,13 @@ +use proc_macro2::TokenStream; + +use crate::item_enum::define_config_type_on_enum; +use crate::item_struct::define_config_type_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() +} diff --git a/config_proc_macro/src/item_enum.rs b/config_proc_macro/src/item_enum.rs new file mode 100644 index 0000000000000..acb00a99cb4e5 --- /dev/null +++ b/config_proc_macro/src/item_enum.rs @@ -0,0 +1,177 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use crate::attrs::*; +use crate::utils::*; + +type Variants = syn::punctuated::Punctuated; + +pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result { + 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::>() + .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 { + #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(&self, serializer: S) -> Result + 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) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de::{Error, Visitor}; + use std::marker::PhantomData; + use std::fmt; + struct StringOnly(PhantomData); + impl<'de, T> Visitor<'de> for StringOnly + where T: serde::Deserializer<'de> { + type Value = String; + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("string") + } + fn visit_str(self, value: &str) -> Result { + Ok(String::from(value)) + } + } + let s = &d.deserialize_string(StringOnly::(PhantomData))?; + + #if_patterns + + static ALLOWED: &'static[&str] = &[#allowed]; + Err(D::Error::unknown_variant(&s, ALLOWED)) + } + } + } +} diff --git a/config_proc_macro/src/item_struct.rs b/config_proc_macro/src/item_struct.rs new file mode 100644 index 0000000000000..f03ff7e30d82e --- /dev/null +++ b/config_proc_macro/src/item_struct.rs @@ -0,0 +1,5 @@ +use proc_macro2::TokenStream; + +pub fn define_config_type_on_struct(_st: &syn::ItemStruct) -> syn::Result { + unimplemented!() +} diff --git a/config_proc_macro/src/lib.rs b/config_proc_macro/src/lib.rs new file mode 100644 index 0000000000000..66cfd3c727dda --- /dev/null +++ b/config_proc_macro/src/lib.rs @@ -0,0 +1,27 @@ +//! This crate provides a derive macro for `ConfigType`. + +#![recursion_limit = "256"] + +extern crate proc_macro; + +mod attrs; +mod config_type; +mod item_enum; +mod item_struct; +mod utils; + +use proc_macro::TokenStream; +use syn::parse_macro_input; + +#[proc_macro_attribute] +pub fn config_type(_args: TokenStream, input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as syn::Item); + let output = config_type::define_config_type(&input); + + #[cfg(feature = "debug-with-rustfmt")] + { + utils::debug_with_rustfmt(&output); + } + + TokenStream::from(output) +} diff --git a/config_proc_macro/src/utils.rs b/config_proc_macro/src/utils.rs new file mode 100644 index 0000000000000..fec6a8e4907a6 --- /dev/null +++ b/config_proc_macro/src/utils.rs @@ -0,0 +1,45 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +pub fn fold_quote(input: impl Iterator, f: F) -> TokenStream +where + F: Fn(I) -> T, + T: ToTokens, +{ + input.fold(quote! {}, |acc, x| { + let y = f(x); + quote! { #acc #y } + }) +} + +pub fn is_unit(v: &syn::Variant) -> bool { + match v.fields { + syn::Fields::Unit => true, + _ => false, + } +} + +#[cfg(feature = "debug-with-rustfmt")] +/// Pretty-print the output of proc macro using rustfmt. +pub fn debug_with_rustfmt(input: &TokenStream) { + use std::io::Write; + use std::process::{Command, Stdio}; + + let mut child = Command::new("rustfmt") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("Failed to spawn rustfmt in stdio mode"); + { + let stdin = child.stdin.as_mut().expect("Failed to get stdin"); + stdin + .write_all(format!("{}", input).as_bytes()) + .expect("Failed to write to stdin"); + } + let rustfmt_output = child.wait_with_output().expect("rustfmt has failed"); + + eprintln!( + "{}", + String::from_utf8(rustfmt_output.stdout).expect("rustfmt returned non-UTF8 string") + ); +} diff --git a/config_proc_macro/tests/smoke.rs b/config_proc_macro/tests/smoke.rs new file mode 100644 index 0000000000000..a8b8a901ac93c --- /dev/null +++ b/config_proc_macro/tests/smoke.rs @@ -0,0 +1,20 @@ +pub mod config { + pub trait ConfigType: Sized { + fn doc_hint() -> String; + } +} + +#[allow(dead_code)] +#[allow(unused_imports)] +mod tests { + use config_proc_macro::config_type; + + #[config_type] + enum Bar { + Foo, + Bar, + #[doc_hint = "foo_bar"] + FooBar, + FooFoo(i32), + } +} From 20bdb2fb9dd11f1313edfbe0a0c94904beaca19f Mon Sep 17 00:00:00 2001 From: topecongiro Date: Sun, 31 Mar 2019 18:44:35 +0900 Subject: [PATCH 2/3] Use config_type proc macro --- Cargo.lock | 4 +- src/config/lists.rs | 15 ++-- src/config/options.rs | 171 +++++++++--------------------------------- 3 files changed, 43 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ed465c66fe90..dd148264be358 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -148,8 +148,8 @@ name = "config_proc_macro" version = "0.1.0" dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.32 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/src/config/lists.rs b/src/config/lists.rs index 79fd3db5b6e2a..86faab57d16ac 100644 --- a/src/config/lists.rs +++ b/src/config/lists.rs @@ -1,6 +1,7 @@ //! Configuration options related to rewriting a list. -use crate::config::config_type::ConfigType; +use config_proc_macro::config_type; + use crate::config::IndentStyle; /// The definitive formatting tactic for lists. @@ -25,7 +26,7 @@ impl DefinitiveListTactic { /// Formatting tactic for lists. This will be cast down to a /// `DefinitiveListTactic` depending on the number and length of the items and /// their comments. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] +#[config_type] pub enum ListTactic { // One item per row. Vertical, @@ -39,17 +40,13 @@ pub enum ListTactic { Mixed, } -impl_enum_serialize_and_deserialize!(ListTactic, Vertical, Horizontal, HorizontalVertical, Mixed); - -#[derive(Eq, PartialEq, Debug, Copy, Clone)] +#[config_type] pub enum SeparatorTactic { Always, Never, Vertical, } -impl_enum_serialize_and_deserialize!(SeparatorTactic, Always, Never, Vertical); - impl SeparatorTactic { pub fn from_bool(b: bool) -> SeparatorTactic { if b { @@ -61,14 +58,12 @@ impl SeparatorTactic { } /// Where to put separator. -#[derive(Eq, PartialEq, Debug, Copy, Clone)] +#[config_type] pub enum SeparatorPlace { Front, Back, } -impl_enum_serialize_and_deserialize!(SeparatorPlace, Front, Back); - impl SeparatorPlace { pub fn is_front(self) -> bool { self == SeparatorPlace::Front diff --git a/src/config/options.rs b/src/config/options.rs index bded90b869339..e8a26a4aeea45 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -3,133 +3,18 @@ use std::fmt; use std::path::{Path, PathBuf}; use atty; +use config_proc_macro::config_type; use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor}; -use crate::config::config_type::ConfigType; use crate::config::lists::*; use crate::config::Config; -/// Macro that will stringify the enum variants or a provided textual repr -#[macro_export] -macro_rules! configuration_option_enum_stringify { - ($variant:ident) => { - stringify!($variant) - }; - - ($_variant:ident: $value:expr) => { - stringify!($value) - }; -} - -/// Macro for deriving implementations of Serialize/Deserialize for enums -#[macro_export] -macro_rules! impl_enum_serialize_and_deserialize { - ( $e:ident, $( $variant:ident $(: $value:expr)* ),* ) => { - impl ::serde::ser::Serialize for $e { - fn serialize(&self, serializer: S) -> Result - where S: ::serde::ser::Serializer - { - use serde::ser::Error; - - // We don't know whether the user of the macro has given us all options. - #[allow(unreachable_patterns)] - match *self { - $( - $e::$variant => serializer.serialize_str( - configuration_option_enum_stringify!($variant $(: $value)*) - ), - )* - _ => { - Err(S::Error::custom(format!("Cannot serialize {:?}", self))) - } - } - } - } - - impl<'de> ::serde::de::Deserialize<'de> for $e { - fn deserialize(d: D) -> Result - where D: ::serde::Deserializer<'de> { - use serde::de::{Error, Visitor}; - use std::marker::PhantomData; - use std::fmt; - struct StringOnly(PhantomData); - impl<'de, T> Visitor<'de> for StringOnly - where T: ::serde::Deserializer<'de> { - type Value = String; - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("string") - } - fn visit_str(self, value: &str) -> Result { - Ok(String::from(value)) - } - } - let s = d.deserialize_string(StringOnly::(PhantomData))?; - $( - if configuration_option_enum_stringify!($variant $(: $value)*) - .eq_ignore_ascii_case(&s) { - return Ok($e::$variant); - } - )* - static ALLOWED: &'static[&str] = &[ - $(configuration_option_enum_stringify!($variant $(: $value)*),)*]; - Err(D::Error::unknown_variant(&s, ALLOWED)) - } - } - - impl ::std::str::FromStr for $e { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - $( - if configuration_option_enum_stringify!($variant $(: $value)*) - .eq_ignore_ascii_case(s) { - return Ok($e::$variant); - } - )* - Err("Bad variant") - } - } - - impl ConfigType for $e { - fn doc_hint() -> String { - let mut variants = Vec::new(); - $( - variants.push( - configuration_option_enum_stringify!($variant $(: $value)*) - ); - )* - format!("[{}]", variants.join("|")) - } - } - }; -} - -macro_rules! configuration_option_enum { - ($e:ident: $( $name:ident $(: $value:expr)* ),+ $(,)*) => ( - #[derive(Copy, Clone, Eq, PartialEq)] - pub enum $e { - $( $name ),+ - } - - impl ::std::fmt::Debug for $e { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { - f.write_str(match self { - $( - $e::$name => configuration_option_enum_stringify!($name $(: $value)*), - )+ - }) - } - } - - impl_enum_serialize_and_deserialize!($e, $( $name $(: $value)* ),+); - ); -} - -configuration_option_enum! { NewlineStyle: - Auto, // Auto-detect based on the raw source input +#[config_type] +pub enum NewlineStyle { + Auto, // Auto-detect based on the raw source input Windows, // \r\n - Unix, // \n - Native, // \r\n in Windows, \n on other platforms + Unix, // \n + Native, // \r\n in Windows, \n on other platforms } impl NewlineStyle { @@ -188,7 +73,8 @@ impl NewlineStyle { } } -configuration_option_enum! { BraceStyle: +#[config_type] +pub enum BraceStyle { AlwaysNextLine, PreferSameLine, // Prefer same line except where there is a where-clause, in which case force @@ -196,7 +82,8 @@ configuration_option_enum! { BraceStyle: SameLineWhere, } -configuration_option_enum! { ControlBraceStyle: +#[config_type] +pub enum ControlBraceStyle { // K&R style, Rust community default AlwaysSameLine, // Stroustrup style @@ -205,7 +92,8 @@ configuration_option_enum! { ControlBraceStyle: AlwaysNextLine, } -configuration_option_enum! { IndentStyle: +#[config_type] +pub enum IndentStyle { // First line on the same line as the opening brace, all lines aligned with // the first line. Visual, @@ -213,7 +101,8 @@ configuration_option_enum! { IndentStyle: Block, } -configuration_option_enum! { Density: +#[config_type] +pub enum Density { // Fit as much on one line as possible. Compressed, // Use more lines. @@ -222,14 +111,16 @@ configuration_option_enum! { Density: Vertical, } -configuration_option_enum! { TypeDensity: +#[config_type] +pub enum TypeDensity { // No spaces around "=" and "+" Compressed, // Spaces around " = " and " + " Wide, } -configuration_option_enum! { Heuristics: +#[config_type] +pub enum Heuristics { // Turn off any heuristics Off, // Turn on max heuristics @@ -249,7 +140,8 @@ impl Density { } } -configuration_option_enum! { ReportTactic: +#[config_type] +pub enum ReportTactic { Always, Unnumbered, Never, @@ -257,7 +149,8 @@ configuration_option_enum! { ReportTactic: // What Rustfmt should emit. Mostly corresponds to the `--emit` command line // option. -configuration_option_enum! { EmitMode: +#[config_type] +pub enum EmitMode { // Emits to files. Files, // Writes the output to stdout. @@ -275,7 +168,8 @@ configuration_option_enum! { EmitMode: } // Client-preference for coloured output. -configuration_option_enum! { Color: +#[config_type] +pub enum Color { // Always use color, whether it is a piped or terminal output Always, // Never use color @@ -284,7 +178,8 @@ configuration_option_enum! { Color: Auto, } -configuration_option_enum! { Version: +#[config_type] +pub enum Version { // 1.x.y One, // 2.x.y @@ -303,7 +198,8 @@ impl Color { } // How chatty should Rustfmt be? -configuration_option_enum! { Verbosity: +#[config_type] +pub enum Verbosity { // Emit more. Verbose, Normal, @@ -474,9 +370,14 @@ pub trait CliOptions { } // The edition of the compiler (RFC 2052) -configuration_option_enum! { Edition: - Edition2015: 2015, - Edition2018: 2018, +#[config_type] +pub enum Edition { + #[value = "2015"] + #[doc_hint = "2015"] + Edition2015, + #[value = "2018"] + #[doc_hint = "2018"] + Edition2018, } impl Default for Edition { From 3dec18a681c7ff4eef2d4eb5e62c0278b6110e22 Mon Sep 17 00:00:00 2001 From: topecongiro Date: Sun, 31 Mar 2019 19:13:15 +0900 Subject: [PATCH 3/3] Add doc comment --- config_proc_macro/src/attrs.rs | 16 +++++ config_proc_macro/src/config_type.rs | 2 + config_proc_macro/src/item_enum.rs | 1 + src/config/lists.rs | 10 +-- src/config/options.rs | 97 ++++++++++++++++------------ 5 files changed, 81 insertions(+), 45 deletions(-) diff --git a/config_proc_macro/src/attrs.rs b/config_proc_macro/src/attrs.rs index cd0a701479b09..0df68772ae633 100644 --- a/config_proc_macro/src/attrs.rs +++ b/config_proc_macro/src/attrs.rs @@ -1,23 +1,39 @@ +//! 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 { 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 { 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 { 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 { 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") } diff --git a/config_proc_macro/src/config_type.rs b/config_proc_macro/src/config_type.rs index daff3f5c61cfe..93a78b8463ec5 100644 --- a/config_proc_macro/src/config_type.rs +++ b/config_proc_macro/src/config_type.rs @@ -3,6 +3,8 @@ 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), diff --git a/config_proc_macro/src/item_enum.rs b/config_proc_macro/src/item_enum.rs index acb00a99cb4e5..eb7254ada7a85 100644 --- a/config_proc_macro/src/item_enum.rs +++ b/config_proc_macro/src/item_enum.rs @@ -6,6 +6,7 @@ use crate::utils::*; type Variants = syn::punctuated::Punctuated; +/// Defines and implements `config_type` enum. pub fn define_config_type_on_enum(em: &syn::ItemEnum) -> syn::Result { let syn::ItemEnum { vis, diff --git a/src/config/lists.rs b/src/config/lists.rs index 86faab57d16ac..0270bd0eb0b31 100644 --- a/src/config/lists.rs +++ b/src/config/lists.rs @@ -28,15 +28,15 @@ impl DefinitiveListTactic { /// their comments. #[config_type] pub enum ListTactic { - // One item per row. + /// One item per row. Vertical, - // All items on one row. + /// All items on one row. Horizontal, - // Try Horizontal layout, if that fails then vertical. + /// Try Horizontal layout, if that fails then vertical. HorizontalVertical, - // HorizontalVertical with a soft limit of n characters. + /// HorizontalVertical with a soft limit of n characters. LimitedHorizontalVertical(usize), - // Pack as many items as possible per row over (possibly) many rows. + /// Pack as many items as possible per row over (possibly) many rows. Mixed, } diff --git a/src/config/options.rs b/src/config/options.rs index e8a26a4aeea45..9b0060bfdb6ba 100644 --- a/src/config/options.rs +++ b/src/config/options.rs @@ -11,10 +11,14 @@ use crate::config::Config; #[config_type] pub enum NewlineStyle { - Auto, // Auto-detect based on the raw source input - Windows, // \r\n - Unix, // \n - Native, // \r\n in Windows, \n on other platforms + /// Auto-detect based on the raw source input. + Auto, + /// Force CRLF (`\r\n`). + Windows, + /// Force CR (`\n). + Unix, + /// `\r\n` in Windows, `\n`` on other platforms. + Native, } impl NewlineStyle { @@ -74,58 +78,66 @@ impl NewlineStyle { } #[config_type] +/// Where to put the opening brace of items (`fn`, `impl`, etc.). pub enum BraceStyle { + /// Put the opening brace on the next line. AlwaysNextLine, + /// Put the opening brace on the same line, if possible. PreferSameLine, - // Prefer same line except where there is a where-clause, in which case force - // the brace to the next line. + /// Prefer the same line except where there is a where-clause, in which + /// case force the brace to be put on the next line. SameLineWhere, } #[config_type] +/// Where to put the opening brace of conditional expressions (`if`, `match`, etc.). pub enum ControlBraceStyle { - // K&R style, Rust community default + /// K&R style, Rust community default AlwaysSameLine, - // Stroustrup style + /// Stroustrup style ClosingNextLine, - // Allman style + /// Allman style AlwaysNextLine, } #[config_type] +/// How to indent. pub enum IndentStyle { - // First line on the same line as the opening brace, all lines aligned with - // the first line. + /// First line on the same line as the opening brace, all lines aligned with + /// the first line. Visual, - // First line is on a new line and all lines align with block indent. + /// First line is on a new line and all lines align with **block** indent. Block, } #[config_type] +/// How to place a list-like items. pub enum Density { - // Fit as much on one line as possible. + /// Fit as much on one line as possible. Compressed, - // Use more lines. + /// Use more lines. Tall, - // Place every item on a separate line. + /// Place every item on a separate line. Vertical, } #[config_type] +/// Spacing around type combinators. pub enum TypeDensity { - // No spaces around "=" and "+" + /// No spaces around "=" and "+" Compressed, - // Spaces around " = " and " + " + /// Spaces around " = " and " + " Wide, } #[config_type] +/// To what extent does rustfmt pursue its heuristics? pub enum Heuristics { - // Turn off any heuristics + /// Turn off any heuristics Off, - // Turn on max heuristics + /// Turn on max heuristics Max, - // Use Rustfmt's defaults + /// Use Rustfmt's defaults Default, } @@ -147,42 +159,44 @@ pub enum ReportTactic { Never, } -// What Rustfmt should emit. Mostly corresponds to the `--emit` command line -// option. +/// What Rustfmt should emit. Mostly corresponds to the `--emit` command line +/// option. #[config_type] pub enum EmitMode { - // Emits to files. + /// Emits to files. Files, - // Writes the output to stdout. + /// Writes the output to stdout. Stdout, - // Displays how much of the input file was processed + /// Displays how much of the input file was processed Coverage, - // Unfancy stdout + /// Unfancy stdout Checkstyle, - // Output the changed lines (for internal value only) + /// Output the changed lines (for internal value only) ModifiedLines, - // Checks if a diff can be generated. If so, rustfmt outputs a diff and quits with exit code 1. - // This option is designed to be run in CI where a non-zero exit signifies non-standard code - // formatting. Used for `--check`. + /// Checks if a diff can be generated. If so, rustfmt outputs a diff and + /// quits with exit code 1. + /// This option is designed to be run in CI where a non-zero exit signifies + /// non-standard code formatting. Used for `--check`. Diff, } -// Client-preference for coloured output. +/// Client-preference for coloured output. #[config_type] pub enum Color { - // Always use color, whether it is a piped or terminal output + /// Always use color, whether it is a piped or terminal output Always, - // Never use color + /// Never use color Never, - // Automatically use color, if supported by terminal + /// Automatically use color, if supported by terminal Auto, } #[config_type] +/// rustfmt format style version. pub enum Version { - // 1.x.y + /// 1.x.y. When specified, rustfmt will format in the same style as 1.0.0. One, - // 2.x.y + /// 2.x.y. When specified, rustfmt will formatin the the latest style. Two, } @@ -197,13 +211,14 @@ impl Color { } } -// How chatty should Rustfmt be? +/// How chatty should Rustfmt be? #[config_type] pub enum Verbosity { - // Emit more. + /// Emit more. Verbose, + /// Default. Normal, - // Emit as little as possible. + /// Emit as little as possible. Quiet, } @@ -369,14 +384,16 @@ pub trait CliOptions { fn config_path(&self) -> Option<&Path>; } -// The edition of the compiler (RFC 2052) +/// The edition of the syntax and semntics of code (RFC 2052). #[config_type] pub enum Edition { #[value = "2015"] #[doc_hint = "2015"] + /// Edition 2015. Edition2015, #[value = "2018"] #[doc_hint = "2018"] + /// Edition 2018. Edition2018, }