-
Notifications
You must be signed in to change notification settings - Fork 14
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
StructMatcher procedural macro #447
Comments
There is some support for this with To avoid the disadvantages, we could use cfg_attr, which should avoid adding the macro to the public dependencies. Nice trick with Declaration
But it is not possible to implement a Wait for rust-lang/rust#86555rust-lang/rust#86555 will allow to implement:
which can be passed as default:
Use a builder patternWe can easily implement a generic builder to set fields like:
but this is less elegant than using a constructor directly. |
I completely missed the The "declaration" idea was my first thought (not fully formed of course). Its main advantage being omitting I definitely don't like the builder pattern, especially for nesting these. I can only imagine what horrors rustfmt would come up with. It is an important alternative though. |
In the matcher-generator approach, there's a further nuance of nested structs and figuring out how to do the right thing there if the nested struct has a corresponding matcher struct generated vs not. So given that (Looking to possibly take this on.) |
@marcianx, nested structs should be handled like any other field. So for a struct like: #[googletest::StructMatcher]
struct MyType {
field_1: MyNestedStruct,
field_2: String,
}
struct MyNestedStruct {
a: i32,
b: u32,
} It should generate something like: #[cfg(test)]
struct MyTypeStructMatcher {
field_1: Option<Box<dyn Matcher<ActualT = MyNestedStruct>>>,
field_2: Option<Box<dyn Matcher<ActualT = String>>>,
} Whether a matcher exists that you can put there is up to the caller. For example, they can use a As for |
Ah, you're right about nested structs -- your representation is quite generic. It would be a pity to make a second strongly overlapping way to do the same thing that trait ReflectFields {
fn field_names() -> Vec<String>;
} possibly even defined in a separate crate of we want for any reason to avoid making all of
I've not tested the feasibility of this approach yet, though it would likely require converting The disadvantage of this approach is that lack of exhaustiveness would be caught at runtime instead of compile time. WDYT? |
Per chat with @bjacotg:
I'll investigate a solution similar to the original post. Please LMK @bjacotg if you'd like me to attempt a different direction. |
I was experimenting and chatting with @bjacotg, and this is where we landed so far Tool behaviorTo understand the constraints imposed by
So maybe using a macro-based solution, even with nested macros, wouldn't hurt auto-complete too badly as noted above and maybe we could reduce the scope to just focus on making field matching exhaustive in the absence of an explicit Field exhaustivenessIt occurred to me that we don't really need the full-powered @bjacotg then had the brilliant idea of modifying the strategy to generate a Given: verify_that!(
actual,
matches_pattern!(
Foo {
a: eq(1),
b: eq("foo"),
method(): starts_with!("3"),
}
),
)
{
fn __matches_pattern_ensure_exhastive_match(val: Foo) {
let _ = match val {
Foo { a: _, b: _ } => "ensure exhaustive matches_pattern!",
};
}
// ... actual matching logic ...
} which would result in a compilation error when a new field (say,
The compilation error isn't the most direct, but hopefuly the inline string makes it obvious. |
Woah, the field exhaustiveness idea is so clever!!! The cost of a bad error message is sad, but for the simplicity of not needing a struct attribute, it might be worth it! I wonder if the error message would get better or worse by forcing newlines into the generated code (if that's even allowed in declarative macros). I would imagine seeing the struct definition on multiple lines might make it more readable. Thanks for proving me wrong about making the macro exhaustive hahaha. |
…implify subsequent improvements to its functionality (specifically #447). PiperOrigin-RevId: 699526998
…implify subsequent improvements to its functionality (specifically #447). PiperOrigin-RevId: 699526998
…implify subsequent improvements to its functionality (specifically #447). PiperOrigin-RevId: 699526998
…implify subsequent improvements to its functionality (specifically #447). PiperOrigin-RevId: 700322995
Ugh, one of the issues I ran into after implementing (thanks to extensive tests!): if quote! {
fn __matches_pattern_ensure_exhastive_match(i: usize) {
let val: [_; 0] = [];
let _ = match val[i] {
#struct_name { #(#field_names: _),* } => "ensure exhaustive matches_pattern!",
};
}
} Seems to work for enums! |
…tern` enforce exhaustive field checks for braced structs by default, to be suppressed by explicit `..` at the end of the pattern. This change is not backward-compatible since: * It enforces field exhaustiveness by default in the absence of a trailing `..` in the pattern. * Things that previously fell back to `match` pattern matching now use `match_pattern!`'s specialized matching, which requires the matched object to implement `Debug`. Toward #447 PiperOrigin-RevId: 700843379
This change is not backward-compatible since: * Some things that previously fell back to `match` pattern matching now use `match_pattern!`'s specialized matching, which requires the matched object to implement `Debug`. Toward #447 PiperOrigin-RevId: 700889703
This change is not backward-compatible since: * Some things that previously fell back to `match` pattern matching now use `match_pattern!`'s specialized matching, which requires the matched object to implement `Debug`. Toward #447 PiperOrigin-RevId: 700889703
This change is not backward-compatible since: * Some things that previously fell back to `match` pattern matching now use `match_pattern!`'s specialized matching, which requires the matched object to implement `Debug`. Toward #447 PiperOrigin-RevId: 701969181
…tern` enforce exhaustive field checks for braced structs by default, to be suppressed by explicit `..` at the end of the pattern. This change is not backward-compatible since: * It enforces field exhaustiveness by default in the absence of a trailing `..` in the pattern. * Things that previously fell back to `match` pattern matching now use `match_pattern!`'s specialized matching, which requires the matched object to implement `Debug`. Toward #447 PiperOrigin-RevId: 700843379
This is something I've dreamed of having in the C++ googletest library. The difference is in Rust this seems possible!
This attribute macro would generate something akin to:
(A supporting enum in googletest code to make it easier to construct the field matcher)
This enables usage similar to the following:
Disadvantages
We need to make
googletest
(or some related crate) a public dependency of crates. Thankfully the#[cfg(test)]
attribute should prune the code gen for the entire implementation, but we do still pay the cost of the procedural macro.The text was updated successfully, but these errors were encountered: