From 3d0fa721e15055cf436387b75457c7e535c2e0d6 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sun, 5 Feb 2023 12:10:28 -0500 Subject: [PATCH] Propagate attribute-like macros in define_rule_mapping --- ruff_macros/src/define_rule_mapping.rs | 43 +++++++++++++--------- ruff_macros/src/rule_code_prefix.rs | 49 ++++++++++++++++++-------- 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/ruff_macros/src/define_rule_mapping.rs b/ruff_macros/src/define_rule_mapping.rs index b2e70dbca9c3d5..58a5036fac82cb 100644 --- a/ruff_macros/src/define_rule_mapping.rs +++ b/ruff_macros/src/define_rule_mapping.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use proc_macro2::Span; use quote::quote; use syn::parse::Parse; -use syn::{Ident, LitStr, Path, Token}; +use syn::{Attribute, Ident, LitStr, Path, Token}; pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let mut rule_variants = quote!(); @@ -18,25 +18,32 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let mut diagkind_commit_match_arms = quote!(); let mut from_impls_for_diagkind = quote!(); - for (code, path, name) in &mapping.entries { + for (code, path, name, attr) in &mapping.entries { let code_str = LitStr::new(&code.to_string(), Span::call_site()); rule_variants.extend(quote! { #[doc = #code_str] + #(#attr)* #name, }); - diagkind_variants.extend(quote! {#name(#path),}); + diagkind_variants.extend(quote! {#(#attr)* #name(#path),}); + + // Apply the `attrs` to each arm, like `[cfg(feature = "foo")]`. rule_message_formats_match_arms - .extend(quote! {Self::#name => <#path as Violation>::message_formats(),}); - rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,}); - rule_code_match_arms.extend(quote! {Self::#name => #code_str,}); - rule_from_code_match_arms.extend(quote! {#code_str => Ok(Rule::#name), }); - diagkind_code_match_arms.extend(quote! {Self::#name(..) => &Rule::#name, }); - diagkind_body_match_arms.extend(quote! {Self::#name(x) => Violation::message(x), }); + .extend(quote! {#(#attr)* Self::#name => <#path as Violation>::message_formats(),}); + rule_autofixable_match_arms + .extend(quote! {#(#attr)* Self::#name => <#path as Violation>::AUTOFIX,}); + rule_code_match_arms.extend(quote! {#(#attr)* Self::#name => #code_str,}); + rule_from_code_match_arms.extend(quote! {#(#attr)* #code_str => Ok(Rule::#name), }); + diagkind_code_match_arms.extend(quote! {#(#attr)* Self::#name(..) => &Rule::#name, }); + diagkind_body_match_arms + .extend(quote! {#(#attr)* Self::#name(x) => Violation::message(x), }); diagkind_fixable_match_arms - .extend(quote! {Self::#name(x) => x.autofix_title_formatter().is_some(),}); - diagkind_commit_match_arms - .extend(quote! {Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), }); + .extend(quote! {#(#attr)* Self::#name(x) => x.autofix_title_formatter().is_some(),}); + diagkind_commit_match_arms.extend( + quote! {#(#attr)* Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), }, + ); from_impls_for_diagkind.extend(quote! { + #(#attr)* impl From<#path> for DiagnosticKind { fn from(x: #path) -> Self { DiagnosticKind::#name(x) @@ -48,7 +55,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let code_to_name: HashMap<_, _> = mapping .entries .iter() - .map(|(code, _, name)| (code.to_string(), name)) + .map(|(code, _, name, _)| (code.to_string(), name)) .collect(); let rulecodeprefix = super::rule_code_prefix::expand( @@ -56,6 +63,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { &Ident::new("RuleCodePrefix", Span::call_site()), mapping.entries.iter().map(|(code, ..)| code), |code| code_to_name[code], + mapping.entries.iter().map(|(.., attr)| attr), ); quote! { @@ -104,7 +112,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { } } - impl DiagnosticKind { /// The rule of the diagnostic. pub fn rule(&self) -> &'static Rule { @@ -134,19 +141,23 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { } pub struct Mapping { - entries: Vec<(Ident, Path, Ident)>, + entries: Vec<(Ident, Path, Ident, Vec)>, } impl Parse for Mapping { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut entries = Vec::new(); while !input.is_empty() { + // Grab the `#[cfg(...)]` attributes. + let attrs = input.call(Attribute::parse_outer)?; + + // Parse the `RuleCodePrefix::... => ...` part. let code: Ident = input.parse()?; let _: Token![=>] = input.parse()?; let path: Path = input.parse()?; let name = path.segments.last().unwrap().ident.clone(); let _: Token![,] = input.parse()?; - entries.push((code, path, name)); + entries.push((code, path, name, attrs)); } Ok(Self { entries }) } diff --git a/ruff_macros/src/rule_code_prefix.rs b/ruff_macros/src/rule_code_prefix.rs index 93c939a98eb03a..484cc5115976b2 100644 --- a/ruff_macros/src/rule_code_prefix.rs +++ b/ruff_macros/src/rule_code_prefix.rs @@ -1,22 +1,23 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeMap; use proc_macro2::Span; use quote::quote; -use syn::Ident; +use syn::{Attribute, Ident}; pub fn expand<'a>( rule_type: &Ident, prefix_ident: &Ident, variants: impl Iterator, variant_name: impl Fn(&str) -> &'a Ident, + attr: impl Iterator>, ) -> proc_macro2::TokenStream { // Build up a map from prefix to matching RuleCodes. - let mut prefix_to_codes: BTreeMap> = BTreeMap::default(); + let mut prefix_to_codes: BTreeMap>> = + BTreeMap::default(); - let mut all_codes = BTreeSet::new(); - let mut pl_codes = BTreeSet::new(); + let mut pl_codes = BTreeMap::new(); - for variant in variants { + for (variant, attr) in variants.zip(attr) { let code_str = variant.to_string(); let code_prefix_len = code_str .chars() @@ -28,19 +29,25 @@ pub fn expand<'a>( prefix_to_codes .entry(prefix) .or_default() - .insert(code_str.clone()); + .entry(code_str.clone()) + .or_insert_with(|| attr.clone()); } if code_str.starts_with("PL") { - pl_codes.insert(code_str.to_string()); + pl_codes.insert(code_str, attr.clone()); } - all_codes.insert(code_str); } prefix_to_codes.insert("PL".to_string(), pl_codes); - let prefix_variants = prefix_to_codes.keys().map(|prefix| { + let prefix_variants = prefix_to_codes.iter().map(|(prefix, codes)| { let prefix = Ident::new(prefix, Span::call_site()); + let attr = if codes.len() == 1 { + codes.values().next().unwrap().clone() + } else { + vec![] + }; quote! { + #(#attr)* #prefix } }); @@ -74,24 +81,32 @@ pub fn expand<'a>( fn generate_impls<'a>( rule_type: &Ident, prefix_ident: &Ident, - prefix_to_codes: &BTreeMap>, + prefix_to_codes: &BTreeMap>>, variant_name: impl Fn(&str) -> &'a Ident, ) -> proc_macro2::TokenStream { let into_iter_match_arms = prefix_to_codes.iter().map(|(prefix_str, codes)| { - let codes = codes.iter().map(|code| { + let prefix = Ident::new(prefix_str, Span::call_site()); + let attr = if codes.len() == 1 { + codes.values().next().unwrap().clone() + } else { + vec![] + }; + + let codes = codes.iter().map(|(code, attr)| { let rule_variant = variant_name(code); quote! { + #(#attr)* #rule_type::#rule_variant } }); - let prefix = Ident::new(prefix_str, Span::call_site()); quote! { + #(#attr)* #prefix_ident::#prefix => vec![#(#codes),*].into_iter(), } }); - let specificity_match_arms = prefix_to_codes.keys().map(|prefix_str| { + let specificity_match_arms = prefix_to_codes.iter().map(|(prefix_str, codes)| { let prefix = Ident::new(prefix_str, Span::call_site()); let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count(); if prefix_str != "PL" && prefix_str.starts_with("PL") { @@ -106,7 +121,13 @@ fn generate_impls<'a>( 5 => quote! { Specificity::Code5Chars }, _ => panic!("Invalid prefix: {prefix}"), }; + let attr = if codes.len() == 1 { + codes.values().next().unwrap().clone() + } else { + vec![] + }; quote! { + #(#attr)* #prefix_ident::#prefix => #suffix_len, } });