#![cfg_attr(feature = "nightly_derive", feature(proc_macro_diagnostic))]
#![cfg_attr(test, deny(warnings))]
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;
use crate::automock::Attrs;
use crate::mockable_struct::MockableStruct;
use crate::mock_item::MockItem;
use crate::mock_item_struct::MockItemStruct;
use crate::mockable_item::MockableItem;
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! {
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.");
}
}
}
fn is_concretize(attr: &Attribute) -> bool {
if attr.path().segments.last().unwrap().ident == "concretize" {
true
} else if attr.path().is_ident("cfg_attr") {
match &attr.meta {
Meta::List(ml) => {
ml.tokens.to_string().contains("concretize")
},
_ => false,
}
} else {
false
}
}
fn concretize_args(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
(Generics, Vec<FnArg>, Vec<TokenStream>)
{
let mut hm = HashMap::default();
let mut save_types = |ident: &Ident, tpb: &Punctuated<TypeParamBound, Token![+]>| {
if !tpb.is_empty() {
if let Ok(newty) = parse2::<Type>(quote!(&(dyn #tpb))) {
let subst_ty: Type = parse2(quote!(#ident)).unwrap();
hm.insert(subst_ty, (newty.clone(), None));
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");
}
if let Ok(newty) = parse2::<Type>(quote!(&mut (dyn #tpb))) {
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");
}
if let 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 in gen.params.iter() {
if let GenericParam::Type(tp) = g {
save_types(&tp.ident, &tp.bounds);
}
}
if let Some(wc) = &gen.where_clause {
for pred in wc.predicates.iter() {
if let WherePredicate::Type(pt) = pred {
let bounded_ty = &pt.bounded_ty;
if let Ok(ident) = parse2::<Ident>(quote!(#bounded_ty)) {
save_types(&ident, &pt.bounds);
} else {
}
}
}
}
let outg = Generics {
lt_token: None,
gt_token: None,
params: Punctuated::new(),
where_clause: None
};
let outargs: Vec<FnArg> = args.iter().map(|arg| {
if let FnArg::Typed(pt) = arg {
let mut immutable_pt = pt.clone();
demutify_arg(&mut immutable_pt);
if let Some((newty, _)) = hm.get(&pt.ty) {
FnArg::Typed(PatType {
attrs: Vec::default(),
pat: immutable_pt.pat,
colon_token: pt.colon_token,
ty: Box::new(newty.clone())
})
} else {
FnArg::Typed(PatType {
attrs: Vec::default(),
pat: immutable_pt.pat,
colon_token: pt.colon_token,
ty: pt.ty.clone()
})
}
} else {
arg.clone()
}
}).collect();
let call_exprs = args.iter().filter_map(|arg| {
match arg {
FnArg::Typed(pt) => {
let mut pt2 = pt.clone();
demutify_arg(&mut pt2);
let pat = &pt2.pat;
if pat_is_self(pat) {
None
} else if let Some((_, newbound)) = hm.get(&pt.ty) {
if let Type::Reference(tr) = &*pt.ty {
if let Type::Slice(_ts) = &*tr.elem {
Some(quote!(
&(0..#pat.len())
.map(|__mockall_i| &#pat[__mockall_i] as &(dyn #newbound))
.collect::<Vec<_>>()
))
} else {
Some(quote!(#pat))
}
} else {
Some(quote!(&#pat))
}
} else {
Some(quote!(#pat))
}
},
FnArg::Receiver(_) => None,
}
}).collect();
(outg, outargs, call_exprs)
}
fn deanonymize_lifetime(lt: &mut Lifetime) {
if lt.ident == "_" {
lt.ident = format_ident!("static");
}
}
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() {
if let GenericArgument::Lifetime(lt) = ga {
deanonymize_lifetime(lt)
}
}
},
_ => compile_error(seg.arguments.span(),
"Methods returning functions are TODO"),
}
}
}
fn deanonymize(literal_type: &mut Type) {
match literal_type {
Type::Array(ta) => deanonymize(ta.elem.as_mut()),
Type::BareFn(tbf) => {
if let ReturnType::Type(_, ref mut 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) => {
if let Some(ref mut qself) = tp.qself {
deanonymize(qself.ty.as_mut());
}
deanonymize_path(&mut tp.path);
},
Type::Ptr(tptr) => deanonymize(tptr.elem.as_mut()),
Type::Reference(tr) => {
if let 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")
}
}
fn declosurefy(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
(Generics, Vec<FnArg>, Vec<TokenStream>)
{
let mut hm = HashMap::default();
let mut save_fn_types = |ident: &Ident, tpb: &TypeParamBound| {
if let 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?");
}
}
};
for g in gen.params.iter() {
if let GenericParam::Type(tp) = g {
for tpb in tp.bounds.iter() {
save_fn_types(&tp.ident, tpb);
}
}
}
if let Some(wc) = &gen.where_clause {
for pred in wc.predicates.iter() {
if let WherePredicate::Type(pt) = pred {
let bounded_ty = &pt.bounded_ty;
if let Ok(ident) = parse2::<Ident>(quote!(#bounded_ty)) {
for tpb in pt.bounds.iter() {
save_fn_types(&ident, tpb);
}
} else {
}
}
}
}
let should_remove = |ident: &Ident| {
let ty: Type = parse2(quote!(#ident)).unwrap();
hm.contains_key(&ty)
};
let params = gen.params.iter()
.filter(|g| {
if let GenericParam::Type(tp) = g {
!should_remove(&tp.ident)
} else {
true
}
}).cloned()
.collect::<Punctuated<_, _>>();
let mut wc2 = gen.where_clause.clone();
if let Some(wc) = &mut wc2 {
wc.predicates = wc.predicates.iter()
.filter(|wp| {
if let WherePredicate::Type(pt) = wp {
let bounded_ty = &pt.bounded_ty;
if let Ok(ident) = parse2::<Ident>(quote!(#bounded_ty)) {
!should_remove(&ident)
} else {
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
};
let outargs = args.iter().map(|arg| {
if let FnArg::Typed(pt) = arg {
let mut immutable_pt = pt.clone();
demutify_arg(&mut immutable_pt);
if let Some(newty) = hm.get(&pt.ty) {
FnArg::Typed(PatType {
attrs: Vec::default(),
pat: immutable_pt.pat,
colon_token: pt.colon_token,
ty: Box::new(newty.clone())
})
} else {
FnArg::Typed(PatType {
attrs: Vec::default(),
pat: immutable_pt.pat,
colon_token: pt.colon_token,
ty: pt.ty.clone()
})
}
} else {
arg.clone()
}
}).collect();
let callargs = args.iter().filter_map(|arg| {
match arg {
FnArg::Typed(pt) => {
let mut pt2 = pt.clone();
demutify_arg(&mut pt2);
let pat = &pt2.pat;
if pat_is_self(pat) {
None
} else if hm.contains_key(&pt.ty) {
Some(quote!(Box::new(#pat)))
} else {
Some(quote!(#pat))
}
},
FnArg::Receiver(_) => None,
}
}).collect();
(outg, outargs, callargs)
}
fn deimplify(rt: &mut ReturnType) {
if let ReturnType::Type(_, ty) = rt {
if let Type::ImplTrait(ref tit) = &**ty {
let needs_pin = tit.bounds
.iter()
.any(|tpb| {
if let TypeParamBound::Trait(tb) = tpb {
if let Some(seg) = tb.path.segments.last() {
seg.ident == "Future" || seg.ident == "Stream"
} else {
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();
}
}
}
}
fn dewhereselfify(generics: &mut Generics) {
if let Some(ref mut 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;
}
}
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),
}
}
}
fn demutify_arg(arg: &mut PatType) {
match *arg.pat {
Pat::Wild(_) => {
compile_error(arg.span(),
"Mocked methods must have named arguments");
},
Pat::Ident(ref mut pat_ident) => {
if let Some(r) = &pat_ident.by_ref {
compile_error(r.span(),
"Mockall does not support by-reference argument bindings");
}
if let 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();
if let 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");
}
}
if let 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),
_ => (),
}
}
}
}
}
fn deselfify(literal_type: &mut Type, 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) => {
if let Some(ref mut 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) => {
for bound in tto.bounds.iter_mut() {
if let TypeParamBound::Trait(t) = bound {
deselfify_path(&mut t.path, actual, generics);
}
}
},
Type::ImplTrait(_) => {
},
Type::BareFn(_) => {
},
Type::Infer(_) | Type::Never(_) =>
{
},
_ => compile_error(literal_type.span(), "Unsupported type"),
}
}
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> {
let mut 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> {
let mut ret = HashSet::default();
for seg in path.segments.iter() {
if let 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) => {
let mut ret = find_lifetimes_in_path(&tp.path);
if let 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) => {
let mut ret = find_lifetimes(tr.elem.as_ref());
if let Some(lt) = &tr.lifetime {
ret.insert(lt.clone());
}
ret
},
Type::Slice(ts) => find_lifetimes(ts.elem.as_ref()),
Type::TraitObject(tto) => {
let mut ret = HashSet::default();
for bound in tto.bounds.iter() {
ret.extend(find_lifetimes_in_tpb(bound));
}
ret
}
Type::Tuple(tt) => {
let mut ret = HashSet::default();
for ty in tt.elems.iter() {
ret.extend(find_lifetimes(ty));
}
ret
},
Type::ImplTrait(tit) => {
let mut 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()
}
}
}
struct AttrFormatter<'a>{
attrs: &'a [Attribute],
async_trait: bool,
doc: bool,
}
impl<'a> AttrFormatter<'a> {
fn new(attrs: &'a [Attribute]) -> AttrFormatter<'a> {
Self {
attrs,
async_trait: true,
doc: true
}
}
fn async_trait(&mut self, allowed: bool) -> &mut Self {
self.async_trait = allowed;
self
}
fn doc(&mut self, allowed: bool) -> &mut Self {
self.doc = allowed;
self
}
#[allow(clippy::needless_bool)]
#[allow(clippy::if_same_then_else)]
fn format(&mut self) -> Vec<Attribute> {
self.attrs.iter()
.filter(|attr| {
let i = attr.path().segments.last().map(|ps| &ps.ident);
if is_concretize(attr) {
false
} else if i.is_none() {
false
} else if *i.as_ref().unwrap() == "derive" {
false
} else if *i.as_ref().unwrap() == "doc" {
self.doc
} else if *i.as_ref().unwrap() == "async_trait" {
self.async_trait
} else if *i.as_ref().unwrap() == "instrument" {
false
} else if *i.as_ref().unwrap() == "link_name" {
false
} else {
true
}
}).cloned()
.collect()
}
}
fn pat_is_self(pat: &Pat) -> bool {
if let Pat::Ident(pi) = pat {
pi.ident == "self"
} else {
false
}
}
fn supersuperfy_path(path: &mut Path, levels: usize) -> usize {
if let Some(t) = path.segments.last_mut() {
match &mut t.arguments {
PathArguments::None => (),
PathArguments::AngleBracketed(ref mut abga) => {
for arg in abga.args.iter_mut() {
match arg {
GenericArgument::Type(ref mut ty) => {
*ty = supersuperfy(ty, levels);
},
GenericArgument::AssocType(ref mut at) => {
at.ty = supersuperfy(&at.ty, levels);
},
GenericArgument::Constraint(ref mut constraint) => {
supersuperfy_bounds(&mut constraint.bounds, levels);
},
_ => (),
}
}
},
PathArguments::Parenthesized(ref mut pga) => {
for input in pga.inputs.iter_mut() {
*input = supersuperfy(input, levels);
}
if let ReturnType::Type(_, ref mut ty) = pga.output {
*ty = Box::new(supersuperfy(ty, levels));
}
},
}
}
if let Some(t) = path.segments.first() {
if t.ident == "super" {
let mut ident = format_ident!("super");
ident.set_span(path.segments.span());
let ps = PathSegment {
ident,
arguments: PathArguments::None
};
for _ in 0..levels {
path.segments.insert(0, ps.clone());
}
levels
} else {
0
}
} else {
0
}
}
fn supersuperfy(original: &Type, levels: usize) -> Type {
let mut output = original.clone();
fn recurse(t: &mut Type, levels: usize) {
match t {
Type::Slice(s) => {
recurse(s.elem.as_mut(), levels);
},
Type::Array(a) => {
recurse(a.elem.as_mut(), levels);
},
Type::Ptr(p) => {
recurse(p.elem.as_mut(), levels);
},
Type::Reference(r) => {
recurse(r.elem.as_mut(), levels);
},
Type::BareFn(bfn) => {
if let ReturnType::Type(_, ref mut bt) = bfn.output {
recurse(bt.as_mut(), levels);
}
for input in bfn.inputs.iter_mut() {
recurse(&mut input.ty, levels);
}
},
Type::Tuple(tuple) => {
for elem in tuple.elems.iter_mut() {
recurse(elem, levels);
}
}
Type::Path(type_path) => {
let added = supersuperfy_path(&mut type_path.path, levels);
if let Some(ref mut qself) = type_path.qself {
recurse(qself.ty.as_mut(), levels);
qself.position += added;
}
},
Type::Paren(p) => {
recurse(p.elem.as_mut(), levels);
},
Type::Group(g) => {
recurse(g.elem.as_mut(), levels);
},
Type::Macro(_) | Type::Verbatim(_) => {
compile_error(t.span(),
"mockall_derive does not support this type in this position");
},
Type::TraitObject(tto) => {
for bound in tto.bounds.iter_mut() {
if let TypeParamBound::Trait(tb) = bound {
supersuperfy_path(&mut tb.path, levels);
}
}
},
Type::ImplTrait(_) => {
},
Type::Infer(_) | Type::Never(_) =>
{
},
_ => compile_error(t.span(), "Unsupported type"),
}
}
recurse(&mut output, levels);
output
}
fn supersuperfy_generics(generics: &mut Generics, levels: usize) {
for param in generics.params.iter_mut() {
if let GenericParam::Type(tp) = param {
supersuperfy_bounds(&mut tp.bounds, levels);
if let Some(ty) = tp.default.as_mut() {
*ty = supersuperfy(ty, levels);
}
}
}
if let Some(wc) = generics.where_clause.as_mut() {
for wp in wc.predicates.iter_mut() {
if let WherePredicate::Type(pt) = wp {
pt.bounded_ty = supersuperfy(&pt.bounded_ty, levels);
supersuperfy_bounds(&mut pt.bounds, levels);
}
}
}
}
fn supersuperfy_bounds(
bounds: &mut Punctuated<TypeParamBound, Token![+]>,
levels: usize)
{
for bound in bounds.iter_mut() {
if let TypeParamBound::Trait(tb) = bound {
supersuperfy_path(&mut tb.path, levels);
}
}
}
fn gen_keyid(g: &Generics) -> impl ToTokens {
match g.params.len() {
0 => quote!(<()>),
1 => {
let (_, tg, _) = g.split_for_impl();
quote!(#tg)
},
_ => {
let tps = g.type_params()
.map(|tp| tp.ident.clone())
.collect::<Punctuated::<Ident, Token![,]>>();
quote!(<(#tps)>)
}
}
}
fn gen_mock_ident(ident: &Ident) -> Ident {
format_ident!("Mock{}", ident)
}
fn gen_mod_ident(struct_: &Ident, trait_: Option<&Ident>) -> Ident {
if let Some(t) = trait_ {
format_ident!("__mock_{struct_}_{}", t)
} else {
format_ident!("__mock_{struct_}")
}
}
fn merge_generics(x: &Generics, y: &Generics) -> Generics {
fn cmp_gp_idents(x: &GenericParam, y: &GenericParam) -> bool {
use GenericParam::*;
match (x, y) {
(Type(xtp), Type(ytp)) => xtp.ident == ytp.ident,
(Lifetime(xld), Lifetime(yld)) => xld.lifetime == yld.lifetime,
(Const(xc), Const(yc)) => xc.ident == yc.ident,
_ => false
}
}
fn cmp_wp_idents(x: &WherePredicate, y: &WherePredicate) -> bool {
use WherePredicate::*;
match (x, y) {
(Type(xpt), Type(ypt)) => xpt.bounded_ty == ypt.bounded_ty,
(Lifetime(xpl), Lifetime(ypl)) => xpl.lifetime == ypl.lifetime,
_ => false
}
}
let mut out = if x.lt_token.is_none() && x.where_clause.is_none() {
y.clone()
} else if y.lt_token.is_none() && y.where_clause.is_none() {
x.clone()
} else {
let mut out = x.clone();
'outer_param: for yparam in y.params.iter() {
for outparam in out.params.iter_mut() {
if cmp_gp_idents(outparam, yparam) {
if let (GenericParam::Type(ref mut ot),
GenericParam::Type(yt)) = (outparam, yparam)
{
ot.attrs.extend(yt.attrs.iter().cloned());
ot.colon_token = ot.colon_token.or(yt.colon_token);
ot.eq_token = ot.eq_token.or(yt.eq_token);
if ot.default.is_none() {
ot.default = yt.default.clone();
}
if ot.bounds != yt.bounds {
ot.bounds.extend(yt.bounds.iter().cloned());
}
}
continue 'outer_param;
}
}
out.params.push(yparam.clone());
}
out
};
match (&mut out.where_clause, &y.where_clause) {
(_, None) => (),
(None, Some(wc)) => out.where_clause = Some(wc.clone()),
(Some(out_wc), Some(y_wc)) => {
'outer_wc: for ypred in y_wc.predicates.iter() {
for outpred in out_wc.predicates.iter_mut() {
if cmp_wp_idents(outpred, ypred) {
if let (WherePredicate::Type(ref mut ot),
WherePredicate::Type(yt)) = (outpred, ypred)
{
match (&mut ot.lifetimes, &yt.lifetimes) {
(_, None) => (),
(None, Some(bl)) =>
ot.lifetimes = Some(bl.clone()),
(Some(obl), Some(ybl)) =>
obl.lifetimes.extend(
ybl.lifetimes.iter().cloned()),
};
if ot.bounds != yt.bounds {
ot.bounds.extend(yt.bounds.iter().cloned())
}
}
continue 'outer_wc;
}
}
out_wc.predicates.push(ypred.clone());
}
}
}
out
}
fn lifetimes_to_generic_params(lv: &Punctuated<LifetimeParam, Token![,]>)
-> Punctuated<GenericParam, Token![,]>
{
lv.iter()
.map(|lt| GenericParam::Lifetime(lt.clone()))
.collect()
}
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![<](lv[0].span())),
gt_token: Some(Token![>](lv[0].span())),
params,
where_clause: None
}
}
}
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());
}
let mut alts = HashSet::<Lifetime>::default();
let mut rlts = HashSet::<Lifetime>::default();
for arg in args {
match arg {
FnArg::Receiver(r) => {
if let Some((_, Some(lt))) = &r.reference {
alts.insert(lt.clone());
}
},
FnArg::Typed(pt) => {
alts.extend(find_lifetimes(pt.ty.as_ref()));
},
};
};
if let ReturnType::Type(_, ty) = rt {
rlts.extend(find_lifetimes(ty));
}
let mut tv = Punctuated::new();
let mut alv = Punctuated::new();
let mut 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(_) => {
},
GenericParam::Type(_) => tv.push(p),
_ => (),
}
}
let tg = if tv.is_empty() {
Generics::default()
} else {
Generics {
lt_token: generics.lt_token,
gt_token: generics.gt_token,
params: tv,
where_clause: generics.where_clause
}
};
(tg, alv, rlv)
}
fn expectation_visibility(vis: &Visibility, levels: usize)
-> Visibility
{
if levels == 0 {
return vis.clone();
}
let in_token = Token![in](vis.span());
let super_token = Token![super](vis.span());
match vis {
Visibility::Inherited => {
let mut path = Path::from(super_token);
for _ in 1..levels {
path.segments.push(super_token.into());
}
Visibility::Restricted(VisRestricted{
pub_token: Token![pub](vis.span()),
paren_token: token::Paren::default(),
in_token: Some(in_token),
path: Box::new(path)
})
},
Visibility::Restricted(vr) => {
if vr.path.segments.first().unwrap().ident == "crate" {
Visibility::Restricted(vr.clone())
} else {
let mut out = vr.clone();
out.in_token = Some(in_token);
for _ in 0..levels {
out.path.segments.insert(0, super_token.into());
}
Visibility::Restricted(out)
}
},
_ => vis.clone()
}
}
fn staticize(generics: &Generics) -> Generics {
let mut 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
}
fn do_mock_once(input: TokenStream) -> TokenStream
{
let item: MockableStruct = match syn::parse2(input) {
Ok(mock) => mock,
Err(err) => {
return err.to_compile_error();
}
};
mock_it(item)
}
fn do_mock(input: TokenStream) -> TokenStream
{
cfg_if! {
if #[cfg(reprocheck)] {
let ts_a = do_mock_once(input.clone());
let ts_b = do_mock_once(input.clone());
assert_eq!(ts_a.to_string(), ts_b.to_string());
}
}
do_mock_once(input)
}
#[proc_macro_attribute]
pub fn concretize(
_attrs: proc_macro::TokenStream,
input: proc_macro::TokenStream) -> proc_macro::TokenStream
{
input
}
#[proc_macro]
pub fn mock(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
do_mock(input.into()).into()
}
#[proc_macro_attribute]
pub fn automock(attrs: proc_macro::TokenStream, input: proc_macro::TokenStream)
-> proc_macro::TokenStream
{
let attrs: proc_macro2::TokenStream = attrs.into();
let input: proc_macro2::TokenStream = input.into();
do_automock(attrs, input).into()
}
fn do_automock_once(attrs: TokenStream, input: TokenStream) -> TokenStream {
let mut output = input.clone();
let attrs: Attrs = match parse2(attrs) {
Ok(a) => a,
Err(err) => {
return err.to_compile_error();
}
};
let item: Item = match parse2(input) {
Ok(item) => item,
Err(err) => {
return err.to_compile_error();
}
};
output.extend(mock_it((attrs, item)));
output
}
fn do_automock(attrs: TokenStream, input: TokenStream) -> TokenStream {
cfg_if! {
if #[cfg(reprocheck)] {
let ts_a = do_automock_once(attrs.clone(), input.clone());
let ts_b = do_automock_once(attrs.clone(), input.clone());
assert_eq!(ts_a.to_string(), ts_b.to_string());
}
}
do_automock_once(attrs, input)
}
#[cfg(test)]
mod t {
use super::*;
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);
}
mod mock {
use std::str::FromStr;
use super::super::*;
use super::*;
#[test]
fn inherent_method_visibility() {
let code = "
Foo {
fn foo(&self);
pub fn bar(&self);
pub(crate) fn baz(&self);
pub(super) fn bean(&self);
pub(in crate::outer) fn boom(&self);
}
";
let ts = proc_macro2::TokenStream::from_str(code).unwrap();
let output = do_mock(ts).to_string();
assert_not_contains(&output, quote!(pub fn foo));
assert!(!output.contains(") fn foo"));
assert_contains(&output, quote!(pub fn bar));
assert_contains(&output, quote!(pub(crate) fn baz));
assert_contains(&output, quote!(pub(super) fn bean));
assert_contains(&output, quote!(pub(in crate::outer) fn boom));
assert_not_contains(&output, quote!(pub fn expect_foo));
assert!(!output.contains("pub fn expect_foo"));
assert!(!output.contains(") fn expect_foo"));
assert_contains(&output, quote!(pub fn expect_bar));
assert_contains(&output, quote!(pub(crate) fn expect_baz));
assert_contains(&output, quote!(pub(super) fn expect_bean));
assert_contains(&output, quote!(pub(in crate::outer) fn expect_boom));
}
#[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>));
assert_not_contains(&output, quote!(
self.Bar_expectations.checkpoint();
self.Bar_expectations.checkpoint();
));
assert_contains(&output, quote!(
pub fn expect_bar(&mut self) -> &mut __mock_MockFoo_Bar::__bar::Expectation<u32>
));
assert_contains(&output, quote!(
pub fn expect_bar(&mut self) -> &mut __mock_MockFoo_Bar::__bar::Expectation<i32>
));
}
}
mod automock {
use std::str::FromStr;
use super::super::*;
use super::*;
#[test]
fn doc_comments() {
let code = "
mod foo {
/// Function docs
pub fn 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"] pub fn bar));
}
#[test]
fn method_visibility() {
let code = "
impl Foo {
fn foo(&self) {}
pub fn bar(&self) {}
pub(super) fn baz(&self) {}
pub(crate) fn bang(&self) {}
pub(in super::x) fn bean(&self) {}
}";
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_not_contains(&output, quote!(pub fn foo));
assert!(!output.contains(") fn foo"));
assert_not_contains(&output, quote!(pub fn expect_foo));
assert!(!output.contains(") fn expect_foo"));
assert_contains(&output, quote!(pub fn bar));
assert_contains(&output, quote!(pub fn expect_bar));
assert_contains(&output, quote!(pub(super) fn baz));
assert_contains(&output, quote!(pub(super) fn expect_baz));
assert_contains(&output, quote!(pub ( crate ) fn bang));
assert_contains(&output, quote!(pub ( crate ) fn expect_bang));
assert_contains(&output, quote!(pub ( in super :: x ) fn bean));
assert_contains(&output, quote!(pub ( in super :: x ) fn expect_bean));
}
#[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 {
use super::*;
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 in 0..inputs.len() {
let actual = &inputs[i];
let exp = &expected_inputs[i];
assert_eq!(quote!(#actual).to_string(), quote!(#exp).to_string());
}
for i in 0..call_exprs.len() {
let actual = &call_exprs[i];
let exp = &expected_call_exprs[i];
assert_eq!(quote!(#actual).to_string(), quote!(#exp).to_string());
}
}
#[test]
fn bystanders() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(x: i32, p: P, y: &f64)),
&[quote!(x: i32), quote!(p: &(dyn AsRef<Path>)), quote!(y: &f64)],
&[quote!(x), quote!(&p), quote!(y)]
);
}
#[test]
fn multi_bounds() {
check_concretize(
quote!(fn foo<P: AsRef<String> + AsMut<String>>(p: P)),
&[quote!(p: &(dyn AsRef<String> + AsMut<String>))],
&[quote!(&p)]
);
}
#[test]
fn mutable_reference_arg() {
check_concretize(
quote!(fn foo<P: AsMut<Path>>(p: &mut P)),
&[quote!(p: &mut (dyn AsMut<Path>))],
&[quote!(p)]
);
}
#[test]
fn mutable_reference_multi_bounds() {
check_concretize(
quote!(fn foo<P: AsRef<String> + AsMut<String>>(p: &mut P)),
&[quote!(p: &mut (dyn AsRef<String> + AsMut<String>))],
&[quote!(p)]
);
}
#[test]
fn reference_arg() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(p: &P)),
&[quote!(p: &(dyn AsRef<Path>))],
&[quote!(p)]
);
}
#[test]
fn simple() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(p: P)),
&[quote!(p: &(dyn AsRef<Path>))],
&[quote!(&p)]
);
}
#[test]
fn slice() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(p: &[P])),
&[quote!(p: &[&(dyn AsRef<Path>)])],
&[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &(dyn AsRef<Path>)).collect::<Vec<_>>())]
);
}
#[test]
fn slice_with_multi_bounds() {
check_concretize(
quote!(fn foo<P: AsRef<Path> + AsMut<String>>(p: &[P])),
&[quote!(p: &[&(dyn AsRef<Path> + AsMut<String>)])],
&[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &(dyn AsRef<Path> + AsMut<String>)).collect::<Vec<_>>())]
);
}
#[test]
fn where_clause() {
check_concretize(
quote!(fn foo<P>(p: P) where P: AsRef<Path>),
&[quote!(p: &(dyn AsRef<Path>))],
&[quote!(&p)]
);
}
}
mod deimplify {
use super::*;
fn check_deimplify(orig_ts: TokenStream, expected_ts: TokenStream) {
let mut orig: ReturnType = parse2(orig_ts).unwrap();
let expected: ReturnType = parse2(expected_ts).unwrap();
deimplify(&mut orig);
assert_eq!(quote!(#orig).to_string(), quote!(#expected).to_string());
}
#[test]
fn impl_future() {
check_deimplify(
quote!(-> impl Future<Output=i32>),
quote!(-> ::std::pin::Pin<Box<dyn Future<Output=i32>>>)
);
}
#[test]
fn impl_future_reverse() {
check_deimplify(
quote!(-> impl Send + Future<Output=i32>),
quote!(-> ::std::pin::Pin<Box<dyn Send + Future<Output=i32>>>)
);
}
#[test]
fn impl_stream() {
check_deimplify(
quote!(-> impl Stream<Item=i32>),
quote!(-> ::std::pin::Pin<Box<dyn Stream<Item=i32>>>)
);
}
#[test]
fn impl_trait() {
check_deimplify(
quote!(-> impl Foo),
quote!(-> Box<dyn Foo>)
);
}
#[test]
fn impl_trait2() {
check_deimplify(
quote!(-> impl Foo + Send),
quote!(-> Box<dyn Foo + Send>)
);
}
}
mod deselfify {
use super::*;
fn check_deselfify(
orig_ts: TokenStream,
actual_ts: TokenStream,
generics_ts: TokenStream,
expected_ts: TokenStream)
{
let mut ty: Type = parse2(orig_ts).unwrap();
let actual: Ident = parse2(actual_ts).unwrap();
let generics: Generics = parse2(generics_ts).unwrap();
let expected: Type = parse2(expected_ts).unwrap();
deselfify(&mut ty, &actual, &generics);
assert_eq!(quote!(#ty).to_string(),
quote!(#expected).to_string());
}
#[test]
fn arc() {
check_deselfify(
quote!(Arc<Self>),
quote!(Foo),
quote!(),
quote!(Arc<Foo>)
);
}
#[test]
fn future() {
check_deselfify(
quote!(Box<dyn Future<Output=Self>>),
quote!(Foo),
quote!(),
quote!(Box<dyn Future<Output=Foo>>)
);
}
#[test]
fn qself() {
check_deselfify(
quote!(<Self as Self>::Self),
quote!(Foo),
quote!(),
quote!(<Foo as Foo>::Foo)
);
}
#[test]
fn trait_object() {
check_deselfify(
quote!(Box<dyn Self>),
quote!(Foo),
quote!(),
quote!(Box<dyn Foo>)
);
}
#[test]
fn trait_object2() {
check_deselfify(
quote!(Box<dyn Self + Send>),
quote!(Foo),
quote!(),
quote!(Box<dyn Foo + Send>)
);
}
}
mod dewhereselfify {
use super::*;
#[test]
fn lifetime() {
let mut meth: ImplItemFn = parse2(quote!(
fn foo<'a>(&self) where 'a: 'static, Self: Sized {}
)).unwrap();
let expected: ImplItemFn = parse2(quote!(
fn foo<'a>(&self) where 'a: 'static {}
)).unwrap();
dewhereselfify(&mut meth.sig.generics);
assert_eq!(meth, expected);
}
#[test]
fn normal_method() {
let mut meth: ImplItemFn = parse2(quote!(
fn foo(&self) where Self: Sized {}
)).unwrap();
let expected: ImplItemFn = parse2(quote!(
fn foo(&self) {}
)).unwrap();
dewhereselfify(&mut meth.sig.generics);
assert_eq!(meth, expected);
}
#[test]
fn with_real_generics() {
let mut meth: ImplItemFn = parse2(quote!(
fn foo<T>(&self, t: T) where Self: Sized, T: Copy {}
)).unwrap();
let expected: ImplItemFn = parse2(quote!(
fn foo<T>(&self, t: T) where T: Copy {}
)).unwrap();
dewhereselfify(&mut meth.sig.generics);
assert_eq!(meth, expected);
}
}
mod gen_keyid {
use super::*;
fn check_gen_keyid(orig: TokenStream, expected: TokenStream) {
let g: Generics = parse2(orig).unwrap();
let keyid = gen_keyid(&g);
assert_eq!(quote!(#keyid).to_string(), quote!(#expected).to_string());
}
#[test]
fn empty() {
check_gen_keyid(quote!(), quote!(<()>));
}
#[test]
fn onetype() {
check_gen_keyid(quote!(<T>), quote!(<T>));
}
#[test]
fn twotypes() {
check_gen_keyid(quote!(<T, V>), quote!(<(T, V)>));
}
}
mod merge_generics {
use super::*;
#[test]
fn both() {
let mut g1: Generics = parse2(quote!(<T: 'static, V: Copy> )).unwrap();
let wc1: WhereClause = parse2(quote!(where T: Default)).unwrap();
g1.where_clause = Some(wc1);
let mut g2: Generics = parse2(quote!(<Q: Send, V: Clone>)).unwrap();
let wc2: WhereClause = parse2(quote!(where T: Sync, Q: Debug)).unwrap();
g2.where_clause = Some(wc2);
let gm = super::merge_generics(&g1, &g2);
let gm_wc = &gm.where_clause;
let ge: Generics = parse2(quote!(
<T: 'static, V: Copy + Clone, Q: Send>
)).unwrap();
let wce: WhereClause = parse2(quote!(
where T: Default + Sync, Q: Debug
)).unwrap();
assert_eq!(quote!(#ge #wce).to_string(),
quote!(#gm #gm_wc).to_string());
}
#[test]
fn eq() {
let mut g1: Generics = parse2(quote!(<T: 'static, V: Copy> )).unwrap();
let wc1: WhereClause = parse2(quote!(where T: Default)).unwrap();
g1.where_clause = Some(wc1.clone());
let gm = super::merge_generics(&g1, &g1);
let gm_wc = &gm.where_clause;
assert_eq!(quote!(#g1 #wc1).to_string(),
quote!(#gm #gm_wc).to_string());
}
#[test]
fn lhs_only() {
let mut g1: Generics = parse2(quote!(<T: 'static, V: Copy> )).unwrap();
let wc1: WhereClause = parse2(quote!(where T: Default)).unwrap();
g1.where_clause = Some(wc1.clone());
let g2 = Generics::default();
let gm = super::merge_generics(&g1, &g2);
let gm_wc = &gm.where_clause;
assert_eq!(quote!(#g1 #wc1).to_string(),
quote!(#gm #gm_wc).to_string());
}
#[test]
fn lhs_wc_only() {
let mut g1 = Generics::default();
let wc1: WhereClause = parse2(quote!(where T: Default)).unwrap();
g1.where_clause = Some(wc1.clone());
let g2 = Generics::default();
let gm = super::merge_generics(&g1, &g2);
let gm_wc = &gm.where_clause;
assert_eq!(quote!(#g1 #wc1).to_string(),
quote!(#gm #gm_wc).to_string());
}
#[test]
fn rhs_only() {
let g1 = Generics::default();
let mut g2: Generics = parse2(quote!(<Q: Send, V: Clone>)).unwrap();
let wc2: WhereClause = parse2(quote!(where T: Sync, Q: Debug)).unwrap();
g2.where_clause = Some(wc2.clone());
let gm = super::merge_generics(&g1, &g2);
let gm_wc = &gm.where_clause;
assert_eq!(quote!(#g2 #wc2).to_string(),
quote!(#gm #gm_wc).to_string());
}
}
mod supersuperfy {
use super::*;
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());
}
#[test]
fn array() {
check_supersuperfy(
quote!([super::X; n]),
quote!([super::super::X; n])
);
}
#[test]
fn barefn() {
check_supersuperfy(
quote!(fn(super::A) -> super::B),
quote!(fn(super::super::A) -> super::super::B)
);
}
#[test]
fn group() {
let orig = TypeGroup {
group_token: token::Group::default(),
elem: Box::new(parse2(quote!(super::T)).unwrap())
};
let expected = TypeGroup {
group_token: token::Group::default(),
elem: Box::new(parse2(quote!(super::super::T)).unwrap())
};
let output = supersuperfy(&Type::Group(orig), 1);
assert_eq!(quote!(#output).to_string(),
quote!(#expected).to_string());
}
#[test]
fn infer() {
check_supersuperfy( quote!(_), quote!(_));
}
#[test]
fn never() {
check_supersuperfy( quote!(!), quote!(!));
}
#[test]
fn paren() {
check_supersuperfy(
quote!((super::X)),
quote!((super::super::X))
);
}
#[test]
fn path() {
check_supersuperfy(
quote!(::super::SuperT<u32>),
quote!(::super::super::SuperT<u32>)
);
}
#[test]
fn path_with_qself() {
check_supersuperfy(
quote!(<super::X as super::Y>::Foo<u32>),
quote!(<super::super::X as super::super::Y>::Foo<u32>),
);
}
#[test]
fn angle_bracketed_generic_arguments() {
check_supersuperfy(
quote!(mod_::T<super::X>),
quote!(mod_::T<super::super::X>)
);
}
#[test]
fn ptr() {
check_supersuperfy(
quote!(*const super::X),
quote!(*const super::super::X)
);
}
#[test]
fn reference() {
check_supersuperfy(
quote!(&'a mut super::X),
quote!(&'a mut super::super::X)
);
}
#[test]
fn slice() {
check_supersuperfy(
quote!([super::X]),
quote!([super::super::X])
);
}
#[test]
fn trait_object() {
check_supersuperfy(
quote!(dyn super::X + super::Y),
quote!(dyn super::super::X + super::super::Y)
);
}
#[test]
fn tuple() {
check_supersuperfy(
quote!((super::A, super::B)),
quote!((super::super::A, super::super::B))
);
}
}
mod supersuperfy_generics {
use super::*;
fn check_supersuperfy_generics(
orig: TokenStream,
orig_wc: TokenStream,
expected: TokenStream,
expected_wc: TokenStream)
{
let mut orig_g: Generics = parse2(orig).unwrap();
orig_g.where_clause = parse2(orig_wc).unwrap();
let mut expected_g: Generics = parse2(expected).unwrap();
expected_g.where_clause = parse2(expected_wc).unwrap();
let mut output: Generics = orig_g;
supersuperfy_generics(&mut output, 1);
let (o_ig, o_tg, o_wc) = output.split_for_impl();
let (e_ig, e_tg, e_wc) = expected_g.split_for_impl();
assert_eq!(quote!(#o_ig).to_string(), quote!(#e_ig).to_string());
assert_eq!(quote!(#o_tg).to_string(), quote!(#e_tg).to_string());
assert_eq!(quote!(#o_wc).to_string(), quote!(#e_wc).to_string());
}
#[test]
fn default() {
check_supersuperfy_generics(
quote!(<T: X = super::Y>), quote!(),
quote!(<T: X = super::super::Y>), quote!(),
);
}
#[test]
fn empty() {
check_supersuperfy_generics(quote!(), quote!(), quote!(), quote!());
}
#[test]
fn everything() {
check_supersuperfy_generics(
quote!(<T: super::A = super::B>),
quote!(where super::C: super::D),
quote!(<T: super::super::A = super::super::B>),
quote!(where super::super::C: super::super::D),
);
}
#[test]
fn bound() {
check_supersuperfy_generics(
quote!(<T: super::A>), quote!(),
quote!(<T: super::super::A>), quote!(),
);
}
#[test]
fn closure() {
check_supersuperfy_generics(
quote!(<F: Fn(u32) -> super::SuperT>), quote!(),
quote!(<F: Fn(u32) -> super::super::SuperT>), quote!(),
);
}
#[test]
fn wc_bounded_ty() {
check_supersuperfy_generics(
quote!(), quote!(where super::T: X),
quote!(), quote!(where super::super::T: X),
);
}
#[test]
fn wc_bounds() {
check_supersuperfy_generics(
quote!(), quote!(where T: super::X),
quote!(), quote!(where T: super::super::X),
);
}
}
}