Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement WhenStaticCastTo #4719

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/reference/matchers.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,15 @@ messages, you can use:
| `Pointee(m)` | `argument` (either a smart pointer or a raw pointer) points to a value that matches matcher `m`. |
| `Pointer(m)` | `argument` (either a smart pointer or a raw pointer) contains a pointer that matches `m`. `m` will match against the raw pointer regardless of the type of `argument`. |
| `WhenDynamicCastTo<T>(m)` | when `argument` is passed through `dynamic_cast<T>()`, it matches matcher `m`. |
| `WhenStaticCastTo<T>(m)` | when `argument` is passed through `static_cast<T>()`, it matches matcher `m`. |

`WhenDynamicCast` can be used for safely checking the dynamic type of an object
and navigating the inheritance tree of an object.

`WhenStaticCast` is primarily used to check and argument which was type-erased
as `void*`. It can also be used as an unsafe replacement of `WhenDynamicCast`
in environemnts without RTTI, or for pointer-based type punning by chaining
a cast to `void*` and then another pointer (equivalent to `reinterpret_cast`).

## Multi-argument Matchers {#MultiArgMatchers}

Expand Down
84 changes: 60 additions & 24 deletions googlemock/include/gmock/gmock-matchers.h
Original file line number Diff line number Diff line change
Expand Up @@ -2022,17 +2022,10 @@ class PointerMatcher {
const InnerMatcher matcher_;
};

#if GTEST_HAS_RTTI
// Implements the WhenDynamicCastTo<T>(m) matcher that matches a pointer or
// reference that matches inner_matcher when dynamic_cast<T> is applied.
// The result of dynamic_cast<To> is forwarded to the inner matcher.
// If To is a pointer and the cast fails, the inner matcher will receive NULL.
// If To is a reference and the cast fails, this matcher returns false
// immediately.
template <typename To>
class WhenDynamicCastToMatcherBase {
template <typename To, typename Caster>
class WhenCastToMatcherBase {
public:
explicit WhenDynamicCastToMatcherBase(const Matcher<To>& matcher)
explicit WhenCastToMatcherBase(const Matcher<To>& matcher)
: matcher_(matcher) {}

void DescribeTo(::std::ostream* os) const {
Expand All @@ -2045,39 +2038,53 @@ class WhenDynamicCastToMatcherBase {
matcher_.DescribeNegationTo(os);
}

template <typename From>
bool MatchAndExplain(From&& from, MatchResultListener* listener) const {
decltype(auto) to = Caster::template Cast<To>(from);
return MatchPrintAndExplain(to, this->matcher_, listener);
}

protected:
const Matcher<To> matcher_;

static std::string GetToName() { return GetTypeName<To>(); }

private:
static void GetCastTypeDescription(::std::ostream* os) {
*os << "when dynamic_cast to " << GetToName() << ", ";
*os << "when " << Caster::Name << " to " << GetToName() << ", ";
}
};

// Primary template.
// To is a pointer. Cast and forward the result.
template <typename To>
class WhenDynamicCastToMatcher : public WhenDynamicCastToMatcherBase<To> {
#if GTEST_HAS_RTTI
class DynamicCaster {
public:
explicit WhenDynamicCastToMatcher(const Matcher<To>& matcher)
: WhenDynamicCastToMatcherBase<To>(matcher) {}
static constexpr const char* Name = "dynamic_cast";

template <typename From>
bool MatchAndExplain(From from, MatchResultListener* listener) const {
To to = dynamic_cast<To>(from);
return MatchPrintAndExplain(to, this->matcher_, listener);
template <typename To, typename From>
static To Cast(From&& from) {
return dynamic_cast<To>(from);
}
};

// Implements the WhenDynamicCastTo<T>(m) matcher that matches a pointer or
// reference that matches inner_matcher when dynamic_cast<T> is applied.
// The result of dynamic_cast<To> is forwarded to the inner matcher.

// To is a pointer. Cast and forward the result, which might be nullptr.
template <typename To>
class WhenDynamicCastToMatcher
: public WhenCastToMatcherBase<To, DynamicCaster> {
public:
using WhenCastToMatcherBase<To, DynamicCaster>::WhenCastToMatcherBase;
};

// Specialize for references.
// In this case we return false if the dynamic_cast fails.
template <typename To>
class WhenDynamicCastToMatcher<To&> : public WhenDynamicCastToMatcherBase<To&> {
class WhenDynamicCastToMatcher<To&>
: public WhenCastToMatcherBase<To&, DynamicCaster> {
public:
explicit WhenDynamicCastToMatcher(const Matcher<To&>& matcher)
: WhenDynamicCastToMatcherBase<To&>(matcher) {}
using WhenCastToMatcherBase<To&, DynamicCaster>::WhenCastToMatcherBase;

template <typename From>
bool MatchAndExplain(From& from, MatchResultListener* listener) const {
Expand All @@ -2092,6 +2099,25 @@ class WhenDynamicCastToMatcher<To&> : public WhenDynamicCastToMatcherBase<To&> {
};
#endif // GTEST_HAS_RTTI

// Implements the WhenStaticCastTo<T>(m) matcher that matches a pointer or
// reference that matches inner_matcher when static_cast<T> is applied.
// The result of static_cast<To> is forwarded to the inner matcher.
class StaticCaster {
public:
static constexpr const char* Name = "static_cast";

template <typename To, typename From>
static To Cast(From&& from) {
return static_cast<To>(from);
}
};

template <typename To>
class WhenStaticCastToMatcher : public WhenCastToMatcherBase<To, StaticCaster> {
public:
using WhenCastToMatcherBase<To, StaticCaster>::WhenCastToMatcherBase;
};

// Implements the Field() matcher for matching a field (i.e. member
// variable) of an object.
template <typename Class, typename FieldType>
Expand Down Expand Up @@ -4424,6 +4450,16 @@ WhenDynamicCastTo(const Matcher<To>& inner_matcher) {
}
#endif // GTEST_HAS_RTTI

// Creates a matcher that matches a pointer or reference that matches
// inner_matcher when static_cast<To> is applied.
// The result of static_cast<To> is forwarded to the inner matcher.
template <typename To>
inline PolymorphicMatcher<internal::WhenStaticCastToMatcher<To>>
WhenStaticCastTo(const Matcher<To>& inner_matcher) {
return MakePolymorphicMatcher(
internal::WhenStaticCastToMatcher<To>(inner_matcher));
}

// Creates a matcher that matches an object whose given field matches
// 'matcher'. For example,
// Field(&Foo::number, Ge(5))
Expand Down
35 changes: 35 additions & 0 deletions googlemock/test/gmock-matchers-comparisons_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2333,6 +2333,41 @@ TEST(WhenDynamicCastToTest, BadReference) {
}
#endif // GTEST_HAS_RTTI

TEST(WhenStaticCastToTest, VoidPointer) {
Derived derived;
derived.i = 4;
void* as_void_ptr = &derived;
EXPECT_THAT(as_void_ptr, WhenStaticCastTo<Derived*>(Pointee(FieldIIs(4))));
EXPECT_THAT(as_void_ptr,
WhenStaticCastTo<Derived*>(Pointee(Not(FieldIIs(5)))));
}

TEST(WhenStaticCastToTest, Inheritance) {
Derived derived;
EXPECT_THAT(derived, WhenStaticCastTo<const Base&>(_));
EXPECT_THAT(&derived, WhenStaticCastTo<Base*>(_));
EXPECT_THAT(derived, WhenStaticCastTo<const Derived&>(_));
EXPECT_THAT(&derived, WhenStaticCastTo<Derived*>(_));
// These will not compile because direct sidecasts are invalid
// EXPECT_THAT(derived, WhenStaticCastTo<const OtherDerived&>(_));
// EXPECT_THAT(&derived, WhenStaticCastTo<OtherDerived*>(_));

Base& as_base_ref = derived;
EXPECT_THAT(as_base_ref, WhenStaticCastTo<const Base&>(_));
EXPECT_THAT(&as_base_ref, WhenStaticCastTo<Base*>(_));
EXPECT_THAT(as_base_ref, WhenStaticCastTo<const Derived&>(_));
EXPECT_THAT(&as_base_ref, WhenStaticCastTo<Derived*>(_));
// These will compile and match, but are not safe
EXPECT_THAT(as_base_ref, WhenStaticCastTo<const OtherDerived&>(_));
EXPECT_THAT(&as_base_ref, WhenStaticCastTo<OtherDerived*>(_));
}

TEST(WhenStaticCastToTest, Punning) {
std::uint32_t u32 = 0xCCCCCCCC;
EXPECT_THAT(&u32, WhenStaticCastTo<void*>(
WhenStaticCastTo<std::uint8_t*>(Pointee(Eq(0xCC)))));
}

class DivisibleByImpl {
public:
explicit DivisibleByImpl(int a_divider) : divider_(a_divider) {}
Expand Down