#![allow(stable_features)]
#![feature(if_let_guard)]
#![feature(let_chains)]
#![feature(never_type)]
#![feature(iterator_try_collect)]
#![feature(try_trait_v2)]
#![feature(try_trait_v2_residual)]
use proc_macro::TokenStream as TokenStream1;
use proc_macro2::TokenStream as TokenStream2;
use proc_macro2_diagnostic::prelude::*;
use quote::{format_ident, quote};
use syn::{DeriveInput, GenericParam, parse_quote, spanned::Spanned};
mod parse;
use parse::TryEnum;
#[proc_macro_derive(Try)]
pub fn try_trait_v2_derive(input: TokenStream1) -> TokenStream1 {
impl_derive(input.into()).into()
}
fn impl_derive(input: TokenStream2) -> DiagnosticStream {
let ast: DeriveInput = syn::parse2(input).expect("derive macro");
let tryenum = TryEnum::parse(&ast)?;
let (
name,
output_variant_name,
output_type,
_,
residual_type,
impl_generics,
ty_generics,
where_clause,
) = tryenum.split_for_impl();
if !ast
.attrs
.iter()
.any(|attr| attr.meta.path().is_ident("must_use"))
{
warn_spanned(
(),
ast.span(),
"it is recommended to annotate try-types as `#[must_use]`",
)?
};
let (branch_arms, residual_arms) = tryenum.generate_arms();
let impl_try = quote! {
impl #impl_generics std::ops::Try for #name #ty_generics #where_clause {
type Output = #output_type;
type Residual = #residual_type;
#[inline]
fn from_output(output: Self::Output) -> Self {
Self::#output_variant_name(output)
}
#[inline]
fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
match self {
#(#branch_arms)*
}
}
}
impl #impl_generics std::ops::FromResidual<#residual_type> for #name #ty_generics #where_clause {
#[inline]
#[track_caller]
fn from_residual(residual: #residual_type) -> Self {
match residual {
#(#residual_arms)*
}
}
}
impl #impl_generics std::ops::Residual<#output_type> for #residual_type #where_clause {
type TryType = #name #ty_generics;
}
};
Ok(impl_try)
}
#[proc_macro_derive(Try_ConvertResult)]
pub fn try_trait_v2_convert_result(input: TokenStream1) -> TokenStream1 {
impl_convert_result(input.into()).into()
}
fn impl_convert_result(input: TokenStream2) -> DiagnosticStream {
let ast: DeriveInput = syn::parse2(input).expect("derive macro");
let tryenum = TryEnum::parse(&ast)?;
let (name, _, _, output_type_name, residual_type, _, ty_generics, where_clause) =
tryenum.split_for_impl();
let result_e = format_ident!("Derive_TryConvert_ResultE");
let result_t = format_ident!("Derive_TryConvert_ResultT");
let from_result_generics = tryenum.generics(|g| {
g.params
.push(parse_quote! {#result_e: Into<#residual_type>})
});
let (from_result_impl_generics, _, _) = from_result_generics.split_for_impl();
let mut impl_convert = quote! {
impl #from_result_impl_generics std::ops::FromResidual<std::result::Result<std::convert::Infallible, #result_e>> for #name #ty_generics #where_clause
{
#[inline]
#[track_caller]
fn from_residual(residual: std::result::Result<std::convert::Infallible, #result_e>) -> Self {
match residual {
std::result::Result::Err(e) => {
let bang: #residual_type = e.into();
Self::from_residual(bang)
}
}
}
}
};
let to_result_generics = tryenum.generics_with_params(|p| {
p
.filter(|p| !matches!(p, GenericParam::Type(t) if t.ident == *output_type_name))
.chain([
parse_quote! {#result_t},
parse_quote! {#result_e: From<#residual_type>},
])
});
let (to_result_impl_generics, _, _) = to_result_generics.split_for_impl();
impl_convert.extend(quote! {
impl #to_result_impl_generics std::ops::FromResidual<#residual_type> for std::result::Result<#result_t, #result_e>
{
#[inline]
#[track_caller]
fn from_residual(residual: #residual_type) -> Self {
std::result::Result::Err(residual.into())
}
}
});
Ok(impl_convert)
}
#[proc_macro_derive(Try_Iterator)]
pub fn iterator_traits(input: TokenStream1) -> TokenStream1 {
impl_iterator_traits(input.into()).into()
}
fn impl_iterator_traits(input: TokenStream2) -> DiagnosticStream {
let ast: DeriveInput = syn::parse2(input).expect("derive macro");
let tryenum = TryEnum::parse(&ast)?;
let (
name,
output_variant_name,
output_type,
output_type_name,
_,
impl_generics,
ty_generics,
where_clause,
) = tryenum.split_for_impl();
let mut impl_traits = quote! {
impl #impl_generics std::iter::IntoIterator for #name #ty_generics #where_clause {
type Item = #output_type;
type IntoIter = std::option::IntoIter<#output_type>;
fn into_iter(self) -> Self::IntoIter {
let opt = match self {
Self::#output_variant_name(v) => Some(v),
_ => None,
};
opt.into_iter()
}
}
impl #impl_generics #name #ty_generics {
pub fn iter(&self) -> std::option::IntoIter<&#output_type> {
let opt = match self {
Self::#output_variant_name(v) => Some(v),
_ => None,
};
opt.into_iter()
}
pub fn iter_mut(&mut self) -> std::option::IntoIter<&mut #output_type> {
let opt = match self {
Self::#output_variant_name(v) => Some(v),
_ => None,
};
opt.into_iter()
}
}
};
let defined_type = quote! {#name #ty_generics};
let vec_ish = format_ident!("Derive_TryIterator_V");
let full_generics = tryenum.generics(|g| {
g.params
.push(parse_quote! {#vec_ish: FromIterator<#output_type>})
});
let (full_impl_generics, _, full_where_clause) = full_generics.split_for_impl();
let returned_generics = tryenum.generics(|g| {
for param in g.type_params_mut() {
if param.ident == *output_type_name {
*param = parse_quote! {#vec_ish};
break;
}
}
});
let (_, ret_ty_generics, _) = returned_generics.split_for_impl();
impl_traits.extend(quote! {
impl #full_impl_generics std::iter::FromIterator<#defined_type> for #name #ret_ty_generics #full_where_clause
{
fn from_iter<I: IntoIterator<Item=#defined_type>>(iter: I) -> Self {
iter.into_iter().try_collect()
}
}
});
Ok(impl_traits)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn derive() {
let original: TokenStream2 = quote! {
#[derive(Try)]
#[must_use]
enum Exit<T: Termination> {
Ok(T),
TestsFailed,
OtherError(String),
NamedError{err: String, text: String},
}
};
let derived_impl: TokenStream2 = quote! {
impl<T: Termination> std::ops::Try for Exit<T> {
type Output = T;
type Residual = Exit<!>;
#[inline]
fn from_output(output: Self::Output) -> Self {
Self::Ok(output)
}
#[inline]
fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
match self {
Self::Ok(v0) => std::ops::ControlFlow::Continue(v0),
Self::TestsFailed => std::ops::ControlFlow::Break(Exit::TestsFailed),
Self::OtherError(v0) => std::ops::ControlFlow::Break(Exit::OtherError(v0)),
Self::NamedError{err, text} => std::ops::ControlFlow::Break(Exit::NamedError{err, text}),
}
}
}
impl<T: Termination> std::ops::FromResidual<Exit<!> > for Exit<T> {
#[inline]
#[track_caller]
fn from_residual(residual: Exit<!>) -> Self {
match residual {
Exit::TestsFailed => Exit::TestsFailed,
Exit::OtherError(v0) => Exit::OtherError(v0),
Exit::NamedError{err, text} => Exit::NamedError{err, text},
}
}
}
impl<T: Termination> std::ops::Residual<T> for Exit<!> {
type TryType = Exit<T>;
}
};
assert_eq!(
derived_impl.to_string(),
impl_derive(original).unwrap().to_string()
)
}
#[test]
fn convert_result() {
let original: TokenStream2 = quote! {
#[derive(Try_ConvertResult)]
enum Exit<T: Termination, E> {
Ok(T),
TestsFailed,
OtherError(E),
}
};
let expected_impl: TokenStream2 = quote! {
impl<T: Termination, E, Derive_TryConvert_ResultE: Into< Exit<!, E> > > std::ops::FromResidual<std::result::Result<std::convert::Infallible, Derive_TryConvert_ResultE>> for Exit<T, E>
{
#[inline]
#[track_caller]
fn from_residual(residual: std::result::Result<std::convert::Infallible, Derive_TryConvert_ResultE>) -> Self {
match residual {
std::result::Result::Err(e) => {
let bang: Exit<!, E> = e.into();
Self::from_residual(bang)
}
}
}
}
impl<E, Derive_TryConvert_ResultT, Derive_TryConvert_ResultE: From<Exit<!, E> > > std::ops::FromResidual<Exit<!, E> > for std::result::Result<Derive_TryConvert_ResultT, Derive_TryConvert_ResultE>
{
#[inline]
#[track_caller]
fn from_residual(residual: Exit<!, E>) -> Self {
std::result::Result::Err(residual.into())
}
}
};
assert_eq!(
expected_impl.to_string(),
impl_convert_result(original).unwrap().to_string()
)
}
}