use proc_macro::TokenStream;
use proc_macro_crate::{FoundCrate, crate_name};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{DeriveInput, Generics, Ident, Type, Visibility};
mod analyze;
mod cast;
mod codegen;
mod model;
mod parse;
use analyze::{error_kinds_for_markers, extract_inner_ty};
use cast::{
SUPPORTED_RANGE_TYPES, is_supported_float_type, is_supported_int_type, is_supported_range_type,
validate_cast_config,
};
use model::ErrorKind;
use parse::parse_vouched_args;
struct DerivePlan {
markers: Vec<model::Marker>,
validated_cast_types: Vec<Type>,
inner_ty: Type,
error_kinds: Vec<ErrorKind>,
error_ident: Ident,
error_vis: Visibility,
core_path: TokenStream2,
}
pub fn derive_vouched(input: &DeriveInput) -> TokenStream {
let plan = match build_plan(input) {
Ok(v) => v,
Err(e) => return e.to_compile_error().into(),
};
TokenStream::from(expand_derive(input, &plan))
}
fn build_plan(input: &DeriveInput) -> syn::Result<DerivePlan> {
let (markers, cast_config, configured_error_config) = parse_vouched_args(&input.attrs)?;
let inner_ty = extract_inner_ty(input)?;
validate_range_markers(&markers, &inner_ty)?;
let range_error_kind = range_error_kind_for_type(&inner_ty);
let validated_cast_types = if let Some(config) = cast_config.as_ref() {
validate_cast_config(config, &inner_ty)?
} else {
Vec::new()
};
let mut error_kinds = error_kinds_for_markers(&markers, range_error_kind);
if !validated_cast_types.is_empty() && !error_kinds.contains(&ErrorKind::OutOfRangeInteger) {
error_kinds.push(ErrorKind::OutOfRangeInteger);
}
let configured_error = configured_error_config.unwrap_or_default();
let error_ident = configured_error.ident.unwrap_or_else(|| {
syn::Ident::new(&format!("{}VouchedError", input.ident), input.ident.span())
});
let error_vis = configured_error
.visibility
.unwrap_or_else(|| input.vis.clone());
let core_path = resolve_core_path(input.ident.span());
Ok(DerivePlan {
markers,
validated_cast_types,
inner_ty,
error_kinds,
error_ident,
error_vis,
core_path,
})
}
fn validate_range_markers(markers: &[model::Marker], inner_ty: &Type) -> syn::Result<()> {
if markers
.iter()
.any(|marker| matches!(marker, model::Marker::Range { .. }))
&& !is_supported_range_type(inner_ty)
{
return Err(syn::Error::new_spanned(
inner_ty,
format!(
"range(...) is only supported for integer and float types: {}",
SUPPORTED_RANGE_TYPES.join(", ")
),
));
}
Ok(())
}
fn range_error_kind_for_type(inner_ty: &Type) -> Option<ErrorKind> {
if is_supported_int_type(inner_ty) {
Some(ErrorKind::OutOfRangeInteger)
} else if is_supported_float_type(inner_ty) {
Some(ErrorKind::OutOfRangeFloat)
} else {
None
}
}
fn resolve_core_path(span: proc_macro2::Span) -> TokenStream2 {
match crate_name("vouched") {
Ok(FoundCrate::Itself) => quote! { ::vouched::__private },
Ok(FoundCrate::Name(name)) => {
let ident = Ident::new(&name.replace('-', "_"), span);
quote! { ::#ident::__private }
}
Err(_) => quote! { ::vouched::__private },
}
}
fn expand_derive(input: &DeriveInput, plan: &DerivePlan) -> TokenStream2 {
expand_derive_with_generics(&input.ident, &input.generics, plan)
}
fn expand_derive_with_generics(
name: &Ident,
generics: &Generics,
plan: &DerivePlan,
) -> TokenStream2 {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let core = &plan.core_path;
let error_variants = plan
.error_kinds
.iter()
.map(|k| k.variant(core))
.collect::<Vec<_>>();
let error_display_arms = plan
.error_kinds
.iter()
.map(|k| k.display_arm(&plan.error_ident))
.collect::<Vec<_>>();
let error_source_arms = plan
.error_kinds
.iter()
.map(|k| k.source_arm(&plan.error_ident))
.collect::<Vec<_>>();
let error_as_methods = plan
.error_kinds
.iter()
.map(|k| k.as_method(&plan.error_ident, core))
.collect::<Vec<_>>();
let ordered_markers = plan
.markers
.iter()
.filter(|m| !matches!(m, model::Marker::Chars { .. }))
.chain(
plan.markers
.iter()
.filter(|m| matches!(m, model::Marker::Chars { .. })),
)
.collect::<Vec<_>>();
let checks = ordered_markers
.iter()
.map(|marker| marker.check_tokens(&plan.inner_ty, &plan.error_ident, core))
.collect::<Vec<_>>();
let cast_try_from_impls = plan.validated_cast_types.iter().map(|src_ty| {
let inner_ty = &plan.inner_ty;
let error_ident = &plan.error_ident;
quote! {
impl #impl_generics ::core::convert::TryFrom<#src_ty> for #name #ty_generics #where_clause {
type Error = #error_ident;
fn try_from(src_value: #src_ty) -> ::core::result::Result<Self, Self::Error> {
let value: #inner_ty = <#inner_ty as ::core::convert::TryFrom<#src_ty>>::try_from(src_value)
.map_err(|_| {
#error_ident::OutOfRange(
#core::OutOfRangeIntegerError::new(
#core::IntegerValue::from(src_value),
),
)
})?;
#(
#checks;
)*
::core::result::Result::Ok(Self(value))
}
}
}
});
let inner_ty = &plan.inner_ty;
let error_ident = &plan.error_ident;
let error_vis = &plan.error_vis;
quote! {
#[derive(Debug, Clone, PartialEq, Eq)]
#error_vis enum #error_ident {
#(#error_variants)*
}
impl ::core::fmt::Display for #error_ident {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
match self {
#(#error_display_arms)*
}
}
}
impl ::core::error::Error for #error_ident {
fn source(&self) -> ::core::option::Option<&(dyn ::core::error::Error + 'static)> {
match self {
#(#error_source_arms)*
}
}
}
impl #core::VouchedError for #error_ident {
#(#error_as_methods)*
}
impl #impl_generics ::core::convert::TryFrom<#inner_ty> for #name #ty_generics #where_clause {
type Error = #error_ident;
fn try_from(value: #inner_ty) -> ::core::result::Result<Self, Self::Error> {
#(
#checks;
)*
::core::result::Result::Ok(Self(value))
}
}
#(#cast_try_from_impls)*
}
}