Skip to content

Commit

Permalink
Finish refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
drewcassidy committed Jun 14, 2023
1 parent 9a9e626 commit 7ce8eab
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 90 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

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

## 0.2.0 - 2023-06-14

### Changed

- Major refactor adding more helpful diagnostics

### Added

- Added the optional `fmt` argument for formatting the identifiers of the generated functions


## 0.1.0 - 2022-08-14

first release
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "generic_parameterize"
version = "0.1.0"
version = "0.2.0"
repository = "https://github.com/drewcassidy/generic-parameterize"
authors = ["Andrew Cassidy <[email protected]>"]
description = "A test parameterization macro that works on generic arguments"
Expand Down
140 changes: 58 additions & 82 deletions src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,96 +16,85 @@ use syn::{Expr, GenericParam, Lit, LitStr, Type};

use crate::params::Param;

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

/// One argument in the input to the parameterize macro
#[derive(Clone, Debug)]
pub(crate) struct Argument {
pub ident: Ident,
pub value: ArgumentValue,
}

fn parse_typelist(input: ParseStream) -> Option<syn::Result<Argument>> {
let ident = input.parse::<Ident>().ok()?;
input.parse::<syn::token::Eq>().ok()?;
if input.peek(syn::token::Paren) {
// everything after this point is an error instead of a no-match when it fails
let parse = || -> syn::Result<Argument> {
let tt = input.parse::<syn::TypeTuple>()?;
let entries: Vec<Type> = tt.elems.iter().cloned().collect();
Ok(Argument {
ident,
value: ArgumentValue::TypeList(entries),
})
};
Some(parse())
} else {
None
}
/// Parse a parenthesized list of types
fn parse_typelist(input: ParseStream) -> Option<syn::Result<ArgumentValue>> {
let parse = || {
let tt = input.parse::<syn::TypeTuple>()?;
let entries: Vec<Type> = tt.elems.iter().cloned().collect();
Ok(ArgumentValue::TypeList(entries))
};

// match on parentheses. Anything invalid after is an error
input.peek(syn::token::Paren).then_some(parse())
}

fn parse_litlist(input: ParseStream) -> Option<syn::Result<Argument>> {
let ident = input.parse::<Ident>().ok()?;
input.parse::<syn::token::Eq>().ok()?;
if input.peek(syn::token::Bracket) {
// everything after this point is an error instead of a no-match when it fails
let parse = || -> syn::Result<Argument> {
let exprs = input.parse::<syn::ExprArray>()?;
let entries: syn::Result<Vec<Lit>> = exprs
.elems
.iter()
.map(|expr: &Expr| -> syn::Result<Lit> {
return if let Expr::Lit(lit) = expr {
Ok(lit.lit.clone())
} else {
Err(syn::Error::new(expr.span(), "Expression is not a literal"))
};
})
.collect();
Ok(Argument {
ident,
value: ArgumentValue::LitList(entries?),
/// Parse a bracketed list of literals
fn parse_litlist(input: ParseStream) -> Option<syn::Result<ArgumentValue>> {
let parse = || {
let exprs = input.parse::<syn::ExprArray>()?;
let entries: syn::Result<Vec<Lit>> = exprs
.elems
.iter()
.map(|expr: &Expr| -> syn::Result<Lit> {
return if let Expr::Lit(lit) = expr {
Ok(lit.lit.clone())
} else {
Err(syn::Error::new(expr.span(), "Expression is not a literal"))
};
})
};
.collect();
Ok(ArgumentValue::LitList(entries?))
};

Some(parse())
} else {
None
}
// match on brackets. anything invalid after is an error
input.peek(syn::token::Bracket).then_some(parse())
}

fn parse_str(input: ParseStream) -> Option<syn::Result<Argument>> {
let ident = input.parse::<Ident>().ok()?;
input.parse::<syn::token::Eq>().ok()?;

// everything after this point is an error instead of a no-match when it fails

let parse = || -> syn::Result<Argument> {
let value = input.parse::<LitStr>()?.value();

Ok(Argument {
ident,
value: ArgumentValue::Str(value),
})
};

Some(parse())
/// Parse a string argument
fn parse_str(input: ParseStream) -> Option<syn::Result<ArgumentValue>> {
// no way for a string argument parse to fail, it either matches or it doesnt
input
.parse::<LitStr>()
.ok()
.map(|lit| Ok(ArgumentValue::Str(lit.value())))
}

impl Parse for Argument {
fn parse(input: ParseStream) -> syn::Result<Self> {
// parse the ident and equals sign
let ident = input.parse::<Ident>()?;
input.parse::<syn::token::Eq>()?;

// iterate over the known parse functions for arguments
[parse_typelist, parse_litlist, parse_str]
.iter()
.find_map(|f| {
// fork the buffer, so we can rewind if there isnt a match
let fork = input.fork();
if let Some(arg) = (*f)(&fork) {

// if the parse function returns a match, return a syn::Result<Argument>,
// otherwise None to advance to the next parse function
if let Some(value) = (*f)(&fork) {
input.advance_to(&fork);
Some(arg)
Some(value.map(|v| Self {
ident: ident.clone(),
value: v,
}))
} else {
None
}
Expand All @@ -115,30 +104,14 @@ impl Parse for Argument {
}

impl Argument {
pub fn short_name(&self) -> &str {
/// Get a user-friendly name for the type of argument this is
pub fn short_type(&self) -> &str {
match self.value {
ArgumentValue::TypeList(_) => "type list",
ArgumentValue::LitList(_) => "const list",
ArgumentValue::Str(_) => "string",
}
}
// pub fn match_paramlist(&self, gp: &GenericParam) -> Option<syn::Result<Vec<(Ident, Param)>>> {
// match (&self.ident, &self.value, gp) {
// (id, ArgumentValue::TypeList(tl), GenericParam::Type(tp)) if id == &tp.ident => Some(
// tl.iter()
// .map(|ty| (id.clone(), Param::Type(ty.clone())))
// .collect(),
// ),
//
// (id, ArgumentValue::LitList(ll), GenericParam::Const(cp)) if id == &cp.ident => Some(
// ll.iter()
// .map(|lit| (id.clone(), Param::Lit(lit.clone())))
// .collect(),
// ),
//
// _ => None,
// }
// }
}

/// A list of arguments input to the macro
Expand Down Expand Up @@ -172,6 +145,9 @@ impl Extract for ArgumentList {
}

impl ArgumentList {
/// consume a paramlist from the argument list that matches the given generic parameter
/// and return it.
/// Returns an error if there is a type mismatch, or if there is not exactly one match
pub fn consume_paramlist(&mut self, gp: &GenericParam) -> syn::Result<Vec<(Ident, Param)>> {
let (g_ident, g_name) = match gp {
GenericParam::Lifetime(lt) => Err(syn::Error::new(
Expand All @@ -196,9 +172,9 @@ impl ArgumentList {
),
(ArgumentValue::TypeList(_), _) | (ArgumentValue::LitList(_), _) => Some(Err(syn::Error::new(
arg.ident.span(),
format!("Mismatched parameterization: Expected {} list but found {}", g_name, arg.short_name()),
format!("Mismatched parameterization: Expected {} list but found {}", g_name, arg.short_type()),
))),
/* fall through, in case theres a generic argument named for example "fmt". there probably shouldn't be though*/
/* fall through, in case theres a generic argument named for example "fmt". there probably shouldn't be though */
(_, _) => None }
} else {
None
Expand Down
33 changes: 26 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,23 @@ fn format_params(fmt: &Option<String>, fn_ident: &Ident, params: Vec<&(Ident, Pa

/// Expand a generic test function with the given parameter matrix
///
///
/// # Arguments
/// Arguments are provided in the [_MetaListNameValueStr_][mlnvs] format. Every argument consists
/// of an identifier, an equals sign, and a value. Arguments are seperated by commas.
///
/// ## Type Lists
/// Type lists are passed using the tuple syntax. There must be exactly one type list argument for every
/// generic type parameter in the target function
///
/// ## Const Lists
/// Const lists are passed using the array syntax, however expressions are not allowed, only literals.
/// There must be exactly one const list argument for every generic const parameter in the target function
///
/// A comma separated list of identifiers and their values.
/// Types are passed using a tuple syntax, and literals are passed using an array syntax
/// ## Format String
/// An optional format string can be passed with the ident `fmt`. This uses a syntax similar to [`format!`](std::format),
/// however the colon and everything after it is not supported; only the identifier for each
/// parameter and `fn` for the name of the target function
///
///
/// # Examples
Expand All @@ -56,7 +69,7 @@ fn format_params(fmt: &Option<String>, fn_ident: &Ident, params: Vec<&(Ident, Pa
/// use generic_parameterize::parameterize;
/// use std::fmt::Debug;
///
/// #[parameterize(T = (i32, f32), N = [4,5,6])]
/// #[parameterize(T = (i32, f32), N = [4,5,6], fmt = "{fn}_{T}x{N}")]
/// #[test]
/// fn test_array<T: Default, const N : usize>() where [T;N]: Default + Debug{
/// let foo: [T;N] = Default::default();
Expand All @@ -76,13 +89,16 @@ fn format_params(fmt: &Option<String>, fn_ident: &Ident, params: Vec<&(Ident, Pa
/// }
///
/// #[test]
/// fn test_array_i32_4() {test_array::<i32,4>();}
/// fn test_array_i32x4() {test_array::<i32,4>();}
/// #[test]
/// fn test_array_f32_4() {test_array::<f32,4>();}
/// fn test_array_f32x4() {test_array::<f32,4>();}
/// #[test]
/// fn test_array_i32_5() {test_array::<i32,5>();}
/// fn test_array_i32x5() {test_array::<i32,5>();}
/// // etc...
/// }
/// ```
///
/// [mlnvs]: https://doc.rust-lang.org/reference/attributes.html#meta-item-attribute-syntax
fn parameterize_impl(mut args: ArgumentList, mut inner: ItemFn) -> syn::Result<TokenStream> {
let inner_ident = inner.sig.ident.clone();
let output = inner.sig.output.clone();
Expand Down Expand Up @@ -116,10 +132,12 @@ fn parameterize_impl(mut args: ArgumentList, mut inner: ItemFn) -> syn::Result<T
for arg in args.args.iter() {
return Err(syn::Error::new(
arg.ident.span(),
format!("Unexpected {} argument `{}`", arg.short_name(), arg.ident),
format!("Unexpected {} argument `{}`", arg.short_type(), arg.ident),
));
}

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

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

0 comments on commit 7ce8eab

Please sign in to comment.