diff options
author | Inna Palant <ipalant@google.com> | 2023-12-11 15:47:06 -0800 |
---|---|---|
committer | Inna Palant <ipalant@google.com> | 2023-12-11 15:47:06 -0800 |
commit | 691813a94ed2bceb54c38f623bcc7b9711637876 (patch) | |
tree | 0b3dcb37be9b85ab4a965fe6c4e4dcc1951ede25 | |
parent | fd336aab6ba09c7c46093d98de6005d649598c1a (diff) | |
parent | e15350e1d5e606e109ee7c329405c33f2c63890c (diff) | |
download | mockall_derive-691813a94ed2bceb54c38f623bcc7b9711637876.tar.gz |
Merge remote-tracking branch 'origin/upstream'platform-tools-34.0.5
Import b/310602755
-rw-r--r-- | .cargo_vcs_info.json | 6 | ||||
-rw-r--r-- | Android.bp | 37 | ||||
-rw-r--r-- | Cargo.toml | 58 | ||||
l--------- | LICENSE | 1 | ||||
-rw-r--r-- | LICENSE-APACHE | 201 | ||||
-rw-r--r-- | LICENSE-MIT | 25 | ||||
-rw-r--r-- | METADATA | 20 | ||||
-rw-r--r-- | MODULE_LICENSE_APACHE2 | 0 | ||||
-rw-r--r-- | OWNERS | 2 | ||||
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | cargo_embargo.json | 12 | ||||
-rw-r--r-- | patches/remove-pretty-assertions-dep.patch | 12 | ||||
-rw-r--r-- | patches/use-deprecated-syn1-dep.patch | 21 | ||||
-rw-r--r-- | patches/use-explicit-extern-crate-proc-macro.patch | 14 | ||||
-rw-r--r-- | src/automock.rs | 325 | ||||
-rw-r--r-- | src/lib.rs | 1761 | ||||
-rw-r--r-- | src/mock_function.rs | 2436 | ||||
-rw-r--r-- | src/mock_item.rs | 172 | ||||
-rw-r--r-- | src/mock_item_struct.rs | 456 | ||||
-rw-r--r-- | src/mock_trait.rs | 184 | ||||
-rw-r--r-- | src/mockable_item.rs | 164 | ||||
-rw-r--r-- | src/mockable_struct.rs | 642 |
22 files changed, 6563 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..1232443 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "d5351f7215c6c5bca11f704ed41d9ae768b43007" + }, + "path_in_vcs": "mockall_derive" +}
\ No newline at end of file diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..2220f21 --- /dev/null +++ b/Android.bp @@ -0,0 +1,37 @@ +// This file is generated by cargo_embargo. +// Do not modify this file as changes will be overridden on upgrade. + +rust_proc_macro { + name: "libmockall_derive", + crate_name: "mockall_derive", + cargo_env_compat: true, + cargo_pkg_version: "0.11.4", + srcs: ["src/lib.rs"], + edition: "2018", + rustlibs: [ + "libcfg_if", + "libproc_macro2", + "libquote", + "libsyn_deprecated", + ], +} + +rust_test_host { + name: "mockall_derive_test_src_lib", + crate_name: "mockall_derive", + cargo_env_compat: true, + cargo_pkg_version: "0.11.4", + srcs: ["src/lib.rs"], + test_suites: ["general-tests"], + auto_gen_config: true, + test_options: { + unit_test: true, + }, + edition: "2018", + rustlibs: [ + "libcfg_if", + "libproc_macro2", + "libquote", + "libsyn_deprecated", + ], +} diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..89d8bc1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,58 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2018" +name = "mockall_derive" +version = "0.11.4" +authors = ["Alan Somers <asomers@gmail.com>"] +description = """ +Procedural macros for Mockall +""" +documentation = "https://docs.rs/mockall_derive" +readme = "README.md" +keywords = [ + "mock", + "mocking", + "testing", +] +categories = ["development-tools::testing"] +license = "MIT/Apache-2.0" +repository = "https://github.com/asomers/mockall" + +[package.metadata.release] +push = false +tag = false + +[lib] +proc-macro = true + +[dependencies.cfg-if] +version = "1.0" + +[dependencies.proc-macro2] +version = "1.0" + +[dependencies.quote] +version = "1.0" + +[dependencies.syn] +version = "1.0.87" +features = [ + "extra-traits", + "full", +] + +[dev-dependencies.pretty_assertions] +version = "1.3" + +[features] +nightly_derive = ["proc-macro2/nightly"] @@ -0,0 +1 @@ +LICENSE-APACHE
\ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..39df3da --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2019 Alan Somers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..eaa868e --- /dev/null +++ b/METADATA @@ -0,0 +1,20 @@ +name: "mockall_derive" +description: "()" +third_party { + identifier { + type: "crates.io" + value: "https://crates.io/crates/mockall_derive" + } + identifier { + type: "Archive" + value: "https://static.crates.io/crates/mockall_derive/mockall_derive-0.11.4.crate" + } + version: "0.11.4" + # Dual-licensed, using the least restrictive per go/thirdpartylicenses#same. + license_type: NOTICE + last_upgrade_date { + year: 2023 + month: 11 + day: 6 + } +} diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_APACHE2 @@ -0,0 +1,2 @@ +# Bug component: 688011 +include platform/prebuilts/rust:main:/OWNERS diff --git a/README.md b/README.md new file mode 100644 index 0000000..2fa2632 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Mockall_derive + +This crate should never be used directly. You should use +[`mockall`](https://crates.io/crates/mockall) instead. + +# License + +`mockall` is primarily distributed under the terms of both the MIT license +and the Apache License (Version 2.0), with portions covered by various BSD-like +licenses. + +See LICENSE-APACHE, and LICENSE-MIT for details + + diff --git a/cargo_embargo.json b/cargo_embargo.json new file mode 100644 index 0000000..f14bd80 --- /dev/null +++ b/cargo_embargo.json @@ -0,0 +1,12 @@ +{ + "tests": true, + "package": { + "mockall_derive": { + "device_supported": false, + "dep_blocklist": [ + "libpretty_assertions" + ], + "patch": "patches/use-deprecated-syn1-dep.patch" + } + } +} diff --git a/patches/remove-pretty-assertions-dep.patch b/patches/remove-pretty-assertions-dep.patch new file mode 100644 index 0000000..8340ef6 --- /dev/null +++ b/patches/remove-pretty-assertions-dep.patch @@ -0,0 +1,12 @@ +Not actually needed if the tests pass... + +--- a/src/automock.rs 2006-07-24 03:21:28.000000000 +0200 ++++ b/src/automock.rs 2023-11-06 14:54:02.079601472 +0100 +@@ -280,7 +280,6 @@ + #[cfg(test)] + mod t { + use super::super::*; +- use pretty_assertions::assert_eq; + + fn check_substitute_type( + attrs: TokenStream, diff --git a/patches/use-deprecated-syn1-dep.patch b/patches/use-deprecated-syn1-dep.patch new file mode 100644 index 0000000..5d85ecf --- /dev/null +++ b/patches/use-deprecated-syn1-dep.patch @@ -0,0 +1,21 @@ +Patch Android.bp to use the old syn v1 package. + +--- a/Android.bp 2023-11-06 16:05:46.887866053 +0100 ++++ b/Android.bp 2023-11-06 16:06:15.768058832 +0100 +@@ -14,7 +14,7 @@ + "libcfg_if", + "libproc_macro2", + "libquote", +- "libsyn", ++ "libsyn_deprecated", + ], + } + +@@ -34,6 +34,6 @@ + "libcfg_if", + "libproc_macro2", + "libquote", +- "libsyn", ++ "libsyn_deprecated", + ], + } diff --git a/patches/use-explicit-extern-crate-proc-macro.patch b/patches/use-explicit-extern-crate-proc-macro.patch new file mode 100644 index 0000000..5985b65 --- /dev/null +++ b/patches/use-explicit-extern-crate-proc-macro.patch @@ -0,0 +1,14 @@ +Cargo automatically imports proc_macro in Rust 2018, but Soong only +does this for rust_proc_macro targets. + +--- a/src/lib.rs 2023-11-06 16:32:46.480193187 +0100 ++++ b/src/lib.rs 2023-11-06 16:29:38.743132168 +0100 +@@ -34,6 +34,8 @@ + use crate::mock_item_struct::MockItemStruct; + use crate::mockable_item::MockableItem; + ++extern crate proc_macro; ++ + // 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>>; diff --git a/src/automock.rs b/src/automock.rs new file mode 100644 index 0000000..bba6cb1 --- /dev/null +++ b/src/automock.rs @@ -0,0 +1,325 @@ +// vim: tw=80 +use super::*; +use std::collections::HashMap; +use syn::parse::{Parse, ParseStream}; + +/// A single automock attribute +// This enum is very short-lived, so it's fine not to box it. +#[allow(clippy::large_enum_variant)] +enum Attr { + Mod(ItemMod), + Type(TraitItemType), +} + +impl Parse for Attr { + fn parse(input: ParseStream) -> parse::Result<Self> { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![mod]) { + input.parse().map(Attr::Mod) + } else if lookahead.peek(Token![type]) { + input.parse().map(Attr::Type) + } else { + Err(lookahead.error()) + } + } +} + +/// automock attributes +#[derive(Debug, Default)] +pub(crate) struct Attrs { + pub attrs: HashMap<Ident, Type>, + pub modname: Option<Ident>, +} + +impl Attrs { + fn get_path(&self, path: &Path) -> Option<Type> { + if path.leading_colon.is_none() & (path.segments.len() == 2) { + if path.segments.first().unwrap().ident == "Self" { + let ident = &path.segments.last().unwrap().ident; + self.attrs.get(ident).cloned() + } else { + None + } + } else { + None + } + } + + pub(crate) fn substitute_item_impl(&self, item_impl: &mut ItemImpl) { + let (_, trait_path, _) = item_impl + .trait_ + .as_ref() + .expect("Should only be called for trait item impls"); + let trait_ident = find_ident_from_path(trait_path).0; + for item in item_impl.items.iter_mut() { + if let ImplItem::Method(method) = item { + let sig = &mut method.sig; + for fn_arg in sig.inputs.iter_mut() { + if let FnArg::Typed(arg) = fn_arg { + self.substitute_type(&mut arg.ty, &trait_ident); + } + } + if let ReturnType::Type(_, ref mut ty) = &mut sig.output { + self.substitute_type(ty, &trait_ident); + } + } + } + } + + fn substitute_path_segment(&self, seg: &mut PathSegment, traitname: &Ident) { + match &mut seg.arguments { + PathArguments::None => + /* nothing to do */ + { + () + } + PathArguments::Parenthesized(p) => { + compile_error(p.span(), + "Mockall does not support mocking Fn objects. See https://github.com/asomers/mockall/issues/139"); + } + PathArguments::AngleBracketed(abga) => { + for arg in abga.args.iter_mut() { + match arg { + GenericArgument::Type(ty) => self.substitute_type(ty, traitname), + GenericArgument::Binding(binding) => { + self.substitute_type(&mut binding.ty, traitname); + } + _ => { + /* + * Nothing to do, as long as lifetimes can't be + * associated types + */ + } + } + } + } + } + } + + /// Recursively substitute types in the input + fn substitute_type(&self, ty: &mut Type, traitname: &Ident) { + match ty { + Type::Slice(s) => self.substitute_type(s.elem.as_mut(), traitname), + Type::Array(a) => self.substitute_type(a.elem.as_mut(), traitname), + Type::Ptr(p) => self.substitute_type(p.elem.as_mut(), traitname), + Type::Reference(r) => self.substitute_type(r.elem.as_mut(), traitname), + Type::BareFn(bfn) => { + for fn_arg in bfn.inputs.iter_mut() { + self.substitute_type(&mut fn_arg.ty, traitname); + } + if let ReturnType::Type(_, ref mut ty) = &mut bfn.output { + self.substitute_type(ty, traitname); + } + } + Type::Tuple(tuple) => { + for elem in tuple.elems.iter_mut() { + self.substitute_type(elem, traitname) + } + } + Type::Path(path) => { + if let Some(ref qself) = path.qself { + let qp = if let Type::Path(p) = qself.ty.as_ref() { + &p.path + } else { + panic!("QSelf's type isn't a path?") + }; + let qident = &qp.segments.first().unwrap().ident; + if qself.position != 1 + || qp.segments.len() != 1 + || path.path.segments.len() != 2 + || qident != "Self" + { + compile_error(path.span(), "QSelf is a work in progress"); + } + + let mut seg_iter = path.path.segments.iter().rev(); + let last_seg = seg_iter.next().unwrap(); + let to_sub = &last_seg.ident; + let penultimate_seg = seg_iter.next().unwrap(); + let qident = &penultimate_seg.ident; + drop(seg_iter); + + if qident != traitname { + compile_error(qident.span(), + "Mockall does not support QSelf substitutions except for the trait being mocked"); + } + if let Some(new_type) = self.attrs.get(to_sub) { + *ty = new_type.clone(); + } else { + compile_error(to_sub.span(), "Unknown type substitution for QSelf"); + } + } else if let Some(newty) = self.get_path(&path.path) { + *ty = newty; + } else { + for seg in path.path.segments.iter_mut() { + self.substitute_path_segment(seg, traitname); + } + } + } + Type::TraitObject(to) => { + for bound in to.bounds.iter_mut() { + self.substitute_type_param_bound(bound, traitname); + } + } + Type::ImplTrait(it) => { + for bound in it.bounds.iter_mut() { + self.substitute_type_param_bound(bound, traitname); + } + } + Type::Paren(p) => self.substitute_type(p.elem.as_mut(), traitname), + Type::Group(g) => self.substitute_type(g.elem.as_mut(), traitname), + Type::Macro(_) | Type::Verbatim(_) => { + compile_error( + ty.span(), + "mockall_derive does not support this type when using associated types", + ); + } + Type::Infer(_) | Type::Never(_) => { /* Nothing to do */ } + _ => compile_error(ty.span(), "Unsupported type"), + } + } + + fn substitute_type_param_bound(&self, bound: &mut TypeParamBound, traitname: &Ident) { + if let TypeParamBound::Trait(t) = bound { + match self.get_path(&t.path) { + None => { + for seg in t.path.segments.iter_mut() { + self.substitute_path_segment(seg, traitname); + } + } + Some(Type::Path(type_path)) => { + t.path = type_path.path; + } + Some(_) => { + compile_error(t.path.span(), "Can only substitute paths for trait bounds"); + } + } + } + } + + pub(crate) fn substitute_trait(&self, item: &ItemTrait) -> ItemTrait { + let mut output = item.clone(); + for trait_item in output.items.iter_mut() { + match trait_item { + TraitItem::Type(tity) => { + if let Some(ty) = self.attrs.get(&tity.ident) { + let span = tity.span(); + tity.default = Some((Token![=](span), ty.clone())); + // Concrete associated types aren't allowed to have + // bounds + tity.bounds = Punctuated::new(); + } else { + compile_error(tity.span(), "Default value not given for associated type"); + } + } + TraitItem::Method(method) => { + let sig = &mut method.sig; + for fn_arg in sig.inputs.iter_mut() { + if let FnArg::Typed(arg) = fn_arg { + self.substitute_type(&mut arg.ty, &item.ident); + } + } + if let ReturnType::Type(_, ref mut ty) = &mut sig.output { + self.substitute_type(ty, &item.ident); + } + } + _ => { + // Nothing to do + } + } + } + output + } +} + +impl Parse for Attrs { + fn parse(input: ParseStream) -> parse::Result<Self> { + let mut attrs = HashMap::new(); + let mut modname = None; + while !input.is_empty() { + let attr: Attr = input.parse()?; + match attr { + Attr::Mod(item_mod) => { + if let Some((br, _)) = item_mod.content { + compile_error( + br.span, + "mod name attributes must have the form \"mod my_name;\"", + ); + } + modname = Some(item_mod.ident.clone()); + } + Attr::Type(trait_item_type) => { + let ident = trait_item_type.ident.clone(); + if let Some((_, ty)) = trait_item_type.default { + attrs.insert(ident, ty.clone()); + } else { + compile_error( + trait_item_type.span(), + "automock type attributes must have a default value", + ); + } + } + } + } + Ok(Attrs { attrs, modname }) + } +} + +/// Unit tests for `Attrs`. +#[cfg(test)] +mod t { + use super::super::*; + + fn check_substitute_type( + attrs: TokenStream, + input: TokenStream, + traitname: Ident, + expected: TokenStream, + ) { + let _self: super::Attrs = parse2(attrs).unwrap(); + let mut in_ty: Type = parse2(input).unwrap(); + let expect_ty: Type = parse2(expected).unwrap(); + _self.substitute_type(&mut in_ty, &traitname); + assert_eq!(in_ty, expect_ty); + } + + #[test] + fn qself() { + check_substitute_type( + quote!( + type T = u32; + ), + quote!(<Self as Foo>::T), + format_ident!("Foo"), + quote!(u32), + ); + } + + #[test] + #[should_panic( + expected = "Mockall does not support QSelf substitutions except for the trait being mocked" + )] + fn qself_other() { + check_substitute_type( + quote!( + type T = u32; + ), + quote!(<Self as AsRef>::T), + format_ident!("Foo"), + quote!(u32), + ); + } + + #[test] + #[should_panic(expected = "Unknown type substitution for QSelf")] + fn unknown_substitution() { + check_substitute_type( + quote!( + type T = u32; + ), + quote!(<Self as Foo>::Q), + format_ident!("Foo"), + quote!(u32), + ); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..6308c45 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1761 @@ +// 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. + +#![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; + +extern crate proc_macro; + +// 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!("{}. More information may be available when mockall is built with the \"nightly\" feature.", msg); + } + } +} + +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"), + } + } +} + +/// Replace any references to the anonymous lifetime `'_` with `'static`. +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") + } +} + +// 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>) +{ + 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?"); + } + } + }; + + // First, build a HashMap of all Fn generic types + 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 { + // 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| { + 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 { + // 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 + }; + + // Next substitute Box<Fn> into the arguments + 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(); + + // 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) => { + 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) +} + +/// Replace any "impl trait" types with "Box<dyn trait>" or equivalent. +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 { + // 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) { + 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; + } +} + +/// 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(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::Binding(b) => + deselfify(&mut b.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: &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) => { + // Change types like `dyn Self` into `dyn MockXXX`. + for bound in tto.bounds.iter_mut() { + if let 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() { + if let FnArg::Typed(pt) = arg { + 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::Binding(b) => { + ret.extend(find_lifetimes(&b.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 + } + + // XXX This logic requires that attributes are imported with their + // standard names. + #[allow(clippy::needless_bool)] + #[allow(clippy::if_same_then_else)] + fn format(&mut self) -> Vec<Attribute> { + self.attrs.iter() + .cloned() + .filter(|attr| { + let i = attr.path.get_ident(); + if i.is_none() { + false + } else if *i.as_ref().unwrap() == "derive" { + // We can't usefully derive any traits. Ignore them + 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" { + // 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 + } else { + true + } + }).collect() + } +} + +/// Determine if this Pat is any kind of `self` binding +fn pat_is_self(pat: &Pat) -> bool { + if let Pat::Ident(pi) = pat { + pi.ident == "self" + } else { + false + } +} + +/// Add `levels` `super::` to the path. Return the number of levels added. +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::Binding(ref mut binding) => { + binding.ty = supersuperfy(&binding.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 + } +} + +/// Replace any references to `super::X` in `original` with `super::super::X`. +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(_) => { + /* Should've already been flagged as a compile error */ + }, + Type::Infer(_) | Type::Never(_) => + { + /* Nothing to do */ + }, + _ => 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); + } + } +} + +/// 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) +} + +/// Generate an identifier for the mock struct's private module: eg "Foo" => +/// "__mock_Foo" +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_) + } +} + +/// 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::*; + + 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 + } + } + + /// Compare only the identifiers of two WherePredicates + 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, + (Eq(xeq), Eq(yeq)) => xeq.lhs_ty == yeq.lhs_ty, + _ => 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(); + // First merge the params + 'outer_param: for yparam in y.params.iter() { + // XXX: O(n^2) loop + 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(); + } + // XXX this might result in duplicate bounds + if ot.bounds != yt.bounds { + ot.bounds.extend(yt.bounds.iter().cloned()); + } + } + continue 'outer_param; + } + } + out.params.push(yparam.clone()); + } + out + }; + // Then merge the where clauses + 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() { + // XXX: O(n^2) loop + 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)) => + // XXX: might result in duplicates + obl.lifetimes.extend( + ybl.lifetimes.iter().cloned()), + }; + // XXX: might result in duplicate bounds + if ot.bounds != yt.bounds { + ot.bounds.extend(yt.bounds.iter().cloned()) + } + } + continue 'outer_wc; + } + } + out_wc.predicates.push(ypred.clone()); + } + } + } + out +} + +/// Transform a Vec of lifetimes into a Generics +fn lifetimes_to_generics(lv: &Punctuated<LifetimeDef, Token![,]>)-> Generics { + if lv.is_empty() { + Generics::default() + } else { + let params = lv.iter() + .map(|lt| GenericParam::Lifetime(lt.clone())) + .collect(); + Generics { + lt_token: Some(Token![<](lv[0].span())), + gt_token: Some(Token![>](lv[0].span())), + 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<LifetimeDef, token::Comma>, + Punctuated<LifetimeDef, token::Comma>) +{ + if generics.lt_token.is_none() { + return (generics, Default::default(), Default::default()); + } + + // Check which types and lifetimes are referenced by the arguments + 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(_) => { + // Probably a lifetime parameter from the impl block that isn't + // used by this particular method + }, + 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) +} + +/// 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![in](vis.span()); + let super_token = Token![super](vis.span()); + match vis { + Visibility::Inherited => { + // Private items need pub(in super::[...]) for each level + 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) => { + // 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" { + vr.clone().into() + } else { + let mut out = vr.clone(); + out.in_token = Some(in_token); + for _ in 0..levels { + out.path.segments.insert(0, super_token.into()); + } + out.into() + } + }, + _ => 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] +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); +} + +/// Various tests for overall code generation that are hard or impossible to +/// write as integration tests +mod mock { + use std::str::FromStr; + use super::super::*; + use super::*; + + #[test] + fn inherent_method_visibility() { + let code = r#" + 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 = r#" + 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!( + 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> + )); + } +} + +/// Various tests for overall code generation that are hard or impossible to +/// write as integration tests +mod automock { + use std::str::FromStr; + use super::super::*; + use super::*; + + #[test] + fn doc_comments() { + let code = r#" + 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 = r#" + 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 = r#"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 = r#" + 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 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()); + } + + // 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>>>) + ); + } + + #[test] + fn impl_trait() { + check_deimplify( + quote!(-> impl Foo), + quote!(-> Box<dyn Foo>) + ); + } + + // With extra bounds + #[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 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>) + ); + } + + // A trait object with multiple bounds + #[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: ImplItemMethod = parse2(quote!( + fn foo<'a>(&self) where 'a: 'static, Self: Sized; + )).unwrap(); + let expected: ImplItemMethod = 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: ImplItemMethod = parse2(quote!( + fn foo(&self) where Self: Sized; + )).unwrap(); + let expected: ImplItemMethod = parse2(quote!( + fn foo(&self); + )).unwrap(); + dewhereselfify(&mut meth.sig.generics); + assert_eq!(meth, expected); + } + + #[test] + fn with_real_generics() { + let mut meth: ImplItemMethod = parse2(quote!( + fn foo<T>(&self, t: T) where Self: Sized, T: Copy; + )).unwrap(); + let expected: ImplItemMethod = 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()); + } + + // Just check that it doesn't panic + #[test] + fn infer() { + check_supersuperfy( quote!(_), quote!(_)); + } + + // Just check that it doesn't panic + #[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), + ); + } +} +} diff --git a/src/mock_function.rs b/src/mock_function.rs new file mode 100644 index 0000000..86a8690 --- /dev/null +++ b/src/mock_function.rs @@ -0,0 +1,2436 @@ +// vim: tw=80 +use super::*; + +use quote::ToTokens; + +/// Convert a trait object reference into a reference to a Boxed trait +/// +/// # Returns +/// +/// Returns `true` if it was necessary to box the type. +fn dedynify(ty: &mut Type) -> bool { + if let Type::Reference(ref mut tr) = ty { + if let Type::TraitObject(ref tto) = tr.elem.as_ref() { + if let Some(lt) = &tr.lifetime { + if lt.ident == "static" { + // For methods that return 'static references, the user can + // usually actually supply one, unlike nonstatic references. + // dedynify is unneeded and harmful in such cases. + // + // But we do need to add parens to prevent parsing errors + // when methods like returning add a `+ Send` to the output + // type. + *tr.elem = parse2(quote!((#tto))).unwrap(); + return false; + } + } + + *tr.elem = parse2(quote!(Box<#tto>)).unwrap(); + return true; + } + } + false +} + +/// Convert a special reference type like "&str" into a reference to its owned +/// type like "&String". +fn destrify(ty: &mut Type) { + if let Type::Reference(ref mut tr) = ty { + if let Some(lt) = &tr.lifetime { + if lt.ident == "static" { + // For methods that return 'static references, the user can + // usually actually supply one, unlike nonstatic references. + // destrify is unneeded and harmful in such cases. + return; + } + } + + let path_ty: TypePath = parse2(quote!(Path)).unwrap(); + let pathbuf_ty: Type = parse2(quote!(::std::path::PathBuf)).unwrap(); + + let str_ty: TypePath = parse2(quote!(str)).unwrap(); + let string_ty: Type = parse2(quote!(::std::string::String)).unwrap(); + + let cstr_ty: TypePath = parse2(quote!(CStr)).unwrap(); + let cstring_ty: Type = parse2(quote!(::std::ffi::CString)).unwrap(); + + let osstr_ty: TypePath = parse2(quote!(OsStr)).unwrap(); + let osstring_ty: Type = parse2(quote!(::std::ffi::OsString)).unwrap(); + + match tr.elem.as_ref() { + Type::Path(ref path) if *path == cstr_ty => *tr.elem = cstring_ty, + Type::Path(ref path) if *path == osstr_ty => *tr.elem = osstring_ty, + Type::Path(ref path) if *path == path_ty => *tr.elem = pathbuf_ty, + Type::Path(ref path) if *path == str_ty => *tr.elem = string_ty, + Type::Slice(ts) => { + let inner = (*ts.elem).clone(); + let mut segments = Punctuated::new(); + segments.push(format_ident!("std").into()); + segments.push(format_ident!("vec").into()); + let mut v: PathSegment = format_ident!("Vec").into(); + let mut abga_args = Punctuated::new(); + abga_args.push(GenericArgument::Type(inner)); + v.arguments = PathArguments::AngleBracketed(AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Token![<](Span::call_site()), + args: abga_args, + gt_token: Token![>](Span::call_site()), + }); + segments.push(v); + + *tr.elem = Type::Path(TypePath { + qself: None, + path: Path { + leading_colon: Some(Token![::](Span::call_site())), + segments, + }, + }); + } + _ => (), // Nothing to do + }; + } +} + +/// Return the owned version of the input. +fn ownify(ty: &Type) -> Type { + if let Type::Reference(ref tr) = &ty { + if tr + .lifetime + .as_ref() + .map_or(false, |lt| lt.ident == "static") + { + // Just a static expectation + ty.clone() + } else { + *tr.elem.clone() + } + } else { + ty.clone() + } +} + +/// Add Send + Sync to a where clause +fn send_syncify(wc: &mut Option<WhereClause>, bounded_ty: Type) { + let mut bounds = Punctuated::new(); + bounds.push(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path: Path::from(format_ident!("Send")), + })); + bounds.push(TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path: Path::from(format_ident!("Sync")), + })); + if wc.is_none() { + *wc = Some(WhereClause { + where_token: <Token![where]>::default(), + predicates: Punctuated::new(), + }); + } + wc.as_mut() + .unwrap() + .predicates + .push(WherePredicate::Type(PredicateType { + lifetimes: None, + bounded_ty, + colon_token: Default::default(), + bounds, + })); +} + +/// Build a MockFunction. +#[derive(Clone, Copy, Debug)] +pub(crate) struct Builder<'a> { + attrs: &'a [Attribute], + call_levels: Option<usize>, + levels: usize, + parent: Option<&'a Ident>, + sig: &'a Signature, + struct_: Option<&'a Ident>, + struct_generics: Option<&'a Generics>, + trait_: Option<&'a Ident>, + vis: &'a Visibility, +} + +impl<'a> Builder<'a> { + pub fn attrs(&mut self, attrs: &'a [Attribute]) -> &mut Self { + self.attrs = attrs; + self + } + + pub fn build(self) -> MockFunction { + let mut argnames = Vec::new(); + let mut argty = Vec::new(); + let mut is_static = true; + let mut predexprs = Vec::new(); + let mut predty = Vec::new(); + let mut refpredty = Vec::new(); + + let (mut declosured_generics, declosured_inputs, call_exprs) = + declosurefy(&self.sig.generics, &self.sig.inputs); + + for fa in declosured_inputs.iter() { + if let FnArg::Typed(pt) = fa { + let argname = (*pt.pat).clone(); + if pat_is_self(&argname) { + // A weird receiver like `Box<Self>` + is_static = false; + continue; + } + let aty = supersuperfy(&pt.ty, self.levels); + if let Type::Reference(ref tr) = aty { + predexprs.push(quote!(#argname)); + predty.push((*tr.elem).clone()); + let tr2 = Type::Reference(TypeReference { + and_token: tr.and_token, + lifetime: None, + mutability: None, + elem: tr.elem.clone(), + }); + refpredty.push(tr2); + } else { + predexprs.push(quote!(&#argname)); + predty.push(aty.clone()); + let tr = TypeReference { + and_token: Token![&](Span::call_site()), + lifetime: None, + mutability: None, + elem: Box::new(aty.clone()), + }; + refpredty.push(Type::Reference(tr)); + }; + argnames.push(argname); + argty.push(aty.clone()); + } else { + is_static = false; + } + } + let (output, boxed) = match self.sig.output { + ReturnType::Default => ( + Type::Tuple(TypeTuple { + paren_token: token::Paren::default(), + elems: Punctuated::new(), + }), + false, + ), + ReturnType::Type(_, ref ty) => { + let mut output_ty = supersuperfy(ty, self.levels); + destrify(&mut output_ty); + let boxed = dedynify(&mut output_ty); + (output_ty, boxed) + } + }; + supersuperfy_generics(&mut declosured_generics, self.levels); + let owned_output = ownify(&output); + let mut return_ref = false; + let mut return_refmut = false; + if let Type::Reference(ref tr) = &output { + if tr.lifetime.as_ref().map_or(true, |lt| lt.ident != "static") { + if tr.mutability.is_none() { + return_ref = true; + } else { + return_refmut = true; + } + } + }; + if is_static && (return_ref || return_refmut) { + compile_error(self.sig.span(), + "Mockall cannot mock static methods that return non-'static references. It's unclear what the return value's lifetime should be."); + } + let struct_generics = self.struct_generics.cloned().unwrap_or_default(); + let (type_generics, salifetimes, srlifetimes) = split_lifetimes( + struct_generics.clone(), + &declosured_inputs, + &ReturnType::Type(<Token![->]>::default(), Box::new(owned_output.clone())), + ); + let srltg = lifetimes_to_generics(&srlifetimes); + let (call_generics, malifetimes, mrlifetimes) = split_lifetimes( + declosured_generics, + &declosured_inputs, + &ReturnType::Type(<Token![->]>::default(), Box::new(owned_output.clone())), + ); + let mrltg = lifetimes_to_generics(&mrlifetimes); + let cgenerics = merge_generics(&type_generics, &call_generics); + let egenerics = merge_generics(&merge_generics(&cgenerics, &srltg), &mrltg); + let alifetimes = salifetimes + .into_iter() + .collect::<HashSet<LifetimeDef>>() + .union(&malifetimes.into_iter().collect::<HashSet<_>>()) + .cloned() + .collect(); + + let fn_params = egenerics.type_params().map(|tp| tp.ident.clone()).collect(); + let call_levels = self.call_levels.unwrap_or(self.levels); + + MockFunction { + alifetimes, + argnames, + argty, + attrs: self.attrs.to_vec(), + call_exprs, + call_generics, + call_vis: expectation_visibility(self.vis, call_levels), + egenerics, + cgenerics, + fn_params, + is_static, + mod_ident: self + .parent + .unwrap_or(&Ident::new("FIXME", Span::call_site())) + .clone(), + output, + owned_output, + boxed, + predexprs, + predty, + refpredty, + return_ref, + return_refmut, + sig: self.sig.clone(), + struct_: self.struct_.cloned(), + struct_generics, + trait_: self.trait_.cloned(), + type_generics, + privmod_vis: expectation_visibility(self.vis, self.levels), + } + } + + /// How many levels of modules beneath the original function this one is + /// nested. + pub fn call_levels(&mut self, levels: usize) -> &mut Self { + self.call_levels = Some(levels); + self + } + + /// How many levels of modules beneath the original function this one's + /// private module is nested. + pub fn levels(&mut self, levels: usize) -> &mut Self { + self.levels = levels; + self + } + + /// # Arguments + /// + /// * sig: The signature of the mockable function + /// * v: The visibility of the mockable function + pub fn new(sig: &'a Signature, vis: &'a Visibility) -> Self { + Builder { + attrs: &[], + levels: 0, + call_levels: None, + parent: None, + sig, + struct_: None, + struct_generics: None, + trait_: None, + vis, + } + } + + /// Supply the name of the parent module + pub fn parent(&mut self, ident: &'a Ident) -> &mut Self { + self.parent = Some(ident); + self + } + + /// Supply the name of the parent struct, if any + pub fn struct_(&mut self, ident: &'a Ident) -> &mut Self { + self.struct_ = Some(ident); + self + } + + /// Supply the Generics of the parent struct, if any + pub fn struct_generics(&mut self, generics: &'a Generics) -> &mut Self { + self.struct_generics = Some(generics); + self + } + + /// Supply the name of the method's trait, if any + pub fn trait_(&mut self, ident: &'a Ident) -> &mut Self { + self.trait_ = Some(ident); + self + } +} + +#[derive(Clone)] +pub(crate) struct MockFunction { + /// Lifetimes of the mocked method that relate to the arguments but not the + /// return value + alifetimes: Punctuated<LifetimeDef, token::Comma>, + /// Names of the method arguments + argnames: Vec<Pat>, + /// Types of the method arguments + argty: Vec<Type>, + /// any attributes on the original function, like #[inline] + pub attrs: Vec<Attribute>, + /// Expressions that should be used for Expectation::call's arguments + call_exprs: Vec<TokenStream>, + /// Generics used for the expectation call + call_generics: Generics, + /// Visibility of the mock function itself + call_vis: Visibility, + /// Generics of the Expectation object + egenerics: Generics, + /// Generics of the Common object + cgenerics: Generics, + /// The mock function's generic types as a list of types + fn_params: Vec<Ident>, + /// Is this for a static method or free function? + is_static: bool, + /// name of the function's parent module + mod_ident: Ident, + /// Output type of the Method, supersuperfied. + output: Type, + /// Owned version of the output type of the Method, supersuperfied. + /// + /// If the real output type is a non-'static reference, then it will differ + /// from this field. + owned_output: Type, + /// True if the `owned_type` is boxed by `Box<>`. + boxed: bool, + /// Expressions that create the predicate arguments from the call arguments + predexprs: Vec<TokenStream>, + /// Types used for Predicates. Will be almost the same as args, but every + /// type will be a non-reference type. + predty: Vec<Type>, + /// Does the function return a non-'static reference? + return_ref: bool, + /// Does the function return a mutable reference? + return_refmut: bool, + /// References to every type in `predty`. + refpredty: Vec<Type>, + /// The signature of the mockable function + sig: Signature, + /// Name of the parent structure, if any + struct_: Option<Ident>, + /// Generics of the parent structure + struct_generics: Generics, + /// Name of this method's trait, if the method comes from a trait + trait_: Option<Ident>, + /// Type generics of the mock structure + type_generics: Generics, + /// Visibility of the expectation and its methods + privmod_vis: Visibility, +} + +impl MockFunction { + /// Return the mock function itself + /// + /// # Arguments + /// + /// * `modname`: Name of the parent struct's private module + // Supplying modname is an unfortunately hack. Ideally MockFunction + // wouldn't need to know that. + pub fn call(&self, modname: Option<&Ident>) -> impl ToTokens { + let attrs = AttrFormatter::new(&self.attrs).format(); + let call_exprs = &self.call_exprs; + let (_, tg, _) = if self.is_method_generic() || self.is_static() { + &self.egenerics + } else { + &self.call_generics + } + .split_for_impl(); + let tbf = tg.as_turbofish(); + let name = self.name(); + let desc = self.desc(); + let no_match_msg = quote!(std::format!( + "{}: No matching expectation found", #desc)); + let sig = &self.sig; + let (vis, dead_code) = if self.trait_.is_some() { + (&Visibility::Inherited, quote!()) + } else { + let dead_code = if let Visibility::Inherited = self.call_vis { + // This private method may be a helper only used by the struct's + // other methods, which we are mocking. If so, the mock method + // will be dead code. But we can't simply eliminate it, because + // it might also be used by other code in the same module. + quote!(#[allow(dead_code)]) + } else { + quote!() + }; + (&self.call_vis, dead_code) + }; + let substruct_obj = if let Some(trait_) = &self.trait_ { + let ident = format_ident!("{}_expectations", trait_); + quote!(#ident.) + } else { + quote!() + }; + let call = if self.return_refmut { + Ident::new("call_mut", Span::call_site()) + } else { + Ident::new("call", Span::call_site()) + }; + let mut deref = quote!(); + if self.boxed { + if self.return_ref { + deref = quote!(&**); + } else if self.return_refmut { + deref = quote!(&mut **); + } + } + if self.is_static { + let outer_mod_path = self.outer_mod_path(modname); + quote!( + // Don't add a doc string. The original is included in #attrs + #(#attrs)* + #dead_code + #vis #sig { + let no_match_msg = #no_match_msg; + #deref { + let __mockall_guard = #outer_mod_path::EXPECTATIONS + .lock().unwrap(); + /* + * TODO: catch panics, then gracefully release the mutex + * so it won't be poisoned. This requires bounding any + * generic parameters with UnwindSafe + */ + /* std::panic::catch_unwind(|| */ + __mockall_guard.#call#tbf(#(#call_exprs,)*) + /*)*/ + }.expect(&no_match_msg) + } + ) + } else { + quote!( + // Don't add a doc string. The original is included in #attrs + #(#attrs)* + #dead_code + #vis #sig { + let no_match_msg = #no_match_msg; + #deref self.#substruct_obj #name.#call#tbf(#(#call_exprs,)*) + .expect(&no_match_msg) + } + + ) + } + } + + /// Return this method's contribution to its parent's checkpoint method + pub fn checkpoint(&self) -> impl ToTokens { + let attrs = AttrFormatter::new(&self.attrs).doc(false).format(); + let inner_mod_ident = self.inner_mod_ident(); + if self.is_static { + quote!( + #(#attrs)* + { + let __mockall_timeses = #inner_mod_ident::EXPECTATIONS.lock() + .unwrap() + .checkpoint() + .collect::<Vec<_>>(); + } + ) + } else { + let name = &self.name(); + quote!(#(#attrs)* { self.#name.checkpoint(); }) + } + } + + /// Return a function that creates a Context object for this function + /// + /// # Arguments + /// + /// * `modname`: Name of the parent struct's private module + // Supplying modname is an unfortunately hack. Ideally MockFunction + // wouldn't need to know that. + pub fn context_fn(&self, modname: Option<&Ident>) -> impl ToTokens { + let attrs = AttrFormatter::new(&self.attrs).doc(false).format(); + let context_docstr = format!( + "Create a [`Context`]({}{}/struct.Context.html) for mocking the `{}` method", + modname.map(|m| format!("{}/", m)).unwrap_or_default(), + self.inner_mod_ident(), + self.name() + ); + let context_ident = format_ident!("{}_context", self.name()); + let (_, tg, _) = self.type_generics.split_for_impl(); + let outer_mod_path = self.outer_mod_path(modname); + let v = &self.call_vis; + quote!( + #(#attrs)* + #[doc = #context_docstr] + #v fn #context_ident() -> #outer_mod_path::Context #tg + { + #outer_mod_path::Context::default() + } + ) + } + + /// Generate a code fragment that will print a description of the invocation + fn desc(&self) -> impl ToTokens { + let argnames = &self.argnames; + let name = if let Some(s) = &self.struct_ { + format!("{}::{}", s, self.sig.ident) + } else { + format!("{}::{}", self.mod_ident, self.sig.ident) + }; + let fields = vec!["{:?}"; argnames.len()].join(", "); + let fstr = format!("{}({})", name, fields); + quote!(std::format!(#fstr, #(::mockall::MaybeDebugger(&#argnames)),*)) + } + + /// Generate code for the expect_ method + /// + /// # Arguments + /// + /// * `modname`: Name of the parent struct's private module + /// * `self_args`: If supplied, these are the + /// AngleBracketedGenericArguments of the self type of the + /// trait impl. e.g. The `T` in `impl Foo for Bar<T>`. + // Supplying modname is an unfortunately hack. Ideally MockFunction + // wouldn't need to know that. + pub fn expect(&self, modname: &Ident, self_args: Option<&PathArguments>) -> impl ToTokens { + let attrs = AttrFormatter::new(&self.attrs).doc(false).format(); + let name = self.name(); + let expect_ident = format_ident!("expect_{}", &name); + let expectation_obj = self.expectation_obj(self_args); + let funcname = &self.sig.ident; + let (_, tg, _) = if self.is_method_generic() { + &self.egenerics + } else { + &self.call_generics + } + .split_for_impl(); + let (ig, _, wc) = self.call_generics.split_for_impl(); + let mut wc = wc.cloned(); + if self.is_method_generic() && (self.return_ref || self.return_refmut) { + // Add Senc + Sync, required for downcast, since Expectation + // stores an Option<#owned_output> + send_syncify(&mut wc, self.owned_output.clone()); + } + let tbf = tg.as_turbofish(); + let vis = &self.call_vis; + + #[cfg(not(feature = "nightly_derive"))] + let must_use = quote!(#[must_use = + "Must set return value when not using the \"nightly\" feature" + ]); + #[cfg(feature = "nightly_derive")] + let must_use = quote!(); + + let substruct_obj = if let Some(trait_) = &self.trait_ { + let ident = format_ident!("{}_expectations", trait_); + quote!(#ident.) + } else { + quote!() + }; + let docstr = format!( + "Create an [`Expectation`]({}/{}/struct.Expectation.html) for mocking the `{}` method", + modname, + self.inner_mod_ident(), + funcname + ); + quote!( + #must_use + #[doc = #docstr] + #(#attrs)* + #vis fn #expect_ident #ig(&mut self) + -> &mut #modname::#expectation_obj + #wc + { + self.#substruct_obj #name.expect#tbf() + } + ) + } + + /// Return the name of this function's expecation object + fn expectation_obj(&self, self_args: Option<&PathArguments>) -> impl ToTokens { + let inner_mod_ident = self.inner_mod_ident(); + if let Some(PathArguments::AngleBracketed(abga)) = self_args { + // staticize any lifetimes that might be present in the Expectation + // object but not in the self args. These come from the method's + // return type. + let mut abga2 = abga.clone(); + for _ in self.egenerics.lifetimes() { + let lt = Lifetime::new("'static", Span::call_site()); + let la = GenericArgument::Lifetime(lt); + abga2.args.insert(0, la); + } + assert!( + !self.is_method_generic(), + "specific impls with generic methods are TODO" + ); + quote!(#inner_mod_ident::Expectation #abga2) + } else { + // staticize any lifetimes. This is necessary for methods that + // return non-static types, because the Expectation itself must be + // 'static. + let segenerics = staticize(&self.egenerics); + let (_, tg, _) = segenerics.split_for_impl(); + quote!(#inner_mod_ident::Expectation #tg) + } + } + + /// Return the name of this function's expecations object + pub fn expectations_obj(&self) -> impl ToTokens { + let inner_mod_ident = self.inner_mod_ident(); + if self.is_method_generic() { + quote!(#inner_mod_ident::GenericExpectations) + } else { + quote!(#inner_mod_ident::Expectations) + } + } + + pub fn field_definition(&self, modname: Option<&Ident>) -> TokenStream { + let name = self.name(); + let attrs = AttrFormatter::new(&self.attrs).doc(false).format(); + let expectations_obj = &self.expectations_obj(); + if self.is_method_generic() { + quote!(#(#attrs)* #name: #modname::#expectations_obj) + } else { + // staticize any lifetimes. This is necessary for methods that + // return non-static types, because the Expectation itself must be + // 'static. + let segenerics = staticize(&self.egenerics); + let (_, tg, _) = segenerics.split_for_impl(); + quote!(#(#attrs)* #name: #modname::#expectations_obj #tg) + } + } + + /// Human-readable name of the mock function + fn funcname(&self) -> String { + if let Some(si) = &self.struct_ { + format!("{}::{}", si, self.name()) + } else { + format!("{}", self.name()) + } + } + + fn hrtb(&self) -> Option<BoundLifetimes> { + if self.alifetimes.is_empty() { + None + } else { + Some(BoundLifetimes { + lifetimes: self.alifetimes.clone(), + lt_token: <Token![<]>::default(), + gt_token: <Token![>]>::default(), + ..Default::default() + }) + } + } + + fn is_expectation_generic(&self) -> bool { + self.egenerics + .params + .iter() + .any(|p| matches!(p, GenericParam::Type(_))) + || self.egenerics.where_clause.is_some() + } + + /// Is the mock method generic (as opposed to a non-generic method of a + /// generic mock struct)? + pub fn is_method_generic(&self) -> bool { + self.call_generics + .params + .iter() + .any(|p| matches!(p, GenericParam::Type(_))) + || self.call_generics.where_clause.is_some() + } + + fn outer_mod_path(&self, modname: Option<&Ident>) -> Path { + let mut path = if let Some(m) = modname { + Path::from(PathSegment::from(m.clone())) + } else { + Path { + leading_colon: None, + segments: Punctuated::new(), + } + }; + path.segments + .push(PathSegment::from(self.inner_mod_ident())); + path + } + + fn inner_mod_ident(&self) -> Ident { + format_ident!("__{}", &self.name()) + } + + pub fn is_static(&self) -> bool { + self.is_static + } + + pub fn name(&self) -> &Ident { + &self.sig.ident + } + + /// Generate code for this function's private module + pub fn priv_module(&self) -> impl ToTokens { + let attrs = AttrFormatter::new(&self.attrs).doc(false).format(); + let common = &Common { f: self }; + let context = &Context { f: self }; + let expectation: Box<dyn ToTokens> = if self.return_ref { + Box::new(RefExpectation { f: self }) + } else if self.return_refmut { + Box::new(RefMutExpectation { f: self }) + } else { + Box::new(StaticExpectation { f: self }) + }; + let expectations: Box<dyn ToTokens> = if self.return_ref { + Box::new(RefExpectations { f: self }) + } else if self.return_refmut { + Box::new(RefMutExpectations { f: self }) + } else { + Box::new(StaticExpectations { f: self }) + }; + let generic_expectations = GenericExpectations { f: self }; + let guard: Box<dyn ToTokens> = if self.is_expectation_generic() { + Box::new(GenericExpectationGuard { f: self }) + } else { + Box::new(ConcreteExpectationGuard { f: self }) + }; + let matcher = &Matcher { f: self }; + let std_mutexguard = if self.is_static { + quote!( + use std::sync::MutexGuard; + ) + } else { + quote!() + }; + let inner_mod_ident = self.inner_mod_ident(); + let rfunc: Box<dyn ToTokens> = if self.return_ref { + Box::new(RefRfunc { f: self }) + } else if self.return_refmut { + Box::new(RefMutRfunc { f: self }) + } else { + Box::new(StaticRfunc { f: self }) + }; + quote!( + #(#attrs)* + #[allow(missing_docs)] + pub mod #inner_mod_ident { + use super::*; + use ::mockall::CaseTreeExt; + #std_mutexguard + use ::std::{ + boxed::Box, + mem, + ops::{DerefMut, Range}, + sync::Mutex, + vec::Vec, + }; + #rfunc + #matcher + #common + #expectation + #expectations + #generic_expectations + #guard + #context + } + ) + } +} + +/// Holds parts of the expectation that are common for all output types +struct Common<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for Common<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let predty = &self.f.predty; + let hrtb = self.f.hrtb(); + let funcname = self.f.funcname(); + let (ig, tg, wc) = self.f.cgenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let refpredty = &self.f.refpredty; + let with_generics_idents = (0..self.f.predty.len()) + .map(|i| format_ident!("MockallMatcher{}", i)) + .collect::<Vec<_>>(); + let with_generics = with_generics_idents + .iter() + .zip(self.f.predty.iter()) + .map(|(id, mt)| quote!(#id: #hrtb ::mockall::Predicate<#mt> + Send + 'static, )) + .collect::<TokenStream>(); + let with_args = self + .f + .argnames + .iter() + .zip(with_generics_idents.iter()) + .map(|(argname, id)| quote!(#argname: #id, )) + .collect::<TokenStream>(); + let boxed_withargs = argnames + .iter() + .map(|aa| quote!(Box::new(#aa), )) + .collect::<TokenStream>(); + quote!( + /// Holds the stuff that is independent of the output type + struct Common #ig #wc { + matcher: Mutex<Matcher #tg>, + seq_handle: Option<::mockall::SeqHandle>, + times: ::mockall::Times + } + + impl #ig std::default::Default for Common #tg #wc + { + fn default() -> Self { + Common { + matcher: Mutex::new(Matcher::default()), + seq_handle: None, + times: ::mockall::Times::default() + } + } + } + + impl #ig Common #tg #wc { + fn call(&self, desc: &str) { + self.times.call() + .unwrap_or_else(|m| { + let desc = std::format!( + "{}", self.matcher.lock().unwrap()); + panic!("{}: Expectation({}) {}", #funcname, desc, + m); + }); + self.verify_sequence(desc); + if ::mockall::ExpectedCalls::TooFew != self.times.is_satisfied() { + self.satisfy_sequence() + } + } + + fn in_sequence(&mut self, __mockall_seq: &mut ::mockall::Sequence) + -> &mut Self + { + assert!(self.times.is_exact(), + "Only Expectations with an exact call count have sequences"); + self.seq_handle = Some(__mockall_seq.next_handle()); + self + } + + fn is_done(&self) -> bool { + self.times.is_done() + } + + #[allow(clippy::ptr_arg)] + fn matches #lg (&self, #( #argnames: &#predty, )*) -> bool { + self.matcher.lock().unwrap().matches(#(#argnames, )*) + } + + /// Forbid this expectation from ever being called. + fn never(&mut self) { + self.times.never(); + } + + fn satisfy_sequence(&self) { + if let Some(__mockall_handle) = &self.seq_handle { + __mockall_handle.satisfy() + } + } + + /// Expect this expectation to be called any number of times + /// contained with the given range. + fn times<MockallR>(&mut self, __mockall_r: MockallR) + where MockallR: Into<::mockall::TimesRange> + { + self.times.times(__mockall_r) + } + + fn with<#with_generics>(&mut self, #with_args) + { + let mut __mockall_guard = self.matcher.lock().unwrap(); + *__mockall_guard.deref_mut() = + Matcher::Pred(Box::new((#boxed_withargs))); + } + + fn withf<MockallF>(&mut self, __mockall_f: MockallF) + where MockallF: #hrtb Fn(#( #refpredty, )*) + -> bool + Send + 'static + { + let mut __mockall_guard = self.matcher.lock().unwrap(); + *__mockall_guard.deref_mut() = + Matcher::Func(Box::new(__mockall_f)); + } + + fn withf_st<MockallF>(&mut self, __mockall_f: MockallF) + where MockallF: #hrtb Fn(#( #refpredty, )*) + -> bool + 'static + { + let mut __mockall_guard = self.matcher.lock().unwrap(); + *__mockall_guard.deref_mut() = + Matcher::FuncSt( + ::mockall::Fragile::new(Box::new(__mockall_f)) + ); + } + + fn verify_sequence(&self, desc: &str) { + if let Some(__mockall_handle) = &self.seq_handle { + __mockall_handle.verify(desc) + } + } + } + + impl #ig Drop for Common #tg #wc { + fn drop(&mut self) { + if !::std::thread::panicking() { + let desc = std::format!( + "{}", self.matcher.lock().unwrap()); + match self.times.is_satisfied() { + ::mockall::ExpectedCalls::TooFew => { + panic!("{}: Expectation({}) called {} time(s) which is fewer than expected {}", + #funcname, + desc, + self.times.count(), + self.times.minimum()); + }, + ::mockall::ExpectedCalls::TooMany => { + panic!("{}: Expectation({}) called {} time(s) which is more than expected {}", + #funcname, + desc, + self.times.count(), + self.times.maximum()); + }, + _ => () + } + } + } + } + ).to_tokens(tokens); + } +} + +/// Generates methods that are common for all Expectation types +struct CommonExpectationMethods<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for CommonExpectationMethods<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let hrtb = self.f.hrtb(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let predty = &self.f.predty; + let with_generics_idents = (0..self.f.predty.len()) + .map(|i| format_ident!("MockallMatcher{}", i)) + .collect::<Vec<_>>(); + let with_generics = with_generics_idents + .iter() + .zip(self.f.predty.iter()) + .map(|(id, mt)| quote!(#id: #hrtb ::mockall::Predicate<#mt> + Send + 'static, )) + .collect::<TokenStream>(); + let with_args = self + .f + .argnames + .iter() + .zip(with_generics_idents.iter()) + .map(|(argname, id)| quote!(#argname: #id, )) + .collect::<TokenStream>(); + let v = &self.f.privmod_vis; + quote!( + /// Add this expectation to a + /// [`Sequence`](../../../mockall/struct.Sequence.html). + #v fn in_sequence(&mut self, __mockall_seq: &mut ::mockall::Sequence) + -> &mut Self + { + self.common.in_sequence(__mockall_seq); + self + } + + fn is_done(&self) -> bool { + self.common.is_done() + } + + /// Validate this expectation's matcher. + #[allow(clippy::ptr_arg)] + fn matches #lg (&self, #(#argnames: &#predty, )*) -> bool { + self.common.matches(#(#argnames, )*) + } + + /// Forbid this expectation from ever being called. + #v fn never(&mut self) -> &mut Self { + self.common.never(); + self + } + + /// Create a new, default, [`Expectation`](struct.Expectation.html) + #v fn new() -> Self { + Self::default() + } + + /// Expect this expectation to be called exactly once. Shortcut for + /// [`times(1)`](#method.times). + #v fn once(&mut self) -> &mut Self { + self.times(1) + } + + /// Restrict the number of times that that this method may be called. + /// + /// The argument may be: + /// * A fixed number: `.times(4)` + /// * Various types of range: + /// - `.times(5..10)` + /// - `.times(..10)` + /// - `.times(5..)` + /// - `.times(5..=10)` + /// - `.times(..=10)` + /// * The wildcard: `.times(..)` + #v fn times<MockallR>(&mut self, __mockall_r: MockallR) -> &mut Self + where MockallR: Into<::mockall::TimesRange> + { + self.common.times(__mockall_r); + self + } + + /// Set matching crieteria for this Expectation. + /// + /// The matching predicate can be anything implemening the + /// [`Predicate`](../../../mockall/trait.Predicate.html) trait. Only + /// one matcher can be set per `Expectation` at a time. + #v fn with<#with_generics>(&mut self, #with_args) -> &mut Self + { + self.common.with(#(#argnames, )*); + self + } + + /// Set a matching function for this Expectation. + /// + /// This is equivalent to calling [`with`](#method.with) with a + /// function argument, like `with(predicate::function(f))`. + #v fn withf<MockallF>(&mut self, __mockall_f: MockallF) -> &mut Self + where MockallF: #hrtb Fn(#(&#predty, )*) + -> bool + Send + 'static + { + self.common.withf(__mockall_f); + self + } + + /// Single-threaded version of [`withf`](#method.withf). + /// Can be used when the argument type isn't `Send`. + #v fn withf_st<MockallF>(&mut self, __mockall_f: MockallF) -> &mut Self + where MockallF: #hrtb Fn(#(&#predty, )*) + -> bool + 'static + { + self.common.withf_st(__mockall_f); + self + } + ) + .to_tokens(tokens); + } +} + +/// Holds the moethods of the Expectations object that are common for all +/// Expectation types +struct CommonExpectationsMethods<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for CommonExpectationsMethods<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let v = &self.f.privmod_vis; + quote!( + /// A collection of [`Expectation`](struct.Expectations.html) + /// objects. Users will rarely if ever use this struct directly. + #[doc(hidden)] + #v struct Expectations #ig ( Vec<Expectation #tg>) #wc; + + impl #ig Expectations #tg #wc { + /// Verify that all current expectations are satisfied and clear + /// them. + #v fn checkpoint(&mut self) -> std::vec::Drain<Expectation #tg> + { + self.0.drain(..) + } + + /// Create a new expectation for this method. + #v fn expect(&mut self) -> &mut Expectation #tg + { + self.0.push(Expectation::default()); + let __mockall_l = self.0.len(); + &mut self.0[__mockall_l - 1] + } + + #v fn new() -> Self { + Self::default() + } + } + impl #ig Default for Expectations #tg #wc + { + fn default() -> Self { + Expectations(Vec::new()) + } + } + ) + .to_tokens(tokens); + } +} + +/// The ExpectationGuard structure for static methods with no generic types +struct ExpectationGuardCommonMethods<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for ExpectationGuardCommonMethods<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if !self.f.is_static { + return; + } + + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let (_, tg, _) = self.f.egenerics.split_for_impl(); + let keyid = gen_keyid(&self.f.egenerics); + let expectations = if self.f.is_expectation_generic() { + quote!(self.guard + .store + .get_mut(&::mockall::Key::new::#keyid()) + .unwrap() + .downcast_mut::<Expectations #tg>() + .unwrap()) + } else { + quote!(self.guard) + }; + let hrtb = self.f.hrtb(); + let output = &self.f.output; + let predty = &self.f.predty; + let with_generics_idents = (0..self.f.predty.len()) + .map(|i| format_ident!("MockallMatcher{}", i)) + .collect::<Vec<_>>(); + let with_generics = with_generics_idents + .iter() + .zip(self.f.predty.iter()) + .map(|(id, mt)| quote!(#id: #hrtb ::mockall::Predicate<#mt> + Send + 'static, )) + .collect::<TokenStream>(); + let with_args = self + .f + .argnames + .iter() + .zip(with_generics_idents.iter()) + .map(|(argname, id)| quote!(#argname: #id, )) + .collect::<TokenStream>(); + let v = &self.f.privmod_vis; + quote!( + /// Just like + /// [`Expectation::in_sequence`](struct.Expectation.html#method.in_sequence) + #v fn in_sequence(&mut self, + __mockall_seq: &mut ::mockall::Sequence) + -> &mut Expectation #tg + { + #expectations.0[self.i].in_sequence(__mockall_seq) + } + + /// Just like + /// [`Expectation::never`](struct.Expectation.html#method.never) + #v fn never(&mut self) -> &mut Expectation #tg { + #expectations.0[self.i].never() + } + + /// Just like + /// [`Expectation::once`](struct.Expectation.html#method.once) + #v fn once(&mut self) -> &mut Expectation #tg { + #expectations.0[self.i].once() + } + + /// Just like + /// [`Expectation::return_const`](struct.Expectation.html#method.return_const) + #v fn return_const<MockallOutput> + (&mut self, __mockall_c: MockallOutput) + -> &mut Expectation #tg + where MockallOutput: Clone + Into<#output> + Send + 'static + { + #expectations.0[self.i].return_const(__mockall_c) + } + + /// Just like + /// [`Expectation::return_const_st`](struct.Expectation.html#method.return_const_st) + #v fn return_const_st<MockallOutput> + (&mut self, __mockall_c: MockallOutput) + -> &mut Expectation #tg + where MockallOutput: Clone + Into<#output> + 'static + { + #expectations.0[self.i].return_const_st(__mockall_c) + } + + /// Just like + /// [`Expectation::returning`](struct.Expectation.html#method.returning) + #v fn returning<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Expectation #tg + where MockallF: #hrtb FnMut(#(#argty, )*) + -> #output + Send + 'static + { + #expectations.0[self.i].returning(__mockall_f) + } + + /// Just like + /// [`Expectation::return_once`](struct.Expectation.html#method.return_once) + #v fn return_once<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Expectation #tg + where MockallF: #hrtb FnOnce(#(#argty, )*) + -> #output + Send + 'static + { + #expectations.0[self.i].return_once(__mockall_f) + } + + /// Just like + /// [`Expectation::return_once_st`](struct.Expectation.html#method.return_once_st) + #v fn return_once_st<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Expectation #tg + where MockallF: #hrtb FnOnce(#(#argty, )*) + -> #output + 'static + { + #expectations.0[self.i].return_once_st(__mockall_f) + } + + + /// Just like + /// [`Expectation::returning_st`](struct.Expectation.html#method.returning_st) + #v fn returning_st<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Expectation #tg + where MockallF: #hrtb FnMut(#(#argty, )*) + -> #output + 'static + { + #expectations.0[self.i].returning_st(__mockall_f) + } + + /// Just like + /// [`Expectation::times`](struct.Expectation.html#method.times) + #v fn times<MockallR>(&mut self, __mockall_r: MockallR) + -> &mut Expectation #tg + where MockallR: Into<::mockall::TimesRange> + { + #expectations.0[self.i].times(__mockall_r) + } + + /// Just like + /// [`Expectation::with`](struct.Expectation.html#method.with) + #v fn with<#with_generics> (&mut self, #with_args) + -> &mut Expectation #tg + { + #expectations.0[self.i].with(#(#argnames, )*) + } + + /// Just like + /// [`Expectation::withf`](struct.Expectation.html#method.withf) + #v fn withf<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Expectation #tg + where MockallF: #hrtb Fn(#(&#predty, )*) + -> bool + Send + 'static + { + #expectations.0[self.i].withf(__mockall_f) + } + + /// Just like + /// [`Expectation::withf_st`](struct.Expectation.html#method.withf_st) + #v fn withf_st<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Expectation #tg + where MockallF: #hrtb Fn(#(&#predty, )*) + -> bool + 'static + { + #expectations.0[self.i].withf_st(__mockall_f) + } + ) + .to_tokens(tokens); + } +} + +/// The ExpectationGuard structure for static methods with no generic types +struct ConcreteExpectationGuard<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for ConcreteExpectationGuard<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if !self.f.is_static { + return; + } + + let common_methods = ExpectationGuardCommonMethods { f: self.f }; + let (_, tg, _) = self.f.egenerics.split_for_impl(); + let ltdef = LifetimeDef::new(Lifetime::new("'__mockall_lt", Span::call_site())); + let mut e_generics = self.f.egenerics.clone(); + e_generics.lt_token.get_or_insert(<Token![<]>::default()); + e_generics.params.push(GenericParam::Lifetime(ltdef)); + e_generics.gt_token.get_or_insert(<Token![>]>::default()); + let (e_ig, e_tg, e_wc) = e_generics.split_for_impl(); + let (ei_ig, _, _) = e_generics.split_for_impl(); + let v = &self.f.privmod_vis; + quote!( + ::mockall::lazy_static! { + #[doc(hidden)] + #v static ref EXPECTATIONS: + ::std::sync::Mutex<Expectations #tg> = + ::std::sync::Mutex::new(Expectations::new()); + } + /// Like an [`&Expectation`](struct.Expectation.html) but + /// protected by a Mutex guard. Useful for mocking static + /// methods. Forwards accesses to an `Expectation` object. + // We must return the MutexGuard to the caller so he can + // configure the expectation. But we can't bundle both the + // guard and the &Expectation into the same structure; the + // borrow checker won't let us. Instead we'll record the + // expectation's position within the Expectations vector so we + // can proxy its methods. + // + // ExpectationGuard is only defined for expectations that return + // 'static return types. + #v struct ExpectationGuard #e_ig #e_wc { + guard: MutexGuard<'__mockall_lt, Expectations #tg>, + i: usize + } + + #[allow(clippy::unused_unit)] + impl #ei_ig ExpectationGuard #e_tg #e_wc + { + // Should only be called from the mockall_derive generated + // code + #[doc(hidden)] + #v fn new(mut __mockall_guard: MutexGuard<'__mockall_lt, Expectations #tg>) + -> Self + { + __mockall_guard.expect(); // Drop the &Expectation + let __mockall_i = __mockall_guard.0.len() - 1; + ExpectationGuard{guard: __mockall_guard, i: __mockall_i} + } + + #common_methods + } + ) + .to_tokens(tokens); + } +} + +/// The ExpectationGuard structure for static methods with generic types +struct GenericExpectationGuard<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for GenericExpectationGuard<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if !self.f.is_static { + return; + } + + let common_methods = ExpectationGuardCommonMethods { f: self.f }; + let (_, tg, _) = self.f.egenerics.split_for_impl(); + let keyid = gen_keyid(&self.f.egenerics); + let ltdef = LifetimeDef::new(Lifetime::new("'__mockall_lt", Span::call_site())); + let mut egenerics = self.f.egenerics.clone(); + egenerics.lt_token.get_or_insert(<Token![<]>::default()); + egenerics.params.push(GenericParam::Lifetime(ltdef)); + egenerics.gt_token.get_or_insert(<Token![>]>::default()); + let (e_ig, e_tg, e_wc) = egenerics.split_for_impl(); + let fn_params = &self.f.fn_params; + let tbf = tg.as_turbofish(); + let v = &self.f.privmod_vis; + quote!( + ::mockall::lazy_static! { + #v static ref EXPECTATIONS: + ::std::sync::Mutex<GenericExpectations> = + ::std::sync::Mutex::new(GenericExpectations::new()); + } + /// Like an [`&Expectation`](struct.Expectation.html) but + /// protected by a Mutex guard. Useful for mocking static + /// methods. Forwards accesses to an `Expectation` object. + #v struct ExpectationGuard #e_ig #e_wc{ + guard: MutexGuard<'__mockall_lt, GenericExpectations>, + i: usize, + _phantom: ::std::marker::PhantomData<(#(#fn_params,)*)>, + } + + #[allow(clippy::unused_unit)] + impl #e_ig ExpectationGuard #e_tg #e_wc + { + // Should only be called from the mockall_derive generated + // code + #[doc(hidden)] + #v fn new(mut __mockall_guard: MutexGuard<'__mockall_lt, GenericExpectations>) + -> Self + { + let __mockall_ee: &mut Expectations #tg = + __mockall_guard.store.entry( + ::mockall::Key::new::#keyid() + ).or_insert_with(|| + Box::new(Expectations #tbf ::new())) + .downcast_mut() + .unwrap(); + __mockall_ee.expect(); // Drop the &Expectation + let __mockall_i = __mockall_ee.0.len() - 1; + ExpectationGuard{guard: __mockall_guard, i: __mockall_i, + _phantom: ::std::marker::PhantomData} + } + + #common_methods + } + ) + .to_tokens(tokens); + } +} + +/// Generates Context, which manages the context for expectations of static +/// methods. +struct Context<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for Context<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if !self.f.is_static { + return; + } + + let ltdef = LifetimeDef::new(Lifetime::new("'__mockall_lt", Span::call_site())); + let mut egenerics = self.f.egenerics.clone(); + egenerics.lt_token.get_or_insert(<Token![<]>::default()); + egenerics.params.push(GenericParam::Lifetime(ltdef)); + egenerics.gt_token.get_or_insert(<Token![>]>::default()); + let (_, e_tg, _) = egenerics.split_for_impl(); + let (ty_ig, ty_tg, ty_wc) = self.f.type_generics.split_for_impl(); + let mut meth_generics = self.f.call_generics.clone(); + let ltdef = LifetimeDef::new(Lifetime::new("'__mockall_lt", Span::call_site())); + meth_generics.params.push(GenericParam::Lifetime(ltdef)); + let (meth_ig, _meth_tg, meth_wc) = meth_generics.split_for_impl(); + let ctx_fn_params = self + .f + .struct_generics + .type_params() + .map(|tp| tp.ident.clone()) + .collect::<Punctuated<Ident, Token![,]>>(); + let v = &self.f.privmod_vis; + + #[cfg(not(feature = "nightly_derive"))] + let must_use = quote!(#[must_use = + "Must set return value when not using the \"nightly\" feature" + ]); + #[cfg(feature = "nightly_derive")] + let must_use = quote!(); + + quote!( + /// Manages the context for expectations of static methods. + /// + /// Expectations on this method will be validated and cleared when + /// the `Context` object drops. The `Context` object does *not* + /// provide any form of synchronization, so multiple tests that set + /// expectations on the same static method must provide their own. + #[must_use = "Context only serves to create expectations" ] + #v struct Context #ty_ig #ty_wc { + // Prevent "unused type parameter" errors + // Surprisingly, PhantomData<Fn(generics)> is Send even if + // generics are not, unlike PhantomData<generics> + _phantom: ::std::marker::PhantomData< + Box<dyn Fn(#ctx_fn_params) + Send> + > + } + impl #ty_ig Context #ty_tg #ty_wc { + /// Verify that all current expectations for this method are + /// satisfied and clear them. + #v fn checkpoint(&self) { + Self::do_checkpoint() + } + #[doc(hidden)] + #v fn do_checkpoint() { + let __mockall_timeses = EXPECTATIONS + .lock() + .unwrap() + .checkpoint() + .collect::<Vec<_>>(); + } + + /// Create a new expectation for this method. + #must_use + #v fn expect #meth_ig ( &self,) -> ExpectationGuard #e_tg + #meth_wc + { + ExpectationGuard::new(EXPECTATIONS.lock().unwrap()) + } + } + impl #ty_ig Default for Context #ty_tg #ty_wc { + fn default() -> Self { + Context {_phantom: std::marker::PhantomData} + } + } + impl #ty_ig Drop for Context #ty_tg #ty_wc { + fn drop(&mut self) { + Self::do_checkpoint() + } + } + ) + .to_tokens(tokens); + } +} + +struct Matcher<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for Matcher<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let (ig, tg, wc) = self.f.cgenerics.split_for_impl(); + let argnames = &self.f.argnames; + let braces = argnames.iter().fold(String::new(), |mut acc, _argname| { + if acc.is_empty() { + acc.push_str("{}"); + } else { + acc.push_str(", {}"); + } + acc + }); + let fn_params = &self.f.fn_params; + let hrtb = self.f.hrtb(); + let indices = (0..argnames.len()) + .map(|i| syn::Index::from(i)) + .collect::<Vec<_>>(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let pred_matches = argnames + .iter() + .enumerate() + .map(|(i, argname)| { + let idx = syn::Index::from(i); + quote!(__mockall_pred.#idx.eval(#argname),) + }) + .collect::<TokenStream>(); + let preds = self + .f + .predty + .iter() + .map(|t| quote!(Box<dyn #hrtb ::mockall::Predicate<#t> + Send>,)) + .collect::<TokenStream>(); + let predty = &self.f.predty; + let refpredty = &self.f.refpredty; + quote!( + enum Matcher #ig #wc { + Always, + Func(Box<dyn #hrtb Fn(#( #refpredty, )*) -> bool + Send>), + // Version of Matcher::Func for closures that aren't Send + FuncSt(::mockall::Fragile<Box<dyn #hrtb Fn(#( #refpredty, )*) -> bool>>), + Pred(Box<(#preds)>), + // Prevent "unused type parameter" errors + // Surprisingly, PhantomData<Fn(generics)> is Send even if + // generics are not, unlike PhantomData<generics> + _Phantom(Box<dyn Fn(#(#fn_params,)*) + Send>) + } + impl #ig Matcher #tg #wc { + #[allow(clippy::ptr_arg)] + fn matches #lg (&self, #( #argnames: &#predty, )*) -> bool { + match self { + Matcher::Always => true, + Matcher::Func(__mockall_f) => + __mockall_f(#(#argnames, )*), + Matcher::FuncSt(__mockall_f) => + (__mockall_f.get())(#(#argnames, )*), + Matcher::Pred(__mockall_pred) => + [#pred_matches] + .iter() + .all(|__mockall_x| *__mockall_x), + _ => unreachable!() + } + } + } + + impl #ig Default for Matcher #tg #wc { + #[allow(unused_variables)] + fn default() -> Self { + Matcher::Always + } + } + + impl #ig ::std::fmt::Display for Matcher #tg #wc { + fn fmt(&self, __mockall_fmt: &mut ::std::fmt::Formatter<'_>) + -> ::std::fmt::Result + { + match self { + Matcher::Always => write!(__mockall_fmt, "<anything>"), + Matcher::Func(_) => write!(__mockall_fmt, "<function>"), + Matcher::FuncSt(_) => write!(__mockall_fmt, "<single threaded function>"), + Matcher::Pred(__mockall_p) => { + write!(__mockall_fmt, #braces, + #(__mockall_p.#indices,)*) + } + _ => unreachable!(), + } + } + } + ) + .to_tokens(tokens); + } +} + +struct RefRfunc<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for RefRfunc<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let fn_params = &self.f.fn_params; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let owned_output = &self.f.owned_output; + + #[cfg(not(feature = "nightly_derive"))] + let default_err_msg = "Returning default values requires the \"nightly\" feature"; + #[cfg(feature = "nightly_derive")] + let default_err_msg = "Can only return default values for types that impl std::Default"; + + quote!( + enum Rfunc #ig #wc { + Default(Option<#owned_output>), + Const(#owned_output), + // Prevent "unused type parameter" errors Surprisingly, + // PhantomData<Fn(generics)> is Send even if generics are not, + // unlike PhantomData<generics> + _Phantom(Mutex<Box<dyn Fn(#(#fn_params,)*) + Send>>) + } + + impl #ig Rfunc #tg #wc { + fn call #lg (&self) + -> std::result::Result<&#owned_output, &'static str> + { + match self { + Rfunc::Default(Some(ref __mockall_o)) => { + ::std::result::Result::Ok(__mockall_o) + }, + Rfunc::Default(None) => { + Err(#default_err_msg) + }, + Rfunc::Const(ref __mockall_o) => { + ::std::result::Result::Ok(__mockall_o) + }, + Rfunc::_Phantom(_) => unreachable!() + } + } + } + + impl #ig std::default::Default for Rfunc #tg #wc + { + fn default() -> Self { + use ::mockall::ReturnDefault; + Rfunc::Default(::mockall::DefaultReturner::<#owned_output> + ::maybe_return_default()) + } + } + ) + .to_tokens(tokens); + } +} + +struct RefMutRfunc<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for RefMutRfunc<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let fn_params = &self.f.fn_params; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let owned_output = &self.f.owned_output; + let output = &self.f.output; + + #[cfg(not(feature = "nightly_derive"))] + let default_err_msg = "Returning default values requires the \"nightly\" feature"; + #[cfg(feature = "nightly_derive")] + let default_err_msg = "Can only return default values for types that impl std::Default"; + + quote!( + #[allow(clippy::unused_unit)] + enum Rfunc #ig #wc { + Default(Option<#owned_output>), + Mut((Box<dyn FnMut(#(#argty, )*) -> #owned_output + Send + Sync>), + Option<#owned_output>), + // Version of Rfunc::Mut for closures that aren't Send + MutSt((::mockall::Fragile< + Box<dyn FnMut(#(#argty, )*) -> #owned_output >> + ), Option<#owned_output> + ), + Var(#owned_output), + // Prevent "unused type parameter" errors Surprisingly, + // PhantomData<Fn(generics)> is Send even if generics are not, + // unlike PhantomData<generics> + _Phantom(Mutex<Box<dyn Fn(#(#fn_params,)*) + Send>>) + } + + impl #ig Rfunc #tg #wc { + fn call_mut #lg (&mut self, #(#argnames: #argty, )*) + -> std::result::Result<#output, &'static str> + { + match self { + Rfunc::Default(Some(ref mut __mockall_o)) => { + ::std::result::Result::Ok(__mockall_o) + }, + Rfunc::Default(None) => { + Err(#default_err_msg) + }, + Rfunc::Mut(ref mut __mockall_f, ref mut __mockall_o) => + { + *__mockall_o = Some(__mockall_f(#(#argnames, )*)); + if let Some(ref mut __mockall_o2) = __mockall_o { + ::std::result::Result::Ok(__mockall_o2) + } else { + unreachable!() + } + }, + Rfunc::MutSt(ref mut __mockall_f, ref mut __mockall_o)=> + { + *__mockall_o = Some((__mockall_f.get_mut())( + #(#argnames, )*) + ); + if let Some(ref mut __mockall_o2) = __mockall_o { + ::std::result::Result::Ok(__mockall_o2) + } else { + unreachable!() + } + }, + Rfunc::Var(ref mut __mockall_o) => { + ::std::result::Result::Ok(__mockall_o) + }, + Rfunc::_Phantom(_) => unreachable!() + } + } + } + + impl #ig std::default::Default for Rfunc #tg #wc + { + fn default() -> Self { + use ::mockall::ReturnDefault; + Rfunc::Default(::mockall::DefaultReturner::<#owned_output> + ::maybe_return_default()) + } + } + ) + .to_tokens(tokens); + } +} + +struct StaticRfunc<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for StaticRfunc<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let fn_params = &self.f.fn_params; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let hrtb = self.f.hrtb(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let output = &self.f.output; + quote!( + #[allow(clippy::unused_unit)] + enum Rfunc #ig #wc { + Default, + // Indicates that a `return_once` expectation has already + // returned + Expired, + Mut(Box<dyn #hrtb FnMut(#(#argty, )*) -> #output + Send>), + // Version of Rfunc::Mut for closures that aren't Send + MutSt(::mockall::Fragile< + Box<dyn #hrtb FnMut(#(#argty, )*) -> #output >> + ), + Once(Box<dyn #hrtb FnOnce(#(#argty, )*) -> #output + Send>), + // Version of Rfunc::Once for closure that aren't Send + OnceSt(::mockall::Fragile< + Box<dyn #hrtb FnOnce(#(#argty, )*) -> #output>> + ), + // Prevent "unused type parameter" errors Surprisingly, + // PhantomData<Fn(generics)> is Send even if generics are not, + // unlike PhantomData<generics> + _Phantom(Box<dyn Fn(#(#fn_params,)*) + Send>) + } + + impl #ig Rfunc #tg #wc { + fn call_mut #lg (&mut self, #( #argnames: #argty, )* ) + -> std::result::Result<#output, &'static str> + { + match self { + Rfunc::Default => { + use ::mockall::ReturnDefault; + ::mockall::DefaultReturner::<#output> + ::return_default() + }, + Rfunc::Expired => { + Err("called twice, but it returns by move") + }, + Rfunc::Mut(__mockall_f) => { + ::std::result::Result::Ok(__mockall_f( #(#argnames, )* )) + }, + Rfunc::MutSt(__mockall_f) => { + ::std::result::Result::Ok((__mockall_f.get_mut())(#(#argnames,)*)) + }, + Rfunc::Once(_) => { + if let Rfunc::Once(mut __mockall_f) = + mem::replace(self, Rfunc::Expired) { + ::std::result::Result::Ok(__mockall_f( #(#argnames, )* )) + } else { + unreachable!() + } + }, + Rfunc::OnceSt(_) => { + if let Rfunc::OnceSt(mut __mockall_f) = + mem::replace(self, Rfunc::Expired) { + ::std::result::Result::Ok((__mockall_f.into_inner())(#(#argnames,)*)) + } else { + unreachable!() + } + }, + Rfunc::_Phantom(_) => unreachable!() + } + } + } + + impl #ig std::default::Default for Rfunc #tg #wc + { + fn default() -> Self { + Rfunc::Default + } + } + ).to_tokens(tokens); + } +} + +/// An expectation type for functions that take a &self and return a reference +struct RefExpectation<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for RefExpectation<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let common_methods = CommonExpectationMethods { f: self.f }; + let desc = self.f.desc(); + let funcname = self.f.funcname(); + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + + let (_, common_tg, _) = self.f.cgenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let output = &self.f.output; + let owned_output = &self.f.owned_output; + let v = &self.f.privmod_vis; + quote!( + /// Expectation type for methods taking a `&self` argument and + /// returning immutable references. This is the type returned by + /// the `expect_*` methods. + #v struct Expectation #ig #wc { + common: Common #common_tg, + rfunc: Rfunc #tg, + } + + #[allow(clippy::unused_unit)] + impl #ig Expectation #tg #wc { + /// Call this [`Expectation`] as if it were the real method. + #v fn call #lg (&self, #(#argnames: #argty, )*) -> #output + { + self.common.call(&#desc); + self.rfunc.call().unwrap_or_else(|m| { + let desc = std::format!( + "{}", self.common.matcher.lock().unwrap()); + panic!("{}: Expectation({}) {}", #funcname, desc, + m); + }) + } + + /// Return a reference to a constant value from the `Expectation` + #v fn return_const(&mut self, __mockall_o: #owned_output) + -> &mut Self + { + self.rfunc = Rfunc::Const(__mockall_o); + self + } + + #common_methods + } + impl #ig Default for Expectation #tg #wc + { + fn default() -> Self { + Expectation { + common: Common::default(), + rfunc: Rfunc::default() + } + } + } + ) + .to_tokens(tokens); + } +} + +/// For methods that take &mut self and return a reference +struct RefMutExpectation<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for RefMutExpectation<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let common_methods = CommonExpectationMethods { f: self.f }; + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let desc = self.f.desc(); + let funcname = self.f.funcname(); + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let (_, common_tg, _) = self.f.cgenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let owned_output = &self.f.owned_output; + let v = &self.f.privmod_vis; + quote!( + /// Expectation type for methods taking a `&mut self` argument and + /// returning references. This is the type returned by the + /// `expect_*` methods. + #v struct Expectation #ig #wc { + common: Common #common_tg, + rfunc: Rfunc #tg + } + + #[allow(clippy::unused_unit)] + impl #ig Expectation #tg #wc { + /// Simulating calling the real method for this expectation + #v fn call_mut #lg (&mut self, #(#argnames: #argty, )*) + -> &mut #owned_output + { + self.common.call(&#desc); + let desc = std::format!( + "{}", self.common.matcher.lock().unwrap()); + self.rfunc.call_mut(#(#argnames, )*).unwrap_or_else(|m| { + panic!("{}: Expectation({}) {}", #funcname, desc, + m); + }) + } + + /// Convenience method that can be used to supply a return value + /// for a `Expectation`. The value will be returned by mutable + /// reference. + #v fn return_var(&mut self, __mockall_o: #owned_output) -> &mut Self + { + self.rfunc = Rfunc::Var(__mockall_o); + self + } + + /// Supply a closure that the `Expectation` will use to create its + /// return value. The return value will be returned by mutable + /// reference. + #v fn returning<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Self + where MockallF: FnMut(#(#argty, )*) -> #owned_output + Send + Sync + 'static + { + self.rfunc = Rfunc::Mut(Box::new(__mockall_f), None); + self + } + + /// Single-threaded version of [`returning`](#method.returning). + /// Can be used when the argument or return type isn't `Send`. + #v fn returning_st<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Self + where MockallF: FnMut(#(#argty, )*) -> #owned_output + 'static + { + self.rfunc = Rfunc::MutSt( + ::mockall::Fragile::new(Box::new(__mockall_f)), None); + self + } + + #common_methods + } + impl #ig Default for Expectation #tg #wc + { + fn default() -> Self { + Expectation { + common: Common::default(), + rfunc: Rfunc::default() + } + } + } + ) + .to_tokens(tokens); + } +} + +/// An expectation type for functions return a `'static` value +struct StaticExpectation<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for StaticExpectation<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let common_methods = CommonExpectationMethods { f: self.f }; + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let desc = self.f.desc(); + let hrtb = self.f.hrtb(); + let funcname = self.f.funcname(); + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let (_, common_tg, _) = self.f.cgenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let output = &self.f.output; + let v = &self.f.privmod_vis; + + quote!( + /// Expectation type for methods that return a `'static` type. + /// This is the type returned by the `expect_*` methods. + #v struct Expectation #ig #wc { + common: Common #common_tg, + rfunc: Mutex<Rfunc #tg>, + } + + #[allow(clippy::unused_unit)] + impl #ig Expectation #tg #wc { + /// Call this [`Expectation`] as if it were the real method. + #[doc(hidden)] + #v fn call #lg (&self, #(#argnames: #argty, )* ) -> #output + { + self.common.call(&#desc); + self.rfunc.lock().unwrap().call_mut(#(#argnames, )*) + .unwrap_or_else(|message| { + let desc = std::format!( + "{}", self.common.matcher.lock().unwrap()); + panic!("{}: Expectation({}) {}", #funcname, desc, + message); + }) + } + + /// Return a constant value from the `Expectation` + /// + /// The output type must be `Clone`. The compiler can't always + /// infer the proper type to use with this method; you will + /// usually need to specify it explicitly. i.e. + /// `return_const(42i32)` instead of `return_const(42)`. + // We must use Into<#output> instead of #output because where + // clauses don't accept equality constraints. + // https://github.com/rust-lang/rust/issues/20041 + #[allow(unused_variables)] + #v fn return_const<MockallOutput>(&mut self, + __mockall_c: MockallOutput) + -> &mut Self + where MockallOutput: Clone + Into<#output> + Send + 'static + { + self.returning(move |#(#argnames, )*| __mockall_c.clone().into()) + } + + /// Single-threaded version of + /// [`return_const`](#method.return_const). This is useful for + /// return types that are not `Send`. + /// + /// The output type must be `Clone`. The compiler can't always + /// infer the proper type to use with this method; you will + /// usually need to specify it explicitly. i.e. + /// `return_const(42i32)` instead of `return_const(42)`. + /// + /// It is a runtime error to call the mock method from a + /// different thread than the one that originally called this + /// method. + // We must use Into<#output> instead of #output because where + // clauses don't accept equality constraints. + // https://github.com/rust-lang/rust/issues/20041 + #[allow(unused_variables)] + #v fn return_const_st<MockallOutput>(&mut self, + __mockall_c: MockallOutput) + -> &mut Self + where MockallOutput: Clone + Into<#output> + 'static + { + self.returning_st(move |#(#argnames, )*| __mockall_c.clone().into()) + } + + /// Supply an `FnOnce` closure that will provide the return + /// value for this Expectation. This is useful for return types + /// that aren't `Clone`. It will be an error to call this + /// method multiple times. + #v fn return_once<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Self + where MockallF: #hrtb FnOnce(#(#argty, )*) + -> #output + Send + 'static + { + { + let mut __mockall_guard = self.rfunc.lock().unwrap(); + *__mockall_guard.deref_mut() = + Rfunc::Once(Box::new(__mockall_f)); + } + self + } + + /// Single-threaded version of + /// [`return_once`](#method.return_once). This is useful for + /// return types that are neither `Send` nor `Clone`. + /// + /// It is a runtime error to call the mock method from a + /// different thread than the one that originally called this + /// method. It is also a runtime error to call the method more + /// than once. + #v fn return_once_st<MockallF>(&mut self, __mockall_f: + MockallF) -> &mut Self + where MockallF: #hrtb FnOnce(#(#argty, )*) + -> #output + 'static + { + { + let mut __mockall_guard = self.rfunc.lock().unwrap(); + *__mockall_guard.deref_mut() = Rfunc::OnceSt( + ::mockall::Fragile::new(Box::new(__mockall_f))); + } + self + } + + /// Supply a closure that will provide the return value for this + /// `Expectation`. The method's arguments are passed to the + /// closure by value. + #v fn returning<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Self + where MockallF: #hrtb FnMut(#(#argty, )*) + -> #output + Send + 'static + { + { + let mut __mockall_guard = self.rfunc.lock().unwrap(); + *__mockall_guard.deref_mut() = + Rfunc::Mut(Box::new(__mockall_f)); + } + self + } + + /// Single-threaded version of [`returning`](#method.returning). + /// Can be used when the argument or return type isn't `Send`. + /// + /// It is a runtime error to call the mock method from a + /// different thread than the one that originally called this + /// method. + #v fn returning_st<MockallF>(&mut self, __mockall_f: MockallF) + -> &mut Self + where MockallF: #hrtb FnMut(#(#argty, )*) + -> #output + 'static + { + { + let mut __mockall_guard = self.rfunc.lock().unwrap(); + *__mockall_guard.deref_mut() = Rfunc::MutSt( + ::mockall::Fragile::new(Box::new(__mockall_f))); + } + self + } + + #common_methods + } + impl #ig Default for Expectation #tg #wc + { + fn default() -> Self { + Expectation { + common: Common::default(), + rfunc: Mutex::new(Rfunc::default()) + } + } + } + ) + .to_tokens(tokens); + } +} + +/// An collection of RefExpectation's +struct RefExpectations<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for RefExpectations<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let common_methods = CommonExpectationsMethods { f: self.f }; + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let output = &self.f.output; + let predexprs = &self.f.predexprs; + let v = &self.f.privmod_vis; + quote!( + #common_methods + impl #ig Expectations #tg #wc { + /// Simulate calling the real method. Every current expectation + /// will be checked in FIFO order and the first one with + /// matching arguments will be used. + #v fn call #lg (&self, #(#argnames: #argty, )* ) + -> Option<#output> + { + self.0.iter() + .find(|__mockall_e| + __mockall_e.matches(#(#predexprs, )*) && + (!__mockall_e.is_done() || self.0.len() == 1)) + .map(move |__mockall_e| + __mockall_e.call(#(#argnames),*) + ) + } + + } + ) + .to_tokens(tokens); + } +} + +/// An collection of RefMutExpectation's +struct RefMutExpectations<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for RefMutExpectations<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let common_methods = CommonExpectationsMethods { f: self.f }; + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let output = &self.f.output; + let predexprs = &self.f.predexprs; + let v = &self.f.privmod_vis; + quote!( + #common_methods + impl #ig Expectations #tg #wc { + /// Simulate calling the real method. Every current expectation + /// will be checked in FIFO order and the first one with + /// matching arguments will be used. + #v fn call_mut #lg (&mut self, #(#argnames: #argty, )* ) + -> Option<#output> + { + let __mockall_n = self.0.len(); + self.0.iter_mut() + .find(|__mockall_e| + __mockall_e.matches(#(#predexprs, )*) && + (!__mockall_e.is_done() || __mockall_n == 1)) + .map(move |__mockall_e| + __mockall_e.call_mut(#(#argnames, )*) + ) + } + + } + ) + .to_tokens(tokens); + } +} + +/// An collection of Expectation's for methods returning static values +struct StaticExpectations<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for StaticExpectations<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let common_methods = CommonExpectationsMethods { f: self.f }; + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let lg = lifetimes_to_generics(&self.f.alifetimes); + let output = &self.f.output; + let predexprs = &self.f.predexprs; + let v = &self.f.privmod_vis; + quote!( + #common_methods + impl #ig Expectations #tg #wc { + /// Simulate calling the real method. Every current expectation + /// will be checked in FIFO order and the first one with + /// matching arguments will be used. + #v fn call #lg (&self, #(#argnames: #argty, )* ) + -> Option<#output> + { + self.0.iter() + .find(|__mockall_e| + __mockall_e.matches(#(#predexprs, )*) && + (!__mockall_e.is_done() || self.0.len() == 1)) + .map(move |__mockall_e| + __mockall_e.call(#(#argnames, )*) + ) + } + + } + ) + .to_tokens(tokens); + } +} + +struct GenericExpectations<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for GenericExpectations<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + if !self.f.is_expectation_generic() { + return; + } + if !self.f.is_static() && !self.f.is_method_generic() { + return; + } + + let ge = StaticGenericExpectations { f: self.f }; + let v = &self.f.privmod_vis; + quote!( + /// A collection of [`Expectation`](struct.Expectations.html) + /// objects for a generic method. Users will rarely if ever use + /// this struct directly. + #[doc(hidden)] + #[derive(Default)] + #v struct GenericExpectations{ + store: std::collections::hash_map::HashMap<::mockall::Key, + Box<dyn ::mockall::AnyExpectations>> + } + impl GenericExpectations { + /// Verify that all current expectations are satisfied and clear + /// them. This applies to all sets of generic parameters! + #v fn checkpoint(&mut self) -> + std::collections::hash_map::Drain<::mockall::Key, + Box<dyn ::mockall::AnyExpectations>> + { + self.store.drain() + } + + #v fn new() -> Self { + Self::default() + } + } + #ge + ) + .to_tokens(tokens); + } +} + +/// Generates methods for GenericExpectations for methods returning static +/// values +struct StaticGenericExpectations<'a> { + f: &'a MockFunction, +} + +impl<'a> ToTokens for StaticGenericExpectations<'a> { + fn to_tokens(&self, tokens: &mut TokenStream) { + let argnames = &self.f.argnames; + let argty = &self.f.argty; + let (ig, tg, wc) = self.f.egenerics.split_for_impl(); + let keyid = gen_keyid(&self.f.egenerics); + let mut any_wc = wc.cloned(); + if self.f.return_ref || self.f.return_refmut { + // Add Senc + Sync, required for downcast, since Expectation + // stores an Option<#owned_output> + send_syncify(&mut any_wc, self.f.owned_output.clone()); + } + let tbf = tg.as_turbofish(); + let output = &self.f.output; + let v = &self.f.privmod_vis; + let (call, get, self_, downcast) = if self.f.return_refmut { + ( + format_ident!("call_mut"), + format_ident!("get_mut"), + quote!(&mut self), + format_ident!("downcast_mut"), + ) + } else { + ( + format_ident!("call"), + format_ident!("get"), + quote!(&self), + format_ident!("downcast_ref"), + ) + }; + quote!( + impl #ig ::mockall::AnyExpectations for Expectations #tg #any_wc {} + impl GenericExpectations { + /// Simulating calling the real method. + #v fn #call #ig (#self_, #(#argnames: #argty, )* ) + -> Option<#output> #wc + { + self.store.#get(&::mockall::Key::new::#keyid()) + .map(|__mockall_e| { + __mockall_e.#downcast::<Expectations #tg>() + .unwrap() + .#call(#(#argnames, )*) + }).flatten() + } + + /// Create a new Expectation. + #v fn expect #ig (&mut self) -> &mut Expectation #tg #any_wc + { + self.store.entry(::mockall::Key::new::#keyid()) + .or_insert_with(|| Box::new(Expectations #tbf::new())) + .downcast_mut::<Expectations #tg>() + .unwrap() + .expect() + } + } + ) + .to_tokens(tokens) + } +} diff --git a/src/mock_item.rs b/src/mock_item.rs new file mode 100644 index 0000000..109d9c2 --- /dev/null +++ b/src/mock_item.rs @@ -0,0 +1,172 @@ +// vim: tw=80 +use super::*; + +use crate::{ + mock_function::MockFunction, + mockable_item::{MockableItem, MockableModule}, +}; + +/// A Mock item +pub(crate) enum MockItem { + Module(MockItemModule), + Struct(MockItemStruct), +} + +impl From<MockableItem> for MockItem { + fn from(mockable: MockableItem) -> MockItem { + match mockable { + MockableItem::Struct(s) => MockItem::Struct(MockItemStruct::from(s)), + MockableItem::Module(mod_) => MockItem::Module(MockItemModule::from(mod_)), + } + } +} + +impl ToTokens for MockItem { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + MockItem::Module(mod_) => mod_.to_tokens(tokens), + MockItem::Struct(s) => s.to_tokens(tokens), + } + } +} + +enum MockItemContent { + Fn(Box<MockFunction>), + Tokens(TokenStream), +} + +pub(crate) struct MockItemModule { + attrs: TokenStream, + vis: Visibility, + mock_ident: Ident, + orig_ident: Option<Ident>, + content: Vec<MockItemContent>, +} + +impl From<MockableModule> for MockItemModule { + fn from(mod_: MockableModule) -> MockItemModule { + let mock_ident = mod_.mock_ident.clone(); + let orig_ident = mod_.orig_ident; + let mut content = Vec::new(); + for item in mod_.content.into_iter() { + let span = item.span(); + match item { + Item::ExternCrate(_) | Item::Impl(_) => { + // Ignore + } + Item::Static(is) => { + content.push(MockItemContent::Tokens(is.into_token_stream())); + } + Item::Const(ic) => { + content.push(MockItemContent::Tokens(ic.into_token_stream())); + } + Item::Fn(f) => { + let mf = mock_function::Builder::new(&f.sig, &f.vis) + .attrs(&f.attrs) + .parent(&mock_ident) + .levels(1) + .call_levels(0) + .build(); + content.push(MockItemContent::Fn(Box::new(mf))); + } + Item::ForeignMod(ifm) => { + for item in ifm.items { + if let ForeignItem::Fn(mut f) = item { + // Foreign functions are always unsafe. Mock + // foreign functions should be unsafe too, to + // prevent "warning: unused unsafe" messages. + f.sig.unsafety = Some(Token![unsafe](f.span())); + let mf = mock_function::Builder::new(&f.sig, &f.vis) + .attrs(&f.attrs) + .parent(&mock_ident) + .levels(1) + .call_levels(0) + .build(); + content.push(MockItemContent::Fn(Box::new(mf))); + } else { + compile_error(item.span(), + "Mockall does not yet support this type in this position. Please open an issue with your use case at https://github.com/asomers/mockall"); + } + } + } + Item::Mod(_) + | Item::Struct(_) + | Item::Enum(_) + | Item::Union(_) + | Item::Trait(_) => { + compile_error(span, "Mockall does not yet support deriving nested mocks"); + } + Item::Type(ty) => { + content.push(MockItemContent::Tokens(ty.into_token_stream())); + } + Item::TraitAlias(ta) => { + content.push(MockItemContent::Tokens(ta.into_token_stream())); + } + Item::Use(u) => { + content.push(MockItemContent::Tokens(u.into_token_stream())); + } + _ => compile_error(span, "Unsupported item"), + } + } + MockItemModule { + attrs: mod_.attrs, + vis: mod_.vis, + mock_ident: mod_.mock_ident, + orig_ident, + content, + } + } +} + +impl ToTokens for MockItemModule { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut body = TokenStream::new(); + let mut cp_body = TokenStream::new(); + let attrs = &self.attrs; + let modname = &self.mock_ident; + let vis = &self.vis; + + for item in self.content.iter() { + match item { + MockItemContent::Tokens(ts) => ts.to_tokens(&mut body), + MockItemContent::Fn(f) => { + let call = f.call(None); + let ctx_fn = f.context_fn(None); + let priv_mod = f.priv_module(); + quote!( + #priv_mod + #call + #ctx_fn + ) + .to_tokens(&mut body); + f.checkpoint().to_tokens(&mut cp_body); + } + } + } + + quote!( + /// Verify that all current expectations for every function in + /// this module are satisfied and clear them. + pub fn checkpoint() { #cp_body } + ) + .to_tokens(&mut body); + let docstr = { + if let Some(ident) = &self.orig_ident { + let inner = format!("Mock version of the `{}` module", ident); + quote!( #[doc = #inner]) + } else { + // Typically an extern FFI block. Not really anything good we + // can put in the doc string. + quote!(#[allow(missing_docs)]) + } + }; + quote!( + #[allow(unused_imports)] + #attrs + #docstr + #vis mod #modname { + #body + }) + .to_tokens(tokens); + } +} diff --git a/src/mock_item_struct.rs b/src/mock_item_struct.rs new file mode 100644 index 0000000..9bf5c13 --- /dev/null +++ b/src/mock_item_struct.rs @@ -0,0 +1,456 @@ +// vim: tw=80 +use super::*; + +use quote::ToTokens; +use std::collections::HashSet; + +use crate::{mock_function::MockFunction, mock_trait::MockTrait}; + +fn phantom_default_inits(generics: &Generics) -> Vec<TokenStream> { + generics + .params + .iter() + .enumerate() + .map(|(count, _param)| { + let phident = format_ident!("_t{}", count); + quote!(#phident: ::std::marker::PhantomData) + }) + .collect() +} + +/// Generate any PhantomData field definitions +fn phantom_fields(generics: &Generics) -> Vec<TokenStream> { + generics + .params + .iter() + .enumerate() + .filter_map(|(count, param)| { + let phident = format_ident!("_t{}", count); + match param { + syn::GenericParam::Lifetime(l) => { + if !l.bounds.is_empty() { + compile_error( + l.bounds.span(), + "#automock does not yet support lifetime bounds on structs", + ); + } + let lifetime = &l.lifetime; + Some(quote!(#phident: ::std::marker::PhantomData<&#lifetime ()>)) + } + syn::GenericParam::Type(tp) => { + let ty = &tp.ident; + Some(quote!(#phident: ::std::marker::PhantomData<#ty>)) + } + syn::GenericParam::Const(_) => { + compile_error( + param.span(), + "#automock does not yet support generic constants", + ); + None + } + } + }) + .collect() +} + +/// Filter out multiple copies of the same trait, even if they're implemented on +/// different types. But allow them if they have different attributes, which +/// probably indicates that they aren't meant to be compiled together. +fn unique_trait_iter<'a, I: Iterator<Item = &'a MockTrait>>( + i: I, +) -> impl Iterator<Item = &'a MockTrait> { + let mut hs = HashSet::<(Path, Vec<Attribute>)>::default(); + i.filter(move |mt| { + let impl_attrs = AttrFormatter::new(&mt.attrs) + .async_trait(false) + .doc(false) + .format(); + let key = (mt.trait_path.clone(), impl_attrs); + if hs.contains(&key) { + false + } else { + hs.insert(key); + true + } + }) +} + +/// A collection of methods defined in one spot +struct Methods(Vec<MockFunction>); + +impl Methods { + /// Are all of these methods static? + fn all_static(&self) -> bool { + self.0.iter().all(|meth| meth.is_static()) + } + + fn checkpoints(&self) -> Vec<impl ToTokens> { + self.0 + .iter() + .filter(|meth| !meth.is_static()) + .map(|meth| meth.checkpoint()) + .collect::<Vec<_>>() + } + + /// Return a fragment of code to initialize struct fields during default() + fn default_inits(&self) -> Vec<TokenStream> { + self.0 + .iter() + .filter(|meth| !meth.is_static()) + .map(|meth| { + let name = meth.name(); + let attrs = AttrFormatter::new(&meth.attrs).doc(false).format(); + quote!(#(#attrs)* #name: Default::default()) + }) + .collect::<Vec<_>>() + } + + fn field_definitions(&self, modname: &Ident) -> Vec<TokenStream> { + self.0 + .iter() + .filter(|meth| !meth.is_static()) + .map(|meth| meth.field_definition(Some(modname))) + .collect::<Vec<_>>() + } + + fn priv_mods(&self) -> Vec<impl ToTokens> { + self.0 + .iter() + .map(|meth| meth.priv_module()) + .collect::<Vec<_>>() + } +} + +pub(crate) struct MockItemStruct { + attrs: Vec<Attribute>, + consts: Vec<ImplItemConst>, + generics: Generics, + /// Should Mockall generate a Debug implementation? + auto_debug: bool, + /// Does the original struct have a `new` method? + has_new: bool, + /// Inherent methods of the mock struct + methods: Methods, + /// Name of the overall module that holds all of the mock stuff + modname: Ident, + name: Ident, + /// Is this a whole MockStruct or just a substructure for a trait impl? + traits: Vec<MockTrait>, + vis: Visibility, +} + +impl MockItemStruct { + fn debug_impl(&self) -> impl ToTokens { + if self.auto_debug { + let (ig, tg, wc) = self.generics.split_for_impl(); + let struct_name = &self.name; + let struct_name_str = format!("{}", self.name); + quote!( + impl #ig ::std::fmt::Debug for #struct_name #tg #wc { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) + -> ::std::result::Result<(), std::fmt::Error> + { + f.debug_struct(#struct_name_str).finish() + } + } + ) + } else { + quote!() + } + } + + fn new_method(&self) -> impl ToTokens { + if self.has_new { + TokenStream::new() + } else { + quote!( + /// Create a new mock object with no expectations. + /// + /// This method will not be generated if the real struct + /// already has a `new` method. However, it *will* be + /// generated if the struct implements a trait with a `new` + /// method. The trait's `new` method can still be called + /// like `<MockX as TraitY>::new` + pub fn new() -> Self { + Self::default() + } + ) + } + } + + fn phantom_default_inits(&self) -> Vec<TokenStream> { + phantom_default_inits(&self.generics) + } + + fn phantom_fields(&self) -> Vec<TokenStream> { + phantom_fields(&self.generics) + } +} + +impl From<MockableStruct> for MockItemStruct { + fn from(mockable: MockableStruct) -> MockItemStruct { + let auto_debug = mockable.derives_debug(); + let modname = gen_mod_ident(&mockable.name, None); + let generics = mockable.generics.clone(); + let struct_name = &mockable.name; + let vis = mockable.vis; + let has_new = mockable.methods.iter().any(|meth| meth.sig.ident == "new") + || mockable.impls.iter().any(|impl_| { + impl_.items.iter().any(|ii| { + if let ImplItem::Method(iim) = ii { + iim.sig.ident == "new" + } else { + false + } + }) + }); + let methods = Methods( + mockable + .methods + .into_iter() + .map(|meth| { + mock_function::Builder::new(&meth.sig, &meth.vis) + .attrs(&meth.attrs) + .struct_(struct_name) + .struct_generics(&generics) + .levels(2) + .call_levels(0) + .build() + }) + .collect::<Vec<_>>(), + ); + let structname = &mockable.name; + let traits = mockable + .impls + .into_iter() + .map(|i| MockTrait::new(structname, &generics, i, &vis)) + .collect(); + + MockItemStruct { + attrs: mockable.attrs, + auto_debug, + consts: mockable.consts, + generics, + has_new, + methods, + modname, + name: mockable.name, + traits, + vis, + } + } +} + +impl ToTokens for MockItemStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let attrs = AttrFormatter::new(&self.attrs).async_trait(false).format(); + let consts = &self.consts; + let debug_impl = self.debug_impl(); + let struct_name = &self.name; + let (ig, tg, wc) = self.generics.split_for_impl(); + let modname = &self.modname; + let calls = self + .methods + .0 + .iter() + .map(|meth| meth.call(Some(modname))) + .collect::<Vec<_>>(); + let contexts = self + .methods + .0 + .iter() + .filter(|meth| meth.is_static()) + .map(|meth| meth.context_fn(Some(modname))) + .collect::<Vec<_>>(); + let expects = self + .methods + .0 + .iter() + .filter(|meth| !meth.is_static()) + .map(|meth| meth.expect(modname, None)) + .collect::<Vec<_>>(); + let method_checkpoints = self.methods.checkpoints(); + let new_method = self.new_method(); + let priv_mods = self.methods.priv_mods(); + let substructs = unique_trait_iter(self.traits.iter()) + .map(|trait_| MockItemTraitImpl { + attrs: trait_.attrs.clone(), + generics: self.generics.clone(), + fieldname: format_ident!("{}_expectations", trait_.ss_name()), + methods: Methods(trait_.methods.clone()), + modname: format_ident!("{}_{}", &self.modname, trait_.ss_name()), + name: format_ident!("{}_{}", &self.name, trait_.ss_name()), + }) + .collect::<Vec<_>>(); + let substruct_expectations = substructs + .iter() + .filter(|ss| !ss.all_static()) + .map(|ss| { + let attrs = AttrFormatter::new(&ss.attrs) + .async_trait(false) + .doc(false) + .format(); + let fieldname = &ss.fieldname; + quote!(#(#attrs)* self.#fieldname.checkpoint();) + }) + .collect::<Vec<_>>(); + let mut field_definitions = substructs + .iter() + .filter(|ss| !ss.all_static()) + .map(|ss| { + let attrs = AttrFormatter::new(&ss.attrs) + .async_trait(false) + .doc(false) + .format(); + let fieldname = &ss.fieldname; + let tyname = &ss.name; + quote!(#(#attrs)* #fieldname: #tyname #tg) + }) + .collect::<Vec<_>>(); + field_definitions.extend(self.methods.field_definitions(modname)); + field_definitions.extend(self.phantom_fields()); + let mut default_inits = substructs + .iter() + .filter(|ss| !ss.all_static()) + .map(|ss| { + let attrs = AttrFormatter::new(&ss.attrs) + .async_trait(false) + .doc(false) + .format(); + let fieldname = &ss.fieldname; + quote!(#(#attrs)* #fieldname: Default::default()) + }) + .collect::<Vec<_>>(); + default_inits.extend(self.methods.default_inits()); + default_inits.extend(self.phantom_default_inits()); + let trait_impls = self + .traits + .iter() + .map(|trait_| { + let modname = format_ident!("{}_{}", &self.modname, trait_.ss_name()); + trait_.trait_impl(&modname) + }) + .collect::<Vec<_>>(); + let vis = &self.vis; + quote!( + #[allow(non_snake_case)] + #[allow(missing_docs)] + pub mod #modname { + use super::*; + #(#priv_mods)* + } + #[allow(non_camel_case_types)] + #[allow(non_snake_case)] + #[allow(missing_docs)] + #(#attrs)* + #vis struct #struct_name #ig #wc + { + #(#field_definitions),* + } + #debug_impl + impl #ig ::std::default::Default for #struct_name #tg #wc { + #[allow(clippy::default_trait_access)] + fn default() -> Self { + Self { + #(#default_inits),* + } + } + } + #(#substructs)* + impl #ig #struct_name #tg #wc { + #(#consts)* + #(#calls)* + #(#contexts)* + #(#expects)* + /// Validate that all current expectations for all methods have + /// been satisfied, and discard them. + pub fn checkpoint(&mut self) { + #(#substruct_expectations)* + #(#method_checkpoints)* + } + #new_method + } + #(#trait_impls)* + ) + .to_tokens(tokens); + } +} + +pub(crate) struct MockItemTraitImpl { + attrs: Vec<Attribute>, + generics: Generics, + /// Inherent methods of the mock struct + methods: Methods, + /// Name of the overall module that holds all of the mock stuff + modname: Ident, + name: Ident, + /// Name of the field of this type in the parent's structure + fieldname: Ident, +} + +impl MockItemTraitImpl { + /// Are all of this traits's methods static? + fn all_static(&self) -> bool { + self.methods.all_static() + } + + fn phantom_default_inits(&self) -> Vec<TokenStream> { + phantom_default_inits(&self.generics) + } + + fn phantom_fields(&self) -> Vec<TokenStream> { + phantom_fields(&self.generics) + } +} + +impl ToTokens for MockItemTraitImpl { + fn to_tokens(&self, tokens: &mut TokenStream) { + let attrs = AttrFormatter::new(&self.attrs) + .async_trait(false) + .doc(false) + .format(); + let struct_name = &self.name; + let (ig, tg, wc) = self.generics.split_for_impl(); + let modname = &self.modname; + let method_checkpoints = self.methods.checkpoints(); + let mut default_inits = self.methods.default_inits(); + default_inits.extend(self.phantom_default_inits()); + let mut field_definitions = self.methods.field_definitions(modname); + field_definitions.extend(self.phantom_fields()); + let priv_mods = self.methods.priv_mods(); + quote!( + #[allow(non_snake_case)] + #[allow(missing_docs)] + #(#attrs)* + pub mod #modname { + use super::*; + #(#priv_mods)* + } + #[allow(non_camel_case_types)] + #[allow(non_snake_case)] + #[allow(missing_docs)] + #(#attrs)* + struct #struct_name #ig #wc + { + #(#field_definitions),* + } + #(#attrs)* + impl #ig ::std::default::Default for #struct_name #tg #wc { + fn default() -> Self { + Self { + #(#default_inits),* + } + } + } + #(#attrs)* + impl #ig #struct_name #tg #wc { + /// Validate that all current expectations for all methods have + /// been satisfied, and discard them. + pub fn checkpoint(&mut self) { + #(#method_checkpoints)* + } + } + ) + .to_tokens(tokens); + } +} diff --git a/src/mock_trait.rs b/src/mock_trait.rs new file mode 100644 index 0000000..eba29b6 --- /dev/null +++ b/src/mock_trait.rs @@ -0,0 +1,184 @@ +// vim: tw=80 +use proc_macro2::Span; +use quote::{format_ident, quote, ToTokens}; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, +}; +use syn::{spanned::Spanned, *}; + +use crate::{ + compile_error, + mock_function::{self, MockFunction}, + AttrFormatter, +}; + +pub(crate) struct MockTrait { + pub attrs: Vec<Attribute>, + pub consts: Vec<ImplItemConst>, + pub generics: Generics, + pub methods: Vec<MockFunction>, + /// Internally-used name of the trait used. + pub ss_name: Ident, + /// Fully-qualified name of the trait + pub trait_path: Path, + /// Path on which the trait is implemented. Usually will be the same as + /// structname, but might include concrete generic parameters. + self_path: PathSegment, + pub types: Vec<ImplItemType>, + pub unsafety: Option<Token![unsafe]>, +} + +impl MockTrait { + fn ss_name_priv(trait_path: &Path) -> Ident { + let path_args = &trait_path.segments.last().unwrap().arguments; + if path_args.is_empty() { + // Skip the hashing step for easie debugging of generated code + format_ident!("{}", trait_path.segments.last().unwrap().ident) + } else { + // Hash the path args to permit mocking structs that implement + // multiple traits distinguished only by their path args + let mut hasher = DefaultHasher::new(); + path_args.hash(&mut hasher); + format_ident!( + "{}_{}", + trait_path.segments.last().unwrap().ident, + hasher.finish() + ) + } + } + + pub fn ss_name(&self) -> &Ident { + &self.ss_name + } + + /// Create a new MockTrait + /// + /// # Arguments + /// * `structname` - name of the struct that implements this trait + /// * `struct_generics` - Generics of the parent structure + /// * `impl_` - Mockable ItemImpl for a trait + /// * `vis` - Visibility of the struct + pub fn new( + structname: &Ident, + struct_generics: &Generics, + impl_: ItemImpl, + vis: &Visibility, + ) -> Self { + let mut consts = Vec::new(); + let mut methods = Vec::new(); + let mut types = Vec::new(); + let trait_path = if let Some((_, path, _)) = impl_.trait_ { + path + } else { + compile_error(impl_.span(), "impl block must implement a trait"); + Path::from(format_ident!("__mockall_invalid")) + }; + let ss_name = MockTrait::ss_name_priv(&trait_path); + let self_path = match *impl_.self_ty { + Type::Path(mut type_path) => type_path.path.segments.pop().unwrap().into_value(), + x => { + compile_error( + x.span(), + "mockall_derive only supports mocking traits and structs", + ); + PathSegment::from(Ident::new("", Span::call_site())) + } + }; + + for ii in impl_.items.into_iter() { + match ii { + ImplItem::Const(iic) => { + consts.push(iic); + } + ImplItem::Method(iim) => { + let mf = mock_function::Builder::new(&iim.sig, vis) + .attrs(&iim.attrs) + .levels(2) + .call_levels(0) + .struct_(structname) + .struct_generics(struct_generics) + .trait_(&ss_name) + .build(); + methods.push(mf); + } + ImplItem::Type(iit) => { + types.push(iit); + } + _ => { + compile_error(ii.span(), "This impl item is not yet supported by MockAll"); + } + } + } + MockTrait { + attrs: impl_.attrs, + consts, + generics: impl_.generics, + methods, + ss_name, + trait_path, + self_path, + types, + unsafety: impl_.unsafety, + } + } + + /// Generate code for the trait implementation on the mock struct + /// + /// # Arguments + /// + /// * `modname`: Name of the parent struct's private module + // Supplying modname is an unfortunately hack. Ideally MockTrait + // wouldn't need to know that. + pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens { + let trait_impl_attrs = &self.attrs; + let impl_attrs = AttrFormatter::new(&self.attrs) + .async_trait(false) + .doc(false) + .format(); + let (ig, _tg, wc) = self.generics.split_for_impl(); + let consts = &self.consts; + let path_args = &self.self_path.arguments; + let calls = self + .methods + .iter() + .map(|meth| meth.call(Some(modname))) + .collect::<Vec<_>>(); + let contexts = self + .methods + .iter() + .filter(|meth| meth.is_static()) + .map(|meth| meth.context_fn(Some(modname))) + .collect::<Vec<_>>(); + let expects = self + .methods + .iter() + .filter(|meth| !meth.is_static()) + .map(|meth| { + if meth.is_method_generic() { + // Specific impls with generic methods are TODO. + meth.expect(modname, None) + } else { + meth.expect(modname, Some(path_args)) + } + }) + .collect::<Vec<_>>(); + let trait_path = &self.trait_path; + let self_path = &self.self_path; + let types = &self.types; + let unsafety = &self.unsafety; + quote!( + #(#trait_impl_attrs)* + #unsafety impl #ig #trait_path for #self_path #wc { + #(#consts)* + #(#types)* + #(#calls)* + } + #(#impl_attrs)* + impl #ig #self_path #wc { + #(#expects)* + #(#contexts)* + } + ) + } +} diff --git a/src/mockable_item.rs b/src/mockable_item.rs new file mode 100644 index 0000000..03c894d --- /dev/null +++ b/src/mockable_item.rs @@ -0,0 +1,164 @@ +// vim: tw=80 +use super::*; + +/// Performs transformations on a function to make it mockable +fn mockable_fn(mut item_fn: ItemFn) -> ItemFn { + demutify(&mut item_fn.sig.inputs); + deimplify(&mut item_fn.sig.output); + item_fn +} + +/// Performs transformations on an Item to make it mockable +fn mockable_item(item: Item) -> Item { + match item { + Item::Fn(item_fn) => Item::Fn(mockable_fn(item_fn)), + x => x, + } +} + +/// An item that's ready to be mocked. +/// +/// It should be functionally identical or near-identical to the original item, +/// but with minor alterations that make it suitable for mocking, such as +/// altered lifetimes. +pub(crate) enum MockableItem { + Module(MockableModule), + Struct(MockableStruct), +} + +impl From<(Attrs, Item)> for MockableItem { + fn from((attrs, item): (Attrs, Item)) -> MockableItem { + match item { + Item::Impl(item_impl) => MockableItem::Struct(MockableStruct::from(item_impl)), + Item::ForeignMod(item_foreign_mod) => { + MockableItem::Module(MockableModule::from((attrs, item_foreign_mod))) + } + Item::Mod(item_mod) => MockableItem::Module(MockableModule::from(item_mod)), + Item::Trait(trait_) => MockableItem::Struct(MockableStruct::from((attrs, trait_))), + _ => panic!("automock does not support this item type"), + } + } +} + +impl From<MockableStruct> for MockableItem { + fn from(mock: MockableStruct) -> MockableItem { + MockableItem::Struct(mock) + } +} + +pub(crate) struct MockableModule { + pub attrs: TokenStream, + pub vis: Visibility, + pub mock_ident: Ident, + /// Ident of the original module, if any + pub orig_ident: Option<Ident>, + pub content: Vec<Item>, +} + +impl From<(Attrs, ItemForeignMod)> for MockableModule { + fn from((attrs, foreign): (Attrs, ItemForeignMod)) -> MockableModule { + let orig_ident = None; + let mock_ident = attrs.modname.expect(concat!( + "module name is required when mocking foreign functions,", + " like `#[automock(mod mock_ffi)]`" + )); + let vis = Visibility::Public(VisPublic { + pub_token: <Token![pub]>::default(), + }); + let attrs = quote!( + #[deprecated(since = "0.9.0", note = "Using automock directly on an extern block is deprecated. Instead, wrap the extern block in a module, and automock that, like #[automock] mod ffi { extern \"C\" { fn foo ... } }")] + ); + let mut content = vec![ + // When mocking extern blocks, we pretend that they're modules, so + // we need a "use super::*;" to ensure that types can resolve + Item::Use(ItemUse { + attrs: Vec::new(), + vis: Visibility::Inherited, + use_token: token::Use::default(), + leading_colon: None, + tree: UseTree::Path(UsePath { + ident: Ident::new("super", Span::call_site()), + colon2_token: token::Colon2::default(), + tree: Box::new(UseTree::Glob(UseGlob { + star_token: token::Star::default(), + })), + }), + semi_token: token::Semi::default(), + }), + ]; + content.extend(foreign.items.into_iter().map(|foreign_item| { + match foreign_item { + ForeignItem::Fn(f) => { + let span = f.sig.span(); + let mut sig = f.sig; + + // When mocking extern blocks, we pretend that they're + // modules. So we must supersuperfy everything by one + // level. + let vis = expectation_visibility(&f.vis, 1); + + for arg in sig.inputs.iter_mut() { + if let FnArg::Typed(pt) = arg { + *pt.ty = supersuperfy(pt.ty.as_ref(), 1); + } + } + if let ReturnType::Type(_, ty) = &mut sig.output { + **ty = supersuperfy(&*ty, 1); + } + + // Foreign functions are always unsafe. Mock foreign + // functions should be unsafe too, to prevent "warning: + // unused unsafe" messages. + sig.unsafety = Some(Token![unsafe](span)); + let block = Box::new(Block { + brace_token: token::Brace::default(), + stmts: Vec::new(), + }); + + Item::Fn(ItemFn { + attrs: f.attrs, + vis, + sig, + block, + }) + } + _ => { + compile_error(foreign_item.span(), "Unsupported foreign item type"); + Item::Verbatim(TokenStream::default()) + } + } + })); + MockableModule { + attrs, + vis, + mock_ident, + orig_ident, + content, + } + } +} + +impl From<ItemMod> for MockableModule { + fn from(mod_: ItemMod) -> MockableModule { + let span = mod_.span(); + let vis = mod_.vis; + let mock_ident = format_ident!("mock_{}", mod_.ident); + let orig_ident = Some(mod_.ident); + let content = if let Some((_, content)) = mod_.content { + content.into_iter().map(mockable_item).collect() + } else { + compile_error( + span, + "automock can only mock inline modules, not modules from another file", + ); + Vec::new() + }; + MockableModule { + attrs: TokenStream::new(), + vis, + mock_ident, + orig_ident, + content, + } + } +} diff --git a/src/mockable_struct.rs b/src/mockable_struct.rs new file mode 100644 index 0000000..fe28199 --- /dev/null +++ b/src/mockable_struct.rs @@ -0,0 +1,642 @@ +// vim: tw=80 +use super::*; +use syn::parse::{Parse, ParseStream}; + +/// Make any implicit lifetime parameters explicit +fn add_lifetime_parameters(sig: &mut Signature) { + fn add_to_trait_object(generics: &mut Generics, var: &Pat, to: &mut TypeTraitObject) { + let mut has_lifetime = false; + for bound in to.bounds.iter() { + if let TypeParamBound::Lifetime(_) = bound { + has_lifetime = true; + } + } + if !has_lifetime { + let arg_ident = match *var { + Pat::Wild(_) => { + compile_error(var.span(), "Mocked methods must have named arguments"); + format_ident!("dont_care") + } + Pat::Ident(ref 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.ident.clone() + } + _ => { + compile_error(var.span(), "Unsupported argument type"); + format_ident!("dont_care") + } + }; + let s = format!("'__mockall_{}", arg_ident); + let span = Span::call_site(); + let lt = Lifetime::new(&s, span); + to.bounds.push(TypeParamBound::Lifetime(lt.clone())); + generics.lt_token.get_or_insert(Token![<](span)); + generics.gt_token.get_or_insert(Token![>](span)); + let gpl = GenericParam::Lifetime(LifetimeDef::new(lt)); + generics.params.push(gpl); + } + } + + fn add_to_type(generics: &mut Generics, var: &Pat, ty: &mut Type) { + match ty { + Type::Array(ta) => add_to_type(generics, var, ta.elem.as_mut()), + Type::BareFn(_) => (), + Type::ImplTrait(_) => (), + Type::Path(_) => (), + Type::Ptr(_) => (), + Type::Reference(tr) => { + match tr.elem.as_mut() { + Type::Paren(tp) => { + if let Type::TraitObject(to) = tp.elem.as_mut() { + add_to_trait_object(generics, var, to); + } else { + add_to_type(generics, var, tr.elem.as_mut()); + } + } + Type::TraitObject(to) => { + add_to_trait_object(generics, var, to); + // We need to wrap it in a Paren. Otherwise it won't be + // syntactically valid after we add a lifetime bound, + // due to a "ambiguous `+` in a type" error + *tr.elem = Type::Paren(TypeParen { + paren_token: token::Paren::default(), + elem: Box::new(Type::TraitObject(to.clone())), + }); + } + _ => add_to_type(generics, var, tr.elem.as_mut()), + } + } + Type::Slice(ts) => add_to_type(generics, var, ts.elem.as_mut()), + Type::Tuple(tt) => { + for ty in tt.elems.iter_mut() { + add_to_type(generics, var, ty) + } + } + _ => compile_error(ty.span(), "unsupported type in this position"), + } + } + + for arg in sig.inputs.iter_mut() { + if let FnArg::Typed(pt) = arg { + add_to_type(&mut sig.generics, &pt.pat, &mut pt.ty) + } + } +} + +/// Generate a #[derive(Debug)] Attribute +fn derive_debug() -> Attribute { + Attribute { + pound_token: <Token![#]>::default(), + style: AttrStyle::Outer, + bracket_token: token::Bracket::default(), + path: Path::from(format_ident!("derive")), + tokens: quote!((Debug)), + } +} + +/// Add "Mock" to the front of the named type +fn mock_ident_in_type(ty: &mut Type) { + match ty { + Type::Path(type_path) => { + if type_path.path.segments.len() != 1 { + compile_error( + type_path.path.span(), + "mockall_derive only supports structs defined in the current module", + ); + return; + } + let ident = &mut type_path.path.segments.last_mut().unwrap().ident; + *ident = gen_mock_ident(ident) + } + x => { + compile_error( + x.span(), + "mockall_derive only supports mocking traits and structs", + ); + } + }; +} + +/// Performs transformations on the ItemImpl to make it mockable +fn mockable_item_impl(mut impl_: ItemImpl, name: &Ident, generics: &Generics) -> ItemImpl { + mock_ident_in_type(&mut impl_.self_ty); + for item in impl_.items.iter_mut() { + if let ImplItem::Method(ref mut iim) = item { + mockable_method(iim, name, generics); + } + } + impl_ +} + +/// Performs transformations on the method to make it mockable +fn mockable_method(meth: &mut ImplItemMethod, name: &Ident, generics: &Generics) { + demutify(&mut meth.sig.inputs); + deselfify_args(&mut meth.sig.inputs, name, generics); + add_lifetime_parameters(&mut meth.sig); + deimplify(&mut meth.sig.output); + dewhereselfify(&mut meth.sig.generics); + if let ReturnType::Type(_, ty) = &mut meth.sig.output { + deselfify(ty, name, generics); + deanonymize(ty); + } + sanity_check_sig(&meth.sig); +} + +/// Performs transformations on the method to make it mockable +fn mockable_trait_method(meth: &mut TraitItemMethod, name: &Ident, generics: &Generics) { + demutify(&mut meth.sig.inputs); + deselfify_args(&mut meth.sig.inputs, name, generics); + add_lifetime_parameters(&mut meth.sig); + deimplify(&mut meth.sig.output); + dewhereselfify(&mut meth.sig.generics); + if let ReturnType::Type(_, ty) = &mut meth.sig.output { + deselfify(ty, name, generics); + deanonymize(ty); + } + sanity_check_sig(&meth.sig); +} + +/// Generates a mockable item impl from a trait method definition +fn mockable_trait(trait_: ItemTrait, name: &Ident, generics: &Generics) -> ItemImpl { + let items = trait_ + .items + .into_iter() + .map(|ti| match ti { + TraitItem::Method(mut tim) => { + mockable_trait_method(&mut tim, name, generics); + ImplItem::Method(tim2iim(tim, &Visibility::Inherited)) + } + TraitItem::Const(tic) => ImplItem::Const(tic2iic(tic, &Visibility::Inherited)), + TraitItem::Type(tit) => ImplItem::Type(tit2iit(tit, &Visibility::Inherited)), + _ => { + compile_error(ti.span(), "Unsupported in this context"); + ImplItem::Verbatim(TokenStream::new()) + } + }) + .collect::<Vec<_>>(); + let mut trait_path = Path::from(trait_.ident); + let mut struct_path = Path::from(name.clone()); + let (_, stg, _) = generics.split_for_impl(); + let (_, ttg, _) = trait_.generics.split_for_impl(); + if let Ok(abga) = parse2::<AngleBracketedGenericArguments>(quote!(#stg)) { + struct_path.segments.last_mut().unwrap().arguments = PathArguments::AngleBracketed(abga); + } + if let Ok(abga) = parse2::<AngleBracketedGenericArguments>(quote!(#ttg)) { + trait_path.segments.last_mut().unwrap().arguments = PathArguments::AngleBracketed(abga); + } + let self_ty = Box::new(Type::Path(TypePath { + qself: None, + path: struct_path, + })); + ItemImpl { + attrs: trait_.attrs, + defaultness: None, + unsafety: trait_.unsafety, + impl_token: <Token![impl]>::default(), + generics: generics.clone(), + trait_: Some((None, trait_path, <Token![for]>::default())), + self_ty, + brace_token: trait_.brace_token, + items, + } +} + +fn sanity_check_sig(sig: &Signature) { + for arg in sig.inputs.iter() { + if let FnArg::Typed(pt) = arg { + if let Type::ImplTrait(it) = pt.ty.as_ref() { + let bounds = &it.bounds; + let s = format!( + "Mockall does not support \"impl trait\" in argument position. Use \"T: {}\" instead", + quote!(#bounds) + ); + compile_error(it.span(), &s); + } + } + } +} + +/// Converts a TraitItemConst into an ImplItemConst +fn tic2iic(tic: TraitItemConst, vis: &syn::Visibility) -> ImplItemConst { + let span = tic.span(); + let (eq_token, expr) = tic.default.unwrap_or_else(|| { + compile_error( + span, + "Mocked associated consts must have a default implementation", + ); + (<Token![=]>::default(), Expr::Verbatim(TokenStream::new())) + }); + ImplItemConst { + attrs: tic.attrs, + vis: vis.clone(), + defaultness: None, + const_token: tic.const_token, + ident: tic.ident, + colon_token: tic.colon_token, + ty: tic.ty, + eq_token, + expr, + semi_token: tic.semi_token, + } +} + +/// Converts a TraitItemMethod into an ImplItemMethod +fn tim2iim(m: syn::TraitItemMethod, vis: &syn::Visibility) -> syn::ImplItemMethod { + let empty_block = Block { + brace_token: token::Brace::default(), + stmts: Vec::new(), + }; + syn::ImplItemMethod { + attrs: m.attrs, + vis: vis.clone(), + defaultness: None, + sig: m.sig, + block: empty_block, + } +} + +/// Converts a TraitItemType into an ImplItemType +fn tit2iit(tit: TraitItemType, vis: &Visibility) -> ImplItemType { + let span = tit.span(); + let (eq_token, ty) = tit.default.unwrap_or_else(|| { + compile_error(span, "associated types in mock! must be fully specified"); + (token::Eq::default(), Type::Verbatim(TokenStream::new())) + }); + ImplItemType { + attrs: tit.attrs, + vis: vis.clone(), + defaultness: None, + type_token: tit.type_token, + ident: tit.ident, + generics: tit.generics, + eq_token, + ty, + semi_token: tit.semi_token, + } +} + +pub(crate) struct MockableStruct { + pub attrs: Vec<Attribute>, + pub consts: Vec<ImplItemConst>, + pub generics: Generics, + /// Inherent methods of the mockable struct + pub methods: Vec<ImplItemMethod>, + pub name: Ident, + pub vis: Visibility, + pub impls: Vec<ItemImpl>, +} + +impl MockableStruct { + /// Does this struct derive Debug? + pub fn derives_debug(&self) -> bool { + self.attrs.iter().any(|attr| { + if let Ok(Meta::List(ml)) = attr.parse_meta() { + let i = ml.path.get_ident(); + if i.map_or(false, |i| *i == "derive") { + ml.nested.iter().any(|nm| { + if let NestedMeta::Meta(m) = nm { + let i = m.path().get_ident(); + i.map_or(false, |i| *i == "Debug") + } else { + false + } + }) + } else { + false + } + } else { + false + } + }) + } +} + +impl From<(Attrs, ItemTrait)> for MockableStruct { + fn from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct { + let trait_ = attrs.substitute_trait(&item_trait); + let mut attrs = trait_.attrs.clone(); + attrs.push(derive_debug()); + let vis = trait_.vis.clone(); + let name = gen_mock_ident(&trait_.ident); + let generics = trait_.generics.clone(); + let impls = vec![mockable_trait(trait_, &name, &generics)]; + MockableStruct { + attrs, + consts: Vec::new(), + vis, + name, + generics, + methods: Vec::new(), + impls, + } + } +} + +impl From<ItemImpl> for MockableStruct { + fn from(mut item_impl: ItemImpl) -> MockableStruct { + let name = match &*item_impl.self_ty { + Type::Path(type_path) => { + let n = find_ident_from_path(&type_path.path).0; + gen_mock_ident(&n) + } + x => { + compile_error( + x.span(), + "mockall_derive only supports mocking traits and structs", + ); + Ident::new("", Span::call_site()) + } + }; + let mut attrs = item_impl.attrs.clone(); + attrs.push(derive_debug()); + let mut consts = Vec::new(); + let generics = item_impl.generics.clone(); + let mut methods = Vec::new(); + let pub_token = Token![pub](Span::call_site()); + let vis = Visibility::Public(VisPublic { pub_token }); + let mut impls = Vec::new(); + if let Some((bang, _path, _)) = &item_impl.trait_ { + if bang.is_some() { + compile_error(bang.span(), "Unsupported by automock"); + } + + // Substitute any associated types in this ItemImpl. + // NB: this would not be necessary if the user always fully + // qualified them, e.g. `<Self as MyTrait>::MyType` + let mut attrs = Attrs::default(); + for item in item_impl.items.iter() { + match item { + ImplItem::Const(_iic) => (), + ImplItem::Method(_meth) => (), + ImplItem::Type(ty) => { + attrs.attrs.insert(ty.ident.clone(), ty.ty.clone()); + } + x => compile_error(x.span(), "Unsupported by automock"), + } + } + attrs.substitute_item_impl(&mut item_impl); + impls.push(mockable_item_impl(item_impl, &name, &generics)); + } else { + for item in item_impl.items.into_iter() { + match item { + ImplItem::Method(mut meth) => { + mockable_method(&mut meth, &name, &item_impl.generics); + methods.push(meth) + } + ImplItem::Const(iic) => consts.push(iic), + // Rust doesn't allow types in an inherent impl + x => compile_error(x.span(), "Unsupported by Mockall in this context"), + } + } + }; + MockableStruct { + attrs, + consts, + generics, + methods, + name, + vis, + impls, + } + } +} + +impl Parse for MockableStruct { + fn parse(input: ParseStream) -> syn::parse::Result<Self> { + let attrs = input.call(syn::Attribute::parse_outer)?; + let vis: syn::Visibility = input.parse()?; + let original_name: syn::Ident = input.parse()?; + let mut generics: syn::Generics = input.parse()?; + let wc: Option<syn::WhereClause> = input.parse()?; + generics.where_clause = wc; + let name = gen_mock_ident(&original_name); + let impl_content; + let _brace_token = braced!(impl_content in input); + let mut consts = Vec::new(); + let mut methods = Vec::new(); + while !impl_content.is_empty() { + let item: ImplItem = impl_content.parse()?; + match item { + ImplItem::Method(mut iim) => { + mockable_method(&mut iim, &name, &generics); + methods.push(iim); + } + ImplItem::Const(iic) => consts.push(iic), + _ => { + return Err(input.error("Unsupported in this context")); + } + } + } + + let mut impls = Vec::new(); + while !input.is_empty() { + let item: Item = input.parse()?; + match item { + Item::Trait(it) => { + let note = "Deprecated mock! syntax. Instead of \"trait X\", write \"impl X for Y\". See PR #205"; + let mut impl_ = mockable_trait(it, &name, &generics); + impl_.attrs.push(Attribute { + pound_token: <token::Pound>::default(), + style: AttrStyle::Outer, + bracket_token: token::Bracket::default(), + path: Path::from(format_ident!("deprecated")), + tokens: quote!((since = "0.9.0", note = #note)), + }); + impls.push(impl_) + } + Item::Impl(ii) => impls.push(mockable_item_impl(ii, &name, &generics)), + _ => return Err(input.error("Unsupported in this context")), + } + } + + Ok(MockableStruct { + attrs, + consts, + generics, + methods, + name, + vis, + impls, + }) + } +} + +#[cfg(test)] +mod t { + use super::*; + + mod add_lifetime_parameters { + use super::*; + + #[test] + fn array() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: [&dyn T; 1]); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo<'__mockall_x>(&self, x: [&(dyn T + '__mockall_x); 1]); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn bare_fn_with_named_args() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: fn(&dyn T)); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo(&self, x: fn(&dyn T)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn plain() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: &dyn T); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn slice() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: &[&dyn T]); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo<'__mockall_x>(&self, x: &[&(dyn T + '__mockall_x)]); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn tuple() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: (&dyn T, u32)); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo<'__mockall_x>(&self, x: (&(dyn T + '__mockall_x), u32)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn with_anonymous_lifetime() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: &(dyn T + '_)); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo(&self, x: &(dyn T + '_)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn with_parens() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: &(dyn T)); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn with_lifetime_parameter() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo<'a>(&self, x: &(dyn T + 'a)); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo<'a>(&self, x: &(dyn T + 'a)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + + #[test] + fn with_static_lifetime() { + let mut meth: TraitItemMethod = parse2(quote!( + fn foo(&self, x: &(dyn T + 'static)); + )) + .unwrap(); + add_lifetime_parameters(&mut meth.sig); + assert_eq!( + quote!( + fn foo(&self, x: &(dyn T + 'static)); + ) + .to_string(), + quote!(#meth).to_string() + ); + } + } + + mod sanity_check_sig { + use super::*; + + #[test] + #[should_panic( + expected = "Mockall does not support \"impl trait\" in argument position. Use \"T: SomeTrait\" instead." + )] + fn impl_trait() { + let meth: ImplItemMethod = parse2(quote!( + fn foo(&self, x: impl SomeTrait); + )) + .unwrap(); + sanity_check_sig(&meth.sig); + } + } +} |