From 81996f1bcc837bb317ed3fbc2ae70f64c6e43fec Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sun, 15 Jan 2023 05:54:33 +0100 Subject: [PATCH] Convert define_rule_mapping! to a procedural macro define_rule_mapping! was previously implemented as a declarative macro, which was however partially relying on an origin_by_code! proc macro because declarative macros cannot match on substrings of identifiers. Currently all define_rule_mapping! lines look like the following: TID251 => violations::BannedApi, TID252 => violations::BannedRelativeImport, We want to break up violations.rs, moving the violation definitions to the respective rule modules. To do this we want to change the previous lines to: TID251 => rules::flake8_tidy_imports::banned_api::BannedApi, TID252 => rules::flake8_tidy_imports::relative_imports::RelativeImport, This however doesn't work because the define_rule_mapping! macro is currently defined as: ($($code:ident => $mod:ident::$name:ident,)+) => { ... } That is it only supported $module::$name but not longer paths with multiple modules. While we could define `=> $path:path`[1] then we could no longer access the last path segment, which we need because we use it for the DiagnosticKind variant names. And `$path:path::$last:ident` doesn't work either because it would be ambiguous (Rust wouldn't know where the path ends ... so path fragments have to be followed by some punctuation/keyword that may not be part of paths). And we also cannot just introduce a procedural macro like path_basename!(...) because the following is not valid Rust code: enum Foo { foo!(...), } (macros cannot be called in the place where you define variants.) So we have to convert define_rule_mapping! into a proc macro in order to support paths of arbitrary length and this commit implements that. [1]: https://doc.rust-lang.org/reference/macros-by-example.html#metavariables --- ruff_macros/src/define_rule_mapping.rs | 133 +++++++++++++++++++++++++ ruff_macros/src/lib.rs | 25 +---- src/registry.rs | 101 +------------------ 3 files changed, 139 insertions(+), 120 deletions(-) create mode 100644 ruff_macros/src/define_rule_mapping.rs diff --git a/ruff_macros/src/define_rule_mapping.rs b/ruff_macros/src/define_rule_mapping.rs new file mode 100644 index 0000000000..a4b70b6761 --- /dev/null +++ b/ruff_macros/src/define_rule_mapping.rs @@ -0,0 +1,133 @@ +use proc_macro2::Span; +use quote::quote; +use syn::parse::Parse; +use syn::{Ident, Path, Token}; + +pub fn define_rule_mapping(mapping: Mapping) -> proc_macro2::TokenStream { + let mut rulecode_variants = quote!(); + let mut diagkind_variants = quote!(); + let mut rulecode_kind_match_arms = quote!(); + let mut rulecode_origin_match_arms = quote!(); + let mut diagkind_code_match_arms = quote!(); + let mut diagkind_body_match_arms = quote!(); + let mut diagkind_fixable_match_arms = quote!(); + let mut diagkind_commit_match_arms = quote!(); + let mut from_impls_for_diagkind = quote!(); + + for (code, path, name) in mapping.entries { + rulecode_variants.extend(quote! {#code,}); + diagkind_variants.extend(quote! {#name(#path),}); + rulecode_kind_match_arms.extend( + quote! {RuleCode::#code => DiagnosticKind::#name(<#path as Violation>::placeholder()),}, + ); + let origin = get_origin(&code); + rulecode_origin_match_arms.extend(quote! {RuleCode::#code => RuleOrigin::#origin,}); + diagkind_code_match_arms.extend(quote! {DiagnosticKind::#name(..) => &RuleCode::#code, }); + diagkind_body_match_arms + .extend(quote! {DiagnosticKind::#name(x) => Violation::message(x), }); + diagkind_fixable_match_arms + .extend(quote! {DiagnosticKind::#name(x) => x.autofix_title_formatter().is_some(),}); + diagkind_commit_match_arms.extend( + quote! {DiagnosticKind::#name(x) => x.autofix_title_formatter().map(|f| f(x)), }, + ); + from_impls_for_diagkind.extend(quote! { + impl From<#path> for DiagnosticKind { + fn from(x: #path) -> Self { + DiagnosticKind::#name(x) + } + } + }); + } + + quote! { + #[derive( + AsRefStr, + RuleCodePrefix, + EnumIter, + EnumString, + Debug, + Display, + PartialEq, + Eq, + Clone, + Serialize, + Deserialize, + Hash, + PartialOrd, + Ord, + )] + pub enum RuleCode { #rulecode_variants } + + #[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)] + pub enum DiagnosticKind { #diagkind_variants } + + + impl RuleCode { + /// A placeholder representation of the `DiagnosticKind` for the diagnostic. + pub fn kind(&self) -> DiagnosticKind { + match self { #rulecode_kind_match_arms } + } + + pub fn origin(&self) -> RuleOrigin { + match self { #rulecode_origin_match_arms } + } + } + + + impl DiagnosticKind { + /// A four-letter shorthand code for the diagnostic. + pub fn code(&self) -> &'static RuleCode { + match self { #diagkind_code_match_arms } + } + + /// The body text for the diagnostic. + pub fn body(&self) -> String { + match self { #diagkind_body_match_arms } + } + + /// Whether the diagnostic is (potentially) fixable. + pub fn fixable(&self) -> bool { + match self { #diagkind_fixable_match_arms } + } + + /// The message used to describe the fix action for a given `DiagnosticKind`. + pub fn commit(&self) -> Option { + match self { #diagkind_commit_match_arms } + } + } + + #from_impls_for_diagkind + } +} + +fn get_origin(ident: &Ident) -> Ident { + let ident = ident.to_string(); + let mut iter = crate::prefixes::PREFIX_TO_ORIGIN.iter(); + let origin = loop { + let (prefix, origin) = iter + .next() + .unwrap_or_else(|| panic!("code doesn't start with any recognized prefix: {ident}")); + if ident.starts_with(prefix) { + break origin; + } + }; + Ident::new(origin, Span::call_site()) +} +pub struct Mapping { + entries: Vec<(Ident, Path, Ident)>, +} + +impl Parse for Mapping { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut entries = Vec::new(); + while !input.is_empty() { + 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)); + } + Ok(Mapping { entries }) + } +} diff --git a/ruff_macros/src/lib.rs b/ruff_macros/src/lib.rs index 91faef74e7..9d96373976 100644 --- a/ruff_macros/src/lib.rs +++ b/ruff_macros/src/lib.rs @@ -13,11 +13,10 @@ )] #![forbid(unsafe_code)] -use proc_macro2::Span; -use quote::quote; -use syn::{parse_macro_input, DeriveInput, Ident}; +use syn::{parse_macro_input, DeriveInput}; mod config; +mod define_rule_mapping; mod prefixes; mod rule_code_prefix; @@ -40,21 +39,7 @@ pub fn derive_rule_code_prefix(input: proc_macro::TokenStream) -> proc_macro::To } #[proc_macro] -pub fn origin_by_code(item: proc_macro::TokenStream) -> proc_macro::TokenStream { - let ident = parse_macro_input!(item as Ident).to_string(); - let mut iter = prefixes::PREFIX_TO_ORIGIN.iter(); - let origin = loop { - let (prefix, origin) = iter - .next() - .unwrap_or_else(|| panic!("code doesn't start with any recognized prefix: {ident}")); - if ident.starts_with(prefix) { - break origin; - } - }; - let prefix = Ident::new(origin, Span::call_site()); - - quote! { - RuleOrigin::#prefix - } - .into() +pub fn define_rule_mapping(item: proc_macro::TokenStream) -> proc_macro::TokenStream { + let mapping = parse_macro_input!(item as define_rule_mapping::Mapping); + define_rule_mapping::define_rule_mapping(mapping).into() } diff --git a/src/registry.rs b/src/registry.rs index c95058a89d..3456da7796 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -15,106 +15,7 @@ use crate::fix::Fix; use crate::violation::Violation; use crate::violations; -macro_rules! define_rule_mapping { - ($($code:ident => $mod:ident::$name:ident,)+) => { - #[derive( - AsRefStr, - RuleCodePrefix, - EnumIter, - EnumString, - Debug, - Display, - PartialEq, - Eq, - Clone, - Serialize, - Deserialize, - Hash, - PartialOrd, - Ord, - )] - pub enum RuleCode { - $( - $code, - )+ - } - - #[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)] - pub enum DiagnosticKind { - $( - $name($mod::$name), - )+ - } - - impl RuleCode { - /// A placeholder representation of the `DiagnosticKind` for the diagnostic. - pub fn kind(&self) -> DiagnosticKind { - match self { - $( - RuleCode::$code => DiagnosticKind::$name(<$mod::$name as Violation>::placeholder()), - )+ - } - } - - pub fn origin(&self) -> RuleOrigin { - match self { - $( - RuleCode::$code => ruff_macros::origin_by_code!($code), - )+ - } - } - } - - impl DiagnosticKind { - /// A four-letter shorthand code for the diagnostic. - pub fn code(&self) -> &'static RuleCode { - match self { - $( - DiagnosticKind::$name(..) => &RuleCode::$code, - )+ - } - } - - /// The body text for the diagnostic. - pub fn body(&self) -> String { - match self { - $( - DiagnosticKind::$name(x) => Violation::message(x), - )+ - } - } - - /// Whether the diagnostic is (potentially) fixable. - pub fn fixable(&self) -> bool { - match self { - $( - DiagnosticKind::$name(x) => x.autofix_title_formatter().is_some(), - )+ - } - } - - /// The message used to describe the fix action for a given `DiagnosticKind`. - pub fn commit(&self) -> Option { - match self { - $( - DiagnosticKind::$name(x) => x.autofix_title_formatter().map(|f| f(x)), - )+ - } - } - } - - $( - impl From<$mod::$name> for DiagnosticKind { - fn from(x: $mod::$name) -> Self { - DiagnosticKind::$name(x) - } - } - )+ - - }; -} - -define_rule_mapping!( +ruff_macros::define_rule_mapping!( // pycodestyle errors E401 => violations::MultipleImportsOnOneLine, E402 => violations::ModuleImportNotAtTopOfFile,