Skip to content
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

Add assert_matches macro. #82770

Merged
merged 6 commits into from
Mar 5, 2021
Merged

Add assert_matches macro. #82770

merged 6 commits into from
Mar 5, 2021

Conversation

m-ou-se
Copy link
Member

@m-ou-se m-ou-se commented Mar 4, 2021

This adds assert_matches!(expression, pattern).

Unlike the other asserts, this one consumes the expression may consume the expression, to be able to match the pattern. (It could add a & implicitly, but that's noticable in the pattern, and will make a consuming guard impossible.)

See #62633 (comment)

This re-uses the same left: .. right: .. output as the assert_eq and assert_ne macros, but with the pattern as the right part:

assert_eq:

assertion failed: `(left == right)`
  left: `Some("asdf")`,
 right: `None`

assert_matches:

assertion failed: `(left matches right)`
  left: `Ok("asdf")`,
 right: `Err(_)`

cc @cuviper

@m-ou-se m-ou-se added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Mar 4, 2021
@rust-highfive
Copy link
Collaborator

r? @joshtriplett

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 4, 2021
@cuviper
Copy link
Member

cuviper commented Mar 4, 2021

Unlike the other asserts, this one consumes the expression, to be able to match the pattern.

I don't understand this point -- the user's pattern might not consume anything, and I think the failure case could use ref, no? That way you could write non-consuming checks like:

assert_matches!(borrowed.field, Some(_));

@m-ou-se
Copy link
Member Author

m-ou-se commented Mar 4, 2021

@cuviper Yeah the issue is a bit more subtle:

    let a = Some("asdf".to_string());
    assert_matches!(a, Some(_));
    dbg!(a); // OK, `a` still exists.
    let a = Some("asdf".to_string());
    assert_matches!(a, Some(_x));
    dbg!(a); // Error, `a` was consumed by the pattern.
    let a = Some("asdf".to_string());
    assert_matches!(a, Some(ref _x));
    dbg!(a); // OK, `a` still exists.

It could be a little surprising, but I think it makes sense.

@rust-log-analyzer

This comment has been minimized.

@cuviper
Copy link
Member

cuviper commented Mar 4, 2021

If the user's pattern consumes, that's at least something they can fix. I was thinking of cases more like this:

#![feature(assert_matches)]
fn main() {
    let pair = (42, Some("asdf".to_string()));

    assert_matches!(pair.1, Some(_));
    dbg!(&pair); // OK, `pair` is still complete.

    let ref_pair = &pair;
    assert_matches!(ref_pair.1, Some(_));
    // error[E0507]: cannot move out of `ref_pair.1` which is behind a shared reference
    //  --> src/main.rs:9:21
    //   |
    // 9 |     assert_matches!(ref_pair.1, Some(_));
    //   |     ----------------^^^^^^^^^^-----------
    //   |     |
    //   |     data moved here
    //   |     move occurs because `left_val` has type `Option<String>`, which does not implement the `Copy` trait
    //   |
}

In the first case, the failure path would consume the option, but that's fine since it diverges. In the second case, it's not possible to consume it at all. But it will work if the failed pattern is ref left_val, since it only uses a reference anyway.

@m-ou-se
Copy link
Member Author

m-ou-se commented Mar 4, 2021

@cuviper Ah, right. Thanks. Updated.

@cuviper
Copy link
Member

cuviper commented Mar 4, 2021

When you file a tracking issue, I think it should take this TODO item from #62633:

@joshtriplett
Copy link
Member

@bors r+

@bors
Copy link
Contributor

bors commented Mar 4, 2021

📌 Commit f223aff has been approved by joshtriplett

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 4, 2021
@m-ou-se
Copy link
Member Author

m-ou-se commented Mar 4, 2021

Forgot the tracking issue.

@bors r=joshtriplett

@bors
Copy link
Contributor

bors commented Mar 4, 2021

📌 Commit 80fcdef has been approved by joshtriplett

JohnTitor added a commit to JohnTitor/rust that referenced this pull request Mar 5, 2021
Add assert_matches macro.

This adds `assert_matches!(expression, pattern)`.

Unlike the other asserts, this one ~~consumes the expression~~ may consume the expression, to be able to match the pattern. (It could add a `&` implicitly, but that's noticable in the pattern, and will make a consuming guard impossible.)

See rust-lang#62633 (comment)

This re-uses the same `left: .. right: ..` output as the `assert_eq` and `assert_ne` macros, but with the pattern as the right part:

assert_eq:
```
assertion failed: `(left == right)`
  left: `Some("asdf")`,
 right: `None`
```
assert_matches:
```
assertion failed: `(left matches right)`
  left: `Ok("asdf")`,
 right: `Err(_)`
```

cc `@cuviper`
m-ou-se added a commit to m-ou-se/rust that referenced this pull request Mar 5, 2021
Add assert_matches macro.

This adds `assert_matches!(expression, pattern)`.

Unlike the other asserts, this one ~~consumes the expression~~ may consume the expression, to be able to match the pattern. (It could add a `&` implicitly, but that's noticable in the pattern, and will make a consuming guard impossible.)

See rust-lang#62633 (comment)

This re-uses the same `left: .. right: ..` output as the `assert_eq` and `assert_ne` macros, but with the pattern as the right part:

assert_eq:
```
assertion failed: `(left == right)`
  left: `Some("asdf")`,
 right: `None`
```
assert_matches:
```
assertion failed: `(left matches right)`
  left: `Ok("asdf")`,
 right: `Err(_)`
```

cc ``@cuviper``
bors added a commit to rust-lang-ci/rust that referenced this pull request Mar 5, 2021
Rollup of 10 pull requests

Successful merges:

 - rust-lang#80723 (Implement NOOP_METHOD_CALL lint)
 - rust-lang#80763 (resolve: Reduce scope of `pub_use_of_private_extern_crate` deprecation lint)
 - rust-lang#81136 (Improved IO Bytes Size Hint)
 - rust-lang#81939 (Add suggestion `.collect()` for iterators in iterators)
 - rust-lang#82289 (Fix underflow in specialized ZipImpl::size_hint)
 - rust-lang#82728 (Avoid unnecessary Vec construction in BufReader)
 - rust-lang#82764 (Add {BTreeMap,HashMap}::try_insert)
 - rust-lang#82770 (Add assert_matches macro.)
 - rust-lang#82773 (Add diagnostic item to `Default` trait)
 - rust-lang#82787 (Remove unused code from main.js)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 04045cc into rust-lang:master Mar 5, 2021
@rustbot rustbot added this to the 1.52.0 milestone Mar 5, 2021
@m-ou-se m-ou-se deleted the assert-match branch March 6, 2021 08:32
@tmandry
Copy link
Member

tmandry commented Mar 8, 2021

FYI, this broke a few places in Fuchsia where we were using assert_matches! from the matches crate, and pulling it in from a wildcard import. e.g.

error[E0659]: `assert_matches` is ambiguous (glob import vs any other name from outer scope during import/macro resolution)
   --> ../../src/lib/test_executor/rust/src/diagnostics.rs:363:13
    |
363 |             assert_matches!(
    |             ^^^^^^^^^^^^^^ ambiguous name
    |
    = note: `assert_matches` could refer to a macro from prelude
note: `assert_matches` could also refer to the macro imported here
   --> ../../src/lib/test_executor/rust/src/diagnostics.rs:322:13
    |
322 |             super::*,
    |             ^^^^^^^^
    = help: consider adding an explicit import of `assert_matches` to disambiguate
    = help: or use `self::assert_matches` to refer to this macro unambiguously

Adding the explicit import fixes the error, but causes an unused import lint (which definitely seems like a bug, albeit pre-existing).

@joshtriplett
Copy link
Member

joshtriplett commented Mar 8, 2021

@tmandry Interesting. Do you also get such errors for matches! since that's in std now?

The matches crate could switch to re-exporting assert_matches!; I've confirmed that that fixes the issue, as re-exported versions of a macro from the prelude don't create conflicts. SimonSapin/rust-std-candidates#22

Re-exporting assert_matches will be a pain while it's nightly-only; that would require compile-time detection of availability. (I really wish cfg(accessible) was available for that.)

@tmandry
Copy link
Member

tmandry commented Mar 9, 2021

@tmandry Interesting. Do you also get such errors for matches! since that's in std now?

I don't recall seeing any of these, but the explanation could be that assert_matches! is often used in tests, which are often in their own module with use super::*;.

@SimonSapin
Copy link
Contributor

It’s unfortunate that the compiler does not resolve this ambiguity automatically (favoring the explicit use over the prelude).

@gilescope
Copy link
Contributor

If we are adding new assert methods, worth questioning if we want {:#?} for printing out the assertion failure so that it's nicely formatted?
Certainly for consumption by rustc json output this would be helpful as then a diff tool won't show the assert failure all on one loooong line.

@gilescope
Copy link
Contributor

If it's all on one line it's hard to post-process it back to multiline, but the other way is pretty easy. Then if we have assertions with many lines we could in the cli output hide identical lines (but a little context is always nice). All possible if we include the '#'.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants