summary refs log tree commit diff
path: root/src/tools/rustfmt/config_proc_macro
diff options
context:
space:
mode:
authorCaleb Cartwright <caleb.cartwright@outlook.com>2021-05-14 21:53:36 -0500
committerCaleb Cartwright <caleb.cartwright@outlook.com>2021-05-14 21:53:36 -0500
commitb2d45c0d4b2d44789000ebec6d702cc27db19782 (patch)
treec3accc00616767e5de0f89f69ce87519f02de6d5 /src/tools/rustfmt/config_proc_macro
parente659b6de9170c055b6f2d16e2679b22d67297b13 (diff)
parent7872306edf2e11a69aaffb9434088fd66b46a863 (diff)
downloadrust-b2d45c0d4b2d44789000ebec6d702cc27db19782.tar.gz
rust-b2d45c0d4b2d44789000ebec6d702cc27db19782.zip
Add 'src/tools/rustfmt/' from commit '7872306edf2e11a69aaffb9434088fd66b46a863'
git-subtree-dir: src/tools/rustfmt
git-subtree-mainline: e659b6de9170c055b6f2d16e2679b22d67297b13
git-subtree-split: 7872306edf2e11a69aaffb9434088fd66b46a863
Diffstat (limited to 'src/tools/rustfmt/config_proc_macro')
-rw-r--r--src/tools/rustfmt/config_proc_macro/.gitignore1
-rw-r--r--src/tools/rustfmt/config_proc_macro/Cargo.lock68
-rw-r--r--src/tools/rustfmt/config_proc_macro/Cargo.toml24
-rw-r--r--src/tools/rustfmt/config_proc_macro/src/attrs.rs57
-rw-r--r--src/tools/rustfmt/config_proc_macro/src/config_type.rs15
-rw-r--r--src/tools/rustfmt/config_proc_macro/src/item_enum.rs208
-rw-r--r--src/tools/rustfmt/config_proc_macro/src/item_struct.rs5
-rw-r--r--src/tools/rustfmt/config_proc_macro/src/lib.rs25
-rw-r--r--src/tools/rustfmt/config_proc_macro/src/utils.rs52
-rw-r--r--src/tools/rustfmt/config_proc_macro/tests/smoke.rs20
10 files changed, 475 insertions, 0 deletions
diff --git a/src/tools/rustfmt/config_proc_macro/.gitignore b/src/tools/rustfmt/config_proc_macro/.gitignore
new file mode 100644
index 00000000000..9f970225adb
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/.gitignore
@@ -0,0 +1 @@
+target/
\ No newline at end of file
diff --git a/src/tools/rustfmt/config_proc_macro/Cargo.lock b/src/tools/rustfmt/config_proc_macro/Cargo.lock
new file mode 100644
index 00000000000..abcf9654e5d
--- /dev/null
+++ b/src/tools/rustfmt/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 = "proc-macro2"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustfmt-config_proc_macro"
+version = "0.1.2"
+dependencies = [
+ "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum proc-macro2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e98a83a9f9b331f54b924e68a66acb1bb35cb01fb0a23645139967abefb697e8"
+"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+"checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f"
+"checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425"
+"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"
+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
diff --git a/src/tools/rustfmt/config_proc_macro/Cargo.toml b/src/tools/rustfmt/config_proc_macro/Cargo.toml
new file mode 100644
index 00000000000..cc995571602
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "rustfmt-config_proc_macro"
+version = "0.2.0"
+authors = ["topecongiro <seuchida@gmail.com>"]
+edition = "2018"
+description = "A collection of procedural macros for rustfmt"
+license = "Apache-2.0/MIT"
+categories = ["development-tools::procedural-macro-helpers"]
+repository = "https://github.com/rust-lang/rustfmt"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1.0"
+quote = "1.0"
+syn = { version = "1.0", features = ["full", "visit"] }
+
+[dev-dependencies]
+serde = { version = "1.0", features = ["derive"] }
+
+[features]
+default = []
+debug-with-rustfmt = []
diff --git a/src/tools/rustfmt/config_proc_macro/src/attrs.rs b/src/tools/rustfmt/config_proc_macro/src/attrs.rs
new file mode 100644
index 00000000000..0baba046f9e
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/src/attrs.rs
@@ -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.parse_meta().ok().map_or(false, |meta| match meta {
+        syn::Meta::NameValue(syn::MetaNameValue { ref path, .. }) if path.is_ident(name) => true,
+        _ => false,
+    })
+}
+
+fn get_name_value_str_lit(attr: &syn::Attribute, name: &str) -> Option<String> {
+    attr.parse_meta().ok().and_then(|meta| match meta {
+        syn::Meta::NameValue(syn::MetaNameValue {
+            ref path,
+            lit: syn::Lit::Str(ref lit_str),
+            ..
+        }) if path.is_ident(name) => Some(lit_str.value()),
+        _ => None,
+    })
+}
diff --git a/src/tools/rustfmt/config_proc_macro/src/config_type.rs b/src/tools/rustfmt/config_proc_macro/src/config_type.rs
new file mode 100644
index 00000000000..93a78b8463e
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/src/config_type.rs
@@ -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()
+}
diff --git a/src/tools/rustfmt/config_proc_macro/src/item_enum.rs b/src/tools/rustfmt/config_proc_macro/src/item_enum.rs
new file mode 100644
index 00000000000..dcee77a8549
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/src/item_enum.rs
@@ -0,0 +1,208 @@
+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_display = impl_display(&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_display
+            #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_display(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 match_patterns = fold_quote(vs, |(s, v)| {
+        quote! {
+            #ident::#v => write!(f, "{}", #s),
+        }
+    });
+    quote! {
+        use std::fmt;
+        impl fmt::Display for #ident {
+            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                match self {
+                    #match_patterns
+                    _ => unimplemented!(),
+                }
+            }
+        }
+    }
+}
+
+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);
+            }
+        }
+    });
+    let mut err_msg = String::from("Bad variant, expected one of:");
+    for v in variants.iter().filter(|v| is_unit(v)) {
+        err_msg.push_str(&format!(" `{}`", v.ident));
+    }
+
+    quote! {
+        impl ::std::str::FromStr for #ident {
+            type Err = &'static str;
+
+            fn from_str(s: &str) -> Result<Self, Self::Err> {
+                #if_patterns
+                return Err(#err_msg);
+            }
+        }
+    }
+}
+
+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))
+            }
+        }
+    }
+}
diff --git a/src/tools/rustfmt/config_proc_macro/src/item_struct.rs b/src/tools/rustfmt/config_proc_macro/src/item_struct.rs
new file mode 100644
index 00000000000..f03ff7e30d8
--- /dev/null
+++ b/src/tools/rustfmt/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<TokenStream> {
+    unimplemented!()
+}
diff --git a/src/tools/rustfmt/config_proc_macro/src/lib.rs b/src/tools/rustfmt/config_proc_macro/src/lib.rs
new file mode 100644
index 00000000000..78e7e098ed9
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/src/lib.rs
@@ -0,0 +1,25 @@
+//! This crate provides a derive macro for `ConfigType`.
+
+#![recursion_limit = "256"]
+
+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/src/tools/rustfmt/config_proc_macro/src/utils.rs b/src/tools/rustfmt/config_proc_macro/src/utils.rs
new file mode 100644
index 00000000000..5b68d274849
--- /dev/null
+++ b/src/tools/rustfmt/config_proc_macro/src/utils.rs
@@ -0,0 +1,52 @@
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+
+pub fn fold_quote<F, I, T>(input: impl Iterator<Item = I>, 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};
+    use std::env;
+    use std::ffi::OsStr;
+
+    let rustfmt_var = env::var_os("RUSTFMT");
+    let rustfmt = match &rustfmt_var {
+        Some(rustfmt) => rustfmt,
+        None => OsStr::new("rustfmt"),
+    };
+    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/src/tools/rustfmt/config_proc_macro/tests/smoke.rs b/src/tools/rustfmt/config_proc_macro/tests/smoke.rs
new file mode 100644
index 00000000000..940a8a0c251
--- /dev/null
+++ b/src/tools/rustfmt/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 rustfmt_config_proc_macro::config_type;
+
+    #[config_type]
+    enum Bar {
+        Foo,
+        Bar,
+        #[doc_hint = "foo_bar"]
+        FooBar,
+        FooFoo(i32),
+    }
+}