diff --git a/Cargo.lock b/Cargo.lock index a06040137e0..7bbfd8861b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,6 +294,15 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "basic-toml" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bfc506e7a2370ec239e1d072507b2a80c833083699d3c6fa176fbb4de8448c6" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -1208,6 +1217,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 +1805,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "gix-macros" +version = "0.0.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "trybuild", +] + [[package]] name = "gix-mailmap" version = "0.17.0" @@ -2565,6 +2585,12 @@ dependencies = [ "symlink", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gloo-timers" version = "0.2.6" @@ -4418,6 +4444,21 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +[[package]] +name = "trybuild" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df60d81823ed9c520ee897489573da4b1d79ffbe006b8134f46de1a1aa03555" +dependencies = [ + "basic-toml", + "glob", + "once_cell", + "serde", + "serde_derive", + "serde_json", + "termcolor", +] + [[package]] name = "tui-react" version = "0.20.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..2cb8bff3600 --- /dev/null +++ b/gix-macros/Cargo.toml @@ -0,0 +1,25 @@ +[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" + +[dev-dependencies] +trybuild = "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..4693a79e824 --- /dev/null +++ b/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/gix-macros/src/momo.rs b/gix-macros/src/momo.rs new file mode 100644 index 00000000000..802f4b5b024 --- /dev/null +++ b/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 { + 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) -> 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 == "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> { + 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, + ty_conversions: &HashMap>, +) -> (bool, Punctuated, Punctuated, 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), + } +} diff --git a/gix-macros/tests/macros.rs b/gix-macros/tests/macros.rs new file mode 100644 index 00000000000..821fc9835b0 --- /dev/null +++ b/gix-macros/tests/macros.rs @@ -0,0 +1 @@ +mod momo; diff --git a/gix-macros/tests/momo/mod.rs b/gix-macros/tests/momo/mod.rs new file mode 100644 index 00000000000..a6b7b63174f --- /dev/null +++ b/gix-macros/tests/momo/mod.rs @@ -0,0 +1,302 @@ +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) +} + +#[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(()) + } + } +} + +#[allow(unused)] +#[momo] +fn test_fn_pat( + a: impl Into, + b: impl AsRef, + mut c: impl AsMut, + d: impl TryInto, + S(_g): S, +) -> Result<(), E> { + let mut s = a.into(); + s += b.as_ref(); + s += c.as_mut(); + s += &d.try_into()?; + + drop(s); + + Ok(()) +} + +#[test] +fn 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 struct_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 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(); +} + +#[test] +fn ux() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/momo/ux/*.rs"); +} diff --git a/gix-macros/tests/momo/ux/error_if_ineffective.rs b/gix-macros/tests/momo/ux/error_if_ineffective.rs new file mode 100644 index 00000000000..cf41170c5da --- /dev/null +++ b/gix-macros/tests/momo/ux/error_if_ineffective.rs @@ -0,0 +1,3 @@ +/// If nothing is done, momo should fail. +#[gix_macros::momo] +fn main() {} diff --git a/gix-macros/tests/momo/ux/error_if_ineffective.stderr b/gix-macros/tests/momo/ux/error_if_ineffective.stderr new file mode 100644 index 00000000000..1d93bc3ff39 --- /dev/null +++ b/gix-macros/tests/momo/ux/error_if_ineffective.stderr @@ -0,0 +1,11 @@ +error: Couldn't apply a single conversion - momo is ineffective here + --> tests/momo/ux/error_if_ineffective.rs:1:1 + | +1 | /// If nothing is done, momo should fail. + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0601]: `main` function not found in crate `$CRATE` + --> tests/momo/ux/error_if_ineffective.rs:3:13 + | +3 | fn main() {} + | ^ consider adding a `main` function to `$DIR/tests/momo/ux/error_if_ineffective.rs` diff --git a/gix-macros/tests/momo/ux/error_on_struct.rs b/gix-macros/tests/momo/ux/error_on_struct.rs new file mode 100644 index 00000000000..ff8db306c2f --- /dev/null +++ b/gix-macros/tests/momo/ux/error_on_struct.rs @@ -0,0 +1,5 @@ +fn main() {} + +/// Only functions work with momo +#[gix_macros::momo] +struct S; diff --git a/gix-macros/tests/momo/ux/error_on_struct.stderr b/gix-macros/tests/momo/ux/error_on_struct.stderr new file mode 100644 index 00000000000..e54864dcf8f --- /dev/null +++ b/gix-macros/tests/momo/ux/error_on_struct.stderr @@ -0,0 +1,5 @@ +error: expect a function + --> tests/momo/ux/error_on_struct.rs:3:1 + | +3 | /// Only functions work with momo + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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/config/snapshot/access.rs b/gix/src/config/snapshot/access.rs index 284fe0fb843..0a82d966c00 100644 --- a/gix/src/config/snapshot/access.rs +++ b/gix/src/config/snapshot/access.rs @@ -2,6 +2,7 @@ use std::borrow::Cow; use gix_features::threading::OwnShared; +use gix_macros::momo; use crate::{ bstr::{BStr, BString}, @@ -25,6 +26,7 @@ impl<'repo> Snapshot<'repo> { } /// Like [`boolean()`][Self::boolean()], but it will report an error if the value couldn't be interpreted as boolean. + #[momo] pub fn try_boolean<'a>(&self, key: impl Into<&'a BStr>) -> Option> { self.repo.config.resolved.boolean_by_key(key) } @@ -40,6 +42,7 @@ impl<'repo> Snapshot<'repo> { } /// Like [`integer()`][Self::integer()], but it will report an error if the value couldn't be interpreted as boolean. + #[momo] pub fn try_integer<'a>(&self, key: impl Into<&'a BStr>) -> Option> { self.repo.config.resolved.integer_by_key(key) } @@ -47,6 +50,7 @@ impl<'repo> Snapshot<'repo> { /// Return the string at `key`, or `None` if there is no such value. /// /// Note that this method takes the most recent value at `key` even if it is from a file with reduced trust. + #[momo] pub fn string<'a>(&self, key: impl Into<&'a BStr>) -> Option> { self.repo.config.resolved.string_by_key(key) } @@ -54,6 +58,7 @@ impl<'repo> Snapshot<'repo> { /// Return the trusted and fully interpolated path at `key`, or `None` if there is no such value /// or if no value was found in a trusted file. /// An error occurs if the path could not be interpolated to its final value. + #[momo] pub fn trusted_path<'a>( &self, key: impl Into<&'a BStr>, @@ -101,6 +106,7 @@ impl<'repo> SnapshotMut<'repo> { /// Set the value at `key` to `new_value`, possibly creating the section if it doesn't exist yet, or overriding the most recent existing /// value, which will be returned. + #[momo] pub fn set_value<'b>( &mut self, key: &'static dyn crate::config::tree::Key, @@ -119,6 +125,7 @@ impl<'repo> SnapshotMut<'repo> { /// Set the value at `key` to `new_value` in the given `subsection`, possibly creating the section and sub-section if it doesn't exist yet, /// or overriding the most recent existing value, which will be returned. + #[momo] pub fn set_subsection_value<'a, 'b>( &mut self, key: &'static dyn crate::config::tree::Key, diff --git a/gix/src/create.rs b/gix/src/create.rs index 4cc01402c07..c3839f66fcf 100644 --- a/gix/src/create.rs +++ b/gix/src/create.rs @@ -7,6 +7,7 @@ use std::{ use gix_config::parse::section; use gix_discover::DOT_GIT_DIR; +use gix_macros::momo; /// The error used in [`into()`]. #[derive(Debug, thiserror::Error)] @@ -124,6 +125,7 @@ pub struct Options { /// Note that this is a simple template-based initialization routine which should be accompanied with additional corrections /// to respect git configuration, which is accomplished by [its callers][crate::ThreadSafeRepository::init_opts()] /// that return a [Repository][crate::Repository]. +#[momo] pub fn into( directory: impl Into, kind: Kind, diff --git a/gix/src/discover.rs b/gix/src/discover.rs index da43092c318..50dcb9fa773 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,7 @@ 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] 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..21c2debd807 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,7 @@ impl ThreadSafeRepository { /// /// Instead of naming the default branch `master`, we name it `main` unless configured explicitly using the `init.defaultBranch` /// configuration key. + #[momo] 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..2f9127bbd7d 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,7 @@ 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] pub fn open_opts(path: impl Into, mut options: Options) -> Result { let _span = gix_trace::coarse!("ThreadSafeRepository::open()"); let (path, kind) = { @@ -109,6 +111,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..4c56be28e81 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,7 @@ impl<'repo> Pathspec<'repo> { alias = "matches_path", alias = "git2" )] + #[momo] pub fn pattern_matching_relative_path<'a>( &mut self, relative_path: impl Into<&'a BStr>, @@ -111,6 +113,7 @@ 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] 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/remote/save.rs b/gix/src/remote/save.rs index a61df64c0d6..2a91dfa9c1d 100644 --- a/gix/src/remote/save.rs +++ b/gix/src/remote/save.rs @@ -1,5 +1,7 @@ use std::convert::TryInto; +use gix_macros::momo; + use crate::{ bstr::{BStr, BString}, config, remote, Remote, @@ -111,6 +113,7 @@ impl Remote<'_> { /// If this name is different from the current one, the git configuration will still contain the previous name, /// and the caller should account for that. #[allow(clippy::result_large_err)] + #[momo] pub fn save_as_to( &mut self, name: impl Into, diff --git a/gix/src/repository/config/transport.rs b/gix/src/repository/config/transport.rs index 81107076c31..99b5a7f47ff 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,6 +23,7 @@ impl crate::Repository { )), allow(unused_variables) )] + #[momo] pub fn transport_options<'a>( &self, url: impl Into<&'a BStr>, diff --git a/gix/src/repository/object.rs b/gix/src/repository/object.rs index bab7d1b5c50..e7300a6250a 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,6 +22,7 @@ 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()) { @@ -40,6 +42,7 @@ 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()) { @@ -54,6 +57,7 @@ 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, @@ -69,6 +73,7 @@ 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()) { @@ -163,6 +168,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, diff --git a/gix/src/repository/reference.rs b/gix/src/repository/reference.rs index 0428699e533..4fd6aeb9f69 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,6 +15,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_reference( &self, name: impl AsRef, diff --git a/gix/src/repository/remote.rs b/gix/src/repository/remote.rs index 74ebbaea027..ebd0f52f3a1 100644 --- a/gix/src/repository/remote.rs +++ b/gix/src/repository/remote.rs @@ -62,7 +62,7 @@ impl crate::Repository { /// /// We will only include information if we deem it [trustworthy][crate::open::Options::filter_config_section()]. pub fn try_find_remote<'a>(&self, name_or_url: impl Into<&'a BStr>) -> Option, find::Error>> { - self.try_find_remote_inner(name_or_url, true) + self.try_find_remote_inner(name_or_url.into(), true) } /// Similar to [`try_find_remote()`][Self::try_find_remote()], but removes a failure mode if rewritten URLs turn out to be invalid @@ -72,7 +72,7 @@ impl crate::Repository { &self, name_or_url: impl Into<&'a BStr>, ) -> Option, find::Error>> { - self.try_find_remote_inner(name_or_url, false) + self.try_find_remote_inner(name_or_url.into(), false) } fn try_find_remote_inner<'a>( diff --git a/gix/src/repository/revision.rs b/gix/src/repository/revision.rs index 4eedf0867e2..bb9b56d57ac 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,7 @@ 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")] + #[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..af2ff10434c 100644 --- a/gix/src/repository/worktree.rs +++ b/gix/src/repository/worktree.rs @@ -57,6 +57,7 @@ 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, @@ -66,7 +67,7 @@ impl crate::Repository { let header = self.objects.header(id)?; if !header.kind().is_tree() { return Err(crate::repository::worktree_stream::Error::NotATree { - id: id.to_owned(), + id, actual: header.kind(), }); } diff --git a/gix/src/revision/spec/parse/mod.rs b/gix/src/revision/spec/parse/mod.rs index f69ecc4afb0..950dfa0040b 100644 --- a/gix/src/revision/spec/parse/mod.rs +++ b/gix/src/revision/spec/parse/mod.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use gix_hash::ObjectId; +use gix_macros::momo; use gix_revision::spec::parse; use crate::{bstr::BStr, revision::Spec, Repository}; @@ -30,6 +31,7 @@ impl<'repo> Spec<'repo> { /// Parse `spec` and use information from `repo` to resolve it, using `opts` to learn how to deal with ambiguity. /// /// Note that it's easier and to use [`repo.rev_parse()`][Repository::rev_parse()] instead. + #[momo] pub fn from_bstr<'a>(spec: impl Into<&'a BStr>, repo: &'repo Repository, opts: Options) -> Result { let mut delegate = Delegate::new(repo, opts); match gix_revision::spec::parse(spec.into(), &mut delegate) {