#![forbid(unsafe_op_in_unsafe_fn)]
#![deny(missing_docs)]
#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
use std::collections::HashSet;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
*,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NameValueExpr {
pub path: Ident,
pub eq_token: Token![=],
pub expr: Expr,
}
impl Parse for NameValueExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(NameValueExpr {
path: input.parse()?,
eq_token: input.parse()?,
expr: input.parse()?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldThenParams {
pub field: Field,
pub comma: Option<Token![,]>,
pub params: Punctuated<NameValueExpr, Token![,]>,
}
impl Parse for FieldThenParams {
fn parse(input: ParseStream) -> syn::Result<Self> {
let field = Field::parse_unnamed(input)?;
let comma: Option<Token![,]> = input.parse()?;
let params = if comma.is_some() {
Punctuated::parse_terminated_with(input, NameValueExpr::parse)?
} else {
Punctuated::new()
};
Ok(FieldThenParams {
field,
comma,
params,
})
}
}
pub fn try_extract_inner_type(
ty: &Type,
inner_of: &str,
skip_over: &HashSet<&str>,
) -> (Type, bool) {
if let Type::Path(p) = &ty {
let type_segment = p.path.segments.last().unwrap();
if type_segment.ident == inner_of {
match &type_segment.arguments {
PathArguments::AngleBracketed(p) => {
if let GenericArgument::Type(t) = p.args.first().unwrap().clone() {
(t, true)
} else {
panic!("Argument in angle brackets must be a type")
}
}
_ => (ty.clone(), false),
}
} else if skip_over.contains(type_segment.ident.to_string().as_str()) {
match &type_segment.arguments {
PathArguments::AngleBracketed(p) => {
if let GenericArgument::Type(t) = p.args.first().unwrap().clone() {
let (inner, extracted) = try_extract_inner_type(&t, inner_of, skip_over);
if extracted {
(inner, true)
} else {
(ty.clone(), false)
}
} else {
panic!("Argument in angle brackets must be a type")
}
}
_ => (ty.clone(), false),
}
} else {
(ty.clone(), false)
}
} else {
(ty.clone(), false)
}
}
pub fn filter_inner_type(ty: &Type, skip_over: &HashSet<&str>) -> Type {
if let Type::Path(p) = &ty {
let type_segment = p.path.segments.last().unwrap();
if skip_over.contains(type_segment.ident.to_string().as_str()) {
match &type_segment.arguments {
PathArguments::AngleBracketed(p) => {
if let GenericArgument::Type(t) = p.args.first().unwrap().clone() {
filter_inner_type(&t, skip_over)
} else {
panic!("Argument in angle brackets must be a type")
}
}
_ => ty.clone(),
}
} else {
ty.clone()
}
} else {
ty.clone()
}
}
pub fn wrap_leaf_type(ty: &Type, skip_over: &HashSet<&str>) -> Type {
let mut ty = ty.clone();
if let Type::Path(p) = &mut ty {
let type_segment = p.path.segments.last_mut().unwrap();
if skip_over.contains(type_segment.ident.to_string().as_str()) {
match &mut type_segment.arguments {
PathArguments::AngleBracketed(args) => {
for a in args.args.iter_mut() {
if let syn::GenericArgument::Type(t) = a {
*t = wrap_leaf_type(t, skip_over);
}
}
ty
}
_ => ty,
}
} else {
parse_quote!(adze::WithLeaf<#ty>)
}
} else {
parse_quote!(adze::WithLeaf<#ty>)
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_parse_name_value_expr() {
let input: NameValueExpr = parse_quote!(key = "value");
assert_eq!(input.path.to_string(), "key");
let input: NameValueExpr = parse_quote!(precedence = 5);
assert_eq!(input.path.to_string(), "precedence");
}
#[test]
fn test_parse_field_then_params() {
let input: FieldThenParams = parse_quote!(Type);
assert!(input.comma.is_none());
assert!(input.params.is_empty());
let input: FieldThenParams = parse_quote!(Type, name = "test", value = 42);
assert!(input.comma.is_some());
assert_eq!(input.params.len(), 2);
assert_eq!(input.params[0].path.to_string(), "name");
assert_eq!(input.params[1].path.to_string(), "value");
}
}