bind_it 0.1.3

A macro that allows impl traits in let/static/const bindings until they actually stabilized
Documentation
#![doc = include_str!("../README.md")]
#![feature(type_alias_impl_trait)]

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use std::fmt::Debug;
use syn::{
    parse_macro_input, parse_quote, GenericArgument, Item, Pat, Path, PathArguments, ReturnType,
    Stmt, Type, TypeParamBound,
};
use uuid::Uuid;

#[proc_macro]
pub fn bind_it(item: TokenStream) -> TokenStream {
    let mut input: Stmt = parse_macro_input!(item);
    let extracted_taits = match &mut input {
        Stmt::Local(ref mut local) => {
            if let Pat::Type(ref mut pt) = local.pat {
                extract_and_replace_ty(&mut *pt.ty)
            } else {
                todo!("other types are not yet impld");
            }
        }
        Stmt::Item(itm) => match itm {
            Item::Static(s) => extract_and_replace_ty(&mut *s.ty),
            Item::Const(c) => extract_and_replace_ty(&mut *c.ty),
            _ => panic!("only let, static and const items are supported"),
        },
        _ => unimplemented!(),
    };
    let mut taits = TokenStream2::new();
    taits.extend(extracted_taits);
    quote! {
        #taits
        #input
    }
    .into()
}

fn extract_and_replace_ty(ty: &mut Type) -> Vec<TokenStream2> {
    let mut extracted = vec![];

    let mut traverse_path = |path: &mut Path| match path
        .segments
        .last_mut()
        .expect("how path is empty wtf bug")
        .arguments
    {
        PathArguments::None => (),
        PathArguments::AngleBracketed(ref mut ab) => {
            for generic in ab.args.iter_mut() {
                let new_taits = match generic {
                    GenericArgument::Type(t) => extract_and_replace_ty(t),
                    GenericArgument::Binding(b) => extract_and_replace_ty(&mut b.ty),
                    GenericArgument::Constraint(_) => unimplemented!(),
                    GenericArgument::Const(_) => unimplemented!(),
                    GenericArgument::Lifetime(_) => vec![],
                };
                extracted.extend(new_taits);
            }
        }
        PathArguments::Parenthesized(ref mut ps) => {
            for fn_arg in ps.inputs.iter_mut() {
                extracted.extend(extract_and_replace_ty(fn_arg))
            }
            match ps.output {
                ReturnType::Default => (),
                ReturnType::Type(_, ref mut ty) => {
                    extracted.extend(extract_and_replace_ty(&mut *ty))
                }
            }
        }
    };

    match ty {
        Type::ImplTrait(it) => {
            for trait_bounds in it.bounds.iter_mut() {
                if let TypeParamBound::Trait(ref mut tb) = trait_bounds {
                    traverse_path(&mut tb.path);
                }
            }
            let rnd = Uuid::new_v4().to_simple().to_string();
            let tait_name = format_ident!("Bind_TAIT_{}", &rnd[..16]);
            let tait = quote! {
                type #tait_name = #it;
            };
            *ty = parse_quote! {
                #tait_name
            };
            extracted.push(tait);
        }
        Type::Array(a) => extracted.extend(extract_and_replace_ty(&mut *a.elem)),
        Type::Infer(_) => (),
        Type::Path(p) => traverse_path(&mut p.path),
        Type::Ptr(p) => extracted.extend(extract_and_replace_ty(&mut *p.elem)),
        Type::Reference(r) => extracted.extend(extract_and_replace_ty(&mut *r.elem)),
        Type::Tuple(t) => {
            for tpl_el in t.elems.iter_mut() {
                extracted.extend(extract_and_replace_ty(tpl_el));
            }
        }
        t => todo!("{t:?}"),
    }
    extracted
}

fn _p(s: impl Debug) {
    panic!("{:#?}", s);
}