// Copyright 2022 Google LLC // // 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. // There are no visible documentation elements in this module; the declarative // macro is documented in the matchers module. #![doc(hidden)] /// Matches a structure or enum with a given field which is matched by a given /// matcher. /// /// This takes two arguments: /// /// * a specification of the field against which to match, and /// * an inner [`Matcher`][crate::matcher::Matcher] to apply to that field. /// /// For example: /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// struct IntField { /// int: i32 /// } /// # fn should_pass() -> Result<()> { /// verify_that!(IntField{int: 32}, field!(&IntField.int, eq(32)))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// Tuple structs are also supported via the index syntax: /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// struct IntField(i32); /// # fn should_pass() -> Result<()> { /// verify_that!(IntField(32), field!(&IntField.0, eq(32)))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// Enums are also supported, in which case only the specified variant is /// matched: /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// enum MyEnum { /// A(i32), /// B, /// } /// # fn should_pass() -> Result<()> { /// verify_that!(MyEnum::A(32), field!(&MyEnum::A.0, eq(32)))?; // Passes /// # Ok(()) /// # } /// # fn should_fail() -> Result<()> { /// verify_that!(MyEnum::B, field!(&MyEnum::A.0, eq(32)))?; // Fails: wrong enum variant /// # Ok(()) /// # } /// # should_pass().unwrap(); /// # should_fail().unwrap_err(); /// ``` /// /// The structure or enum may also be referenced from a separate module: /// /// ``` /// # use googletest::prelude::*; /// mod a_module { /// #[derive(Debug)] /// pub struct AStruct(pub i32); /// } /// # fn should_pass() -> Result<()> { /// verify_that!(a_module::AStruct(32), field!(&a_module::AStruct.0, eq(32)))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// If the inner matcher is `eq(...)`, it can be omitted: /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// struct IntField { /// int: i32 /// } /// # fn should_pass() -> Result<()> { /// verify_that!(IntField{int: 32}, field!(&IntField.int, 32))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// Nested structures are *not supported*, however: /// /// ```compile_fail /// # use googletest::prelude::*; /// #[derive(Debug)] /// struct InnerStruct(i32); /// #[derive(Debug)] /// struct OuterStruct { /// inner: InnerStruct, /// } /// # fn should_not_compile() -> Result<()> { /// verify_that!(value, field!(OuterStruct.inner.0, eq(32)))?; // Does not compile /// # Ok(()) /// # } /// ``` /// /// # Specification of the field pattern /// /// The specification of the field follow the syntax: `(ref)? (&)? /// $TYPE.$FIELD`. /// The `&` allows to specify whether this matcher matches against an actual of /// type `$TYPE` (`$TYPE`` must implement `Copy`) or a `&$TYPE`. /// /// For instance: /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// pub struct AStruct{a_field: i32}; /// # fn should_pass() -> Result<()> { /// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug, Clone, Copy)] /// pub struct AStruct{a_field: i32}; /// # fn should_pass() -> Result<()> { /// verify_that!(AStruct{a_field: 32}, field!(AStruct.a_field, eq(32)))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// The `ref` allows to bind the field value by reference, which is required if /// the field type does not implement `Copy`. /// /// For instance: /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// pub struct AStruct{a_field: i32}; /// # fn should_pass() -> Result<()> { /// verify_that!(AStruct{a_field: 32}, field!(&AStruct.a_field, eq(32)))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// If `field!` is qualified by both `&` and `ref`, they can both be omitted. /// /// ``` /// # use googletest::prelude::*; /// #[derive(Debug)] /// pub struct AStruct{a_field: String}; /// # fn should_pass() -> Result<()> { /// verify_that!(AStruct{a_field: "32".into()}, field!(&AStruct.a_field, ref eq("32")))?; /// verify_that!(AStruct{a_field: "32".into()}, field!(AStruct.a_field, eq("32")))?; /// # Ok(()) /// # } /// # should_pass().unwrap(); /// ``` /// /// See also the macro [`property`][crate::matchers::property] for an analogous /// mechanism to extract a datum by invoking a method. #[macro_export] #[doc(hidden)] macro_rules! __field { ($($t:tt)*) => { $crate::field_internal!($($t)*) } } // Internal-only macro created so that the macro definition does not appear in // generated documentation. // We cannot use `path` or `ty` to capture the type as we are terminating the // type with a . (dot). #[doc(hidden)] #[macro_export] macro_rules! field_internal { // `ref` variant. ( // The 3 cases of `$(& $($_amp:literal)?)?` -> `$(& $($_amp)*)*` // 1. outer group captures nothing => expansion produces nothing // 2. outer group captures just `&` => expansion produces `&` // 3. outer group captures `& ` => disallowed by `@assert_empty` subrule invocation // // `$_amp:literal` works only because the following `$t:ident` or `::` can't be captured by // it. $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`. $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`. $($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]) }}; // Non-`ref` variant. ( // See comment on previous variant above. $(& $($_amp:literal)?)? // Optional `&`'s presence is implicitly captured by `_amp`. $(:: $($_cs:literal)?)? // Optional `::`'s presence is implicitly captured by `_cs`. $($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), // The pattern below is unreachable if the type is a struct (as opposed to an // enum). Since the macro can't know which it is, we always include it and just // tell the compiler not to complain. #[allow(unreachable_patterns)] _ => None, } }, &stringify!($field), $crate::matcher_support::__internal_unstable_do_not_depend_on_these::auto_eq!($m)) }} } /// Functions for use only by the declarative macros in this module. /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] pub mod internal { use crate::{ description::Description, matcher::{Matcher, MatcherBase, MatcherResult}, }; use std::fmt::Debug; /// Creates a matcher to verify a specific field of the actual struct using /// the provided inner matcher. /// /// **For internal use only. API stablility is not guaranteed!** #[doc(hidden)] pub fn field_matcher( field_accessor: fn(&OuterT) -> Option<&InnerT>, field_path: &'static str, inner: InnerMatcher, ) -> FieldMatcher { FieldMatcher { field_accessor, field_path, inner } } #[derive(MatcherBase)] pub struct FieldMatcher { 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 { 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> Matcher for FieldMatcher { 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() } } }