-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Tracking Issue for assert_matches #82775
Comments
Several implementations of
This is currently not supported by Adding it would allow things like: let thing = assert_matches!(thing, Some(x) => x); and assert_matches!(thing, Some((x, y)) => {
assert_eq!(x.a, 10);
assert_eq!(y.b(), 20);
}); |
As a (almost) daily user of the I.e. asserting a certain sequence of messages with a mocked context and assuring the messages have expected content (which by design may not impl type A = u8;
struct B(u8);
struct C(u8);
struct D(u8);
enum Msgs {
Specific { a: A, b: B, c: C, d: D},
// snip
}
assert_matches!(rx.next().unwrap(), Msgs::Specific { a, b: B(b), .. } => { assert_eq!(a, b); })
assert_matches!(rx.next().unwrap(), Msgs::Specific { b, c: C(c), .. } => { assert_eq!(b, c); })
.. so for this the extension with |
In cases like that, the assert_matches!(rx.next(), Some(Msgs::Specific { a, b: B(b), .. }) if a == b); |
While your approach is valid for simple cases, I disagree for the general use case. This is not very ergonomic as soon as there are various sub patterns, that have to be asserted against transformed struct member values or larger enums with multiple elements common for messaging enums. assert_matches!(rx.next(), Some(Msgs::Specific { a, b: B(b), .. }) => {
assert_eq!(a, b);
let container = transform($container);
if container.is_empty() {
assert!(..);
} else {
assert!(..);
}
});
.. It could still be expressed with boolean expression combinators using the current impl, yet that adds a lot visual complexity that imho should not exist in a test case. Note that I am not saying the impl is bad or anything :) - I would very much like to see |
@rust-lang/libs Ping for opinions. Should |
It's an interesting extension. At first it looked a bit odd to me, but after ruminating on it a bit, it does seem fairly natural to me. In terms of moving forward:
|
This makes me think of the proposal to add This seems very similar, and I feel like the arguments are the same. If we have I personally feel that in both cases the |
We could require the |
For me as well; that was not clear to me at all. |
A separate macro that always requires a |
Well if we're agreed on leaving it as is then we can head towards stabilising it... |
@drahnr in #82775 (comment)
Your example can be rewritten to a match guard that always returns true except when it panics, like this. -assert_matches!(rx.next(), Some(Msgs::Specific { a, b: B(b), .. }) => {
+assert_matches!(rx.next(), Some(Msgs::Specific { a, b: B(b), .. }) if ({
assert_eq!(a, b);
let container = transform($container);
if container.is_empty() {
assert!(/* … */);
} else {
assert!(/* … */);
}
-});
+ true
+})); Definitely non-obvious when you don't know about it, but hardly worse to write if you are adding to an existing test suite with the pattern everywhere. |
Re-reading the comments, I fully support the current impl and withdraw my previous concerns. |
I would prefer the macro to expand as Does this sound reasonable? Would |
@fee1-dead that would be very confusing to me. An assertion should assert and nothing more imo |
I think there's a lot of value in having |
Looks like we reached consensus on not having the @rfcbot merge |
Team member @m-ou-se has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
For me, my biggest concern is ensuring that, once the nature of pattern syntax has been learned, the number of "dialects" of it that must be memorized doesn't grow. (i.e. "refutable" vs. "irrefutable" is already enough in a language with so much other complexity that also needs to be learned.) |
I agree that having multiple pattern syntaxes would be a bad thing. I believe that, in this situation the choice of the word |
I think you are misunderstanding what i am trying to say. I am saying if you think the if syntax is confusing, we should fix it in the general sense, not for Maybe I am misunderstanding you if you are talking about different comments. |
To me I'm mostly fine with pattern syntax in existing places, though it's difficult to asses ones own biases and knowledge curse. For me Also there is precedence for not allowing the full pattern syntax everywhere, for example |
assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); is basically equivalent to :
If the
I don't think this is any less of a problem in the match example above. Although, in my opinion is is pretty clear that
Well, I've mostly just agreed with others have said and haven't had a lot else to add to it. Or time to formulate a more thorough response. I do have a couple of ideas that might help with this though:
|
I should clarify, I meant it's more clear it applies to the branch, as highlighted by the separate line and something following it, the branch body. In |
Something like this could apply in all cases patterns are used in |
FWIW, Also, about this running example: binding a variable in multiple arms and then using it in a condition is pretty unusual. If you don't like the fact that |
I think it's also worth thinking about what the worst case is. If I write this, expecting the assert_matches!(c, Ok(x) | Err(x) if x.len() < 100); The result is a strictly stronger assertion than I intended to write. There are cases where that might be undesirable, but it's nowhere near as bad as an unintentionally weak assertion. It will never let invariant violations slip through, the worst case is an unneeded panic. |
I think the difference comes down to english grammar. "match if" makes sense, "assert that pattern if condition" also makes sense, however that's not the semantics. It's either "panic if not pattern and or not condition" or "assert that pattern and condition". Am I really the only one here that finds that problematic? Might be a language thing, my native language is German, so maybe this aspects affects me more? |
Perhaps I can assume that if you only wanted the pattern to apply to the assert_matches!(c, Ok(_) | Err(x) if x.len() < 100); and get an error:
which could give you some clue that you can't really do this. (if this confusion is common, then it would be a diagnostics issue, and we can suggest removing the For people who aren't familiar with if matches!(c, Ok(x) | Err(x) if x.len() < 100) {
} and if you tried to read it out loud token by token it would sound like "if matches c Ok(x) or Err(x) if the length of x is smaller than 100". I think if we were to stabilize |
I agree that using something like
List of all suggestions: _. assert_matches!(c, Ok(x) | Err(x) if x.len() < 100);
1. assert_matches!(Ok(x) | Err(x) if x.len() < 100, c);
2. assert!(match c => Ok(x) | Err(x) if x.len() < 100);
2. assert!(match c { Ok(x) | Err(x) if x.len() < 100 });
3. Not express-able
4. assert_matches!(c, Ok(x) | Err(x) => assert!(x.len() < 100));
5. assert_pattern!(match x { Ok(x) | Err(x) if x.len() < 100 });
6. matcher!(Ok(x) | Err(x) if x.len() < 100).assert(c);
6. pattern!(Ok(x) | Err(x) if x.len() < 100).assert_matches(c);
7. assert_matches!(c, (Ok(x) | Err(x)) where x.len() < 100); That's after roughly an hour of exploring the design space. I believe there are more options. Of course there is the elephant in the room that any deviation from the assert_matches crate will make adoption more work. However if the criteria is purely based on that, what point is there in discussing design? |
Another option: assert_matches!( c { Ok(x) | Err(x) if x.len() < 100 }) And for consistentency also allow the following syntax with the matches!( c { Ok(x) | Err(x) if len(x) < 100 }) I don't like 1 because then the order is the opposite of the existing |
I'm going to add some meta-commentary because I think this conversation has gotten to a point that's not productive.
Ultimately it's up to the libs-api team to decide this. I'm sure they will consider the newest arguments put forth. |
This comment pretty much nails it. assert_matches!() should be equivalent to "assert!(matches!( .. ))" else people will be surprised if it acts differently. |
My two cents are that the precedence of |
Why are you, guys, arguing in the P.S. And if |
Please carefully read what I've previously written. I think there is a meaningful difference. Here are the two core sources of confusion:
|
Everyone, please stop, the same points are just being rehashed. |
3 years and no stabilization for something as simple as this? 😦 |
Tracking issues have a tendency to become unmanageable. Please open a dedicated new issue and link to this issue for absolutely any topics you want to discuss or have questions about. See rust-lang/compiler-team#739 for details and discussions on this prospective policy. |
I came looking for Come to think of it, maybe what I want would be better named as |
Feature gate:
#![feature(assert_matches)]
This is a tracking issue for the
assert_matches!()
anddebug_assert_matches!()
macros.Public API
Steps / History
std::assert_matches::assert_matches
to avoid breakage: Is there a gentler way to land the assert_matches macro? #82913must_use
message onis_none
(per Add messages toOption
's andResult
'smust_use
annotation foris_*
#62431 (review))matches!()
Unresolved Questions
=> expr
syntax?The text was updated successfully, but these errors were encountered: