#![doc(hidden)]
#[macro_export]
#[doc(hidden)]
macro_rules! __field {
($($t:tt)*) => { $crate::field_internal!($($t)*) }
}
#[doc(hidden)]
#[macro_export]
macro_rules! field_internal {
(
// The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*`
$(& $($_amp:literal)?)? $(:: $($_cs:literal)?)? $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt,
ref $m:expr) => {{
$crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
$crate::field_internal!(@internal
struct_type: [&_]
field_prefix: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::* $(::<$($t_ty_args),*>)*]
[$field] [ref] [$m])
}};
(
$(& $($_amp:literal)?)? $(:: $($_cs:literal)?)? $($t:ident)::+ $(::<$($t_ty_args:ty),* $(,)?>)? .$field:tt, $m:expr) => {{
$crate::field_internal!(@assert_empty $($($_amp)*)* $($($_cs)*)*);
$crate::field_internal!(@internal
struct_type: [$(& $($_amp)*)* &_]
field_prefix: [$(& $($_amp)*)* $(:: $($_cs)*)* $($t)::* $(::<$($t_ty_args),*>)*]
[$field] [] [$m])
}};
(@assert_empty) => {};
(@assert_empty $($l:literal)+) => {
compile_error!("property! argument must start with an optional `&` followed by a path")
};
(@internal struct_type: [$struct_ty:ty]
field_prefix: [$($field_prefix:tt)*]
[$field:tt] [$($ref:tt)?] [$m:expr]) => {{
$crate::matchers::__internal_unstable_do_not_depend_on_these::field_matcher(
|o: $struct_ty| {
match o {
$($field_prefix)* {$field: $($ref)* value, .. } => Some(value),
#[allow(unreachable_patterns)]
_ => None,
}
},
&stringify!($field),
$crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m))
}}
}
#[doc(hidden)]
pub mod internal {
use crate::{
description::Description,
matcher::{Matcher, MatcherBase, MatcherResult},
};
use std::fmt::Debug;
#[doc(hidden)]
pub fn field_matcher<OuterT, InnerT, InnerMatcher>(
field_accessor: fn(&OuterT) -> Option<&InnerT>,
field_path: &'static str,
inner: InnerMatcher,
) -> FieldMatcher<OuterT, InnerT, InnerMatcher> {
FieldMatcher { field_accessor, field_path, inner }
}
#[derive(MatcherBase)]
pub struct FieldMatcher<OuterT, InnerT, InnerMatcher> {
field_accessor: fn(&OuterT) -> Option<&InnerT>,
field_path: &'static str,
inner: InnerMatcher,
}
impl<'a, OuterT: Debug + 'a, InnerT: Debug + 'a, InnerMatcher: Matcher<&'a InnerT>>
Matcher<&'a OuterT> for FieldMatcher<OuterT, InnerT, InnerMatcher>
{
fn matches(&self, actual: &'a OuterT) -> MatcherResult {
if let Some(value) = (self.field_accessor)(actual) {
self.inner.matches(value)
} else {
MatcherResult::NoMatch
}
}
fn explain_match(&self, actual: &'a OuterT) -> Description {
if let Some(actual) = (self.field_accessor)(actual) {
format!(
"which has field `{}`, {}",
self.field_path,
self.inner.explain_match(actual)
)
.into()
} else {
let formatted_actual_value = format!("{actual:?}");
let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
format!("which has the wrong enum variant `{without_fields}`").into()
}
}
fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"has field `{}`, which {}",
self.field_path,
self.inner.describe(matcher_result)
)
.into()
}
}
impl<OuterT: Debug + Copy, InnerT: Debug + Copy, InnerMatcher: Matcher<InnerT>> Matcher<OuterT>
for FieldMatcher<OuterT, InnerT, InnerMatcher>
{
fn matches(&self, actual: OuterT) -> MatcherResult {
if let Some(value) = (self.field_accessor)(&actual) {
self.inner.matches(*value)
} else {
MatcherResult::NoMatch
}
}
fn explain_match(&self, actual: OuterT) -> Description {
if let Some(actual) = (self.field_accessor)(&actual) {
format!(
"which has field `{}`, {}",
self.field_path,
self.inner.explain_match(*actual)
)
.into()
} else {
let formatted_actual_value = format!("{actual:?}");
let without_fields = formatted_actual_value.split('(').next().unwrap_or("");
let without_fields = without_fields.split('{').next().unwrap_or("").trim_end();
format!("which has the wrong enum variant `{without_fields}`").into()
}
}
fn describe(&self, matcher_result: MatcherResult) -> Description {
format!(
"has field `{}`, which {}",
self.field_path,
self.inner.describe(matcher_result)
)
.into()
}
}
}