Skip to content

Commit fe1d1f8

Browse files
committed
wip
1 parent d0fe41c commit fe1d1f8

File tree

7 files changed

+522
-18
lines changed

7 files changed

+522
-18
lines changed

module/core/former/plan.md

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,15 +151,49 @@
151151
* Test Matrix: Not applicable for this refactoring increment directly, but existing tests cover behavior.
152152
* Commit Message: `refactor(former_meta): Improve unit variant handling using macro_tools`
153153

154-
* [] **Increment 6: Implement Generalizations (New Utilities in `macro_tools`)**
154+
* [] **Increment 6: Implement Generalizations (New Utilities in `macro_tools`)**
155155
* Target Crate(s): `macro_tools`
156-
* Pre-Analysis: Review the approved new utilities for `macro_tools` from Increment 4.
157-
* Detailed Plan Step 1: Implement the new general-purpose utilities in the appropriate modules within `macro_tools/src/`.
158-
* Detailed Plan Step 2: Add comprehensive unit tests for these new utilities within `macro_tools/tests/inc/`. Each new public function/method should have corresponding tests.
159-
* Detailed Plan Step 3: Update `macro_tools/src/lib.rs` and relevant module files (`mod.rs`) to correctly export the new utilities under the appropriate namespaces (`own`, `orphan`, `exposed`, `prelude`).
160-
* Detailed Plan Step 4: Add clear ///doc comments for all new public items in `macro_tools`.
161-
* Crucial Design Rules: [Traits: Encourage Modular Design], [Visibility: Keep Implementation Details Private], [Comments and Documentation].
162-
* Verification Strategy: User applies changes. `cargo test --package macro_tools` must pass. `cargo doc --package macro_tools --no-deps` should build successfully.
156+
* Pre-Analysis: Review the approved new utilities for `macro_tools` from Increment 4. These are:
157+
1. `macro_tools::ident::new_ident_from_cased_str`
158+
2. `macro_tools::generic_params::GenericsRef` enhanced methods:
159+
* `impl_generics_tokens_if_any()`
160+
* `ty_generics_tokens_if_any()`
161+
* `where_clause_tokens_if_any()`
162+
* `type_path_tokens_if_any()`
163+
* (And the conceptual private helper `split_for_impl_syn_components` or equivalent logic to access decomposed generic parts).
164+
* Detailed Plan Step 1: Implement these utilities in `module/core/macro_tools/src/ident.rs` and `module/core/macro_tools/src/generic_params.rs`.
165+
* Detailed Plan Step 2: Add comprehensive unit tests for these new utilities. This will involve creating new test files or extending existing ones in `module/core/macro_tools/tests/inc/` (e.g., a new `ident_general_tests.rs`, `generic_params_ref_tests.rs` or similar, and updating `module/core/macro_tools/tests/inc/mod.rs`).
166+
* Detailed Plan Step 3: Update `module/core/macro_tools/src/lib.rs` and relevant module files (`ident.rs`, `generic_params.rs` themselves if they define `pub` items, or their parent `mod.rs` if they are submodules) to correctly export the new public utilities.
167+
* Detailed Plan Step 4: Add clear `///doc` comments for all new public items in `macro_tools`.
168+
* Crucial Design Rules: [Traits: Encourage Modular Design], [Visibility: Keep Implementation Details Private], [Comments and Documentation], [Testing: Plan with a Test Matrix When Writing Tests].
169+
* Relevant Behavior Rules: N/A directly, but API design should be robust and adhere to Rust conventions.
170+
* Verification Strategy:
171+
* User applies changes to `macro_tools`.
172+
* `cargo check --package macro_tools` must pass.
173+
* `cargo test --package macro_tools` must pass.
174+
* `cargo doc --package macro_tools --no-deps` should build successfully.
175+
* `cargo clippy --package macro_tools --all-targets -- -D warnings` should pass.
176+
* Test Matrix:
177+
* **For `new_ident_from_cased_str` (in `macro_tools::ident`):**
178+
* ID: T6.1, Input: (`"normal_ident"`, `span`, `false`), Expected: `Ok(syn::Ident::new("normal_ident", span))`
179+
* ID: T6.2, Input: (`"fn"`, `span`, `false`), Expected: `Ok(syn::Ident::new_raw("fn", span))` (keyword becomes raw)
180+
* ID: T6.3, Input: (`"fn"`, `span`, `true`), Expected: `Ok(syn::Ident::new_raw("fn", span))` (original raw, cased is keyword)
181+
* ID: T6.4, Input: (`"my_raw_ident"`, `span`, `true`), Expected: `Ok(syn::Ident::new_raw("my_raw_ident", span))` (original raw, cased not keyword)
182+
* ID: T6.5, Input: (`""`, `span`, `false`), Expected: `Err(_)` (empty string)
183+
* ID: T6.6, Input: (`"with space"`, `span`, `false`), Expected: `Err(_)` (invalid ident chars)
184+
* ID: T6.7, Input: (`"ValidIdent"`, `span`, `false`), Expected: `Ok(syn::Ident::new("ValidIdent", span))` (function assumes input is already cased as desired for the ident name itself, only keyword/raw status is handled).
185+
* **For `GenericsRef` methods (in `macro_tools::generic_params`):**
186+
* (Setup: `let generics_std: syn::Generics = syn::parse_quote! { <T: Display + 'a, 'a, const N: usize> where T: Debug > };`)
187+
* (Setup: `let generics_empty: syn::Generics = syn::parse_quote! { };`)
188+
* (Setup: `let enum_name: syn::Ident = syn::parse_quote! { MyEnum };`)
189+
* ID: T6.8 (`impl_generics_tokens_if_any` with `generics_std`): Expected: `Ok(quote!( <T: Display + 'a, 'a, const N: usize> ))`
190+
* ID: T6.9 (`impl_generics_tokens_if_any` with `generics_empty`): Expected: `Ok(quote!( ))`
191+
* ID: T6.10 (`ty_generics_tokens_if_any` with `generics_std`): Expected: `Ok(quote!( <T, 'a, N> ))`
192+
* ID: T6.11 (`ty_generics_tokens_if_any` with `generics_empty`): Expected: `Ok(quote!( ))`
193+
* ID: T6.12 (`where_clause_tokens_if_any` with `generics_std`): Expected: `Ok(quote!( where T: Debug ))`
194+
* ID: T6.13 (`where_clause_tokens_if_any` with `generics_empty`): Expected: `Ok(quote!( ))`
195+
* ID: T6.14 (`type_path_tokens_if_any` with `generics_std`, `enum_name`): Expected: `Ok(quote!( MyEnum::<T, 'a, N> ))`
196+
* ID: T6.15 (`type_path_tokens_if_any` with `generics_empty`, `enum_name`): Expected: `Ok(quote!( MyEnum ))`
163197
* Commit Message: `feat(macro_tools): Add new utilities generalized from former_meta enum handling`
164198

165199
* [] **Increment 7: Final Verification and Documentation Update**

module/core/former_meta/src/derive_former/former_enum/unit_variant_handler.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use macro_tools::
55
diag, // For diag::return_syn_err!
66
generic_params::GenericsRef, // For enhanced generics handling
77
ident, // For proposed ident::new_ident_from_cased_str
8-
tokens::qt, // For qt! macro, if preferred over quote::quote!
8+
qt, // For qt! macro, if preferred over quote::quote!
99
syn,
1010
quote::quote_spanned, // Keep for specific span control if needed, or replace with qt!
1111
};

module/core/macro_tools/src/generic_params.rs

Lines changed: 199 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,204 @@ mod private
9393
}
9494
}
9595

96+
/// A wrapper around a reference to `syn::Generics` to provide convenient helper methods
97+
/// for generating token streams related to generic parameters.
98+
///
99+
/// This is particularly useful in procedural macros for constructing parts of function
100+
/// signatures, type paths, and where clauses that involve generics.
101+
#[derive(Debug, Clone, Copy)]
102+
pub struct GenericsRef<'a>
103+
{
104+
syn_generics: &'a syn::Generics,
105+
}
106+
107+
impl<'a> GenericsRef<'a>
108+
{
109+
/// Creates a new `GenericsRef` from a reference to `syn::Generics`.
110+
#[must_use]
111+
pub fn new_borrowed(syn_generics: &'a syn::Generics) -> Self
112+
{
113+
Self { syn_generics }
114+
}
115+
116+
/// Returns the `impl_generics` part (e.g., `<T: Trait, 'b, const C: usize>`)
117+
/// as a `TokenStream` if generics are present, otherwise an empty `TokenStream`.
118+
///
119+
/// This is suitable for use in `impl <#impl_generics> Struct ...` contexts.
120+
/// It includes bounds and lifetimes.
121+
///
122+
/// # Errors
123+
///
124+
/// Currently, this method is not expected to return an error, but returns `Result`
125+
/// for future-proofing and consistency with other token-generating methods.
126+
pub fn impl_generics_tokens_if_any(&self) -> Result<proc_macro2::TokenStream>
127+
{
128+
if self.syn_generics.params.is_empty()
129+
{
130+
return Ok(quote::quote! {});
131+
}
132+
let (_, impl_g, _, _) = decompose_item_soft(self.syn_generics);
133+
Ok(quote::quote! { < #impl_g > })
134+
}
135+
136+
/// Returns the `ty_generics` part (e.g., `<T, 'b, C>`) as a `TokenStream`
137+
/// if generics are present, otherwise an empty `TokenStream`.
138+
///
139+
/// This is suitable for use in type paths like `Struct::<#ty_generics>`.
140+
/// It includes only the identifiers of the generic parameters (types, lifetimes, consts).
141+
///
142+
/// # Errors
143+
///
144+
/// Currently, this method is not expected to return an error, but returns `Result`
145+
/// for future-proofing and consistency.
146+
pub fn ty_generics_tokens_if_any(&self) -> Result<proc_macro2::TokenStream>
147+
{
148+
if self.syn_generics.params.is_empty()
149+
{
150+
return Ok(quote::quote! {});
151+
}
152+
let (_, _, ty_g, _) = decompose_item_soft(self.syn_generics);
153+
Ok(quote::quote! { < #ty_g > })
154+
}
155+
156+
/// Returns the `where_clause` (e.g., `where T: Trait`) as a `TokenStream`
157+
/// if a where clause is present in the original generics, otherwise an empty `TokenStream`.
158+
///
159+
/// # Errors
160+
///
161+
/// Currently, this method is not expected to return an error, but returns `Result`
162+
/// for future-proofing and consistency.
163+
pub fn where_clause_tokens_if_any(&self) -> Result<proc_macro2::TokenStream>
164+
{
165+
let (_, _, _, where_c_punctuated) = decompose_item_soft(self.syn_generics);
166+
if where_c_punctuated.is_empty() {
167+
Ok(quote::quote! {})
168+
} else {
169+
Ok(quote::quote! { where #where_c_punctuated })
170+
}
171+
}
172+
173+
/// Returns a token stream representing a path to a type, including its generic arguments
174+
/// if present (e.g., `MyType::<T, U>`). If no generics are present, it returns
175+
/// just the `base_ident`.
176+
///
177+
/// # Arguments
178+
///
179+
/// * `base_ident`: The identifier of the base type (e.g., `MyType`).
180+
///
181+
/// # Errors
182+
///
183+
/// Currently, this method is not expected to return an error, but returns `Result`
184+
/// for future-proofing and consistency.
185+
pub fn type_path_tokens_if_any(&self, base_ident: &syn::Ident) -> Result<proc_macro2::TokenStream>
186+
{
187+
if self.syn_generics.params.is_empty()
188+
{
189+
Ok(quote::quote! { #base_ident })
190+
} else
191+
{
192+
let (_, _, ty_g, _) = decompose_item_soft(self.syn_generics);
193+
Ok(quote::quote! { #base_ident ::< #ty_g > })
194+
}
195+
}
196+
}
197+
198+
// Helper function similar to the original `decompose`.
199+
#[allow(clippy::type_complexity)]
200+
fn decompose_item_soft
201+
(
202+
generics: &syn::Generics,
203+
) ->
204+
(
205+
syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>, // with_defaults
206+
syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>, // for_impl
207+
syn::punctuated::Punctuated<syn::GenericParam, syn::token::Comma>, // for_ty
208+
syn::punctuated::Punctuated<syn::WherePredicate, syn::token::Comma>, // where_clause
209+
)
210+
{
211+
let mut generics_with_defaults = generics.params.clone();
212+
punctuated::ensure_trailing_comma(&mut generics_with_defaults);
213+
214+
let mut generics_for_impl = syn::punctuated::Punctuated::new();
215+
let mut generics_for_ty = syn::punctuated::Punctuated::new();
216+
217+
for param in &generics.params {
218+
match param {
219+
syn::GenericParam::Type(type_param) => {
220+
let impl_param = syn::GenericParam::Type(syn::TypeParam {
221+
attrs: vec![],
222+
ident: type_param.ident.clone(),
223+
colon_token: type_param.colon_token,
224+
bounds: type_param.bounds.clone(),
225+
eq_token: None,
226+
default: None,
227+
});
228+
generics_for_impl.push_value(impl_param);
229+
generics_for_impl.push_punct(syn::token::Comma::default());
230+
231+
let ty_param = syn::GenericParam::Type(syn::TypeParam {
232+
attrs: vec![],
233+
ident: type_param.ident.clone(),
234+
colon_token: None,
235+
bounds: syn::punctuated::Punctuated::new(),
236+
eq_token: None,
237+
default: None,
238+
});
239+
generics_for_ty.push_value(ty_param);
240+
generics_for_ty.push_punct(syn::token::Comma::default());
241+
}
242+
syn::GenericParam::Const(const_param) => {
243+
let impl_param = syn::GenericParam::Const(syn::ConstParam {
244+
attrs: vec![],
245+
const_token: const_param.const_token,
246+
ident: const_param.ident.clone(),
247+
colon_token: const_param.colon_token,
248+
ty: const_param.ty.clone(),
249+
eq_token: None,
250+
default: None,
251+
});
252+
generics_for_impl.push_value(impl_param);
253+
generics_for_impl.push_punct(syn::token::Comma::default());
254+
255+
let ty_param = syn::GenericParam::Type(syn::TypeParam { // Const params are represented by their idents for ty_generics
256+
attrs: vec![],
257+
ident: const_param.ident.clone(),
258+
colon_token: None,
259+
bounds: syn::punctuated::Punctuated::new(),
260+
eq_token: None,
261+
default: None,
262+
});
263+
generics_for_ty.push_value(ty_param);
264+
generics_for_ty.push_punct(syn::token::Comma::default());
265+
}
266+
syn::GenericParam::Lifetime(lifetime_param) => {
267+
generics_for_impl.push_value(syn::GenericParam::Lifetime(lifetime_param.clone()));
268+
generics_for_impl.push_punct(syn::token::Comma::default());
269+
270+
let ty_param = syn::GenericParam::Lifetime(syn::LifetimeParam {
271+
attrs: vec![],
272+
lifetime: lifetime_param.lifetime.clone(),
273+
colon_token: None,
274+
bounds: syn::punctuated::Punctuated::new(),
275+
});
276+
generics_for_ty.push_value(ty_param);
277+
generics_for_ty.push_punct(syn::token::Comma::default());
278+
}
279+
}
280+
}
281+
282+
let generics_where = if let Some(where_clause) = &generics.where_clause {
283+
let mut predicates = where_clause.predicates.clone();
284+
punctuated::ensure_trailing_comma(&mut predicates);
285+
predicates
286+
} else {
287+
syn::punctuated::Punctuated::new()
288+
};
289+
290+
(generics_with_defaults, generics_for_impl, generics_for_ty, generics_where)
291+
}
292+
293+
96294
/// Merges two `syn::Generics` instances into a new one.
97295
///
98296
/// This function takes two references to `syn::Generics` and combines their
@@ -147,7 +345,6 @@ mod private
147345
};
148346

149347
// Merge params
150-
// result.params.extend( a.params.iter().chain( b.params.iter() ) );
151348
for param in &a.params
152349
{
153350
result.params.push( param.clone() );
@@ -213,7 +410,6 @@ mod private
213410
#[ must_use ]
214411
pub fn only_names( generics : &syn::Generics ) -> syn::Generics
215412
{
216-
// use syn::{ Generics, GenericParam, LifetimeDef, TypeParam, ConstParam };
217413
use syn::{ Generics, GenericParam, LifetimeParam, TypeParam, ConstParam };
218414

219415
let result = Generics
@@ -291,11 +487,6 @@ mod private
291487
#[ must_use ]
292488
pub fn names( generics : &syn::Generics )
293489
-> impl IterTrait< '_, &syn::Ident >
294-
// -> std::iter::Map
295-
// <
296-
// syn::punctuated::Iter< 'a, syn::GenericParam >,
297-
// impl FnMut( &'a syn::GenericParam ) -> &'a syn::Ident + 'a,
298-
// >
299490
{
300491
generics.params.iter().map( | param | match param
301492
{
@@ -531,6 +722,7 @@ pub mod own
531722
only_names,
532723
names,
533724
decompose,
725+
GenericsRef,
534726
};
535727
}
536728

module/core/macro_tools/src/ident.rs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,67 @@ mod private
4444
ident.clone()
4545
}
4646
}
47+
48+
/// Creates a `syn::Ident` from a string that is already in the target case.
49+
/// Handles Rust keywords and original raw identifier status.
50+
/// If `cased_name_str` is a keyword, or if `source_had_raw_prefix` is true,
51+
/// `syn::Ident::new_raw` is used. Otherwise, `syn::Ident::new` is used.
52+
///
53+
/// Returns an error if `cased_name_str` is empty or an invalid identifier.
54+
pub fn new_ident_from_cased_str
55+
(
56+
cased_name_str: &str,
57+
span: proc_macro2::Span,
58+
source_had_raw_prefix: bool
59+
) -> Result<syn::Ident> // Use local Result<T> alias
60+
{
61+
if cased_name_str.is_empty() {
62+
return Err(syn::Error::new(span, "Cannot create identifier from empty string"));
63+
}
64+
65+
// Comprehensive list of Rust 2021 keywords that are problematic as idents.
66+
// Based on https://doc.rust-lang.org/reference/keywords.html
67+
const RUST_KEYWORDS: &[&str] = &[
68+
// Strict keywords
69+
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
70+
"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub",
71+
"ref", "return", "self", "Self", "static", "struct", "super", "trait", "true",
72+
"type", "unsafe", "use", "where", "while",
73+
// Reserved keywords
74+
"abstract", "async", "await", "become", "box", "do", "final", "macro", "override",
75+
"priv", "try", "typeof", "unsized", "virtual", "yield",
76+
// Weak keywords
77+
"dyn", "union",
78+
];
79+
80+
let is_keyword = RUST_KEYWORDS.contains(&cased_name_str);
81+
82+
if source_had_raw_prefix || is_keyword {
83+
// Validate if the string is permissible for new_raw, even if it's a keyword.
84+
// For example, "123" is not a keyword but also not valid for new_raw("123", span).
85+
// A simple validation is to check if it would parse if it *weren't* a keyword.
86+
// This is tricky because `syn::parse_str` would fail for actual keywords.
87+
// Let's rely on `syn::Ident::new_raw` to do its job, but catch obvious non-ident chars.
88+
if cased_name_str.chars().any(|c| !c.is_alphanumeric() && c != '_') {
89+
if !( cased_name_str.starts_with('_') && cased_name_str.chars().skip(1).all(|c| c.is_alphanumeric() || c == '_') ) && cased_name_str != "_" {
90+
return Err(syn::Error::new(span, format!("Invalid characters in identifier string for raw creation: {}", cased_name_str)));
91+
}
92+
}
93+
Ok(syn::Ident::new_raw(cased_name_str, span))
94+
} else {
95+
// Not a keyword and source was not raw. Try to create a normal identifier.
96+
// syn::Ident::new would panic on keywords, but we've established it's not a keyword.
97+
// It will also panic on other invalid idents like "123" or "with space".
98+
// To provide a Result, we attempt to parse it.
99+
match syn::parse_str::<syn::Ident>(cased_name_str) {
100+
Ok(ident) => Ok(ident),
101+
Err(_e) => {
102+
// Construct a new error, because the error from parse_str might not have the right span or context.
103+
Err(syn::Error::new(span, format!("Invalid identifier string: '{}'", cased_name_str)))
104+
}
105+
}
106+
}
107+
}
47108
}
48109

49110
#[ doc( inline ) ]
@@ -59,7 +120,9 @@ pub mod own
59120
#[ doc( inline ) ]
60121
pub use orphan::*;
61122
#[ doc( inline ) ]
62-
pub use private::ident_maybe_raw; // Export the renamed function
123+
pub use private::ident_maybe_raw;
124+
#[ doc( inline ) ]
125+
pub use private::new_ident_from_cased_str;
63126
}
64127

65128
/// Orphan namespace of the module.

0 commit comments

Comments
 (0)