diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-30 18:31:36 +0000 |
commit | e02c5b5930c2c9ba3e5423fe12e2ef0155017297 (patch) | |
tree | fd60ebbbb5299e16e5fca8c773ddb74f764760db /vendor/gix-macros/src | |
parent | Adding debian version 1.73.0+dfsg1-1. (diff) | |
download | rustc-e02c5b5930c2c9ba3e5423fe12e2ef0155017297.tar.xz rustc-e02c5b5930c2c9ba3e5423fe12e2ef0155017297.zip |
Merging upstream version 1.74.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-macros/src')
-rw-r--r-- | vendor/gix-macros/src/lib.rs | 25 | ||||
-rw-r--r-- | vendor/gix-macros/src/momo.rs | 300 |
2 files changed, 325 insertions, 0 deletions
diff --git a/vendor/gix-macros/src/lib.rs b/vendor/gix-macros/src/lib.rs new file mode 100644 index 000000000..4693a79e8 --- /dev/null +++ b/vendor/gix-macros/src/lib.rs @@ -0,0 +1,25 @@ +//! A crate of useful macros used in `gix` primarily. +//! +//! Note that within `gix-*` crates, monomorphization should never be used for convenience, but only for performance +//! reasons. And in the latter case, manual denomophization should be considered if the trait in questions isn't called +//! often enough or measurements indicate that `&dyn Trait` is increasing the runtime. Thus, `gix-*` crates should probably +//! by default prefer using `&dyn` unless measurements indicate otherwise. +use proc_macro::TokenStream; + +/// When applied to functions or methods, it will turn it into a wrapper that will immediately call +/// a de-monomorphized implementation (i.e. one that uses `&dyn Trait`). +/// +/// That way, the landing-pads for convenience will be as small as possible which then delegate to a single +/// function or method for implementation. +/// +/// The parameters using the following traits can be de-monomorphized: +/// +/// * `Into` +/// * `AsRef` +/// * `AsMut` +#[proc_macro_attribute] +pub fn momo(_attrs: TokenStream, input: TokenStream) -> TokenStream { + momo::inner(input.into()).into() +} + +mod momo; diff --git a/vendor/gix-macros/src/momo.rs b/vendor/gix-macros/src/momo.rs new file mode 100644 index 000000000..802f4b5b0 --- /dev/null +++ b/vendor/gix-macros/src/momo.rs @@ -0,0 +1,300 @@ +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<'a> { + Into(&'a Type), + AsRef(&'a Type), + AsMut(&'a Type), +} + +impl<'a> Conversion<'a> { + 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().last() { + if let PathArguments::AngleBracketed(ref gen_args) = seg.arguments { + if let GenericArgument::Type(ref arg_ty) = gen_args.args.first().unwrap() { + if seg.ident == "Into" { + return Some(Conversion::Into(arg_ty)); + } else if seg.ident == "AsRef" { + return Some(Conversion::AsRef(arg_ty)); + } else if seg.ident == "AsMut" { + return Some(Conversion::AsMut(arg_ty)); + } + } + } + } + } + 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<'a>( + inputs: &'a Punctuated<FnArg, Token![,]>, + ty_conversions: &HashMap<Ident, Conversion<'a>>, +) -> (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 = Box::new(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), + } +} |