Skip to content

Commit

Permalink
feat: support generic fn params without static bound
Browse files Browse the repository at this point in the history
fixes #52
  • Loading branch information
audunhalland committed Jul 29, 2024
1 parent 4989c03 commit 9a13111
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 85 deletions.
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1234,6 +1234,10 @@ impl MockFnInfo {
#[derive(Debug)]
pub struct Impossible;

/// A marker type used when Unimock is unable to represent the user's generic type because it's missing an explicit 'static bound.
#[derive(Debug)]
pub struct ImpossibleWithoutExplicitStaticBound;

/// A clause represents a recipe for creating a unimock instance.
///
/// Clauses may be _terminal_ (basic) and _non-terminal_ (composite).
Expand Down
33 changes: 32 additions & 1 deletion tests/it/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,23 @@ mod method_generics {
T: 'static;
}

#[unimock(api=G5)]
trait NonStaticArg {
fn m5<T>(&self, arg: T);
}

#[unimock(api=G6)]
trait NonStaticArgMix {
fn m6<T: 'static, U>(&self, t: T, u: U);
}

#[unimock(api=G7)]
trait NonStaticArgMix2 {
fn m7<T, U>(&self, t: T, u: U)
where
T: 'static;
}

#[test]
fn method_generics() {
let u = Unimock::new((
Expand All @@ -237,12 +254,26 @@ mod method_generics {
.with_types::<i32>()
.next_call(matching!())
.returns((4, 4)),
G5::m5
.next_call(matching!(ImpossibleWithoutExplicitStaticBound))
.returns(()),
G6::m6
.with_types::<i32>()
.next_call(matching!(42, ImpossibleWithoutExplicitStaticBound))
.returns(()),
G7::m7
.with_types::<i32>()
.next_call(matching!(42, ImpossibleWithoutExplicitStaticBound))
.returns(()),
));

assert_eq!(1, u.m1("g1"));
assert_eq!(2, u.m2("g2".to_string()));
assert_eq!("g3", u.m3::<&str>());
assert_eq!((4, &4), u.m4::<i32>());
u.m5(42);
u.m6(42, 43);
u.m7(42, 43);
}
}

Expand Down Expand Up @@ -349,7 +380,7 @@ mod issue_37_mutation_with_generics {
trait Mock {
fn func<T>(&self, nasty: T, foo: &mut MyFoo)
where
T: Bound;
T: Bound + 'static;
}

#[test]
Expand Down
119 changes: 63 additions & 56 deletions unimock_macros/src/unimock/answer_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,41 @@ pub fn make_answer_fn(

let mut args: syn::punctuated::Punctuated<syn::Type, syn::token::Comma> = Default::default();

for fn_arg in &method.adapted_sig.inputs {
match fn_arg {
syn::FnArg::Receiver(syn::Receiver {
reference,
mutability,
ty,
..
}) => {
if let Some((_, lifetime)) = reference {
let lifetime = match lifetime {
Some(lifetime) => {
hrtbs.insert(lifetime.clone());
lifetime.clone()
}
None => {
hrtbs.insert(self_lifetime.clone());
self_lifetime.clone()
}
};

args.push(syn::Type::Reference(syn::TypeReference {
and_token: Default::default(),
lifetime: Some(lifetime),
mutability: *mutability,
elem: syn::parse_quote! {
#prefix::Unimock
},
}));
} else if guess_is_pin(ty) {
hrtbs.insert(self_lifetime.clone());
let ty = syn::parse_quote! { & #self_lifetime mut #prefix::Unimock };
args.push(ty);
} else {
let ty = register_lifetimes_and_substitute_missing(
ty.as_ref().clone(),
Some(&self_lifetime),
&mut hrtbs,
);
let receiver = self_type_to_unimock(ty, trait_info, attr);

args.push(receiver);
}
for (arg_index, fn_arg) in method.adapted_sig.inputs.iter().enumerate() {
match method.classify_arg(fn_arg, arg_index) {
crate::unimock::method::ArgClass::GenericMissingStaticBound(_) => {
args.push(syn::parse_quote! {
#prefix::ImpossibleWithoutExplicitStaticBound
});
}
syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => match pat.as_ref() {
syn::Pat::Ident(ident) if ident.ident == "self" => {
if guess_is_pin(ty) {
_ => match fn_arg {
syn::FnArg::Receiver(syn::Receiver {
reference,
mutability,
ty,
..
}) => {
if let Some((_, lifetime)) = reference {
let lifetime = match lifetime {
Some(lifetime) => {
hrtbs.insert(lifetime.clone());
lifetime.clone()
}
None => {
hrtbs.insert(self_lifetime.clone());
self_lifetime.clone()
}
};

args.push(syn::Type::Reference(syn::TypeReference {
and_token: Default::default(),
lifetime: Some(lifetime),
mutability: *mutability,
elem: syn::parse_quote! {
#prefix::Unimock
},
}));
} else if guess_is_pin(ty) {
hrtbs.insert(self_lifetime.clone());
let ty = syn::parse_quote! { & #self_lifetime mut #prefix::Unimock };
args.push(ty);
Expand All @@ -80,20 +69,38 @@ pub fn make_answer_fn(
Some(&self_lifetime),
&mut hrtbs,
);
let ty = self_type_to_unimock(ty, trait_info, attr);
args.push(ty);
let receiver = self_type_to_unimock(ty, trait_info, attr);

args.push(receiver);
}
}
_ => {
let ty = register_lifetimes_and_substitute_missing(
ty.as_ref().clone(),
None,
&mut hrtbs,
);
let ty = self_type_to_unimock(ty, trait_info, attr);
syn::FnArg::Typed(syn::PatType { pat, ty, .. }) => match pat.as_ref() {
syn::Pat::Ident(ident) if ident.ident == "self" => {
if guess_is_pin(ty) {
hrtbs.insert(self_lifetime.clone());
let ty = syn::parse_quote! { & #self_lifetime mut #prefix::Unimock };
args.push(ty);
} else {
let ty = register_lifetimes_and_substitute_missing(
ty.as_ref().clone(),
Some(&self_lifetime),
&mut hrtbs,
);
let ty = self_type_to_unimock(ty, trait_info, attr);
args.push(ty);
}
}
_ => {
let ty = register_lifetimes_and_substitute_missing(
ty.as_ref().clone(),
None,
&mut hrtbs,
);
let ty = self_type_to_unimock(ty, trait_info, attr);

args.push(ty);
}
args.push(ty);
}
},
},
}
}
Expand Down
Loading

0 comments on commit 9a13111

Please sign in to comment.