Skip to content

Fixing type annotation with PhantomData #337

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

Closed
wants to merge 1 commit into from
Closed
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
19 changes: 16 additions & 3 deletions googletest/src/matchers/conjunction_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,14 @@ impl<T: Debug, M1: Matcher<T>, M2: Matcher<T>> Matcher<T> for ConjunctionMatcher

#[cfg(test)]
mod tests {
use super::AndMatcherExt;
#[cfg(not(google3))]
use crate::{field, matchers};
use crate::{verify_that, Result};
use crate::{verify_that, Result, matcher::Matcher, matchers::AndMatcherExt};
#[cfg(google3)]
use matchers::field;
use matchers::{anything, contains_substring, displays_as, eq, err, not};
use matchers::{
anything, contains_substring, displays_as, ends_with, eq, err, not, starts_with,
};

#[test]
fn and_true_true_matches() -> Result<()> {
Expand Down Expand Up @@ -149,4 +150,16 @@ mod tests {
field!(Struct.a, eq(1)).and(field!(Struct.b, eq(2))).and(field!(Struct.c, eq(3)))
)
}

fn chained_and_with_string_slice() -> Result<()> {
let actual = "what goes up must come down";

verify_that!(actual, starts_with("what goes up").and(ends_with("must come down")))
}

fn chained_and_with_owned_string() -> Result<()> {
let actual = "what goes up must come down".to_string();

verify_that!(actual, starts_with("what goes up").and(ends_with("must come down")))
}
}
55 changes: 30 additions & 25 deletions googletest/src/matchers/str_matcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use eq_matcher::EqMatcher;
use googletest::*;
use std::borrow::Cow;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::ops::Deref;

/// Matches a string containing a given substring.
Expand Down Expand Up @@ -52,10 +53,11 @@ use std::ops::Deref;
/// > and expected values when matching strings while
/// > [`ignoring_ascii_case`][StrMatcherConfigurator::ignoring_ascii_case] is
/// > set.
pub fn contains_substring<T>(expected: T) -> StrMatcher<T> {
pub fn contains_substring<T, ActualT>(expected: T) -> StrMatcher<T, ActualT> {
StrMatcher {
configuration: Configuration { mode: MatchMode::Contains, ..Default::default() },
expected,
phantom: PhantomData,
}
}

Expand Down Expand Up @@ -88,10 +90,11 @@ pub fn contains_substring<T>(expected: T) -> StrMatcher<T> {
/// # should_fail_2().unwrap_err();
/// # should_pass_2().unwrap();
/// ```
pub fn starts_with<T>(expected: T) -> StrMatcher<T> {
pub fn starts_with<T, ActualT: ?Sized>(expected: T) -> StrMatcher<T, ActualT> {
StrMatcher {
configuration: Configuration { mode: MatchMode::StartsWith, ..Default::default() },
expected,
phantom: PhantomData
}
}

Expand Down Expand Up @@ -124,10 +127,11 @@ pub fn starts_with<T>(expected: T) -> StrMatcher<T> {
/// # should_fail_2().unwrap_err();
/// # should_pass_2().unwrap();
/// ```
pub fn ends_with<T>(expected: T) -> StrMatcher<T> {
pub fn ends_with<T, ActualT: ?Sized>(expected: T) -> StrMatcher<T, ActualT> {
StrMatcher {
configuration: Configuration { mode: MatchMode::EndsWith, ..Default::default() },
expected,
phantom: PhantomData
}
}

Expand All @@ -136,7 +140,7 @@ pub fn ends_with<T>(expected: T) -> StrMatcher<T> {
/// Matchers which match against string values and, through configuration,
/// specialise to [StrMatcher] implement this trait. Currently that only
/// includes [EqMatcher] and [StrMatcher].
pub trait StrMatcherConfigurator<ExpectedT> {
pub trait StrMatcherConfigurator<ExpectedT, ActualT> {
/// Configures the matcher to ignore any leading whitespace in either the
/// actual or the expected value.
///
Expand All @@ -157,7 +161,7 @@ pub trait StrMatcherConfigurator<ExpectedT> {
/// When all other configuration options are left as the defaults, this is
/// equivalent to invoking [`str::trim_start`] on both the expected and
/// actual value.
fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT>;
fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT, ActualT>;

/// Configures the matcher to ignore any trailing whitespace in either the
/// actual or the expected value.
Expand All @@ -179,7 +183,7 @@ pub trait StrMatcherConfigurator<ExpectedT> {
/// When all other configuration options are left as the defaults, this is
/// equivalent to invoking [`str::trim_end`] on both the expected and
/// actual value.
fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT>;
fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT, ActualT>;

/// Configures the matcher to ignore both leading and trailing whitespace in
/// either the actual or the expected value.
Expand All @@ -205,7 +209,7 @@ pub trait StrMatcherConfigurator<ExpectedT> {
/// When all other configuration options are left as the defaults, this is
/// equivalent to invoking [`str::trim`] on both the expected and actual
/// value.
fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT>;
fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT, ActualT>;

/// Configures the matcher to ignore ASCII case when comparing values.
///
Expand All @@ -229,7 +233,7 @@ pub trait StrMatcherConfigurator<ExpectedT> {
///
/// This is **not guaranteed** to match strings with differing upper/lower
/// case characters outside of the codepoints 0-127 covered by ASCII.
fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT>;
fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT, ActualT>;

/// Configures the matcher to match only strings which otherwise satisfy the
/// conditions a number times matched by the matcher `times`.
Expand Down Expand Up @@ -272,7 +276,7 @@ pub trait StrMatcherConfigurator<ExpectedT> {
/// This is only meaningful when the matcher was constructed with
/// [`contains_substring`]. This method will panic when it is used with any
/// other matcher construction.
fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT>;
fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT, ActualT>;
}

/// A matcher which matches equality or containment of a string-like value in a
Expand All @@ -284,12 +288,13 @@ pub trait StrMatcherConfigurator<ExpectedT> {
/// * [`contains_substring`],
/// * [`starts_with`],
/// * [`ends_with`].
pub struct StrMatcher<ExpectedT> {
pub struct StrMatcher<ExpectedT, ActualT: ?Sized> {
expected: ExpectedT,
configuration: Configuration,
phantom: PhantomData<ActualT>,
}

impl<ExpectedT, ActualT> Matcher<ActualT> for StrMatcher<ExpectedT>
impl<ExpectedT, ActualT> Matcher<ActualT> for StrMatcher<ExpectedT, ActualT>
where
ExpectedT: Deref<Target = str> + Debug,
ActualT: AsRef<str> + Debug + ?Sized,
Expand All @@ -303,36 +308,36 @@ where
}
}

impl<ExpectedT, MatcherT: Into<StrMatcher<ExpectedT>>> StrMatcherConfigurator<ExpectedT>
for MatcherT
impl<ActualT, ExpectedT, MatcherT: Into<StrMatcher<ExpectedT, ActualT>>>
StrMatcherConfigurator<ExpectedT, ActualT> for MatcherT
{
fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT> {
fn ignoring_leading_whitespace(self) -> StrMatcher<ExpectedT, ActualT> {
let existing = self.into();
StrMatcher {
configuration: existing.configuration.ignoring_leading_whitespace(),
..existing
}
}

fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT> {
fn ignoring_trailing_whitespace(self) -> StrMatcher<ExpectedT, ActualT> {
let existing = self.into();
StrMatcher {
configuration: existing.configuration.ignoring_trailing_whitespace(),
..existing
}
}

fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT> {
fn ignoring_outer_whitespace(self) -> StrMatcher<ExpectedT, ActualT> {
let existing = self.into();
StrMatcher { configuration: existing.configuration.ignoring_outer_whitespace(), ..existing }
}

fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT> {
fn ignoring_ascii_case(self) -> StrMatcher<ExpectedT, ActualT> {
let existing = self.into();
StrMatcher { configuration: existing.configuration.ignoring_ascii_case(), ..existing }
}

fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT> {
fn times(self, times: impl Matcher<usize> + 'static) -> StrMatcher<ExpectedT, ActualT> {
let existing = self.into();
if !matches!(existing.configuration.mode, MatchMode::Contains) {
panic!("The times() configurator is only meaningful with contains_substring().");
Expand All @@ -341,19 +346,19 @@ impl<ExpectedT, MatcherT: Into<StrMatcher<ExpectedT>>> StrMatcherConfigurator<Ex
}
}

impl<T: Deref<Target = str>> From<EqMatcher<T>> for StrMatcher<T> {
impl<T: Deref<Target = str>, ActualT> From<EqMatcher<T>> for StrMatcher<T, ActualT> {
fn from(value: EqMatcher<T>) -> Self {
Self::with_default_config(value.expected)
}
}

impl<T> StrMatcher<T> {
impl<T, ActualT> StrMatcher<T, ActualT> {
/// Returns a [`StrMatcher`] with a default configuration to match against
/// the given expected value.
///
/// This default configuration is sensitive to whitespace and case.
fn with_default_config(expected: T) -> Self {
Self { expected, configuration: Default::default() }
Self { expected, configuration: Default::default(), phantom: PhantomData }
}
}

Expand Down Expand Up @@ -636,8 +641,8 @@ mod tests {
}

#[test]
fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case()
-> Result<()> {
fn matches_string_containing_expected_value_in_contains_mode_while_ignoring_ascii_case(
) -> Result<()> {
verify_that!("Some string", contains_substring("STR").ignoring_ascii_case())
}

Expand Down Expand Up @@ -781,8 +786,8 @@ mod tests {
}

#[test]
fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace()
-> Result<()> {
fn describes_itself_for_matching_result_ignoring_ascii_case_and_leading_whitespace(
) -> Result<()> {
let matcher = StrMatcher::with_default_config("A string")
.ignoring_leading_whitespace()
.ignoring_ascii_case();
Expand Down