From 8217b3d6777f2db5d4277f8c7209502a07fbb63e Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Sun, 7 Sep 2025 13:53:47 +0200 Subject: [PATCH 01/11] Extract faux_macros_impl, update workspace --- .gitignore | 4 +- faux_macros/Cargo.toml | 3 +- faux_macros/src/lib.rs | 122 +++--------------- faux_macros_impl/.gitignore | 3 + faux_macros_impl/Cargo.toml | 24 ++++ {faux_macros => faux_macros_impl}/LICENSE | 0 .../src/create.rs | 0 faux_macros_impl/src/lib.rs | 113 ++++++++++++++++ .../src/methods.rs | 0 .../src/methods/morphed.rs | 0 .../src/methods/receiver.rs | 0 .../src/self_type.rs | 0 12 files changed, 161 insertions(+), 108 deletions(-) create mode 100644 faux_macros_impl/.gitignore create mode 100644 faux_macros_impl/Cargo.toml rename {faux_macros => faux_macros_impl}/LICENSE (100%) rename {faux_macros => faux_macros_impl}/src/create.rs (100%) create mode 100644 faux_macros_impl/src/lib.rs rename {faux_macros => faux_macros_impl}/src/methods.rs (100%) rename {faux_macros => faux_macros_impl}/src/methods/morphed.rs (100%) rename {faux_macros => faux_macros_impl}/src/methods/receiver.rs (100%) rename {faux_macros => faux_macros_impl}/src/self_type.rs (100%) diff --git a/.gitignore b/.gitignore index 2f88dba..d379565 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target **/*.rs.bk -Cargo.lock \ No newline at end of file +Cargo.lock +.passivate +.vscode/launch.json diff --git a/faux_macros/Cargo.toml b/faux_macros/Cargo.toml index 98577bf..70e2f36 100644 --- a/faux_macros/Cargo.toml +++ b/faux_macros/Cargo.toml @@ -4,13 +4,14 @@ version = "0.1.12" authors = ["Andres "] edition = "2021" license = "MIT" -description = "Implementations for #[create], #[methods], when!" +description = "Api for #[create], #[methods], when!" homepage = "https://github.com/nrxus/faux" repository = "https://github.com/nrxus/faux" keywords = ["mock", "mocking", "test", "testing", "faux"] rust-version = "1.63" [dependencies] +faux_macros_impl = { path = "../faux_macros_impl" } syn = { version = "2", features = ["full", "extra-traits"] } quote = "1" proc-macro2 = "1" diff --git a/faux_macros/src/lib.rs b/faux_macros/src/lib.rs index 3c32eb5..8f3bd27 100644 --- a/faux_macros/src/lib.rs +++ b/faux_macros/src/lib.rs @@ -1,123 +1,33 @@ -extern crate proc_macro; - -mod create; -mod methods; -mod self_type; - -use darling::{export::NestedMeta, FromMeta}; -use proc_macro::TokenStream; -use quote::quote; +use faux_macros_impl::{create_impl, methods_impl, when_impl}; +use proc_macro2::TokenStream; #[proc_macro_attribute] -pub fn create(args: TokenStream, original: TokenStream) -> TokenStream { +pub fn create(args: proc_macro::TokenStream, original: proc_macro::TokenStream) -> proc_macro::TokenStream { let original = syn::parse_macro_input!(original as syn::ItemStruct); - let args = match NestedMeta::parse_meta_list(args.into()) - .map_err(darling::Error::from) - .and_then(|v| create::Args::from_list(&v)) - { - Ok(v) => v, - Err(e) => return e.write_errors().into(), - }; - - let mockable = create::Mockable::new(original, args); + let result = create_impl( + proc_macro2::TokenStream::from(args), + original); - TokenStream::from(mockable) + proc_macro::TokenStream::from(result) } #[proc_macro_attribute] -pub fn methods(args: TokenStream, original: TokenStream) -> TokenStream { +pub fn methods(args: proc_macro::TokenStream, original: proc_macro::TokenStream) -> proc_macro::TokenStream { let original = syn::parse_macro_input!(original as syn::ItemImpl); - let args = match NestedMeta::parse_meta_list(args.into()) - .map_err(darling::Error::from) - .and_then(|v| methods::Args::from_list(&v)) - { - Ok(v) => v, - Err(e) => return e.write_errors().into(), - }; + let result = methods_impl( + TokenStream::from(args), + original); - match methods::Mockable::new(original, args) { - Ok(mockable) => TokenStream::from(mockable), - Err(e) => e.write_errors().into(), - } + proc_macro::TokenStream::from(result) } #[proc_macro] pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - match syn::parse_macro_input!(input as syn::Expr) { - syn::Expr::Field(syn::ExprField { - base, - member: syn::Member::Named(ident), - .. - }) => { - let when = quote::format_ident!("_when_{}", ident); - TokenStream::from(quote!( { #base.#when() })) - } - syn::Expr::MethodCall(syn::ExprMethodCall { - receiver, - method, - args, - turbofish, - .. - }) => { - let when = quote::format_ident!("_when_{}", method); + let input = syn::parse_macro_input!(input as syn::Expr); - let args = args - .into_iter() - .map(expr_to_matcher) - .collect::, _>>(); - - match args { - Err(e) => e.write_errors().into(), - Ok(args) if args.is_empty() => { TokenStream::from(quote!({ #receiver.#when #turbofish() }))} - Ok(args) => { TokenStream::from(quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }))} - } - } - expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`") - .with_span(&expr) - .write_errors() - .into(), - } -} + let result = when_impl(input); -use quote::ToTokens; - -fn ref_matcher_maybe( - expr: &syn::Expr, - left: &syn::Expr, - matcher: impl FnOnce() -> darling::Result, -) -> darling::Result { - match left { - syn::Expr::Infer(_) => matcher(), - syn::Expr::Unary(syn::ExprUnary { - op: syn::UnOp::Deref(_), - expr, - .. - }) => { - let matcher = matcher()?; - Ok(quote! { faux::matcher::ArgMatcher::<#expr>::into_ref_matcher(#matcher) }) - } - _ => Ok(quote! { faux::matcher::eq(#expr) }), - } -} - -fn expr_to_matcher(expr: syn::Expr) -> darling::Result { - match &expr { - syn::Expr::Infer(_) => Ok(quote! { faux::matcher::any() }), - syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => { - ref_matcher_maybe(&expr, left, || Ok(right.to_token_stream())) - } - syn::Expr::Binary(syn::ExprBinary { - left, op, right, .. - }) => ref_matcher_maybe(&expr, left, || match op { - syn::BinOp::Eq(_) => Ok(quote! { faux::matcher::eq_against(#right) }), - _ => Err(darling::Error::custom(format!( - "faux:when! does not handle argument matchers with syntax: '{}'", - expr.to_token_stream() - )) - .with_span(&expr)), - }), - arg => Ok(quote! { faux::matcher::eq(#arg) }), - } -} + proc_macro::TokenStream::from(result) +} \ No newline at end of file diff --git a/faux_macros_impl/.gitignore b/faux_macros_impl/.gitignore new file mode 100644 index 0000000..2f88dba --- /dev/null +++ b/faux_macros_impl/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/faux_macros_impl/Cargo.toml b/faux_macros_impl/Cargo.toml new file mode 100644 index 0000000..f112dfc --- /dev/null +++ b/faux_macros_impl/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "faux_macros_impl" +version = "0.1.12" +authors = ["Andres "] +edition = "2021" +license = "MIT" +description = "Implementations for #[create], #[methods], when!" +homepage = "https://github.com/nrxus/faux" +repository = "https://github.com/nrxus/faux" +keywords = ["mock", "mocking", "test", "testing", "faux"] +rust-version = "1.63" + +[dependencies] +syn = { version = "2", features = ["full", "extra-traits"] } +quote = "1" +proc-macro2 = "1" +darling = "0.20" +uuid = { version = "1", features = ["v4"] } + +[dev-dependencies] +faux = { path = "../" } + +[lib] +proc-macro = true diff --git a/faux_macros/LICENSE b/faux_macros_impl/LICENSE similarity index 100% rename from faux_macros/LICENSE rename to faux_macros_impl/LICENSE diff --git a/faux_macros/src/create.rs b/faux_macros_impl/src/create.rs similarity index 100% rename from faux_macros/src/create.rs rename to faux_macros_impl/src/create.rs diff --git a/faux_macros_impl/src/lib.rs b/faux_macros_impl/src/lib.rs new file mode 100644 index 0000000..d843fde --- /dev/null +++ b/faux_macros_impl/src/lib.rs @@ -0,0 +1,113 @@ +mod create; +mod methods; +mod self_type; + +use darling::{export::NestedMeta, FromMeta}; +use proc_macro2::TokenStream; +use quote::quote; + +pub fn create_impl(args: TokenStream, original: syn::ItemStruct) -> TokenStream { + let args = match NestedMeta::parse_meta_list(args) + .map_err(darling::Error::from) + .and_then(|v| create::Args::from_list(&v)) + { + Ok(v) => v, + Err(e) => return e.write_errors(), + }; + + let mockable = create::Mockable::new(original, args); + + TokenStream::from(mockable) +} + +pub fn methods_impl(args: TokenStream, original: syn::ItemImpl) -> TokenStream { + let args = match NestedMeta::parse_meta_list(args) + .map_err(darling::Error::from) + .and_then(|v| methods::Args::from_list(&v)) + { + Ok(v) => v, + Err(e) => return e.write_errors(), + }; + + match methods::Mockable::new(original, args) { + Ok(mockable) => TokenStream::from(mockable), + Err(e) => e.write_errors(), + } +} + +pub fn when_impl(input: syn::Expr) -> TokenStream { + match input { + syn::Expr::Field(syn::ExprField { + base, + member: syn::Member::Named(ident), + .. + }) => { + let when = quote::format_ident!("_when_{}", ident); + quote!( { #base.#when() }) + } + syn::Expr::MethodCall(syn::ExprMethodCall { + receiver, + method, + args, + turbofish, + .. + }) => { + let when = quote::format_ident!("_when_{}", method); + + let args = args + .into_iter() + .map(expr_to_matcher) + .collect::, _>>(); + + match args { + Err(e) => e.write_errors(), + Ok(args) if args.is_empty() => { quote!({ #receiver.#when #turbofish() }) } + Ok(args) => { quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }) } + } + } + expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`") + .with_span(&expr) + .write_errors(), + } +} + +use quote::ToTokens; + +fn ref_matcher_maybe( + expr: &syn::Expr, + left: &syn::Expr, + matcher: impl FnOnce() -> darling::Result, +) -> darling::Result { + match left { + syn::Expr::Infer(_) => matcher(), + syn::Expr::Unary(syn::ExprUnary { + op: syn::UnOp::Deref(_), + expr, + .. + }) => { + let matcher = matcher()?; + Ok(quote! { faux::matcher::ArgMatcher::<#expr>::into_ref_matcher(#matcher) }) + } + _ => Ok(quote! { faux::matcher::eq(#expr) }), + } +} + +fn expr_to_matcher(expr: syn::Expr) -> darling::Result { + match &expr { + syn::Expr::Infer(_) => Ok(quote! { faux::matcher::any() }), + syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => { + ref_matcher_maybe(&expr, left, || Ok(right.to_token_stream())) + } + syn::Expr::Binary(syn::ExprBinary { + left, op, right, .. + }) => ref_matcher_maybe(&expr, left, || match op { + syn::BinOp::Eq(_) => Ok(quote! { faux::matcher::eq_against(#right) }), + _ => Err(darling::Error::custom(format!( + "faux:when! does not handle argument matchers with syntax: '{}'", + expr.to_token_stream() + )) + .with_span(&expr)), + }), + arg => Ok(quote! { faux::matcher::eq(#arg) }), + } +} diff --git a/faux_macros/src/methods.rs b/faux_macros_impl/src/methods.rs similarity index 100% rename from faux_macros/src/methods.rs rename to faux_macros_impl/src/methods.rs diff --git a/faux_macros/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs similarity index 100% rename from faux_macros/src/methods/morphed.rs rename to faux_macros_impl/src/methods/morphed.rs diff --git a/faux_macros/src/methods/receiver.rs b/faux_macros_impl/src/methods/receiver.rs similarity index 100% rename from faux_macros/src/methods/receiver.rs rename to faux_macros_impl/src/methods/receiver.rs diff --git a/faux_macros/src/self_type.rs b/faux_macros_impl/src/self_type.rs similarity index 100% rename from faux_macros/src/self_type.rs rename to faux_macros_impl/src/self_type.rs From 9b91a2603fac92291bd800024f62a90a65827e0a Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Sun, 7 Sep 2025 14:00:02 +0200 Subject: [PATCH 02/11] Update From implementations to target proc_macro2 --- faux_macros_impl/Cargo.toml | 3 --- faux_macros_impl/src/create.rs | 6 +++--- faux_macros_impl/src/methods.rs | 6 +++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/faux_macros_impl/Cargo.toml b/faux_macros_impl/Cargo.toml index f112dfc..d42872d 100644 --- a/faux_macros_impl/Cargo.toml +++ b/faux_macros_impl/Cargo.toml @@ -19,6 +19,3 @@ uuid = { version = "1", features = ["v4"] } [dev-dependencies] faux = { path = "../" } - -[lib] -proc-macro = true diff --git a/faux_macros_impl/src/create.rs b/faux_macros_impl/src/create.rs index 52d2cf6..433469f 100644 --- a/faux_macros_impl/src/create.rs +++ b/faux_macros_impl/src/create.rs @@ -47,14 +47,14 @@ impl Mockable { } } -impl From for proc_macro::TokenStream { +impl From for proc_macro2::TokenStream { fn from(mockable: Mockable) -> Self { let Mockable { real, morphed } = mockable; let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); let name = &morphed.ident; let name_str = name.to_string(); - proc_macro::TokenStream::from(quote! { + quote! { #morphed impl #impl_generics #name #ty_generics #where_clause { @@ -65,7 +65,7 @@ impl From for proc_macro::TokenStream { #[allow(non_camel_case_types)] #real - }) + } } } diff --git a/faux_macros_impl/src/methods.rs b/faux_macros_impl/src/methods.rs index 41fcfb8..2a6a13f 100644 --- a/faux_macros_impl/src/methods.rs +++ b/faux_macros_impl/src/methods.rs @@ -126,7 +126,7 @@ impl Mockable { } } -impl From for proc_macro::TokenStream { +impl From for proc_macro2::TokenStream { fn from(mockable: Mockable) -> Self { let Mockable { real, @@ -206,7 +206,7 @@ impl From for proc_macro::TokenStream { }) }; - proc_macro::TokenStream::from(quote! { + quote! { #morphed #whens @@ -219,7 +219,7 @@ impl From for proc_macro::TokenStream { #real } - }) + } } } From 145f3e6ff88b0675770fd0c41ca87a1e99569cb3 Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Mon, 8 Sep 2025 21:17:09 +0200 Subject: [PATCH 03/11] Appears to be working, support for faux constructor returning tuple containing Self --- faux_macros/Cargo.toml | 2 +- faux_macros_impl/src/methods/morphed.rs | 174 +++++++++++++++--------- tests/return_self_method.rs | 8 ++ 3 files changed, 121 insertions(+), 63 deletions(-) diff --git a/faux_macros/Cargo.toml b/faux_macros/Cargo.toml index 70e2f36..37a1d0f 100644 --- a/faux_macros/Cargo.toml +++ b/faux_macros/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["mock", "mocking", "test", "testing", "faux"] rust-version = "1.63" [dependencies] -faux_macros_impl = { path = "../faux_macros_impl" } +faux_macros_impl = { path = "../faux_macros_impl" } syn = { version = "2", features = ["full", "extra-traits"] } quote = "1" proc-macro2 = "1" diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs index e7f0f70..c1d9ad0 100644 --- a/faux_macros_impl/src/methods/morphed.rs +++ b/faux_macros_impl/src/methods/morphed.rs @@ -254,6 +254,10 @@ impl<'a> Signature<'a> { .map(|m| m.create_when(self.output, self.name)) } + fn is_self(ty: &syn::TypePath, morphed_ty: &syn::TypePath) -> bool { + ty == morphed_ty || (ty.qself.is_none() && ty.path.is_ident("Self")) + } + fn wrap_self( &self, morphed_ty: &syn::TypePath, @@ -265,81 +269,127 @@ impl<'a> Signature<'a> { Some(output) => output, None => return Ok(None), }; - if !contains_self(output, morphed_ty) { + + Self::wrap_self_specific(output, morphed_ty, real_self, block) + } + + fn wrap_self_specific( + ty: &Type, + morphed_ty: &syn::TypePath, + real_self: SelfType, + block: &TokenStream, + ) -> darling::Result> { + if !contains_self(ty, morphed_ty) { return Ok(None); } - let is_self = |ty: &syn::TypePath| { - ty == morphed_ty || (ty.qself.is_none() && ty.path.is_ident("Self")) - }; - - let output = match output { + let output = match ty { syn::Type::Path(output) => output, + syn::Type::Tuple(tuple) => { + return Ok(Some(Self::wrap_self_tuple(block, tuple, morphed_ty, real_self))); + }, output => return Err(unhandled_self_return(output)), }; - let wrapped = if is_self(output) { - match real_self { - SelfType::Owned => quote! { Self(faux::MaybeFaux::Real(#block)) }, - generic => { - let new_path = generic - .new_path() - .expect("Generic self should have new() function"); - quote! { Self(faux::MaybeFaux::Real(#new_path(#block))) } - } - } + let wrapped = if Self::is_self(output, morphed_ty) { + Self::wrap_self_simple(real_self, block) } else { - let unpathed_output = output.path.segments.last().unwrap(); - let generics = match &unpathed_output.arguments { - syn::PathArguments::AngleBracketed(args) => args, - g => return Err(unhandled_self_return(g)), - }; - let first_arg = generics - .args - .first() - .expect("faux bug: no generic arguments but expected at least one"); - let first_arg = match first_arg { - syn::GenericArgument::Type(syn::Type::Path(ty)) => ty, - _ => return Err(unhandled_self_return(generics)), - }; - - if !is_self(first_arg) { - return Err(unhandled_self_return(generics)); + match Self::wrap_self_generic(real_self, block, morphed_ty, output) { + Ok(value) => value, + Err(value) => return value, } + }; - let output_ident = &unpathed_output.ident; - match real_self { - SelfType::Rc if output_ident == "Rc" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } - } - SelfType::Arc if output_ident == "Arc" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } - } - SelfType::Box if output_ident == "Box" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } - } - SelfType::Owned if output_ident == "Result" || output_ident == "Option" => { - quote! { { #block }.map(faux::MaybeFaux::Real).map(Self) } - } - SelfType::Owned if output_ident == "Box" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(*#block))) } - } - SelfType::Owned if output_ident == "Rc" || output_ident == "Arc" => { - let ungenerified = { - // clone so we don't modify the original output - let mut output = output.clone(); - output.path.segments.last_mut().unwrap().arguments = PathArguments::None; - output - }; - quote! { <#output>::new(Self(faux::MaybeFaux::Real( - #ungenerified::try_unwrap(#block).ok().expect("faux: failed to grab value from reference counter because it was not unique.") - ))) } + Ok(Some(wrapped)) + } + + fn wrap_self_tuple( + block: &TokenStream, + tuple: &syn::TypeTuple, + morphed_ty: &syn::TypePath, + real_self: SelfType) -> TokenStream { + let elements = tuple.elems + .iter() + .enumerate() + .map(|e| { + let index = syn::Index::from(e.0); + let ty = e.1; + + let wrapped = Self::wrap_self_specific(ty, morphed_ty, real_self, block); + + match wrapped { + Ok(Some(self_type)) => quote! { Self(faux::MaybeFaux::Real(tuple.#index)) }, + Ok(None) => quote! { tuple.#index }, + Err(_) => todo!(), } - _ => return Err(unhandled_self_return(output)), + }); + + quote! {{ + let tuple = #block; + + (# ( #elements),*) + }} + } + + fn wrap_self_simple(real_self: SelfType, block: &TokenStream) -> TokenStream { + match real_self { + SelfType::Owned => quote! { Self(faux::MaybeFaux::Real(#block)) }, + generic => { + let new_path = generic + .new_path() + .expect("Generic self should have new() function"); + quote! { Self(faux::MaybeFaux::Real(#new_path(#block))) } } - }; + } + } - Ok(Some(wrapped)) + fn wrap_self_generic(real_self: SelfType, block: &TokenStream, morphed_ty: &syn::TypePath, output: &TypePath) -> Result, darling::Error>> { + let unpathed_output = output.path.segments.last().unwrap(); + let generics = match &unpathed_output.arguments { + syn::PathArguments::AngleBracketed(args) => args, + g => return Err(Err(unhandled_self_return(g))), + }; + let first_arg = generics + .args + .first() + .expect("faux bug: no generic arguments but expected at least one"); + let first_arg = match first_arg { + syn::GenericArgument::Type(syn::Type::Path(ty)) => ty, + _ => return Err(Err(unhandled_self_return(generics))), + }; + if !Self::is_self(first_arg, morphed_ty) { + return Err(Err(unhandled_self_return(generics))); + } + let output_ident = &unpathed_output.ident; + Ok(match real_self { + SelfType::Rc if output_ident == "Rc" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } + } + SelfType::Arc if output_ident == "Arc" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } + } + SelfType::Box if output_ident == "Box" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } + } + SelfType::Owned if output_ident == "Result" || output_ident == "Option" => { + quote! { { #block }.map(faux::MaybeFaux::Real).map(Self) } + } + SelfType::Owned if output_ident == "Box" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(*#block))) } + } + SelfType::Owned if output_ident == "Rc" || output_ident == "Arc" => { + let ungenerified = { + // clone so we don't modify the original output + let mut output = output.clone(); + output.path.segments.last_mut().unwrap().arguments = PathArguments::None; + output + }; + quote! { <#output>::new(Self(faux::MaybeFaux::Real( + #ungenerified::try_unwrap(#block).ok().expect("faux: failed to grab value from reference counter because it was not unique.") + ))) } + } + _ => return Err(Err(unhandled_self_return(output))), + }) } } diff --git a/tests/return_self_method.rs b/tests/return_self_method.rs index f67cf9e..9f375ff 100644 --- a/tests/return_self_method.rs +++ b/tests/return_self_method.rs @@ -53,6 +53,14 @@ impl Foo { Some(Foo { a: 2 }) } + pub fn new_tuple() -> (Foo, i32) { + (Foo { a: 4 }, 4) + } + + pub fn new_tuple_multiple() -> (Foo, i32, Foo) { + (Foo { a: 4 }, 4, Foo { a: 8 }) + } + pub fn similar_name() -> FooError { FooError {} } From 00bf19df0c5e5e0d61c7e0cfa63b89aca88646d5 Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Mon, 8 Sep 2025 22:12:55 +0200 Subject: [PATCH 04/11] Draft of handling Box (and later others) in Tuple, needs error handling --- faux_macros_impl/src/methods/morphed.rs | 16 +++++++++++++--- tests/return_self_method.rs | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs index c1d9ad0..d0bb78e 100644 --- a/faux_macros_impl/src/methods/morphed.rs +++ b/faux_macros_impl/src/methods/morphed.rs @@ -291,6 +291,10 @@ impl<'a> Signature<'a> { output => return Err(unhandled_self_return(output)), }; + Self::wrap_self_custom(morphed_ty, real_self, block, output) + } + + fn wrap_self_custom(morphed_ty: &TypePath, real_self: SelfType, block: &TokenStream, output: &TypePath) -> Result, darling::Error> { let wrapped = if Self::is_self(output, morphed_ty) { Self::wrap_self_simple(real_self, block) } else { @@ -315,12 +319,18 @@ impl<'a> Signature<'a> { let index = syn::Index::from(e.0); let ty = e.1; - let wrapped = Self::wrap_self_specific(ty, morphed_ty, real_self, block); + let ty = match ty { + Type::Path(type_path) => type_path, + _ => todo!(), + }; + + let tuple_index = quote! { tuple.#index }; + let wrapped = Self::wrap_self_custom(morphed_ty, real_self, &tuple_index, ty); match wrapped { - Ok(Some(self_type)) => quote! { Self(faux::MaybeFaux::Real(tuple.#index)) }, + Ok(Some(self_type)) => self_type, Ok(None) => quote! { tuple.#index }, - Err(_) => todo!(), + Err(_) => quote! { tuple.#index } } }); diff --git a/tests/return_self_method.rs b/tests/return_self_method.rs index 9f375ff..da3e066 100644 --- a/tests/return_self_method.rs +++ b/tests/return_self_method.rs @@ -53,14 +53,18 @@ impl Foo { Some(Foo { a: 2 }) } - pub fn new_tuple() -> (Foo, i32) { + pub fn new_tuple() -> (Self, i32) { (Foo { a: 4 }, 4) } - pub fn new_tuple_multiple() -> (Foo, i32, Foo) { + pub fn new_tuple_multiple() -> (Self, i32, Self) { (Foo { a: 4 }, 4, Foo { a: 8 }) } + pub fn new_tuple_boxed() -> (Self, Box) { + (Foo { a: 4}, Box::new(Foo { a: 8 })) + } + pub fn similar_name() -> FooError { FooError {} } From fc51eb68ce816f5a5d6a9da5ba194c7e5d9597fb Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Tue, 9 Sep 2025 21:55:30 +0200 Subject: [PATCH 05/11] Simplify a bit, move tuple tests to own file --- faux_macros_impl/src/methods/morphed.rs | 11 +------ tests/return_self_method.rs | 12 -------- tests/return_self_tuple.rs | 38 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 22 deletions(-) create mode 100644 tests/return_self_tuple.rs diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs index d0bb78e..6c5a36e 100644 --- a/faux_macros_impl/src/methods/morphed.rs +++ b/faux_macros_impl/src/methods/morphed.rs @@ -291,10 +291,6 @@ impl<'a> Signature<'a> { output => return Err(unhandled_self_return(output)), }; - Self::wrap_self_custom(morphed_ty, real_self, block, output) - } - - fn wrap_self_custom(morphed_ty: &TypePath, real_self: SelfType, block: &TokenStream, output: &TypePath) -> Result, darling::Error> { let wrapped = if Self::is_self(output, morphed_ty) { Self::wrap_self_simple(real_self, block) } else { @@ -319,13 +315,8 @@ impl<'a> Signature<'a> { let index = syn::Index::from(e.0); let ty = e.1; - let ty = match ty { - Type::Path(type_path) => type_path, - _ => todo!(), - }; - let tuple_index = quote! { tuple.#index }; - let wrapped = Self::wrap_self_custom(morphed_ty, real_self, &tuple_index, ty); + let wrapped = Self::wrap_self_specific(ty, morphed_ty, real_self, &tuple_index); match wrapped { Ok(Some(self_type)) => self_type, diff --git a/tests/return_self_method.rs b/tests/return_self_method.rs index da3e066..f67cf9e 100644 --- a/tests/return_self_method.rs +++ b/tests/return_self_method.rs @@ -53,18 +53,6 @@ impl Foo { Some(Foo { a: 2 }) } - pub fn new_tuple() -> (Self, i32) { - (Foo { a: 4 }, 4) - } - - pub fn new_tuple_multiple() -> (Self, i32, Self) { - (Foo { a: 4 }, 4, Foo { a: 8 }) - } - - pub fn new_tuple_boxed() -> (Self, Box) { - (Foo { a: 4}, Box::new(Foo { a: 8 })) - } - pub fn similar_name() -> FooError { FooError {} } diff --git a/tests/return_self_tuple.rs b/tests/return_self_tuple.rs new file mode 100644 index 0000000..9616abf --- /dev/null +++ b/tests/return_self_tuple.rs @@ -0,0 +1,38 @@ +use std::sync::Arc; +use std::rc::Rc; +use std::result::Result; +use std::error::Error; + +#[faux::create] +pub struct Foo; + +#[faux::methods] +impl Foo { + pub fn self_self() -> (Self, Self) { + (Foo, Foo) + } + + pub fn self_path() -> (Self, i32) { + (Foo, 1) + } + + pub fn self_box() -> (Self, Box) { + (Foo, Box::new(Foo)) + } + + pub fn self_rc() -> (Self, Rc) { + (Foo, Rc::new(Foo)) + } + + pub fn self_arc() -> (Self, Arc) { + (Foo, Arc::new(Foo)) + } + + pub fn self_result() -> (Self, Result>) { + (Foo, Ok(Foo)) + } + + pub fn self_option() -> (Self, Option) { + (Foo, Some(Foo)) + } +} From 998ab397d665d38fdde7baedb3fc16ed1ace8687 Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Tue, 9 Sep 2025 22:54:30 +0200 Subject: [PATCH 06/11] Try to reduce amount of diff, remove some extracted functions --- faux_macros_impl/src/methods/morphed.rs | 123 +++++++++++------------- 1 file changed, 57 insertions(+), 66 deletions(-) diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs index 6c5a36e..a0273cd 100644 --- a/faux_macros_impl/src/methods/morphed.rs +++ b/faux_macros_impl/src/methods/morphed.rs @@ -254,7 +254,7 @@ impl<'a> Signature<'a> { .map(|m| m.create_when(self.output, self.name)) } - fn is_self(ty: &syn::TypePath, morphed_ty: &syn::TypePath) -> bool { + fn is_self(ty: &syn::TypePath, morphed_ty: &syn::TypePath) -> bool { ty == morphed_ty || (ty.qself.is_none() && ty.path.is_ident("Self")) } @@ -292,11 +292,63 @@ impl<'a> Signature<'a> { }; let wrapped = if Self::is_self(output, morphed_ty) { - Self::wrap_self_simple(real_self, block) + match real_self { + SelfType::Owned => quote! { Self(faux::MaybeFaux::Real(#block)) }, + generic => { + let new_path = generic + .new_path() + .expect("Generic self should have new() function"); + quote! { Self(faux::MaybeFaux::Real(#new_path(#block))) } + } + } } else { - match Self::wrap_self_generic(real_self, block, morphed_ty, output) { - Ok(value) => value, - Err(value) => return value, + let unpathed_output = output.path.segments.last().unwrap(); + let generics = match &unpathed_output.arguments { + syn::PathArguments::AngleBracketed(args) => args, + g => return Err(unhandled_self_return(g)), + }; + let first_arg = generics + .args + .first() + .expect("faux bug: no generic arguments but expected at least one"); + let first_arg = match first_arg { + syn::GenericArgument::Type(syn::Type::Path(ty)) => ty, + _ => return Err(unhandled_self_return(generics)), + }; + + if !Self::is_self(first_arg, morphed_ty) { + return Err(unhandled_self_return(generics)); + } + + let output_ident = &unpathed_output.ident; + match real_self { + SelfType::Rc if output_ident == "Rc" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } + } + SelfType::Arc if output_ident == "Arc" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } + } + SelfType::Box if output_ident == "Box" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } + } + SelfType::Owned if output_ident == "Result" || output_ident == "Option" => { + quote! { { #block }.map(faux::MaybeFaux::Real).map(Self) } + } + SelfType::Owned if output_ident == "Box" => { + quote! { <#output>::new(Self(faux::MaybeFaux::Real(*#block))) } + } + SelfType::Owned if output_ident == "Rc" || output_ident == "Arc" => { + let ungenerified = { + // clone so we don't modify the original output + let mut output = output.clone(); + output.path.segments.last_mut().unwrap().arguments = PathArguments::None; + output + }; + quote! { <#output>::new(Self(faux::MaybeFaux::Real( + #ungenerified::try_unwrap(#block).ok().expect("faux: failed to grab value from reference counter because it was not unique.") + ))) } + } + _ => return Err(unhandled_self_return(output)), } }; @@ -331,67 +383,6 @@ impl<'a> Signature<'a> { (# ( #elements),*) }} } - - fn wrap_self_simple(real_self: SelfType, block: &TokenStream) -> TokenStream { - match real_self { - SelfType::Owned => quote! { Self(faux::MaybeFaux::Real(#block)) }, - generic => { - let new_path = generic - .new_path() - .expect("Generic self should have new() function"); - quote! { Self(faux::MaybeFaux::Real(#new_path(#block))) } - } - } - } - - fn wrap_self_generic(real_self: SelfType, block: &TokenStream, morphed_ty: &syn::TypePath, output: &TypePath) -> Result, darling::Error>> { - let unpathed_output = output.path.segments.last().unwrap(); - let generics = match &unpathed_output.arguments { - syn::PathArguments::AngleBracketed(args) => args, - g => return Err(Err(unhandled_self_return(g))), - }; - let first_arg = generics - .args - .first() - .expect("faux bug: no generic arguments but expected at least one"); - let first_arg = match first_arg { - syn::GenericArgument::Type(syn::Type::Path(ty)) => ty, - _ => return Err(Err(unhandled_self_return(generics))), - }; - if !Self::is_self(first_arg, morphed_ty) { - return Err(Err(unhandled_self_return(generics))); - } - let output_ident = &unpathed_output.ident; - Ok(match real_self { - SelfType::Rc if output_ident == "Rc" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } - } - SelfType::Arc if output_ident == "Arc" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } - } - SelfType::Box if output_ident == "Box" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(#block))) } - } - SelfType::Owned if output_ident == "Result" || output_ident == "Option" => { - quote! { { #block }.map(faux::MaybeFaux::Real).map(Self) } - } - SelfType::Owned if output_ident == "Box" => { - quote! { <#output>::new(Self(faux::MaybeFaux::Real(*#block))) } - } - SelfType::Owned if output_ident == "Rc" || output_ident == "Arc" => { - let ungenerified = { - // clone so we don't modify the original output - let mut output = output.clone(); - output.path.segments.last_mut().unwrap().arguments = PathArguments::None; - output - }; - quote! { <#output>::new(Self(faux::MaybeFaux::Real( - #ungenerified::try_unwrap(#block).ok().expect("faux: failed to grab value from reference counter because it was not unique.") - ))) } - } - _ => return Err(Err(unhandled_self_return(output))), - }) - } } impl MethodData<'_> { From 5b32cbc3e08053ee263bcaa2a26114cd60e9f91e Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Tue, 9 Sep 2025 23:32:34 +0200 Subject: [PATCH 07/11] Propagate error when wrap_self_specific on a tuple element fails --- faux_macros_impl/src/methods/morphed.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs index a0273cd..e5edfcd 100644 --- a/faux_macros_impl/src/methods/morphed.rs +++ b/faux_macros_impl/src/methods/morphed.rs @@ -286,7 +286,7 @@ impl<'a> Signature<'a> { let output = match ty { syn::Type::Path(output) => output, syn::Type::Tuple(tuple) => { - return Ok(Some(Self::wrap_self_tuple(block, tuple, morphed_ty, real_self))); + return Self::wrap_self_tuple(block, tuple, morphed_ty, real_self); }, output => return Err(unhandled_self_return(output)), }; @@ -359,7 +359,7 @@ impl<'a> Signature<'a> { block: &TokenStream, tuple: &syn::TypeTuple, morphed_ty: &syn::TypePath, - real_self: SelfType) -> TokenStream { + real_self: SelfType) -> darling::Result> { let elements = tuple.elems .iter() .enumerate() @@ -368,20 +368,17 @@ impl<'a> Signature<'a> { let ty = e.1; let tuple_index = quote! { tuple.#index }; - let wrapped = Self::wrap_self_specific(ty, morphed_ty, real_self, &tuple_index); + let wrapped = Self::wrap_self_specific(ty, morphed_ty, real_self, &tuple_index)?; - match wrapped { - Ok(Some(self_type)) => self_type, - Ok(None) => quote! { tuple.#index }, - Err(_) => quote! { tuple.#index } - } - }); + Ok(wrapped.unwrap_or(tuple_index)) + }) + .collect::>>()?; - quote! {{ + Ok(Some(quote! {{ let tuple = #block; (# ( #elements),*) - }} + }})) } } From 12d947ca1d05843353a40ae1577493d0710960d1 Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Wed, 10 Sep 2025 18:49:47 +0200 Subject: [PATCH 08/11] Remove is_self extraction to simplify diff further --- faux_macros_impl/src/methods/morphed.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros_impl/src/methods/morphed.rs index e5edfcd..d0662b4 100644 --- a/faux_macros_impl/src/methods/morphed.rs +++ b/faux_macros_impl/src/methods/morphed.rs @@ -254,10 +254,6 @@ impl<'a> Signature<'a> { .map(|m| m.create_when(self.output, self.name)) } - fn is_self(ty: &syn::TypePath, morphed_ty: &syn::TypePath) -> bool { - ty == morphed_ty || (ty.qself.is_none() && ty.path.is_ident("Self")) - } - fn wrap_self( &self, morphed_ty: &syn::TypePath, @@ -283,6 +279,10 @@ impl<'a> Signature<'a> { return Ok(None); } + let is_self = |ty: &syn::TypePath| { + ty == morphed_ty || (ty.qself.is_none() && ty.path.is_ident("Self")) + }; + let output = match ty { syn::Type::Path(output) => output, syn::Type::Tuple(tuple) => { @@ -291,7 +291,7 @@ impl<'a> Signature<'a> { output => return Err(unhandled_self_return(output)), }; - let wrapped = if Self::is_self(output, morphed_ty) { + let wrapped = if is_self(output) { match real_self { SelfType::Owned => quote! { Self(faux::MaybeFaux::Real(#block)) }, generic => { @@ -316,7 +316,7 @@ impl<'a> Signature<'a> { _ => return Err(unhandled_self_return(generics)), }; - if !Self::is_self(first_arg, morphed_ty) { + if !is_self(first_arg) { return Err(unhandled_self_return(generics)); } From 3c6e5f76b42c92a042f22b0681414f779289e7d9 Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Wed, 10 Sep 2025 20:40:13 +0200 Subject: [PATCH 09/11] Undo extraction of _impl crate for simpler diff --- faux_macros/Cargo.toml | 3 +- {faux_macros_impl => faux_macros}/LICENSE | 0 .../src/create.rs | 6 +- faux_macros/src/lib.rs | 122 +++++++++++++++--- .../src/methods.rs | 6 +- .../src/methods/morphed.rs | 0 .../src/methods/receiver.rs | 0 .../src/self_type.rs | 0 faux_macros_impl/.gitignore | 3 - faux_macros_impl/Cargo.toml | 21 --- faux_macros_impl/src/lib.rs | 113 ---------------- 11 files changed, 113 insertions(+), 161 deletions(-) rename {faux_macros_impl => faux_macros}/LICENSE (100%) rename {faux_macros_impl => faux_macros}/src/create.rs (95%) rename {faux_macros_impl => faux_macros}/src/methods.rs (98%) rename {faux_macros_impl => faux_macros}/src/methods/morphed.rs (100%) rename {faux_macros_impl => faux_macros}/src/methods/receiver.rs (100%) rename {faux_macros_impl => faux_macros}/src/self_type.rs (100%) delete mode 100644 faux_macros_impl/.gitignore delete mode 100644 faux_macros_impl/Cargo.toml delete mode 100644 faux_macros_impl/src/lib.rs diff --git a/faux_macros/Cargo.toml b/faux_macros/Cargo.toml index 37a1d0f..98577bf 100644 --- a/faux_macros/Cargo.toml +++ b/faux_macros/Cargo.toml @@ -4,14 +4,13 @@ version = "0.1.12" authors = ["Andres "] edition = "2021" license = "MIT" -description = "Api for #[create], #[methods], when!" +description = "Implementations for #[create], #[methods], when!" homepage = "https://github.com/nrxus/faux" repository = "https://github.com/nrxus/faux" keywords = ["mock", "mocking", "test", "testing", "faux"] rust-version = "1.63" [dependencies] -faux_macros_impl = { path = "../faux_macros_impl" } syn = { version = "2", features = ["full", "extra-traits"] } quote = "1" proc-macro2 = "1" diff --git a/faux_macros_impl/LICENSE b/faux_macros/LICENSE similarity index 100% rename from faux_macros_impl/LICENSE rename to faux_macros/LICENSE diff --git a/faux_macros_impl/src/create.rs b/faux_macros/src/create.rs similarity index 95% rename from faux_macros_impl/src/create.rs rename to faux_macros/src/create.rs index 433469f..52d2cf6 100644 --- a/faux_macros_impl/src/create.rs +++ b/faux_macros/src/create.rs @@ -47,14 +47,14 @@ impl Mockable { } } -impl From for proc_macro2::TokenStream { +impl From for proc_macro::TokenStream { fn from(mockable: Mockable) -> Self { let Mockable { real, morphed } = mockable; let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); let name = &morphed.ident; let name_str = name.to_string(); - quote! { + proc_macro::TokenStream::from(quote! { #morphed impl #impl_generics #name #ty_generics #where_clause { @@ -65,7 +65,7 @@ impl From for proc_macro2::TokenStream { #[allow(non_camel_case_types)] #real - } + }) } } diff --git a/faux_macros/src/lib.rs b/faux_macros/src/lib.rs index 8f3bd27..3c32eb5 100644 --- a/faux_macros/src/lib.rs +++ b/faux_macros/src/lib.rs @@ -1,33 +1,123 @@ -use faux_macros_impl::{create_impl, methods_impl, when_impl}; -use proc_macro2::TokenStream; +extern crate proc_macro; + +mod create; +mod methods; +mod self_type; + +use darling::{export::NestedMeta, FromMeta}; +use proc_macro::TokenStream; +use quote::quote; #[proc_macro_attribute] -pub fn create(args: proc_macro::TokenStream, original: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn create(args: TokenStream, original: TokenStream) -> TokenStream { let original = syn::parse_macro_input!(original as syn::ItemStruct); - let result = create_impl( - proc_macro2::TokenStream::from(args), - original); + let args = match NestedMeta::parse_meta_list(args.into()) + .map_err(darling::Error::from) + .and_then(|v| create::Args::from_list(&v)) + { + Ok(v) => v, + Err(e) => return e.write_errors().into(), + }; + + let mockable = create::Mockable::new(original, args); - proc_macro::TokenStream::from(result) + TokenStream::from(mockable) } #[proc_macro_attribute] -pub fn methods(args: proc_macro::TokenStream, original: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn methods(args: TokenStream, original: TokenStream) -> TokenStream { let original = syn::parse_macro_input!(original as syn::ItemImpl); - let result = methods_impl( - TokenStream::from(args), - original); + let args = match NestedMeta::parse_meta_list(args.into()) + .map_err(darling::Error::from) + .and_then(|v| methods::Args::from_list(&v)) + { + Ok(v) => v, + Err(e) => return e.write_errors().into(), + }; - proc_macro::TokenStream::from(result) + match methods::Mockable::new(original, args) { + Ok(mockable) => TokenStream::from(mockable), + Err(e) => e.write_errors().into(), + } } #[proc_macro] pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = syn::parse_macro_input!(input as syn::Expr); + match syn::parse_macro_input!(input as syn::Expr) { + syn::Expr::Field(syn::ExprField { + base, + member: syn::Member::Named(ident), + .. + }) => { + let when = quote::format_ident!("_when_{}", ident); + TokenStream::from(quote!( { #base.#when() })) + } + syn::Expr::MethodCall(syn::ExprMethodCall { + receiver, + method, + args, + turbofish, + .. + }) => { + let when = quote::format_ident!("_when_{}", method); - let result = when_impl(input); + let args = args + .into_iter() + .map(expr_to_matcher) + .collect::, _>>(); + + match args { + Err(e) => e.write_errors().into(), + Ok(args) if args.is_empty() => { TokenStream::from(quote!({ #receiver.#when #turbofish() }))} + Ok(args) => { TokenStream::from(quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }))} + } + } + expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`") + .with_span(&expr) + .write_errors() + .into(), + } +} - proc_macro::TokenStream::from(result) -} \ No newline at end of file +use quote::ToTokens; + +fn ref_matcher_maybe( + expr: &syn::Expr, + left: &syn::Expr, + matcher: impl FnOnce() -> darling::Result, +) -> darling::Result { + match left { + syn::Expr::Infer(_) => matcher(), + syn::Expr::Unary(syn::ExprUnary { + op: syn::UnOp::Deref(_), + expr, + .. + }) => { + let matcher = matcher()?; + Ok(quote! { faux::matcher::ArgMatcher::<#expr>::into_ref_matcher(#matcher) }) + } + _ => Ok(quote! { faux::matcher::eq(#expr) }), + } +} + +fn expr_to_matcher(expr: syn::Expr) -> darling::Result { + match &expr { + syn::Expr::Infer(_) => Ok(quote! { faux::matcher::any() }), + syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => { + ref_matcher_maybe(&expr, left, || Ok(right.to_token_stream())) + } + syn::Expr::Binary(syn::ExprBinary { + left, op, right, .. + }) => ref_matcher_maybe(&expr, left, || match op { + syn::BinOp::Eq(_) => Ok(quote! { faux::matcher::eq_against(#right) }), + _ => Err(darling::Error::custom(format!( + "faux:when! does not handle argument matchers with syntax: '{}'", + expr.to_token_stream() + )) + .with_span(&expr)), + }), + arg => Ok(quote! { faux::matcher::eq(#arg) }), + } +} diff --git a/faux_macros_impl/src/methods.rs b/faux_macros/src/methods.rs similarity index 98% rename from faux_macros_impl/src/methods.rs rename to faux_macros/src/methods.rs index 2a6a13f..41fcfb8 100644 --- a/faux_macros_impl/src/methods.rs +++ b/faux_macros/src/methods.rs @@ -126,7 +126,7 @@ impl Mockable { } } -impl From for proc_macro2::TokenStream { +impl From for proc_macro::TokenStream { fn from(mockable: Mockable) -> Self { let Mockable { real, @@ -206,7 +206,7 @@ impl From for proc_macro2::TokenStream { }) }; - quote! { + proc_macro::TokenStream::from(quote! { #morphed #whens @@ -219,7 +219,7 @@ impl From for proc_macro2::TokenStream { #real } - } + }) } } diff --git a/faux_macros_impl/src/methods/morphed.rs b/faux_macros/src/methods/morphed.rs similarity index 100% rename from faux_macros_impl/src/methods/morphed.rs rename to faux_macros/src/methods/morphed.rs diff --git a/faux_macros_impl/src/methods/receiver.rs b/faux_macros/src/methods/receiver.rs similarity index 100% rename from faux_macros_impl/src/methods/receiver.rs rename to faux_macros/src/methods/receiver.rs diff --git a/faux_macros_impl/src/self_type.rs b/faux_macros/src/self_type.rs similarity index 100% rename from faux_macros_impl/src/self_type.rs rename to faux_macros/src/self_type.rs diff --git a/faux_macros_impl/.gitignore b/faux_macros_impl/.gitignore deleted file mode 100644 index 2f88dba..0000000 --- a/faux_macros_impl/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/target -**/*.rs.bk -Cargo.lock \ No newline at end of file diff --git a/faux_macros_impl/Cargo.toml b/faux_macros_impl/Cargo.toml deleted file mode 100644 index d42872d..0000000 --- a/faux_macros_impl/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "faux_macros_impl" -version = "0.1.12" -authors = ["Andres "] -edition = "2021" -license = "MIT" -description = "Implementations for #[create], #[methods], when!" -homepage = "https://github.com/nrxus/faux" -repository = "https://github.com/nrxus/faux" -keywords = ["mock", "mocking", "test", "testing", "faux"] -rust-version = "1.63" - -[dependencies] -syn = { version = "2", features = ["full", "extra-traits"] } -quote = "1" -proc-macro2 = "1" -darling = "0.20" -uuid = { version = "1", features = ["v4"] } - -[dev-dependencies] -faux = { path = "../" } diff --git a/faux_macros_impl/src/lib.rs b/faux_macros_impl/src/lib.rs deleted file mode 100644 index d843fde..0000000 --- a/faux_macros_impl/src/lib.rs +++ /dev/null @@ -1,113 +0,0 @@ -mod create; -mod methods; -mod self_type; - -use darling::{export::NestedMeta, FromMeta}; -use proc_macro2::TokenStream; -use quote::quote; - -pub fn create_impl(args: TokenStream, original: syn::ItemStruct) -> TokenStream { - let args = match NestedMeta::parse_meta_list(args) - .map_err(darling::Error::from) - .and_then(|v| create::Args::from_list(&v)) - { - Ok(v) => v, - Err(e) => return e.write_errors(), - }; - - let mockable = create::Mockable::new(original, args); - - TokenStream::from(mockable) -} - -pub fn methods_impl(args: TokenStream, original: syn::ItemImpl) -> TokenStream { - let args = match NestedMeta::parse_meta_list(args) - .map_err(darling::Error::from) - .and_then(|v| methods::Args::from_list(&v)) - { - Ok(v) => v, - Err(e) => return e.write_errors(), - }; - - match methods::Mockable::new(original, args) { - Ok(mockable) => TokenStream::from(mockable), - Err(e) => e.write_errors(), - } -} - -pub fn when_impl(input: syn::Expr) -> TokenStream { - match input { - syn::Expr::Field(syn::ExprField { - base, - member: syn::Member::Named(ident), - .. - }) => { - let when = quote::format_ident!("_when_{}", ident); - quote!( { #base.#when() }) - } - syn::Expr::MethodCall(syn::ExprMethodCall { - receiver, - method, - args, - turbofish, - .. - }) => { - let when = quote::format_ident!("_when_{}", method); - - let args = args - .into_iter() - .map(expr_to_matcher) - .collect::, _>>(); - - match args { - Err(e) => e.write_errors(), - Ok(args) if args.is_empty() => { quote!({ #receiver.#when #turbofish() }) } - Ok(args) => { quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }) } - } - } - expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`") - .with_span(&expr) - .write_errors(), - } -} - -use quote::ToTokens; - -fn ref_matcher_maybe( - expr: &syn::Expr, - left: &syn::Expr, - matcher: impl FnOnce() -> darling::Result, -) -> darling::Result { - match left { - syn::Expr::Infer(_) => matcher(), - syn::Expr::Unary(syn::ExprUnary { - op: syn::UnOp::Deref(_), - expr, - .. - }) => { - let matcher = matcher()?; - Ok(quote! { faux::matcher::ArgMatcher::<#expr>::into_ref_matcher(#matcher) }) - } - _ => Ok(quote! { faux::matcher::eq(#expr) }), - } -} - -fn expr_to_matcher(expr: syn::Expr) -> darling::Result { - match &expr { - syn::Expr::Infer(_) => Ok(quote! { faux::matcher::any() }), - syn::Expr::Assign(syn::ExprAssign { left, right, .. }) => { - ref_matcher_maybe(&expr, left, || Ok(right.to_token_stream())) - } - syn::Expr::Binary(syn::ExprBinary { - left, op, right, .. - }) => ref_matcher_maybe(&expr, left, || match op { - syn::BinOp::Eq(_) => Ok(quote! { faux::matcher::eq_against(#right) }), - _ => Err(darling::Error::custom(format!( - "faux:when! does not handle argument matchers with syntax: '{}'", - expr.to_token_stream() - )) - .with_span(&expr)), - }), - arg => Ok(quote! { faux::matcher::eq(#arg) }), - } -} From 72938e36c4c02430ce6be243299d45a220e03e5e Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Wed, 10 Sep 2025 20:43:00 +0200 Subject: [PATCH 10/11] Remove changes to .gitignore for simpler diff --- .gitignore | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index d379565..2f88dba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ /target **/*.rs.bk -Cargo.lock -.passivate -.vscode/launch.json +Cargo.lock \ No newline at end of file From 4e0d15e8fe09e95e6f01203c6472ce7d5cc1dd6c Mon Sep 17 00:00:00 2001 From: Tobias van de Ven Date: Tue, 16 Sep 2025 21:05:25 +0200 Subject: [PATCH 11/11] Extract self.output check to remove the need for 2 wrap_self functions --- faux_macros/src/methods/morphed.rs | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/faux_macros/src/methods/morphed.rs b/faux_macros/src/methods/morphed.rs index d0662b4..36bffa9 100644 --- a/faux_macros/src/methods/morphed.rs +++ b/faux_macros/src/methods/morphed.rs @@ -180,8 +180,11 @@ impl<'a> Signature<'a> { if self.is_async { proxy_real.extend(quote! { .await }) } - if let Some(wrapped_self) = self.wrap_self(morphed_ty, real_self, &proxy_real)? { - proxy_real = wrapped_self; + + if let Some(output) = self.output { + if let Some(wrapped_self) = Self::wrap_self(output, morphed_ty, real_self, &proxy_real)? { + proxy_real = wrapped_self; + } } let ret = match &self.method_data { @@ -255,21 +258,6 @@ impl<'a> Signature<'a> { } fn wrap_self( - &self, - morphed_ty: &syn::TypePath, - real_self: SelfType, - block: &TokenStream, - ) -> darling::Result> { - // TODO: use let-else once we bump MSRV to 1.65.0 - let output = match self.output { - Some(output) => output, - None => return Ok(None), - }; - - Self::wrap_self_specific(output, morphed_ty, real_self, block) - } - - fn wrap_self_specific( ty: &Type, morphed_ty: &syn::TypePath, real_self: SelfType, @@ -368,7 +356,7 @@ impl<'a> Signature<'a> { let ty = e.1; let tuple_index = quote! { tuple.#index }; - let wrapped = Self::wrap_self_specific(ty, morphed_ty, real_self, &tuple_index)?; + let wrapped = Self::wrap_self(ty, morphed_ty, real_self, &tuple_index)?; Ok(wrapped.unwrap_or(tuple_index)) })