Skip to content
This repository was archived by the owner on Oct 26, 2025. It is now read-only.

Commit 7ce8eab

Browse files
committed
Finish refactor
1 parent 9a9e626 commit 7ce8eab

File tree

4 files changed

+96
-90
lines changed

4 files changed

+96
-90
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
All notable changes to this project will be documented in this file
44

5+
## 0.2.0 - 2023-06-14
6+
7+
### Changed
8+
9+
- Major refactor adding more helpful diagnostics
10+
11+
### Added
12+
13+
- Added the optional `fmt` argument for formatting the identifiers of the generated functions
14+
15+
516
## 0.1.0 - 2022-08-14
617

718
first release

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "generic_parameterize"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
repository = "https://github.com/drewcassidy/generic-parameterize"
55
authors = ["Andrew Cassidy <[email protected]>"]
66
description = "A test parameterization macro that works on generic arguments"

src/arguments.rs

Lines changed: 58 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -16,96 +16,85 @@ use syn::{Expr, GenericParam, Lit, LitStr, Type};
1616

1717
use crate::params::Param;
1818

19-
/// One argument in the input to the parameterize macro
19+
/// The value of an [`Argument`]; everything after the equal sign
2020
#[derive(Clone, Debug)]
2121
pub(crate) enum ArgumentValue {
2222
TypeList(Vec<Type>),
2323
LitList(Vec<Lit>),
2424
Str(String),
2525
}
2626

27+
/// One argument in the input to the parameterize macro
2728
#[derive(Clone, Debug)]
2829
pub(crate) struct Argument {
2930
pub ident: Ident,
3031
pub value: ArgumentValue,
3132
}
3233

33-
fn parse_typelist(input: ParseStream) -> Option<syn::Result<Argument>> {
34-
let ident = input.parse::<Ident>().ok()?;
35-
input.parse::<syn::token::Eq>().ok()?;
36-
if input.peek(syn::token::Paren) {
37-
// everything after this point is an error instead of a no-match when it fails
38-
let parse = || -> syn::Result<Argument> {
39-
let tt = input.parse::<syn::TypeTuple>()?;
40-
let entries: Vec<Type> = tt.elems.iter().cloned().collect();
41-
Ok(Argument {
42-
ident,
43-
value: ArgumentValue::TypeList(entries),
44-
})
45-
};
46-
Some(parse())
47-
} else {
48-
None
49-
}
34+
/// Parse a parenthesized list of types
35+
fn parse_typelist(input: ParseStream) -> Option<syn::Result<ArgumentValue>> {
36+
let parse = || {
37+
let tt = input.parse::<syn::TypeTuple>()?;
38+
let entries: Vec<Type> = tt.elems.iter().cloned().collect();
39+
Ok(ArgumentValue::TypeList(entries))
40+
};
41+
42+
// match on parentheses. Anything invalid after is an error
43+
input.peek(syn::token::Paren).then_some(parse())
5044
}
5145

52-
fn parse_litlist(input: ParseStream) -> Option<syn::Result<Argument>> {
53-
let ident = input.parse::<Ident>().ok()?;
54-
input.parse::<syn::token::Eq>().ok()?;
55-
if input.peek(syn::token::Bracket) {
56-
// everything after this point is an error instead of a no-match when it fails
57-
let parse = || -> syn::Result<Argument> {
58-
let exprs = input.parse::<syn::ExprArray>()?;
59-
let entries: syn::Result<Vec<Lit>> = exprs
60-
.elems
61-
.iter()
62-
.map(|expr: &Expr| -> syn::Result<Lit> {
63-
return if let Expr::Lit(lit) = expr {
64-
Ok(lit.lit.clone())
65-
} else {
66-
Err(syn::Error::new(expr.span(), "Expression is not a literal"))
67-
};
68-
})
69-
.collect();
70-
Ok(Argument {
71-
ident,
72-
value: ArgumentValue::LitList(entries?),
46+
/// Parse a bracketed list of literals
47+
fn parse_litlist(input: ParseStream) -> Option<syn::Result<ArgumentValue>> {
48+
let parse = || {
49+
let exprs = input.parse::<syn::ExprArray>()?;
50+
let entries: syn::Result<Vec<Lit>> = exprs
51+
.elems
52+
.iter()
53+
.map(|expr: &Expr| -> syn::Result<Lit> {
54+
return if let Expr::Lit(lit) = expr {
55+
Ok(lit.lit.clone())
56+
} else {
57+
Err(syn::Error::new(expr.span(), "Expression is not a literal"))
58+
};
7359
})
74-
};
60+
.collect();
61+
Ok(ArgumentValue::LitList(entries?))
62+
};
7563

76-
Some(parse())
77-
} else {
78-
None
79-
}
64+
// match on brackets. anything invalid after is an error
65+
input.peek(syn::token::Bracket).then_some(parse())
8066
}
8167

82-
fn parse_str(input: ParseStream) -> Option<syn::Result<Argument>> {
83-
let ident = input.parse::<Ident>().ok()?;
84-
input.parse::<syn::token::Eq>().ok()?;
85-
86-
// everything after this point is an error instead of a no-match when it fails
87-
88-
let parse = || -> syn::Result<Argument> {
89-
let value = input.parse::<LitStr>()?.value();
90-
91-
Ok(Argument {
92-
ident,
93-
value: ArgumentValue::Str(value),
94-
})
95-
};
96-
97-
Some(parse())
68+
/// Parse a string argument
69+
fn parse_str(input: ParseStream) -> Option<syn::Result<ArgumentValue>> {
70+
// no way for a string argument parse to fail, it either matches or it doesnt
71+
input
72+
.parse::<LitStr>()
73+
.ok()
74+
.map(|lit| Ok(ArgumentValue::Str(lit.value())))
9875
}
9976

10077
impl Parse for Argument {
10178
fn parse(input: ParseStream) -> syn::Result<Self> {
79+
// parse the ident and equals sign
80+
let ident = input.parse::<Ident>()?;
81+
input.parse::<syn::token::Eq>()?;
82+
83+
// iterate over the known parse functions for arguments
10284
[parse_typelist, parse_litlist, parse_str]
10385
.iter()
10486
.find_map(|f| {
87+
// fork the buffer, so we can rewind if there isnt a match
10588
let fork = input.fork();
106-
if let Some(arg) = (*f)(&fork) {
89+
90+
// if the parse function returns a match, return a syn::Result<Argument>,
91+
// otherwise None to advance to the next parse function
92+
if let Some(value) = (*f)(&fork) {
10793
input.advance_to(&fork);
108-
Some(arg)
94+
Some(value.map(|v| Self {
95+
ident: ident.clone(),
96+
value: v,
97+
}))
10998
} else {
11099
None
111100
}
@@ -115,30 +104,14 @@ impl Parse for Argument {
115104
}
116105

117106
impl Argument {
118-
pub fn short_name(&self) -> &str {
107+
/// Get a user-friendly name for the type of argument this is
108+
pub fn short_type(&self) -> &str {
119109
match self.value {
120110
ArgumentValue::TypeList(_) => "type list",
121111
ArgumentValue::LitList(_) => "const list",
122112
ArgumentValue::Str(_) => "string",
123113
}
124114
}
125-
// pub fn match_paramlist(&self, gp: &GenericParam) -> Option<syn::Result<Vec<(Ident, Param)>>> {
126-
// match (&self.ident, &self.value, gp) {
127-
// (id, ArgumentValue::TypeList(tl), GenericParam::Type(tp)) if id == &tp.ident => Some(
128-
// tl.iter()
129-
// .map(|ty| (id.clone(), Param::Type(ty.clone())))
130-
// .collect(),
131-
// ),
132-
//
133-
// (id, ArgumentValue::LitList(ll), GenericParam::Const(cp)) if id == &cp.ident => Some(
134-
// ll.iter()
135-
// .map(|lit| (id.clone(), Param::Lit(lit.clone())))
136-
// .collect(),
137-
// ),
138-
//
139-
// _ => None,
140-
// }
141-
// }
142115
}
143116

144117
/// A list of arguments input to the macro
@@ -172,6 +145,9 @@ impl Extract for ArgumentList {
172145
}
173146

174147
impl ArgumentList {
148+
/// consume a paramlist from the argument list that matches the given generic parameter
149+
/// and return it.
150+
/// Returns an error if there is a type mismatch, or if there is not exactly one match
175151
pub fn consume_paramlist(&mut self, gp: &GenericParam) -> syn::Result<Vec<(Ident, Param)>> {
176152
let (g_ident, g_name) = match gp {
177153
GenericParam::Lifetime(lt) => Err(syn::Error::new(
@@ -196,9 +172,9 @@ impl ArgumentList {
196172
),
197173
(ArgumentValue::TypeList(_), _) | (ArgumentValue::LitList(_), _) => Some(Err(syn::Error::new(
198174
arg.ident.span(),
199-
format!("Mismatched parameterization: Expected {} list but found {}", g_name, arg.short_name()),
175+
format!("Mismatched parameterization: Expected {} list but found {}", g_name, arg.short_type()),
200176
))),
201-
/* fall through, in case theres a generic argument named for example "fmt". there probably shouldn't be though*/
177+
/* fall through, in case theres a generic argument named for example "fmt". there probably shouldn't be though */
202178
(_, _) => None }
203179
} else {
204180
None

src/lib.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,23 @@ fn format_params(fmt: &Option<String>, fn_ident: &Ident, params: Vec<&(Ident, Pa
4141

4242
/// Expand a generic test function with the given parameter matrix
4343
///
44+
///
4445
/// # Arguments
46+
/// Arguments are provided in the [_MetaListNameValueStr_][mlnvs] format. Every argument consists
47+
/// of an identifier, an equals sign, and a value. Arguments are seperated by commas.
48+
///
49+
/// ## Type Lists
50+
/// Type lists are passed using the tuple syntax. There must be exactly one type list argument for every
51+
/// generic type parameter in the target function
52+
///
53+
/// ## Const Lists
54+
/// Const lists are passed using the array syntax, however expressions are not allowed, only literals.
55+
/// There must be exactly one const list argument for every generic const parameter in the target function
4556
///
46-
/// A comma separated list of identifiers and their values.
47-
/// Types are passed using a tuple syntax, and literals are passed using an array syntax
57+
/// ## Format String
58+
/// An optional format string can be passed with the ident `fmt`. This uses a syntax similar to [`format!`](std::format),
59+
/// however the colon and everything after it is not supported; only the identifier for each
60+
/// parameter and `fn` for the name of the target function
4861
///
4962
///
5063
/// # Examples
@@ -56,7 +69,7 @@ fn format_params(fmt: &Option<String>, fn_ident: &Ident, params: Vec<&(Ident, Pa
5669
/// use generic_parameterize::parameterize;
5770
/// use std::fmt::Debug;
5871
///
59-
/// #[parameterize(T = (i32, f32), N = [4,5,6])]
72+
/// #[parameterize(T = (i32, f32), N = [4,5,6], fmt = "{fn}_{T}x{N}")]
6073
/// #[test]
6174
/// fn test_array<T: Default, const N : usize>() where [T;N]: Default + Debug{
6275
/// let foo: [T;N] = Default::default();
@@ -76,13 +89,16 @@ fn format_params(fmt: &Option<String>, fn_ident: &Ident, params: Vec<&(Ident, Pa
7689
/// }
7790
///
7891
/// #[test]
79-
/// fn test_array_i32_4() {test_array::<i32,4>();}
92+
/// fn test_array_i32x4() {test_array::<i32,4>();}
8093
/// #[test]
81-
/// fn test_array_f32_4() {test_array::<f32,4>();}
94+
/// fn test_array_f32x4() {test_array::<f32,4>();}
8295
/// #[test]
83-
/// fn test_array_i32_5() {test_array::<i32,5>();}
96+
/// fn test_array_i32x5() {test_array::<i32,5>();}
8497
/// // etc...
8598
/// }
99+
/// ```
100+
///
101+
/// [mlnvs]: https://doc.rust-lang.org/reference/attributes.html#meta-item-attribute-syntax
86102
fn parameterize_impl(mut args: ArgumentList, mut inner: ItemFn) -> syn::Result<TokenStream> {
87103
let inner_ident = inner.sig.ident.clone();
88104
let output = inner.sig.output.clone();
@@ -116,10 +132,12 @@ fn parameterize_impl(mut args: ArgumentList, mut inner: ItemFn) -> syn::Result<T
116132
for arg in args.args.iter() {
117133
return Err(syn::Error::new(
118134
arg.ident.span(),
119-
format!("Unexpected {} argument `{}`", arg.short_name(), arg.ident),
135+
format!("Unexpected {} argument `{}`", arg.short_type(), arg.ident),
120136
));
121137
}
122138

139+
// Produce a list of param values for every iteration,
140+
// iterate over them, and map them to wrapper functions
123141
let (wrapper_idents, wrappers): (Vec<_>, Vec<_>) = param_lists
124142
.iter()
125143
.multi_cartesian_product()
@@ -155,6 +173,7 @@ fn parameterize_impl(mut args: ArgumentList, mut inner: ItemFn) -> syn::Result<T
155173
};
156174
let wrapper_len = wrappers.len();
157175

176+
// Make the module that we're replacing the function with
158177
let module: syn::ItemMod = syn::parse_quote! {
159178
/// Autogenerated test module for a generic test function
160179
pub mod #inner_ident {

0 commit comments

Comments
 (0)