// vim: tw=80 //! Proc Macros for use with Mockall //! //! You probably don't want to use this crate directly. Instead, you should use //! its reexports via the [`mockall`](https://docs.rs/mockall/latest/mockall) //! crate.
use cfg_if::cfg_if; use proc_macro2::{Span, TokenStream}; use quote::{ToTokens, format_ident, quote}; use std::{
env,
hash::BuildHasherDefault
}; use syn::{
*,
punctuated::Punctuated,
spanned::Spanned
};
mod automock; mod mock_function; mod mock_item; mod mock_item_struct; mod mock_trait; mod mockable_item; mod mockable_struct; usecrate::automock::Attrs; usecrate::mockable_struct::MockableStruct; usecrate::mock_item::MockItem; usecrate::mock_item_struct::MockItemStruct; usecrate::mockable_item::MockableItem;
// Define deterministic aliases for these common types. type HashMap<K, V> = std::collections::HashMap<K, V, BuildHasherDefault<std::collections::hash_map::DefaultHasher>>; type HashSet<K> = std::collections::HashSet<K, BuildHasherDefault<std::collections::hash_map::DefaultHasher>>;
cfg_if! { // proc-macro2's Span::unstable method requires the nightly feature, and it // doesn't work in test mode. // https://github.com/alexcrichton/proc-macro2/issues/159 if#[cfg(all(feature = "nightly_derive", not(test)))] { fn compile_error(span: Span, msg: &str) {
span.unstable()
.error(msg)
.emit();
}
} else { fn compile_error(_span: Span, msg: &str) {
panic!("{msg}. More information may be available when mockall is built with the \"nightly\" feature.");
}
}
}
/// Does this Attribute represent Mockall's "concretize" pseudo-attribute? fn is_concretize(attr: &Attribute) -> bool { if attr.path().segments.last().unwrap().ident == "concretize" { true
} elseif attr.path().is_ident("cfg_attr") { match &attr.meta {
Meta::List(ml) => {
ml.tokens.to_string().contains("concretize")
}, // cfg_attr should always contain a list
_ => false,
}
} else { false
}
}
letmut save_types = |ident: &Ident, tpb: &Punctuated<TypeParamBound, Token![+]>| { if !tpb.is_empty() { iflet Ok(newty) = parse2::<Type>(quote!(&(dyn#tpb))) { // substitute T arguments let subst_ty: Type = parse2(quote!(#ident)).unwrap();
hm.insert(subst_ty, (newty.clone(), None));
// substitute &T arguments let subst_ty: Type = parse2(quote!(&#ident)).unwrap();
hm.insert(subst_ty, (newty, None));
} else {
compile_error(tpb.span(), "Type cannot be made into a trait object");
}
iflet Ok(newty) = parse2::<Type>(quote!(&mut (dyn#tpb))) { // substitute &mut T arguments let subst_ty: Type = parse2(quote!(&mut#ident)).unwrap();
hm.insert(subst_ty, (newty, None));
} else {
compile_error(tpb.span(), "Type cannot be made into a trait object");
}
// I wish we could substitute &[T] arguments. But there's no way // for the mock method to turn &[T] into &[&dyn T]. iflet Ok(newty) = parse2::<Type>(quote!(&[&(dyn#tpb)])) { let subst_ty: Type = parse2(quote!(&[#ident])).unwrap();
hm.insert(subst_ty, (newty, Some(tpb.clone())));
} else {
compile_error(tpb.span(), "Type cannot be made into a trait object");
}
}
};
for g ingen.params.iter() { iflet GenericParam::Type(tp) = g {
save_types(&tp.ident, &tp.bounds); // else there had better be a where clause
}
} iflet Some(wc) = &gen.where_clause { for pred in wc.predicates.iter() { iflet WherePredicate::Type(pt) = pred { let bounded_ty = &pt.bounded_ty; iflet Ok(ident) = parse2::<Ident>(quote!(#bounded_ty)) {
save_types(&ident, &pt.bounds);
} else { // We can't yet handle where clauses this complicated
}
}
}
}
fn deanonymize_path(path: &mut Path) { for seg in path.segments.iter_mut() { match &mut seg.arguments {
PathArguments::None => (),
PathArguments::AngleBracketed(abga) => { for ga in abga.args.iter_mut() { iflet GenericArgument::Lifetime(lt) = ga {
deanonymize_lifetime(lt)
}
}
},
_ => compile_error(seg.arguments.span(), "Methods returning functions are TODO"),
}
}
}
/// Replace any references to the anonymous lifetime `'_` with `'static`. fn deanonymize(literal_type: &mutType) { match literal_type { Type::Array(ta) => deanonymize(ta.elem.as_mut()), Type::BareFn(tbf) => { iflet ReturnType::Type(_, refmut bt) = tbf.output {
deanonymize(bt.as_mut());
} for input in tbf.inputs.iter_mut() {
deanonymize(&mut input.ty);
}
}, Type::Group(tg) => deanonymize(tg.elem.as_mut()), Type::Infer(_) => (), Type::Never(_) => (), Type::Paren(tp) => deanonymize(tp.elem.as_mut()), Type::Path(tp) => { iflet Some(refmut qself) = tp.qself {
deanonymize(qself.ty.as_mut());
}
deanonymize_path(&mut tp.path);
}, Type::Ptr(tptr) => deanonymize(tptr.elem.as_mut()), Type::Reference(tr) => { iflet Some(lt) = tr.lifetime.as_mut() {
deanonymize_lifetime(lt)
}
deanonymize(tr.elem.as_mut());
}, Type::Slice(s) => deanonymize(s.elem.as_mut()), Type::TraitObject(tto) => { for tpb in tto.bounds.iter_mut() { match tpb {
TypeParamBound::Trait(tb) => deanonymize_path(&mut tb.path),
TypeParamBound::Lifetime(lt) => deanonymize_lifetime(lt),
_ => ()
}
}
}, Type::Tuple(tt) => { for ty in tt.elems.iter_mut() {
deanonymize(ty)
}
}
x => compile_error(x.span(), "Unimplemented type for deanonymize")
}
}
// If there are any closures in the argument list, turn them into boxed // functions fn declosurefy(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
(Generics, Vec<FnArg>, Vec<TokenStream>)
{ letmut hm = HashMap::default();
letmut save_fn_types = |ident: &Ident, tpb: &TypeParamBound| { iflet TypeParamBound::Trait(tb) = tpb { let fident = &tb.path.segments.last().unwrap().ident; if ["Fn", "FnMut", "FnOnce"].iter().any(|s| fident == *s) { let newty: Type = parse2(quote!(Box<dyn#tb>)).unwrap(); let subst_ty: Type = parse2(quote!(#ident)).unwrap();
assert!(hm.insert(subst_ty, newty).is_none(), "A generic parameter had two Fn bounds?");
}
}
};
// First, build a HashMap of all Fn generic types for g ingen.params.iter() { iflet GenericParam::Type(tp) = g { for tpb in tp.bounds.iter() {
save_fn_types(&tp.ident, tpb);
}
}
} iflet Some(wc) = &gen.where_clause { for pred in wc.predicates.iter() { iflet WherePredicate::Type(pt) = pred { let bounded_ty = &pt.bounded_ty; iflet Ok(ident) = parse2::<Ident>(quote!(#bounded_ty)) { for tpb in pt.bounds.iter() {
save_fn_types(&ident, tpb);
}
} else { // We can't yet handle where clauses this complicated
}
}
}
}
// Then remove those types from both the Generics' params and where clause let should_remove = |ident: &Ident| { let ty: Type = parse2(quote!(#ident)).unwrap();
hm.contains_key(&ty)
}; let params = gen.params.iter()
.filter(|g| { iflet GenericParam::Type(tp) = g {
!should_remove(&tp.ident)
} else { true
}
}).cloned()
.collect::<Punctuated<_, _>>(); letmut wc2 = gen.where_clause.clone(); iflet Some(wc) = &mut wc2 {
wc.predicates = wc.predicates.iter()
.filter(|wp| { iflet WherePredicate::Type(pt) = wp { let bounded_ty = &pt.bounded_ty; iflet Ok(ident) = parse2::<Ident>(quote!(#bounded_ty)) {
!should_remove(&ident)
} else { // We can't yet handle where clauses this complicated true
}
} else { true
}
}).cloned()
.collect::<Punctuated<_, _>>(); if wc.predicates.is_empty() {
wc2 = None;
}
} let outg = Generics {
lt_token: if params.is_empty() { None } else { gen.lt_token },
gt_token: if params.is_empty() { None } else { gen.gt_token },
params,
where_clause: wc2
};
// Finally, Box any closure arguments // use filter_map to remove the &self argument let callargs = args.iter().filter_map(|arg| { match arg {
FnArg::Typed(pt) => { letmut pt2 = pt.clone();
demutify_arg(&mut pt2); let pat = &pt2.pat; if pat_is_self(pat) {
None
} elseif hm.contains_key(&pt.ty) {
Some(quote!(Box::new(#pat)))
} else {
Some(quote!(#pat))
}
},
FnArg::Receiver(_) => None,
}
}).collect();
(outg, outargs, callargs)
}
/// Replace any "impl trait" types with "Box<dyn trait>" or equivalent. fn deimplify(rt: &mut ReturnType) { iflet ReturnType::Type(_, ty) = rt { ifletType::ImplTrait(ref tit) = &**ty { let needs_pin = tit.bounds
.iter()
.any(|tpb| { iflet TypeParamBound::Trait(tb) = tpb { iflet Some(seg) = tb.path.segments.last() {
seg.ident == "Future" || seg.ident == "Stream"
} else { // It might still be a Future, but we can't guess // what names it might be imported under. Too bad. false
}
} else { false
}
}); let bounds = &tit.bounds; if needs_pin {
*ty = parse2(quote!(::std::pin::Pin<Box<dyn#bounds>>)).unwrap();
} else {
*ty = parse2(quote!(Box<dyn#bounds>)).unwrap();
}
}
}
}
/// Remove any generics that place constraints on Self. fn dewhereselfify(generics: &mut Generics) { iflet Some(refmut wc) = &mut generics.where_clause { let new_predicates = wc.predicates.iter()
.filter(|wp| match wp {
WherePredicate::Type(pt) => {
pt.bounded_ty != parse2(quote!(Self)).unwrap()
},
_ => true
}).cloned()
.collect::<Punctuated<WherePredicate, Token![,]>>();
wc.predicates = new_predicates;
} if generics.where_clause.as_ref()
.map(|wc| wc.predicates.is_empty())
.unwrap_or(false)
{
generics.where_clause = None;
}
}
/// Remove any mutability qualifiers from a method's argument list fn demutify(inputs: &mut Punctuated<FnArg, token::Comma>) { for arg in inputs.iter_mut() { match arg {
FnArg::Receiver(r) => if r.reference.is_none() {
r.mutability = None
},
FnArg::Typed(pt) => demutify_arg(pt),
}
}
}
/// Remove any "mut" from a method argument's binding. fn demutify_arg(arg: &mut PatType) { match *arg.pat {
Pat::Wild(_) => {
compile_error(arg.span(), "Mocked methods must have named arguments");
},
Pat::Ident(refmut pat_ident) => { iflet Some(r) = &pat_ident.by_ref {
compile_error(r.span(), "Mockall does not support by-reference argument bindings");
} iflet Some((_at, subpat)) = &pat_ident.subpat {
compile_error(subpat.span(), "Mockall does not support subpattern bindings");
}
pat_ident.mutability = None;
},
_ => {
compile_error(arg.span(), "Unsupported argument type");
}
};
}
fn deselfify_path(path: &mut Path, actual: &Ident, generics: &Generics) { for seg in path.segments.iter_mut() { if seg.ident == "Self" {
seg.ident = actual.clone(); iflet PathArguments::None = seg.arguments { if !generics.params.is_empty() { let args = generics.params.iter()
.map(|gp| { match gp {
GenericParam::Type(tp) => { let ident = tp.ident.clone();
GenericArgument::Type( Type::Path(
TypePath {
qself: None,
path: Path::from(ident)
}
)
)
},
GenericParam::Lifetime(ld) =>{
GenericArgument::Lifetime(
ld.lifetime.clone()
)
}
_ => unimplemented!(),
}
}).collect::<Punctuated<_, _>>();
seg.arguments = PathArguments::AngleBracketed(
AngleBracketedGenericArguments {
colon2_token: None,
lt_token: generics.lt_token.unwrap(),
args,
gt_token: generics.gt_token.unwrap(),
}
);
}
} else {
compile_error(seg.arguments.span(), "Type arguments after Self are unexpected");
}
} iflet PathArguments::AngleBracketed(abga) = &mut seg.arguments
{ for arg in abga.args.iter_mut() { match arg {
GenericArgument::Type(ty) =>
deselfify(ty, actual, generics),
GenericArgument::AssocType(at) =>
deselfify(&mut at.ty, actual, generics),
_ => /* Nothing to do */(),
}
}
}
}
}
/// Replace any references to `Self` in `literal_type` with `actual`. /// `generics` is the Generics field of the parent struct. Useful for /// constructor methods. fn deselfify(literal_type: &mutType, actual: &Ident, generics: &Generics) { match literal_type { Type::Slice(s) => {
deselfify(s.elem.as_mut(), actual, generics);
}, Type::Array(a) => {
deselfify(a.elem.as_mut(), actual, generics);
}, Type::Ptr(p) => {
deselfify(p.elem.as_mut(), actual, generics);
}, Type::Reference(r) => {
deselfify(r.elem.as_mut(), actual, generics);
}, Type::Tuple(tuple) => { for elem in tuple.elems.iter_mut() {
deselfify(elem, actual, generics);
}
} Type::Path(type_path) => { iflet Some(refmut qself) = type_path.qself {
deselfify(qself.ty.as_mut(), actual, generics);
}
deselfify_path(&mut type_path.path, actual, generics);
}, Type::Paren(p) => {
deselfify(p.elem.as_mut(), actual, generics);
}, Type::Group(g) => {
deselfify(g.elem.as_mut(), actual, generics);
}, Type::Macro(_) | Type::Verbatim(_) => {
compile_error(literal_type.span(), "mockall_derive does not support this type as a return argument");
}, Type::TraitObject(tto) => { // Change types like `dyn Self` into `dyn MockXXX`. for bound in tto.bounds.iter_mut() { iflet TypeParamBound::Trait(t) = bound {
deselfify_path(&mut t.path, actual, generics);
}
}
}, Type::ImplTrait(_) => { /* Should've already been flagged as a compile_error */
}, Type::BareFn(_) => { /* Bare functions can't have Self arguments. Nothing to do */
}, Type::Infer(_) | Type::Never(_) =>
{ /* Nothing to do */
},
_ => compile_error(literal_type.span(), "Unsupported type"),
}
}
/// Change any `Self` in a method's arguments' types with `actual`. /// `generics` is the Generics field of the parent struct. fn deselfify_args(
args: &mut Punctuated<FnArg, Token![,]>,
actual: &Ident,
generics: &Generics)
{ for arg in args.iter_mut() { match arg {
FnArg::Receiver(r) => { if r.colon_token.is_some() {
deselfify(r.ty.as_mut(), actual, generics)
}
},
FnArg::Typed(pt) => deselfify(pt.ty.as_mut(), actual, generics)
}
}
}
fn find_ident_from_path(path: &Path) -> (Ident, PathArguments) { if path.segments.len() != 1 {
compile_error(path.span(), "mockall_derive only supports structs defined in the current module"); return (Ident::new("", path.span()), PathArguments::None);
} let last_seg = path.segments.last().unwrap();
(last_seg.ident.clone(), last_seg.arguments.clone())
}
fn find_lifetimes_in_tpb(bound: &TypeParamBound) -> HashSet<Lifetime> { letmut ret = HashSet::default(); match bound {
TypeParamBound::Lifetime(lt) => {
ret.insert(lt.clone());
},
TypeParamBound::Trait(tb) => {
ret.extend(find_lifetimes_in_path(&tb.path));
},
_ => ()
};
ret
}
fn find_lifetimes_in_path(path: &Path) -> HashSet<Lifetime> { letmut ret = HashSet::default(); for seg in path.segments.iter() { iflet PathArguments::AngleBracketed(abga) = &seg.arguments { for arg in abga.args.iter() { match arg {
GenericArgument::Lifetime(lt) => {
ret.insert(lt.clone());
},
GenericArgument::Type(ty) => {
ret.extend(find_lifetimes(ty));
},
GenericArgument::AssocType(at) => {
ret.extend(find_lifetimes(&at.ty));
},
GenericArgument::Constraint(c) => { for bound in c.bounds.iter() {
ret.extend(find_lifetimes_in_tpb(bound));
}
},
GenericArgument::Const(_) => (),
_ => ()
}
}
}
}
ret
}
fn find_lifetimes(ty: &Type) -> HashSet<Lifetime> { match ty { Type::Array(ta) => find_lifetimes(ta.elem.as_ref()), Type::Group(tg) => find_lifetimes(tg.elem.as_ref()), Type::Infer(_ti) => HashSet::default(), Type::Never(_tn) => HashSet::default(), Type::Paren(tp) => find_lifetimes(tp.elem.as_ref()), Type::Path(tp) => { letmut ret = find_lifetimes_in_path(&tp.path); iflet Some(qs) = &tp.qself {
ret.extend(find_lifetimes(qs.ty.as_ref()));
}
ret
}, Type::Ptr(tp) => find_lifetimes(tp.elem.as_ref()), Type::Reference(tr) => { letmut ret = find_lifetimes(tr.elem.as_ref()); iflet Some(lt) = &tr.lifetime {
ret.insert(lt.clone());
}
ret
}, Type::Slice(ts) => find_lifetimes(ts.elem.as_ref()), Type::TraitObject(tto) => { letmut ret = HashSet::default(); for bound in tto.bounds.iter() {
ret.extend(find_lifetimes_in_tpb(bound));
}
ret
} Type::Tuple(tt) => { letmut ret = HashSet::default(); for ty in tt.elems.iter() {
ret.extend(find_lifetimes(ty));
}
ret
}, Type::ImplTrait(tit) => { letmut ret = HashSet::default(); for tpb in tit.bounds.iter() {
ret.extend(find_lifetimes_in_tpb(tpb));
}
ret
},
_ => {
compile_error(ty.span(), "unsupported type in this context");
HashSet::default()
}
}
}
// XXX This logic requires that attributes are imported with their // standard names. #[allow(clippy::needless_bool)] #[allow(clippy::if_same_then_else)] fn format(&mutself) -> Vec<Attribute> { self.attrs.iter()
.filter(|attr| { let i = attr.path().segments.last().map(|ps| &ps.ident); if is_concretize(attr) { // Internally used attribute. Never emit. false
} elseif i.is_none() { false
} elseif *i.as_ref().unwrap() == "derive" { // We can't usefully derive any traits. Ignore them false
} elseif *i.as_ref().unwrap() == "doc" { self.doc
} elseif *i.as_ref().unwrap() == "async_trait" { self.async_trait
} elseif *i.as_ref().unwrap() == "instrument" { // We can't usefully instrument the mock method, so just // ignore this attribute. // https://docs.rs/tracing/0.1.23/tracing/attr.instrument.html false
} elseif *i.as_ref().unwrap() == "link_name" { // This shows up sometimes when mocking ffi functions. We // must not emit it on anything that isn't an ffi definition false
} else { true
}
}).cloned()
.collect()
}
}
/// Determine if this Pat is any kind of `self` binding fn pat_is_self(pat: &Pat) -> bool { iflet Pat::Ident(pi) = pat {
pi.ident == "self"
} else { false
}
}
/// Generate a suitable mockall::Key generic paramter from any Generics fn gen_keyid(g: &Generics) -> impl ToTokens { match g.params.len() { 0 => quote!(<()>), 1 => { let (_, tg, _) = g.split_for_impl();
quote!(#tg)
},
_ => { // Rust doesn't support variadic Generics, so mockall::Key must // always have exactly one generic type. We need to add parentheses // around whatever type generics the caller passes. let tps = g.type_params()
.map(|tp| tp.ident.clone())
.collect::<Punctuated::<Ident, Token![,]>>();
quote!(<(#tps)>)
}
}
}
/// Generate a mock identifier from the regular one: eg "Foo" => "MockFoo" fn gen_mock_ident(ident: &Ident) -> Ident {
format_ident!("Mock{}", ident)
}
/// Combine two Generics structs, producing a new one that has the union of /// their parameters. fn merge_generics(x: &Generics, y: &Generics) -> Generics { /// Compare only the identifiers of two GenericParams fn cmp_gp_idents(x: &GenericParam, y: &GenericParam) -> bool { use GenericParam::*;
/// Transform a Vec of lifetimes into a Generics fn lifetimes_to_generics(lv: &Punctuated<LifetimeParam, Token![,]>)-> Generics { if lv.is_empty() {
Generics::default()
} else { let params = lifetimes_to_generic_params(lv);
Generics {
lt_token: Some(Token)),
gt_token: Some(Token)),
params,
where_clause: None
}
}
}
/// Split a generics list into three: one for type generics and where predicates /// that relate to the signature, one for lifetimes that relate to the arguments /// only, and one for lifetimes that relate to the return type only. fn split_lifetimes(
generics: Generics,
args: &[FnArg],
rt: &ReturnType)
-> (Generics,
Punctuated<LifetimeParam, token::Comma>,
Punctuated<LifetimeParam, token::Comma>)
{ if generics.lt_token.is_none() { return (generics, Default::default(), Default::default());
}
// Check which types and lifetimes are referenced by the arguments letmut alts = HashSet::<Lifetime>::default(); letmut rlts = HashSet::<Lifetime>::default(); for arg in args { match arg {
FnArg::Receiver(r) => { iflet Some((_, Some(lt))) = &r.reference {
alts.insert(lt.clone());
}
},
FnArg::Typed(pt) => {
alts.extend(find_lifetimes(pt.ty.as_ref()));
},
};
};
letmut tv = Punctuated::new(); letmut alv = Punctuated::new(); letmut rlv = Punctuated::new(); for p in generics.params.into_iter() { match p {
GenericParam::Lifetime(ltd) if rlts.contains(<d.lifetime) =>
rlv.push(ltd),
GenericParam::Lifetime(ltd) if alts.contains(<d.lifetime) =>
alv.push(ltd),
GenericParam::Lifetime(_) => { // Probably a lifetime parameter from the impl block that isn't // used by this particular method
},
GenericParam::Type(_) => tv.push(p),
_ => (),
}
}
/// Return the visibility that should be used for expectation!, given the /// original method's visibility. /// /// # Arguments /// - `vis`: Original visibility of the item /// - `levels`: How many modules will the mock item be nested in? fn expectation_visibility(vis: &Visibility, levels: usize)
-> Visibility
{ if levels == 0 { return vis.clone();
}
let in_token = Token); let super_token = Token); match vis {
Visibility::Inherited => { // Private items need pub(in super::[...]) for each level letmut path = Path::from(super_token); for _ in1..levels {
path.segments.push(super_token.into());
}
Visibility::Restricted(VisRestricted{
pub_token: Token),
paren_token: token::Paren::default(),
in_token: Some(in_token),
path: Box::new(path)
})
},
Visibility::Restricted(vr) => { // crate => don't change // in crate::* => don't change // super => in super::super::super // self => in super::super // in anything_else => super::super::anything_else if vr.path.segments.first().unwrap().ident == "crate" {
Visibility::Restricted(vr.clone())
} else { letmut out = vr.clone();
out.in_token = Some(in_token); for _ in0..levels {
out.path.segments.insert(0, super_token.into());
}
Visibility::Restricted(out)
}
},
_ => vis.clone()
}
}
fn staticize(generics: &Generics) -> Generics { letmut ret = generics.clone(); for lt in ret.lifetimes_mut() {
lt.lifetime = Lifetime::new("'static", Span::call_site());
};
ret
}
fn mock_it<M: Into<MockableItem>>(inputs: M) -> TokenStream
{ let mockable: MockableItem = inputs.into(); let mock = MockItem::from(mockable); let ts = mock.into_token_stream(); if env::var("MOCKALL_DEBUG").is_ok() {
println!("{ts}");
}
ts
}
#[proc_macro_attribute] pubfn concretize(
_attrs: proc_macro::TokenStream,
input: proc_macro::TokenStream) -> proc_macro::TokenStream
{ // Do nothing. This "attribute" is processed as text by the real proc // macros.
input
}
fn assert_contains(output: &str, tokens: TokenStream) { let s = tokens.to_string();
assert!(output.contains(&s), "output does not contain {:?}", &s);
}
fn assert_not_contains(output: &str, tokens: TokenStream) { let s = tokens.to_string();
assert!(!output.contains(&s), "output does not contain {:?}", &s);
}
/// Various tests for overall code generation that are hard or impossible to /// write as integration tests mod mock { use std::str::FromStr; usesuper::super::*; usesuper::*;
#[test] fn specific_impl() { let code = " pub Foo<T: 'static> {} impl Bar for Foo<u32> { fn bar(&self);
} impl Bar for Foo<i32> { fn bar(&self);
} "; let ts = proc_macro2::TokenStream::from_str(code).unwrap(); let output = do_mock(ts).to_string();
assert_contains(&output, quote!(impl Bar for MockFoo<u32>));
assert_contains(&output, quote!(impl Bar for MockFoo<i32>)); // Ensure we don't duplicate the checkpoint function
assert_not_contains(&output, quote!( self.Bar_expectations.checkpoint(); self.Bar_expectations.checkpoint();
)); // The expect methods should return specific types, not generic ones
assert_contains(&output, quote!( pubfn expect_bar(&mutself) -> &mut __mock_MockFoo_Bar::__bar::Expectation<u32>
));
assert_contains(&output, quote!( pubfn expect_bar(&mutself) -> &mut __mock_MockFoo_Bar::__bar::Expectation<i32>
));
}
}
/// Various tests for overall code generation that are hard or impossible to /// write as integration tests mod automock { use std::str::FromStr; usesuper::super::*; usesuper::*;
#[test] fn doc_comments() { let code = " mod foo { /// Function docs pubfn bar() { unimplemented!() }
} "; let ts = proc_macro2::TokenStream::from_str(code).unwrap(); let attrs_ts = proc_macro2::TokenStream::from_str("").unwrap(); let output = do_automock(attrs_ts, ts).to_string();
assert_contains(&output, quote!(#[doc=" Function docs"] pubfn bar));
}
#[test] #[should_panic(expected = "can only mock inline modules")] fn external_module() { let code = "mod foo;"; let ts = proc_macro2::TokenStream::from_str(code).unwrap(); let attrs_ts = proc_macro2::TokenStream::from_str("").unwrap();
do_automock(attrs_ts, ts).to_string();
}
#[test] fn trait_visibility() { let code = " pub(super) trait Foo {} "; let attrs_ts = proc_macro2::TokenStream::from_str("").unwrap(); let ts = proc_macro2::TokenStream::from_str(code).unwrap(); let output = do_automock(attrs_ts, ts).to_string();
assert_contains(&output, quote!(pub ( super ) struct MockFoo));
}
}
mod concretize_args { usesuper::*;
fn check_concretize(
sig: TokenStream,
expected_inputs: &[TokenStream],
expected_call_exprs: &[TokenStream])
{ let f: Signature = parse2(sig).unwrap(); let (generics, inputs, call_exprs) =
concretize_args(&f.generics, &f.inputs);
assert!(generics.params.is_empty());
assert_eq!(inputs.len(), expected_inputs.len());
assert_eq!(call_exprs.len(), expected_call_exprs.len()); for i in0..inputs.len() { let actual = &inputs[i]; let exp = &expected_inputs[i];
assert_eq!(quote!(#actual).to_string(), quote!(#exp).to_string());
} for i in0..call_exprs.len() { let actual = &call_exprs[i]; let exp = &expected_call_exprs[i];
assert_eq!(quote!(#actual).to_string(), quote!(#exp).to_string());
}
}
// Future is a special case #[test] fn impl_future() {
check_deimplify(
quote!(-> impl Future<Output=i32>),
quote!(-> ::std::pin::Pin<Box<dyn Future<Output=i32>>>)
);
}
// Future is a special case, wherever it appears #[test] fn impl_future_reverse() {
check_deimplify(
quote!(-> impl Send + Future<Output=i32>),
quote!(-> ::std::pin::Pin<Box<dyn Send + Future<Output=i32>>>)
);
}
// Stream is a special case #[test] fn impl_stream() {
check_deimplify(
quote!(-> impl Stream<Item=i32>),
quote!(-> ::std::pin::Pin<Box<dyn Stream<Item=i32>>>)
);
}
fn check_supersuperfy(orig: TokenStream, expected: TokenStream) { let orig_ty: Type = parse2(orig).unwrap(); let expected_ty: Type = parse2(expected).unwrap(); let output = supersuperfy(&orig_ty, 1);
assert_eq!(quote!(#output).to_string(),
quote!(#expected_ty).to_string());
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.