Skip to content

Commit d4477ad

Browse files
authored
Merge pull request #555 from asomers/inline
Handle the #[inline], #[cold], and #[must_use] attributes
2 parents ab81db5 + 6bff15b commit d4477ad

File tree

8 files changed

+238
-13
lines changed

8 files changed

+238
-13
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ This project adheres to [Semantic Versioning](http://semver.org/).
55

66
## [ Unreleased ] - ReleaseDate
77

8+
### Added
9+
10+
- Add the ability to mock methods that use `#[inline]` or `#[cold]`, and
11+
methods or traits that use `#[must_use]`.
12+
([#555](https://github.com/asomers/mockall/pull/555))
13+
814
### Changed
915

1016
- Raised MSRV to 1.70.0 to remove `lazy_static` dependency

mockall/tests/automock_inline.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// vim: tw=80
2+
//! Mockall should ignore and not emit attributes like "inline" that affect
3+
//! code generation.
4+
#![deny(warnings)]
5+
6+
use mockall::*;
7+
8+
pub struct Foo {}
9+
10+
#[automock]
11+
impl Foo {
12+
#[inline]
13+
pub fn foo(&self) -> i32 {unimplemented!()}
14+
#[inline]
15+
pub fn bar() -> i32 {unimplemented!()}
16+
#[cold]
17+
pub fn baz(&self) -> i32 {unimplemented!()}
18+
#[cold]
19+
pub fn bean() -> i32 {unimplemented!()}
20+
}
21+
22+
#[test]
23+
fn inline_method() {
24+
let mut mock = MockFoo::new();
25+
mock.expect_foo()
26+
.return_const(42i32);
27+
assert_eq!(mock.foo(), 42);
28+
}
29+
30+
#[test]
31+
fn inline_static_method() {
32+
let ctx = MockFoo::bar_context();
33+
ctx.expect()
34+
.return_const(42i32);
35+
assert_eq!(MockFoo::bar(), 42);
36+
}
37+
38+
#[test]
39+
fn cold_method() {
40+
let mut mock = MockFoo::new();
41+
mock.expect_baz()
42+
.return_const(42i32);
43+
assert_eq!(mock.baz(), 42);
44+
}
45+
46+
#[test]
47+
fn cold_static_method() {
48+
let ctx = MockFoo::bean_context();
49+
ctx.expect()
50+
.return_const(42i32);
51+
assert_eq!(MockFoo::bean(), 42);
52+
}

mockall/tests/automock_must_use.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// vim: tw=80
2+
//! Mockall should be able to mock #[must_use] methods. The generated code
3+
//! should contain #[must_use] on the mock method, but not the expect method.
4+
#![deny(warnings)]
5+
6+
use mockall::*;
7+
8+
pub struct Foo {}
9+
10+
#[automock]
11+
impl Foo {
12+
#[must_use]
13+
pub fn bloob(&self) -> i32 {unimplemented!()}
14+
#[must_use]
15+
pub fn blarg() -> i32 {unimplemented!()}
16+
}
17+
18+
// test that basic code generation works with must_use structs and traits. The
19+
// exact output will be verified by the unit tests.
20+
#[must_use]
21+
pub struct MustUseStruct {}
22+
#[automock]
23+
impl MustUseStruct {}
24+
#[automock]
25+
#[must_use]
26+
pub trait MustUseTrait {}
27+
28+
#[test]
29+
fn must_use_method() {
30+
let mut mock = MockFoo::new();
31+
mock.expect_bloob()
32+
.return_const(42i32);
33+
assert_eq!(42, mock.bloob());
34+
}
35+
36+
#[cfg(feature = "nightly")]
37+
#[test]
38+
fn may_not_use_expectation() {
39+
let mut mock = MockFoo::new();
40+
// This should not produce a "must_use" warning.
41+
mock.expect_bloob();
42+
}
43+
44+
#[test]
45+
fn must_use_static_method() {
46+
let ctx = MockFoo::blarg_context();
47+
ctx.expect()
48+
.return_const(42i32);
49+
assert_eq!(MockFoo::blarg(), 42);
50+
}
51+
52+
#[cfg(feature = "nightly")]
53+
#[test]
54+
fn may_not_use_static_expectation() {
55+
let ctx = MockFoo::blarg_context();
56+
// This should not produce a "must_use" warning.
57+
ctx.expect();
58+
}
59+
60+

mockall_derive/src/lib.rs

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,19 +727,20 @@ fn find_lifetimes(ty: &Type) -> HashSet<Lifetime> {
727727
}
728728
}
729729

730-
731730
struct AttrFormatter<'a>{
732731
attrs: &'a [Attribute],
733732
async_trait: bool,
734733
doc: bool,
734+
must_use: bool,
735735
}
736736

737737
impl<'a> AttrFormatter<'a> {
738738
fn new(attrs: &'a [Attribute]) -> AttrFormatter<'a> {
739739
Self {
740740
attrs,
741741
async_trait: true,
742-
doc: true
742+
doc: true,
743+
must_use: false,
743744
}
744745
}
745746

@@ -753,6 +754,11 @@ impl<'a> AttrFormatter<'a> {
753754
self
754755
}
755756

757+
fn must_use(&mut self, allowed: bool) -> &mut Self {
758+
self.must_use = allowed;
759+
self
760+
}
761+
756762
// XXX This logic requires that attributes are imported with their
757763
// standard names.
758764
#[allow(clippy::needless_bool)]
@@ -773,6 +779,12 @@ impl<'a> AttrFormatter<'a> {
773779
self.doc
774780
} else if *i.as_ref().unwrap() == "async_trait" {
775781
self.async_trait
782+
} else if *i.as_ref().unwrap() == "inline" {
783+
// No need to inline mock functions.
784+
false
785+
} else if *i.as_ref().unwrap() == "cold" {
786+
// No need for such hints on mock functions.
787+
false
776788
} else if *i.as_ref().unwrap() == "instrument" {
777789
// We can't usefully instrument the mock method, so just
778790
// ignore this attribute.
@@ -782,6 +794,8 @@ impl<'a> AttrFormatter<'a> {
782794
// This shows up sometimes when mocking ffi functions. We
783795
// must not emit it on anything that isn't an ffi definition
784796
false
797+
} else if *i.as_ref().unwrap() == "must_use" {
798+
self.must_use
785799
} else {
786800
true
787801
}
@@ -1323,7 +1337,7 @@ fn assert_contains(output: &str, tokens: TokenStream) {
13231337

13241338
fn assert_not_contains(output: &str, tokens: TokenStream) {
13251339
let s = tokens.to_string();
1326-
assert!(!output.contains(&s), "output does not contain {:?}", &s);
1340+
assert!(!output.contains(&s), "output contains {:?}", &s);
13271341
}
13281342

13291343
/// Various tests for overall code generation that are hard or impossible to
@@ -1362,6 +1376,17 @@ mod mock {
13621376
assert_contains(&output, quote!(pub(in crate::outer) fn expect_boom));
13631377
}
13641378

1379+
#[test]
1380+
fn must_use_struct() {
1381+
let code = "
1382+
#[must_use]
1383+
pub Foo {}
1384+
";
1385+
let ts = proc_macro2::TokenStream::from_str(code).unwrap();
1386+
let output = do_mock(ts).to_string();
1387+
assert_contains(&output, quote!(#[must_use] pub struct MockFoo));
1388+
}
1389+
13651390
#[test]
13661391
fn specific_impl() {
13671392
let code = "
@@ -1440,6 +1465,47 @@ mod automock {
14401465
assert_contains(&output, quote!(pub ( in super :: x ) fn expect_bean));
14411466
}
14421467

1468+
#[test]
1469+
fn must_use_method() {
1470+
let code = "
1471+
impl Foo {
1472+
#[must_use]
1473+
fn foo(&self) -> i32 {42}
1474+
}";
1475+
let ts = proc_macro2::TokenStream::from_str(code).unwrap();
1476+
let attrs_ts = proc_macro2::TokenStream::from_str("").unwrap();
1477+
let output = do_automock(attrs_ts, ts).to_string();
1478+
assert_not_contains(&output, quote!(#[must_use] fn expect_foo));
1479+
assert_contains(&output, quote!(#[must_use] #[allow(dead_code)] fn foo));
1480+
}
1481+
1482+
#[test]
1483+
fn must_use_static_method() {
1484+
let code = "
1485+
impl Foo {
1486+
#[must_use]
1487+
fn foo() -> i32 {42}
1488+
}";
1489+
let ts = proc_macro2::TokenStream::from_str(code).unwrap();
1490+
let attrs_ts = proc_macro2::TokenStream::from_str("").unwrap();
1491+
let output = do_automock(attrs_ts, ts).to_string();
1492+
assert_not_contains(&output, quote!(#[must_use] fn expect));
1493+
assert_not_contains(&output, quote!(#[must_use] fn foo_context));
1494+
assert_contains(&output, quote!(#[must_use] #[allow(dead_code)] fn foo));
1495+
}
1496+
1497+
#[test]
1498+
fn must_use_trait() {
1499+
let code = "
1500+
#[must_use]
1501+
trait Foo {}
1502+
";
1503+
let ts = proc_macro2::TokenStream::from_str(code).unwrap();
1504+
let attrs_ts = proc_macro2::TokenStream::from_str("").unwrap();
1505+
let output = do_automock(attrs_ts, ts).to_string();
1506+
assert_not_contains(&output, quote!(#[must_use] struct MockFoo));
1507+
}
1508+
14431509
#[test]
14441510
#[should_panic(expected = "can only mock inline modules")]
14451511
fn external_module() {

mockall_derive/src/mock_function.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,9 @@ impl MockFunction {
469469
// Supplying modname is an unfortunately hack. Ideally MockFunction
470470
// wouldn't need to know that.
471471
pub fn call(&self, modname: Option<&Ident>) -> impl ToTokens {
472-
let attrs = AttrFormatter::new(&self.attrs).format();
472+
let attrs = AttrFormatter::new(&self.attrs)
473+
.must_use(true)
474+
.format();
473475
let call_exprs = &self.call_exprs;
474476
let (_, tg, _) = if self.is_method_generic() || self.is_static() {
475477
&self.egenerics

mockall_derive/src/mock_item_struct.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ impl ToTokens for MockItemStruct {
245245
fn to_tokens(&self, tokens: &mut TokenStream) {
246246
let attrs = AttrFormatter::new(&self.attrs)
247247
.async_trait(false)
248+
.must_use(true)
248249
.format();
249250
let consts = &self.consts;
250251
let debug_impl = self.debug_impl();
@@ -392,7 +393,16 @@ impl MockItemTraitImpl {
392393

393394
impl ToTokens for MockItemTraitImpl {
394395
fn to_tokens(&self, tokens: &mut TokenStream) {
395-
let attrs = AttrFormatter::new(&self.attrs)
396+
let mod_attrs = AttrFormatter::new(&self.attrs)
397+
.async_trait(false)
398+
.doc(false)
399+
.format();
400+
let struct_attrs = AttrFormatter::new(&self.attrs)
401+
.async_trait(false)
402+
.doc(false)
403+
.must_use(false)
404+
.format();
405+
let impl_attrs = AttrFormatter::new(&self.attrs)
396406
.async_trait(false)
397407
.doc(false)
398408
.format();
@@ -408,28 +418,28 @@ impl ToTokens for MockItemTraitImpl {
408418
quote!(
409419
#[allow(non_snake_case)]
410420
#[allow(missing_docs)]
411-
#(#attrs)*
421+
#(#mod_attrs)*
412422
pub mod #modname {
413423
use super::*;
414424
#(#priv_mods)*
415425
}
416426
#[allow(non_camel_case_types)]
417427
#[allow(non_snake_case)]
418428
#[allow(missing_docs)]
419-
#(#attrs)*
429+
#(#struct_attrs)*
420430
struct #struct_name #ig #wc
421431
{
422432
#(#field_definitions),*
423433
}
424-
#(#attrs)*
434+
#(#impl_attrs)*
425435
impl #ig ::std::default::Default for #struct_name #tg #wc {
426436
fn default() -> Self {
427437
Self {
428438
#(#default_inits),*
429439
}
430440
}
431441
}
432-
#(#attrs)*
442+
#(#impl_attrs)*
433443
impl #ig #struct_name #tg #wc {
434444
/// Validate that all current expectations for all methods have
435445
/// been satisfied, and discard them.

mockall_derive/src/mock_trait.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ impl MockTrait {
130130
// Supplying modname is an unfortunately hack. Ideally MockTrait
131131
// wouldn't need to know that.
132132
pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens {
133-
let trait_impl_attrs = &self.attrs;
133+
let trait_impl_attrs = AttrFormatter::new(&self.attrs)
134+
.must_use(false)
135+
.format();
134136
let impl_attrs = AttrFormatter::new(&self.attrs)
135137
.async_trait(false)
136138
.doc(false)

mockall_derive/src/mockable_struct.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,25 @@
11
// vim: tw=80
2-
use super::*;
3-
use syn::parse::{Parse, ParseStream};
2+
use proc_macro2::{Span, TokenStream};
3+
use quote::{format_ident, quote};
4+
use syn::{
5+
*,
6+
parse::{Parse, ParseStream},
7+
spanned::Spanned
8+
};
9+
10+
use crate::{
11+
Attrs,
12+
compile_error,
13+
deanonymize,
14+
deimplify,
15+
demutify,
16+
deselfify,
17+
deselfify_args,
18+
dewhereselfify,
19+
find_ident_from_path,
20+
gen_mock_ident,
21+
AttrFormatter,
22+
};
423

524
/// Make any implicit lifetime parameters explicit
625
fn add_lifetime_parameters(sig: &mut Signature) {
@@ -345,7 +364,15 @@ impl MockableStruct {
345364
impl From<(Attrs, ItemTrait)> for MockableStruct {
346365
fn from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct {
347366
let trait_ = attrs.substitute_trait(&item_trait);
348-
let mut attrs = trait_.attrs.clone();
367+
// Strip "must_use" from a trait definition. For traits, the "must_use"
368+
// should apply only when the trait is used like "impl Trait" or "dyn
369+
// Trait". So it shouldn't necessarily affect the mock struct that
370+
// implements the trait.
371+
let mut attrs = AttrFormatter::new(&trait_.attrs)
372+
.doc(true)
373+
.async_trait(true)
374+
.must_use(false)
375+
.format();
349376
attrs.push(derive_debug());
350377
let vis = trait_.vis.clone();
351378
let name = gen_mock_ident(&trait_.ident);

0 commit comments

Comments
 (0)