From 1934159ec0a83fff6a417d8184b92ef5e8a184d8 Mon Sep 17 00:00:00 2001 From: Jiahao XU Date: Sat, 19 Aug 2023 21:01:39 +1000 Subject: [PATCH] feature: Add new crate `gix-macros` To provide proc-macro utilities for de-monomorphizatioin and other utilities. Signed-off-by: Jiahao XU --- Cargo.lock | 10 + Cargo.toml | 1 + gix-macros/Cargo.toml | 22 + gix-macros/LICENSE-APACHE | 1 + gix-macros/LICENSE-MIT | 1 + gix-macros/src/lib.rs | 450 ++++++++++++++++++ gix-macros/tests/test.rs | 286 +++++++++++ gix/Cargo.toml | 1 + gix/src/discover.rs | 4 + gix/src/init.rs | 4 + gix/src/object/tree/mod.rs | 3 + gix/src/open/repository.rs | 4 + gix/src/pathspec.rs | 5 + gix/src/reference/edits.rs | 5 +- gix/src/reference/iter.rs | 2 + .../remote/connection/fetch/receive_pack.rs | 1 + gix/src/repository/config/transport.rs | 8 +- gix/src/repository/object.rs | 16 +- gix/src/repository/reference.rs | 6 +- gix/src/repository/remote.rs | 59 +-- gix/src/repository/revision.rs | 3 + gix/src/repository/worktree.rs | 2 +- 22 files changed, 852 insertions(+), 42 deletions(-) create mode 100644 gix-macros/Cargo.toml create mode 120000 gix-macros/LICENSE-APACHE create mode 120000 gix-macros/LICENSE-MIT create mode 100644 gix-macros/src/lib.rs create mode 100644 gix-macros/tests/test.rs diff --git a/Cargo.lock b/Cargo.lock index a06040137e0..e73076b78bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1208,6 +1208,7 @@ dependencies = [ "gix-ignore 0.6.0", "gix-index 0.22.0", "gix-lock 8.0.0", + "gix-macros", "gix-mailmap", "gix-negotiate", "gix-object 0.35.0", @@ -1795,6 +1796,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gix-macros" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "gix-mailmap" version = "0.17.0" diff --git a/Cargo.toml b/Cargo.toml index c79cf19e1d6..ec5728c3551 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -244,6 +244,7 @@ members = [ "gix-packetline", "gix-packetline-blocking", "gix-mailmap", + "gix-macros", "gix-note", "gix-negotiate", "gix-fetchhead", diff --git a/gix-macros/Cargo.toml b/gix-macros/Cargo.toml new file mode 100644 index 00000000000..d208d929c1f --- /dev/null +++ b/gix-macros/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "gix-macros" +version = "0.0.0" +edition = "2021" +description = "Proc-macro utilities for gix" +authors = [ + "Jiahao XU ", + "Andre Bogus ", + "Sebastian Thiel ", +] +repository = "https://github.com/Byron/gitoxide" +license = "MIT OR Apache-2.0" +include = ["src/**/*", "LICENSE-*", "CHANGELOG.md"] +rust-version = "1.65" + +[lib] +proc_macro = true + +[dependencies] +syn = { version = "2.0", features = ["full", "fold"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/gix-macros/LICENSE-APACHE b/gix-macros/LICENSE-APACHE new file mode 120000 index 00000000000..965b606f331 --- /dev/null +++ b/gix-macros/LICENSE-APACHE @@ -0,0 +1 @@ +../LICENSE-APACHE \ No newline at end of file diff --git a/gix-macros/LICENSE-MIT b/gix-macros/LICENSE-MIT new file mode 120000 index 00000000000..76219eb72e8 --- /dev/null +++ b/gix-macros/LICENSE-MIT @@ -0,0 +1 @@ +../LICENSE-MIT \ No newline at end of file diff --git a/gix-macros/src/lib.rs b/gix-macros/src/lib.rs new file mode 100644 index 00000000000..8688ccabaf1 --- /dev/null +++ b/gix-macros/src/lib.rs @@ -0,0 +1,450 @@ +use std::collections::{HashMap, HashSet}; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{fold::Fold, punctuated::Punctuated, spanned::Spanned, *}; + +#[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), + TryInto(&'a Type), + AsRef(&'a Type), + AsMut(&'a Type), +} + +impl<'a> Conversion<'a> { + fn target_type(&self) -> Type { + match *self { + Conversion::Into(ty) => ty.clone(), + Conversion::TryInto(ty) => ty.clone(), + Conversion::AsRef(ty) => parse_quote!(&#ty), + Conversion::AsMut(ty) => parse_quote!(&mut #ty), + } + } + + fn conversion_expr(&self, i: Ident) -> Expr { + match *self { + Conversion::Into(_) => parse_quote!(#i.into()), + Conversion::TryInto(_) => parse_quote!(#i.try_into()?), + Conversion::AsRef(_) => parse_quote!(#i.as_ref()), + Conversion::AsMut(_) => parse_quote!(#i.as_mut()), + } + } +} + +fn parse_bounded_type(ty: &Type) -> Option { + if let Type::Path(TypePath { qself: None, ref path }) = ty { + if path.segments.len() == 1 { + return Some(path.segments[0].ident.clone()); + } + } + None +} + +fn parse_bounds(bounds: &Punctuated) -> Option { + 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 == "TryInto" { + return Some(Conversion::TryInto(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>, Generics) { + let mut ty_conversions = HashMap::new(); + let mut params = Punctuated::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); + continue; + } + } + params.push(gp.clone()); + } + let where_clause = if let Some(ref wc) = decl.generics.where_clause { + let mut idents_to_remove = HashSet::new(); + let mut predicates = Punctuated::new(); + 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) { + idents_to_remove.insert(ident.clone()); + ty_conversions.insert(ident, conversion); + continue; + } + } + } + predicates.push(wp.clone()); + } + params = params + .into_iter() + .filter(|param| { + if let GenericParam::Type(type_param) = param { + !idents_to_remove.contains(&type_param.ident) + } else { + true + } + }) + .collect(); + Some(WhereClause { + predicates, + ..wc.clone() + }) + } else { + None + }; + ( + ty_conversions, + Generics { + params, + where_clause, + ..decl.generics.clone() + }, + ) +} + +fn pat_to_ident(pat: &Pat) -> Ident { + if let Pat::Ident(ref pat_ident) = *pat { + return pat_ident.ident.clone(); + } + unimplemented!("No non-ident patterns for now!"); +} + +fn pat_to_expr(pat: &Pat) -> Expr { + let ident = pat_to_ident(pat); + parse_quote!(#ident) +} + +fn convert<'a>( + inputs: &'a Punctuated, + ty_conversions: &HashMap>, +) -> ( + Punctuated, + Conversions, + Punctuated, + bool, +) { + let mut argtypes = Punctuated::new(); + let mut conversions = Conversions { + intos: Vec::new(), + try_intos: Vec::new(), + as_refs: Vec::new(), + as_muts: Vec::new(), + }; + let mut argexprs = Punctuated::new(); + let mut has_self = false; + inputs.iter().for_each(|input| match input { + FnArg::Receiver(..) => { + has_self = true; + argtypes.push(input.clone()); + } + FnArg::Typed(PatType { + ref pat, + ref ty, + ref colon_token, + .. + }) => match **ty { + Type::ImplTrait(TypeImplTrait { ref bounds, .. }) => { + if let Some(conv) = parse_bounds(bounds) { + argtypes.push(FnArg::Typed(PatType { + attrs: Vec::new(), + pat: pat.clone(), + colon_token: *colon_token, + ty: Box::new(conv.target_type()), + })); + let ident = pat_to_ident(pat); + conversions.add(ident.clone(), conv); + argexprs.push(conv.conversion_expr(ident)); + } else { + argtypes.push(input.clone()); + argexprs.push(pat_to_expr(pat)); + } + } + Type::Path(..) => { + if let Some(conv) = parse_bounded_type(ty).and_then(|ident| ty_conversions.get(&ident)) { + argtypes.push(FnArg::Typed(PatType { + attrs: Vec::new(), + pat: pat.clone(), + colon_token: *colon_token, + ty: Box::new(conv.target_type()), + })); + let ident = pat_to_ident(pat); + conversions.add(ident.clone(), *conv); + argexprs.push(conv.conversion_expr(ident)); + } else { + argtypes.push(input.clone()); + argexprs.push(pat_to_expr(pat)); + } + } + _ => { + argtypes.push(input.clone()); + argexprs.push(pat_to_expr(pat)); + } + }, + }); + (argtypes, conversions, argexprs, has_self) +} + +struct Conversions { + intos: Vec, + try_intos: Vec, + as_refs: Vec, + as_muts: Vec, +} + +impl Conversions { + fn add(&mut self, ident: Ident, conv: Conversion) { + match conv { + Conversion::Into(_) => self.intos.push(ident), + Conversion::TryInto(_) => self.try_intos.push(ident), + Conversion::AsRef(_) => self.as_refs.push(ident), + Conversion::AsMut(_) => self.as_muts.push(ident), + } + } +} + +fn has_conversion(idents: &[Ident], expr: &Expr) -> bool { + if let Expr::Path(ExprPath { ref path, .. }) = *expr { + if path.segments.len() == 1 { + let seg = path.segments.iter().last().unwrap(); + return idents.iter().any(|i| i == &seg.ident); + } + } + false +} + +#[allow(clippy::collapsible_if)] +impl Fold for Conversions { + fn fold_expr(&mut self, expr: Expr) -> Expr { + //TODO: Also catch `Expr::Call` with suitable paths & args + match expr { + Expr::MethodCall(mc) if mc.args.is_empty() => match &*mc.method.to_string() { + "into" if has_conversion(&self.intos, &mc.receiver) => *mc.receiver, + "try_into" if has_conversion(&self.try_intos, &mc.receiver) => *mc.receiver, + + "as_ref" if has_conversion(&self.as_refs, &mc.receiver) => *mc.receiver, + "as_mut" if has_conversion(&self.as_muts, &mc.receiver) => *mc.receiver, + + _ => syn::fold::fold_expr(self, Expr::MethodCall(mc)), + }, + Expr::Call(call) if call.args.len() == 1 => match &*call.func { + Expr::Path(ExprPath { + path: Path { segments, .. }, + .. + }) if segments.len() == 2 => match (&*segments[0].ident.to_string(), &*segments[1].ident.to_string()) { + ("Into", "into") if has_conversion(&self.intos, &call.args[0]) => call.args[0].clone(), + ("TryInto", "try_into") if has_conversion(&self.try_intos, &call.args[0]) => call.args[0].clone(), + + ("AsRef", "as_ref") if matches!(&call.args[0], Expr::Reference(ExprReference { expr, mutability: None, .. }) if has_conversion(&self.as_refs, expr)) => { + if let Expr::Reference(ExprReference { expr, .. }) = &call.args[0] { + (**expr).clone() + } else { + panic!("expr must be Reference") + } + } + ("AsMut", "as_mut") if matches!(&call.args[0], Expr::Reference(ExprReference { expr, mutability: Some(_), .. }) if has_conversion(&self.as_muts, expr)) => { + if let Expr::Reference(ExprReference { expr, .. }) = &call.args[0] { + (**expr).clone() + } else { + panic!("expr must be Reference") + } + } + + _ => syn::fold::fold_expr(self, Expr::Call(call)), + }, + _ => syn::fold::fold_expr(self, Expr::Call(call)), + }, + Expr::Try(expr_try) => match &*expr_try.expr { + Expr::MethodCall(mc) + if mc.args.is_empty() + && mc.method == "try_into" + && has_conversion(&self.try_intos, &mc.receiver) => + { + (*mc.receiver).clone() + } + Expr::Call(call) if call.args.len() == 1 => match &*call.func { + Expr::Path(ExprPath { + path: Path { segments, .. }, + .. + }) if segments.len() == 2 + && segments[0].ident == "TryInto" + && segments[1].ident == "try_into" + && has_conversion(&self.try_intos, &call.args[0]) => + { + call.args[0].clone() + } + _ => syn::fold::fold_expr(self, Expr::Try(expr_try)), + }, + _ => syn::fold::fold_expr(self, Expr::Try(expr_try)), + }, + _ => syn::fold::fold_expr(self, expr), + } + } +} + +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), + } +} + +/// Generate lightweight monomorphized wrapper around main implementation. +/// May be applied to functions and methods. +#[proc_macro_attribute] +pub fn momo(_attrs: TokenStream, input: TokenStream) -> TokenStream { + //TODO: alternatively parse ImplItem::Method + momo_inner(input.into()).into() +} + +fn momo_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, generics) = parse_generics(&item_fn.sig); + let (argtypes, mut conversions, argexprs, has_self) = convert(&item_fn.sig.inputs, &ty_conversions); + + 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 colision. + &format!("_{}_inner_generated_by_gix_macro_momo", item_fn.sig.ident), + proc_macro2::Span::call_site(), + ); + + 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(), + generics, + inputs: argtypes, + ..item_fn.sig.clone() + }, + block: Box::new(conversions.fold_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: item_fn.sig, + block: if has_self { + parse_quote!({ self.#inner_ident(#argexprs) }) + } else { + parse_quote!({ Self::#inner_ident(#argexprs) }) + }, + }); + quote!(#new_item #[allow(unused_mut)] #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: item_fn.sig, + block: parse_quote!({ + #[allow(unused_mut)] + #new_inner_item + + #inner_ident(#argexprs) + }), + }); + quote!(#new_item) + } + } else { + Error::new(fn_item.span(), "expect a function").to_compile_error() + } +} diff --git a/gix-macros/tests/test.rs b/gix-macros/tests/test.rs new file mode 100644 index 00000000000..10006bf1be4 --- /dev/null +++ b/gix-macros/tests/test.rs @@ -0,0 +1,286 @@ +use gix_macros::momo; +use std::pin::Pin; + +struct Options; + +#[allow(dead_code)] +fn test_open_opts_inner(_dir: impl Into, _options: Options) -> Result<(), ()> { + Ok(()) +} + +/// See if doc are kept +#[allow(dead_code)] +#[momo] +fn test_open_opts(directory: impl Into, options: Options) -> Result<(), ()> { + test_open_opts_inner(directory, options) +} + +#[allow(dead_code)] +#[momo] +fn test_with_try(t: impl TryInto, _options: Options) -> Result<(), ()> { + let t = t.try_into()?; + t.strip_prefix('1').ok_or(())?; + Ok(()) +} + +#[momo] +fn test_fn( + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, +) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) +} + +#[momo] +fn test_fn_call_style( + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, +) -> Result { + let mut s = Into::into(a); + s += AsRef::as_ref(&b); + s += AsMut::as_mut(&mut c); + s += &TryInto::try_into(d)?; + + Ok(s) +} + +#[momo] +fn test_fn_where(a: A, b: B, mut c: C, d: D) -> Result +where + A: Into, + B: AsRef, + C: AsMut, + D: TryInto, +{ + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) +} + +struct TestStruct; + +impl TestStruct { + #[momo] + fn test_method( + self, + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + ) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) + } + + #[allow(clippy::needless_arbitrary_self_type)] + #[momo] + fn test_method2( + self: Self, + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + ) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) + } + + #[momo] + fn test_fn( + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + _e: (), + ) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) + } + + #[momo] + fn test_fn2( + _this: Self, + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + _e: (), + _f: (), + ) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) + } + + #[momo] + fn test_fn3( + _this: Pin<&mut Self>, + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + _e: (), + _f: (), + ) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + Ok(s) + } + + #[allow(unused)] + #[momo] + fn test_fn_ret( + _this: Pin<&mut Self>, + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + _e: (), + _f: (), + ) -> Result { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + drop(s); + + Ok(Self) + } +} + +struct S(bool); +impl TryInto for S { + type Error = (); + + fn try_into(self) -> Result { + if self.0 { + Ok(String::from("!2345")) + } else { + Err(()) + } + } +} + +#[test] +fn test_basic_fn() { + assert_eq!( + test_fn("12345", "12345", String::from("12345"), S(true)).unwrap(), + "123451234512345!2345" + ); + + test_fn("12345", "12345", String::from("12345"), S(false)).unwrap_err(); + + assert_eq!( + test_fn_call_style("12345", "12345", String::from("12345"), S(true)).unwrap(), + "123451234512345!2345" + ); + + test_fn_call_style("12345", "12345", String::from("12345"), S(false)).unwrap_err(); + + assert_eq!( + test_fn_where("12345", "12345", String::from("12345"), S(true)).unwrap(), + "123451234512345!2345" + ); + + test_fn_where("12345", "12345", String::from("12345"), S(false)).unwrap_err(); +} + +#[test] +fn test_struct_method() { + // Test test_method + assert_eq!( + TestStruct + .test_method("12345", "12345", String::from("12345"), S(true)) + .unwrap(), + "123451234512345!2345" + ); + + TestStruct + .test_method("12345", "12345", String::from("12345"), S(false)) + .unwrap_err(); + + // Test test_method2 + assert_eq!( + TestStruct + .test_method2("12345", "12345", String::from("12345"), S(true)) + .unwrap(), + "123451234512345!2345" + ); + + TestStruct + .test_method2("12345", "12345", String::from("12345"), S(false)) + .unwrap_err(); +} + +#[test] +fn test_struct_fn() { + assert_eq!( + TestStruct::test_fn("12345", "12345", String::from("12345"), S(true), ()).unwrap(), + "123451234512345!2345" + ); + + TestStruct::test_fn("12345", "12345", String::from("12345"), S(false), ()).unwrap_err(); + + assert_eq!( + TestStruct::test_fn2(TestStruct, "12345", "12345", String::from("12345"), S(true), (), ()).unwrap(), + "123451234512345!2345" + ); + + TestStruct::test_fn2(TestStruct, "12345", "12345", String::from("12345"), S(false), (), ()).unwrap_err(); + + assert_eq!( + TestStruct::test_fn3( + Pin::new(&mut TestStruct), + "12345", + "12345", + String::from("12345"), + S(true), + (), + () + ) + .unwrap(), + "123451234512345!2345" + ); + + TestStruct::test_fn3( + Pin::new(&mut TestStruct), + "12345", + "12345", + String::from("12345"), + S(false), + (), + (), + ) + .unwrap_err(); +} diff --git a/gix/Cargo.toml b/gix/Cargo.toml index 7c3fe382cde..11af7e57621 100644 --- a/gix/Cargo.toml +++ b/gix/Cargo.toml @@ -126,6 +126,7 @@ fast-sha1 = [ "gix-features/fast-sha1" ] [dependencies] +gix-macros = { version = "^0.0.0", path = "../gix-macros" } gix-utils = { version = "^0.1.5", path = "../gix-utils" } gix-fs = { version = "^0.5.0", path = "../gix-fs" } gix-ref = { version = "^0.35.0", path = "../gix-ref" } diff --git a/gix/src/discover.rs b/gix/src/discover.rs index da43092c318..a5a3b8a4ad4 100644 --- a/gix/src/discover.rs +++ b/gix/src/discover.rs @@ -2,6 +2,7 @@ use std::path::Path; pub use gix_discover::*; +use gix_macros::momo; use crate::{bstr::BString, ThreadSafeRepository}; @@ -31,6 +32,7 @@ impl ThreadSafeRepository { /// if the directory that is discovered can indeed be trusted (or else they'd have to implement the discovery themselves /// and be sure that no attacker ever gets access to a directory structure. The cost of this is a permission check, which /// seems acceptable). + #[momo] pub fn discover_opts( directory: impl AsRef, options: upwards::Options<'_>, @@ -61,6 +63,8 @@ impl ThreadSafeRepository { /// /// Finally, use the `trust_map` to determine which of our own repository options to use /// based on the trust level of the effective repository directory. + #[momo] + #[allow(unused_mut)] pub fn discover_with_environment_overrides_opts( directory: impl AsRef, mut options: upwards::Options<'_>, diff --git a/gix/src/init.rs b/gix/src/init.rs index bffd5fc5b8f..9b9cb5a61b0 100644 --- a/gix/src/init.rs +++ b/gix/src/init.rs @@ -1,6 +1,7 @@ #![allow(clippy::result_large_err)] use std::{borrow::Cow, convert::TryInto, path::Path}; +use gix_macros::momo; use gix_ref::{ store::WriteReflog, transaction::{PreviousValue, RefEdit}, @@ -40,6 +41,7 @@ impl ThreadSafeRepository { /// /// Fails without action if there is already a `.git` repository inside of `directory`, but /// won't mind if the `directory` otherwise is non-empty. + #[momo] pub fn init( directory: impl AsRef, kind: crate::create::Kind, @@ -56,6 +58,8 @@ impl ThreadSafeRepository { /// /// Instead of naming the default branch `master`, we name it `main` unless configured explicitly using the `init.defaultBranch` /// configuration key. + #[momo] + #[allow(unused_mut)] pub fn init_opts( directory: impl AsRef, kind: crate::create::Kind, diff --git a/gix/src/object/tree/mod.rs b/gix/src/object/tree/mod.rs index 62a7d02e1e8..888621bf109 100644 --- a/gix/src/object/tree/mod.rs +++ b/gix/src/object/tree/mod.rs @@ -1,4 +1,5 @@ use gix_hash::ObjectId; +use gix_macros::momo; pub use gix_object::tree::EntryMode; use gix_object::{bstr::BStr, TreeRefIter}; use gix_odb::FindExt; @@ -132,6 +133,7 @@ impl<'repo> Tree<'repo> { /// /// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively, /// the returned component will be empty which makes the lookup fail. + #[momo] pub fn lookup_entry_by_path( &self, relative_path: impl AsRef, @@ -154,6 +156,7 @@ impl<'repo> Tree<'repo> { /// /// If any path component contains illformed UTF-8 and thus can't be converted to bytes on platforms which can't do so natively, /// the returned component will be empty which makes the lookup fail. + #[momo] pub fn peel_to_entry_by_path( &mut self, relative_path: impl AsRef, diff --git a/gix/src/open/repository.rs b/gix/src/open/repository.rs index f533770c5b2..df096a4e3dd 100644 --- a/gix/src/open/repository.rs +++ b/gix/src/open/repository.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, path::PathBuf}; use gix_features::threading::OwnShared; +use gix_macros::momo; use super::{Error, Options}; use crate::{ @@ -58,6 +59,8 @@ impl ThreadSafeRepository { /// /// Note that opening a repository for implementing custom hooks is also handle specifically in /// [`open_with_environment_overrides()`][Self::open_with_environment_overrides()]. + #[momo] + #[allow(unused_mut)] pub fn open_opts(path: impl Into, mut options: Options) -> Result { let _span = gix_trace::coarse!("ThreadSafeRepository::open()"); let (path, kind) = { @@ -109,6 +112,7 @@ impl ThreadSafeRepository { // GIT_PROXY_SSL_CERT, GIT_PROXY_SSL_KEY, GIT_PROXY_SSL_CERT_PASSWORD_PROTECTED. // GIT_PROXY_SSL_CAINFO, GIT_SSL_CIPHER_LIST, GIT_HTTP_MAX_REQUESTS, GIT_CURL_FTP_NO_EPSV, #[doc(alias = "open_from_env", alias = "git2")] + #[momo] pub fn open_with_environment_overrides( fallback_directory: impl Into, trust_map: gix_sec::trust::Mapping, diff --git a/gix/src/pathspec.rs b/gix/src/pathspec.rs index f5af7b3228e..01445601a4f 100644 --- a/gix/src/pathspec.rs +++ b/gix/src/pathspec.rs @@ -1,4 +1,5 @@ //! Pathspec plumbing and abstractions +use gix_macros::momo; use gix_odb::FindExt; pub use gix_pathspec::*; @@ -92,6 +93,8 @@ impl<'repo> Pathspec<'repo> { alias = "matches_path", alias = "git2" )] + #[momo] + #[allow(clippy::needless_lifetimes)] pub fn pattern_matching_relative_path<'a>( &mut self, relative_path: impl Into<&'a BStr>, @@ -111,6 +114,8 @@ impl<'repo> Pathspec<'repo> { /// The simplified version of [`pattern_matching_relative_path()`](Self::pattern_matching_relative_path()) which returns /// `true` if `relative_path` is included in the set of positive pathspecs, while not being excluded. + #[momo] + #[allow(clippy::needless_lifetimes)] pub fn is_included<'a>(&mut self, relative_path: impl Into<&'a BStr>, is_dir: Option) -> bool { self.pattern_matching_relative_path(relative_path, is_dir) .map_or(false, |m| !m.is_excluded()) diff --git a/gix/src/reference/edits.rs b/gix/src/reference/edits.rs index c6510c2e00b..20834077001 100644 --- a/gix/src/reference/edits.rs +++ b/gix/src/reference/edits.rs @@ -1,8 +1,8 @@ /// pub mod set_target_id { - use gix_ref::{transaction::PreviousValue, Target}; - use crate::{bstr::BString, Reference}; + use gix_macros::momo; + use gix_ref::{transaction::PreviousValue, Target}; mod error { use gix_ref::FullName; @@ -28,6 +28,7 @@ pub mod set_target_id { /// If multiple reference should be changed, use [`Repository::edit_references()`][crate::Repository::edit_references()] /// or the lower level reference database instead. #[allow(clippy::result_large_err)] + #[momo] pub fn set_target_id( &mut self, id: impl Into, diff --git a/gix/src/reference/iter.rs b/gix/src/reference/iter.rs index a2b022f64df..983a3803fc0 100644 --- a/gix/src/reference/iter.rs +++ b/gix/src/reference/iter.rs @@ -1,6 +1,7 @@ //! use std::path::Path; +use gix_macros::momo; use gix_odb::pack::Find; use gix_ref::file::ReferenceExt; @@ -42,6 +43,7 @@ impl<'r> Platform<'r> { /// These are of the form `refs/heads` or `refs/remotes/origin`, and must not contain relative paths components like `.` or `..`. // TODO: Create a custom `Path` type that enforces the requirements of git naturally, this type is surprising possibly on windows // and when not using a trailing '/' to signal directories. + #[momo] pub fn prefixed(&self, prefix: impl AsRef) -> Result, init::Error> { Ok(Iter::new(self.repo, self.platform.prefixed(prefix)?)) } diff --git a/gix/src/remote/connection/fetch/receive_pack.rs b/gix/src/remote/connection/fetch/receive_pack.rs index 089fe295721..1df1a7174f3 100644 --- a/gix/src/remote/connection/fetch/receive_pack.rs +++ b/gix/src/remote/connection/fetch/receive_pack.rs @@ -73,6 +73,7 @@ where /// - `gitoxide.userAgent` is read to obtain the application user agent for git servers and for HTTP servers as well. /// #[gix_protocol::maybe_async::maybe_async] + #[allow(clippy::drop_non_drop)] pub async fn receive

(mut self, mut progress: P, should_interrupt: &AtomicBool) -> Result where P: Progress, diff --git a/gix/src/repository/config/transport.rs b/gix/src/repository/config/transport.rs index 81107076c31..d43fffaf560 100644 --- a/gix/src/repository/config/transport.rs +++ b/gix/src/repository/config/transport.rs @@ -1,6 +1,8 @@ #![allow(clippy::result_large_err)] use std::any::Any; +use gix_macros::momo; + use crate::bstr::BStr; impl crate::Repository { @@ -21,12 +23,14 @@ impl crate::Repository { )), allow(unused_variables) )] + #[allow(clippy::needless_lifetimes)] + #[momo] pub fn transport_options<'a>( &self, url: impl Into<&'a BStr>, remote_name: Option<&BStr>, ) -> Result>, crate::config::transport::Error> { - let url = gix_url::parse(url.into())?; + let url = gix_url::parse(url)?; use gix_url::Scheme::*; match &url.scheme { @@ -270,7 +274,7 @@ impl crate::Repository { .proxy .as_deref() .filter(|url| !url.is_empty()) - .map(|url| gix_url::parse(url.into())) + .map(|url| gix_url::parse(<&BStr>::from(url))) .transpose()? .filter(|url| url.user().is_some()) .map(|url| -> Result<_, config::transport::http::Error> { diff --git a/gix/src/repository/object.rs b/gix/src/repository/object.rs index bab7d1b5c50..2dafe6b4245 100644 --- a/gix/src/repository/object.rs +++ b/gix/src/repository/object.rs @@ -2,6 +2,7 @@ use std::{convert::TryInto, ops::DerefMut}; use gix_hash::ObjectId; +use gix_macros::momo; use gix_odb::{Find, FindExt, Header, HeaderExt, Write}; use gix_ref::{ transaction::{LogChange, PreviousValue, RefLog}, @@ -21,8 +22,8 @@ impl crate::Repository { /// /// In order to get the kind of the object, is must be fully decoded from storage if it is packed with deltas. /// Loose object could be partially decoded, even though that's not implemented. + #[momo] pub fn find_object(&self, id: impl Into) -> Result, object::find::existing::Error> { - let id = id.into(); if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { return Ok(Object { id, @@ -40,8 +41,8 @@ impl crate::Repository { /// /// Note that despite being cheaper than [`Self::find_object()`], there is still some effort traversing delta-chains. #[doc(alias = "read_header", alias = "git2")] + #[momo] pub fn find_header(&self, id: impl Into) -> Result { - let id = id.into(); if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { return Ok(gix_odb::find::Header::Loose { kind: gix_object::Kind::Tree, @@ -54,11 +55,11 @@ impl crate::Repository { /// Obtain information about an object without fully decoding it, or `None` if the object doesn't exist. /// /// Note that despite being cheaper than [`Self::try_find_object()`], there is still some effort traversing delta-chains. + #[momo] pub fn try_find_header( &self, id: impl Into, ) -> Result, object::find::Error> { - let id = id.into(); if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { return Ok(Some(gix_odb::find::Header::Loose { kind: gix_object::Kind::Tree, @@ -69,8 +70,8 @@ impl crate::Repository { } /// Try to find the object with `id` or return `None` if it wasn't found. + #[momo] pub fn try_find_object(&self, id: impl Into) -> Result>, object::find::Error> { - let id = id.into(); if id == gix_hash::ObjectId::empty_tree(self.object_hash()) { return Ok(Some(Object { id, @@ -163,6 +164,7 @@ impl crate::Repository { /// /// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist] /// or to [force overwriting a possibly existing tag](PreviousValue::Any). + #[momo] pub fn tag( &self, name: impl AsRef, @@ -173,11 +175,11 @@ impl crate::Repository { constraint: PreviousValue, ) -> Result, tag::Error> { let tag = gix_object::Tag { - target: target.as_ref().into(), + target: target.into(), target_kind, - name: name.as_ref().into(), + name: name.into(), tagger: tagger.map(|t| t.to_owned()), - message: message.as_ref().into(), + message: message.into(), pgp_signature: None, }; let tag_id = self.write_object(&tag)?; diff --git a/gix/src/repository/reference.rs b/gix/src/repository/reference.rs index 0428699e533..6c8274e2f68 100644 --- a/gix/src/repository/reference.rs +++ b/gix/src/repository/reference.rs @@ -1,6 +1,7 @@ use std::convert::TryInto; use gix_hash::ObjectId; +use gix_macros::momo; use gix_ref::{ transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog}, FullName, PartialNameRef, Target, @@ -14,20 +15,21 @@ impl crate::Repository { /// /// It will be created with `constraint` which is most commonly to [only create it][PreviousValue::MustNotExist] /// or to [force overwriting a possibly existing tag](PreviousValue::Any). + #[momo] pub fn tag_reference( &self, name: impl AsRef, target: impl Into, constraint: PreviousValue, ) -> Result, reference::edit::Error> { - let id = target.into(); + let id = target; let mut edits = self.edit_reference(RefEdit { change: Change::Update { log: Default::default(), expected: constraint, new: Target::Peeled(id), }, - name: format!("refs/tags/{}", name.as_ref()).try_into()?, + name: format!("refs/tags/{name}").try_into()?, deref: false, })?; assert_eq!(edits.len(), 1, "reference splits should ever happen"); diff --git a/gix/src/repository/remote.rs b/gix/src/repository/remote.rs index 74ebbaea027..f00707891ff 100644 --- a/gix/src/repository/remote.rs +++ b/gix/src/repository/remote.rs @@ -1,6 +1,8 @@ #![allow(clippy::result_large_err)] use std::convert::TryInto; +use gix_macros::momo; + use crate::{bstr::BStr, config, remote, remote::find, Remote}; impl crate::Repository { @@ -75,37 +77,38 @@ impl crate::Repository { self.try_find_remote_inner(name_or_url, false) } + fn config_spec( + specs: Vec>, + name_or_url: &BStr, + key: &'static config::tree::keys::Any, + op: gix_refspec::parse::Operation, + ) -> Result, find::Error> { + let kind = key.name; + specs + .into_iter() + .map(|spec| { + key.try_into_refspec(spec, op).map_err(|err| find::Error::RefSpec { + remote_name: name_or_url.into(), + kind, + source: err, + }) + }) + .collect::, _>>() + .map(|mut specs| { + specs.sort(); + specs.dedup(); + specs + }) + } + + #[momo] + #[allow(clippy::needless_lifetimes)] fn try_find_remote_inner<'a>( &self, name_or_url: impl Into<&'a BStr>, rewrite_urls: bool, ) -> Option, find::Error>> { - fn config_spec( - specs: Vec>, - name_or_url: &BStr, - key: &'static config::tree::keys::Any, - op: gix_refspec::parse::Operation, - ) -> Result, find::Error> { - let kind = key.name; - specs - .into_iter() - .map(|spec| { - key.try_into_refspec(spec, op).map_err(|err| find::Error::RefSpec { - remote_name: name_or_url.into(), - kind, - source: err, - }) - }) - .collect::, _>>() - .map(|mut specs| { - specs.sort(); - specs.dedup(); - specs - }) - } - let mut filter = self.filter_config_section(); - let name_or_url = name_or_url.into(); let mut config_url = |key: &'static config::tree::keys::Url, kind: &'static str| { self.config .resolved @@ -113,7 +116,7 @@ impl crate::Repository { .map(|url| { key.try_into_url(url).map_err(|err| find::Error::Url { kind, - remote_name: name_or_url.into(), + remote_name: name_or_url.to_owned(), source: err, }) }) @@ -125,7 +128,7 @@ impl crate::Repository { let fetch_specs = config .strings_filter("remote", Some(name_or_url), "fetch", &mut filter) .map(|specs| { - config_spec( + Self::config_spec( specs, name_or_url, &config::tree::Remote::FETCH, @@ -135,7 +138,7 @@ impl crate::Repository { let push_specs = config .strings_filter("remote", Some(name_or_url), "push", &mut filter) .map(|specs| { - config_spec( + Self::config_spec( specs, name_or_url, &config::tree::Remote::PUSH, diff --git a/gix/src/repository/revision.rs b/gix/src/repository/revision.rs index 4eedf0867e2..27aeb38e194 100644 --- a/gix/src/repository/revision.rs +++ b/gix/src/repository/revision.rs @@ -1,4 +1,5 @@ use crate::{bstr::BStr, revision, Id}; +use gix_macros::momo; /// Methods for resolving revisions by spec or working with the commit graph. impl crate::Repository { @@ -9,6 +10,8 @@ impl crate::Repository { /// - `@` actually stands for `HEAD`, whereas `git` resolves it to the object pointed to by `HEAD` without making the /// `HEAD` ref available for lookups. #[doc(alias = "revparse", alias = "git2")] + #[allow(clippy::needless_lifetimes)] + #[momo] pub fn rev_parse<'a>(&self, spec: impl Into<&'a BStr>) -> Result, revision::spec::parse::Error> { revision::Spec::from_bstr( spec, diff --git a/gix/src/repository/worktree.rs b/gix/src/repository/worktree.rs index 17db1c0ebda..4773c6cd8e1 100644 --- a/gix/src/repository/worktree.rs +++ b/gix/src/repository/worktree.rs @@ -57,12 +57,12 @@ impl crate::Repository { /// The entries will look exactly like they would if one would check them out, with filters applied. /// The `export-ignore` attribute is used to skip blobs or directories to which it applies. #[cfg(feature = "worktree-stream")] + #[gix_macros::momo] pub fn worktree_stream( &self, id: impl Into, ) -> Result<(gix_worktree_stream::Stream, gix_index::File), crate::repository::worktree_stream::Error> { use gix_odb::{FindExt, HeaderExt}; - let id = id.into(); let header = self.objects.header(id)?; if !header.kind().is_tree() { return Err(crate::repository::worktree_stream::Error::NotATree {