mirror of
https://github.com/awfixers-stuff/src.git
synced 2026-03-27 21:06:00 +00:00
301 lines
11 KiB
Rust
301 lines
11 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use quote::quote;
|
|
use syn::{punctuated::Punctuated, spanned::Spanned, *};
|
|
|
|
pub(crate) fn inner(code: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
|
|
let fn_item: Item = match syn::parse2(code.clone()) {
|
|
Ok(input) => input,
|
|
Err(err) => return err.to_compile_error(),
|
|
};
|
|
|
|
if let Item::Fn(item_fn) = fn_item {
|
|
let ty_conversions = parse_generics(&item_fn.sig);
|
|
let (has_conversion_in_effect, argtypes, argexprs, has_self) = convert(&item_fn.sig.inputs, &ty_conversions);
|
|
if !has_conversion_in_effect {
|
|
return Error::new(
|
|
item_fn.span(),
|
|
"Couldn't apply a single conversion - momo is ineffective here",
|
|
)
|
|
.to_compile_error();
|
|
}
|
|
|
|
let uses_self = has_self
|
|
|| item_fn.sig.inputs.iter().any(has_self_type)
|
|
|| matches!(&item_fn.sig.output, ReturnType::Type(_, ty) if contains_self_type(ty));
|
|
|
|
let inner_ident = Ident::new(
|
|
// Use long qualifier to avoid name collision.
|
|
&format!("_{}_inner_generated_by_gix_macro_momo", item_fn.sig.ident),
|
|
proc_macro2::Span::call_site(),
|
|
);
|
|
|
|
let outer_sig = Signature {
|
|
inputs: argtypes,
|
|
..item_fn.sig.clone()
|
|
};
|
|
|
|
let new_inner_item = Item::Fn(ItemFn {
|
|
// Remove doc comment since they will increase compile-time and
|
|
// also generates duplicate warning/error messages for the doc,
|
|
// especially if it contains doc-tests.
|
|
attrs: {
|
|
let mut attrs = item_fn.attrs.clone();
|
|
attrs.retain(|attr| {
|
|
let segments = &attr.path().segments;
|
|
!(segments.len() == 1 && segments[0].ident == "doc")
|
|
});
|
|
attrs
|
|
},
|
|
vis: Visibility::Inherited,
|
|
sig: Signature {
|
|
ident: inner_ident.clone(),
|
|
..item_fn.sig
|
|
},
|
|
block: item_fn.block,
|
|
});
|
|
|
|
if uses_self {
|
|
// We can use `self` or `Self` inside function defined within
|
|
// the impl-fn, so instead declare two separate functions.
|
|
//
|
|
// Since it's an impl block, it's unlikely to have name conflict,
|
|
// though this won't work for impl-trait.
|
|
//
|
|
// This approach also make sure we can call the right function
|
|
// using `Self` qualifier.
|
|
let new_item = Item::Fn(ItemFn {
|
|
attrs: item_fn.attrs,
|
|
vis: item_fn.vis,
|
|
sig: outer_sig,
|
|
block: if has_self {
|
|
parse_quote!({ self.#inner_ident(#argexprs) })
|
|
} else {
|
|
parse_quote!({ Self::#inner_ident(#argexprs) })
|
|
},
|
|
});
|
|
quote!(#new_item #new_inner_item)
|
|
} else {
|
|
// Put the new inner function within the function block
|
|
// to avoid duplicate function name and support associated
|
|
// function that doesn't use `self` or `Self`.
|
|
let new_item = Item::Fn(ItemFn {
|
|
attrs: item_fn.attrs,
|
|
vis: item_fn.vis,
|
|
sig: outer_sig,
|
|
block: parse_quote!({
|
|
#new_inner_item
|
|
|
|
#inner_ident(#argexprs)
|
|
}),
|
|
});
|
|
quote!(#new_item)
|
|
}
|
|
} else {
|
|
Error::new(fn_item.span(), "expect a function").to_compile_error()
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone)]
|
|
// All conversions we support. Check references to this type for an idea how to add more.
|
|
enum Conversion {
|
|
Into,
|
|
AsRef,
|
|
AsMut,
|
|
}
|
|
|
|
impl Conversion {
|
|
fn conversion_expr(&self, i: &Ident) -> Expr {
|
|
match *self {
|
|
Conversion::Into => parse_quote!(#i.into()),
|
|
Conversion::AsRef => parse_quote!(#i.as_ref()),
|
|
Conversion::AsMut => parse_quote!(#i.as_mut()),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_bounded_type(ty: &Type) -> Option<Ident> {
|
|
match &ty {
|
|
Type::Path(TypePath { qself: None, path }) if path.segments.len() == 1 => Some(path.segments[0].ident.clone()),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn parse_bounds(bounds: &Punctuated<TypeParamBound, Token![+]>) -> Option<Conversion> {
|
|
if bounds.len() != 1 {
|
|
return None;
|
|
}
|
|
if let TypeParamBound::Trait(ref tb) = bounds.first().unwrap() {
|
|
if let Some(seg) = tb.path.segments.iter().next_back() {
|
|
if let PathArguments::AngleBracketed(ref gen_args) = seg.arguments {
|
|
if let GenericArgument::Type(_) = gen_args.args.first().unwrap() {
|
|
if seg.ident == "Into" {
|
|
return Some(Conversion::Into);
|
|
} else if seg.ident == "AsRef" {
|
|
return Some(Conversion::AsRef);
|
|
} else if seg.ident == "AsMut" {
|
|
return Some(Conversion::AsMut);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
// create a map from generic type to Conversion
|
|
fn parse_generics(decl: &Signature) -> HashMap<Ident, Conversion> {
|
|
let mut ty_conversions = HashMap::new();
|
|
for gp in decl.generics.params.iter() {
|
|
if let GenericParam::Type(ref tp) = gp {
|
|
if let Some(conversion) = parse_bounds(&tp.bounds) {
|
|
ty_conversions.insert(tp.ident.clone(), conversion);
|
|
}
|
|
}
|
|
}
|
|
if let Some(ref wc) = decl.generics.where_clause {
|
|
for wp in wc.predicates.iter() {
|
|
if let WherePredicate::Type(ref pt) = wp {
|
|
if let Some(ident) = parse_bounded_type(&pt.bounded_ty) {
|
|
if let Some(conversion) = parse_bounds(&pt.bounds) {
|
|
ty_conversions.insert(ident, conversion);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ty_conversions
|
|
}
|
|
|
|
fn convert(
|
|
inputs: &Punctuated<FnArg, Token![,]>,
|
|
ty_conversions: &HashMap<Ident, Conversion>,
|
|
) -> (bool, Punctuated<FnArg, Token![,]>, Punctuated<Expr, Token![,]>, bool) {
|
|
let mut has_conversion_in_effect = false;
|
|
let mut argtypes = Punctuated::new();
|
|
let mut argexprs = Punctuated::new();
|
|
let mut has_self = false;
|
|
inputs.iter().enumerate().for_each(|(i, input)| match input.clone() {
|
|
FnArg::Receiver(receiver) => {
|
|
has_self = true;
|
|
argtypes.push(FnArg::Receiver(receiver));
|
|
}
|
|
FnArg::Typed(mut pat_type) => {
|
|
let pat_ident = match &mut *pat_type.pat {
|
|
Pat::Ident(pat_ident) if pat_ident.by_ref.is_none() && pat_ident.subpat.is_none() => pat_ident,
|
|
_ => {
|
|
*pat_type.pat = Pat::Ident(PatIdent {
|
|
ident: Ident::new(&format!("arg_{i}_gen_by_momo_"), proc_macro2::Span::call_site()),
|
|
attrs: Default::default(),
|
|
by_ref: None,
|
|
mutability: None,
|
|
subpat: None,
|
|
});
|
|
|
|
if let Pat::Ident(pat_ident) = &mut *pat_type.pat {
|
|
pat_ident
|
|
} else {
|
|
panic!()
|
|
}
|
|
}
|
|
};
|
|
// Outer function type argument pat does not need mut unless its
|
|
// type is `impl AsMut`.
|
|
pat_ident.mutability = None;
|
|
|
|
let ident = &pat_ident.ident;
|
|
|
|
let to_expr = || parse_quote!(#ident);
|
|
|
|
match *pat_type.ty {
|
|
Type::ImplTrait(TypeImplTrait { ref bounds, .. }) => {
|
|
if let Some(conv) = parse_bounds(bounds) {
|
|
has_conversion_in_effect = true;
|
|
argexprs.push(conv.conversion_expr(ident));
|
|
if let Conversion::AsMut = conv {
|
|
pat_ident.mutability = Some(Default::default());
|
|
}
|
|
} else {
|
|
argexprs.push(to_expr());
|
|
}
|
|
}
|
|
Type::Path(..) => {
|
|
if let Some(conv) = parse_bounded_type(&pat_type.ty).and_then(|ident| ty_conversions.get(&ident)) {
|
|
has_conversion_in_effect = true;
|
|
argexprs.push(conv.conversion_expr(ident));
|
|
if let Conversion::AsMut = conv {
|
|
pat_ident.mutability = Some(Default::default());
|
|
}
|
|
} else {
|
|
argexprs.push(to_expr());
|
|
}
|
|
}
|
|
_ => {
|
|
argexprs.push(to_expr());
|
|
}
|
|
}
|
|
|
|
// Now that mutability is decided, push the type into argtypes
|
|
argtypes.push(FnArg::Typed(pat_type));
|
|
}
|
|
});
|
|
(has_conversion_in_effect, argtypes, argexprs, has_self)
|
|
}
|
|
|
|
fn contains_self_type_path(path: &Path) -> bool {
|
|
path.segments.iter().any(|segment| {
|
|
segment.ident == "Self"
|
|
|| match &segment.arguments {
|
|
PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) => {
|
|
args.iter().any(|generic_arg| match generic_arg {
|
|
GenericArgument::Type(ty) => contains_self_type(ty),
|
|
GenericArgument::Const(expr) => contains_self_type_expr(expr),
|
|
_ => false,
|
|
})
|
|
}
|
|
PathArguments::Parenthesized(ParenthesizedGenericArguments { inputs, output, .. }) => {
|
|
inputs.iter().any(contains_self_type)
|
|
|| matches!(output, ReturnType::Type(_, ty) if contains_self_type(ty))
|
|
}
|
|
_ => false,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn contains_self_type_expr(expr: &Expr) -> bool {
|
|
match expr {
|
|
Expr::Path(ExprPath { qself: Some(_), .. }) => true,
|
|
Expr::Path(ExprPath { path, .. }) => contains_self_type_path(path),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn contains_self_type(input: &Type) -> bool {
|
|
match input {
|
|
Type::Array(TypeArray { elem, len, .. }) => {
|
|
// Call `matches!` first so that we can do tail call here
|
|
// as an optimization.
|
|
contains_self_type_expr(len) || contains_self_type(elem)
|
|
}
|
|
Type::Group(TypeGroup { elem, .. }) => contains_self_type(elem),
|
|
Type::Paren(TypeParen { elem, .. }) => contains_self_type(elem),
|
|
Type::Ptr(TypePtr { elem, .. }) => contains_self_type(elem),
|
|
Type::Reference(TypeReference { elem, .. }) => contains_self_type(elem),
|
|
Type::Slice(TypeSlice { elem, .. }) => contains_self_type(elem),
|
|
|
|
Type::Tuple(TypeTuple { elems, .. }) => elems.iter().any(contains_self_type),
|
|
|
|
Type::Path(TypePath { qself: Some(_), .. }) => true,
|
|
Type::Path(TypePath { path, .. }) => contains_self_type_path(path),
|
|
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn has_self_type(input: &FnArg) -> bool {
|
|
match input {
|
|
FnArg::Receiver(_) => true,
|
|
FnArg::Typed(PatType { ty, .. }) => contains_self_type(ty),
|
|
}
|
|
}
|