tryfrom-via-fromstr 0.1.1

Derive `TryFrom` by delegating to the `FromStr` impl
Documentation
use indoc::indoc;
use proc_macro2::TokenStream;
use test_case::test_case;

#[test_case(
    indoc! {r#"
        impl std::str::FromStr for MyType {}
    "#},
    indoc! {r#"
        impl std::str::FromStr for MyType {}
        impl<'tryfrom_str_lifetime> ::std::convert::TryFrom<&'tryfrom_str_lifetime str>
        for MyType {
            type Error = <Self as ::std::str::FromStr>::Err;
            fn try_from(s: &'tryfrom_str_lifetime str) -> Result<Self, Self::Error> {
                use ::std::str::FromStr;

                Self::from_str(s)
            }
        }
    "#}
    ; "basic"
)]
#[test_case(
    indoc! {r#"
        impl<T> FromStr for UnconstrainedWrapper<T> {}
    "#},
    indoc! {r#"
        impl<T> FromStr for UnconstrainedWrapper<T> {}
        impl<'tryfrom_str_lifetime, T> ::std::convert::TryFrom<&'tryfrom_str_lifetime str>
        for UnconstrainedWrapper<T> {
            type Error = <Self as ::std::str::FromStr>::Err;
            fn try_from(s: &'tryfrom_str_lifetime str) -> Result<Self, Self::Error> {
                use ::std::str::FromStr;

                Self::from_str(s)
            }
        }
    "#}
    ; "generic-type-unconstrained"
)]
#[test_case(
    indoc! {r#"
        impl<T> FromStr for ConstrainedWrapper<T> where T: FromStr {}
    "#},
    indoc! {r#"
        impl<T> FromStr for ConstrainedWrapper<T>
        where
            T: FromStr,
        {}
        impl<'tryfrom_str_lifetime, T> ::std::convert::TryFrom<&'tryfrom_str_lifetime str>
        for ConstrainedWrapper<T>
        where
            T: FromStr,
        {
            type Error = <Self as ::std::str::FromStr>::Err;
            fn try_from(s: &'tryfrom_str_lifetime str) -> Result<Self, Self::Error> {
                use ::std::str::FromStr;

                Self::from_str(s)
            }
        }
    "#}
    ; "generic-type-constrained"
)]
fn transform(input: &str, expected: &str) {
    let input = input.trim();
    eprintln!("For input:\n{}", quote_code(input));
    match transform_res(input, expected) {
        Ok((found, expected)) => assert_eq!(
            &expected,
            &found,
            "\n\nexpected:\n{}\n\nfound:\n{}",
            quote_code(&expected),
            quote_code(&found),
        ),
        Err(Error::Lex(e)) => panic!("lex error: {e}"),
        Err(Error::Syn(e, src)) => {
            panic!("syn error {e}\n-while parsing:\n{}", quote_code(&src))
        }
    }
}

fn quote_code(s: &str) -> String {
    format!("| {}", s.replace('\n', "\n| "))
}

#[derive(Debug, derive_more::From)]
enum Error {
    Lex(proc_macro2::LexError),
    Syn(syn::Error, String),
}

type Result<T> = std::result::Result<T, Error>;

fn transform_res(input: &str, expected: &str) -> Result<(String, String)> {
    use quote::quote;

    let input: TokenStream = input.parse()?;
    let inputstr = input.to_string();
    let output = crate::transform(quote! {}, input).map_err(|e| Error::Syn(e, inputstr))?;
    let output_pretty = unparse(output)?;
    let expected_pretty = prettify(expected)?;
    Ok((output_pretty, expected_pretty))
}

fn prettify(src: &str) -> Result<String> {
    let ts: TokenStream = src.parse()?;
    let pretty = unparse(ts)?;
    Ok(pretty)
}

fn unparse(itemstream: TokenStream) -> Result<String> {
    let f: syn::File = parse(itemstream)?;
    Ok(prettyplease::unparse(&f))
}

fn parse<T>(stream: TokenStream) -> Result<T>
where
    T: syn::parse::Parse,
{
    let streamstr = stream.to_string();
    syn::parse2(stream).map_err(|e| Error::Syn(e, streamstr))
}