missing match arms diagnostic - #3706
Conversation
flodiebold
left a comment
There was a problem hiding this comment.
Seems fine so far, except for using the AST; this analysis should only use the HIR.
| let match_expr: &hir_def::expr::Expr = &body[expr]; | ||
|
|
||
| // TODO do validation | ||
| // should I be doing validation against the hir_def::expr::Expr? |
There was a problem hiding this comment.
yes, these kinds of analyses shouldn't touch the AST at all. Actually, you shouldn't even be using the body_with_source_map query, but just body (probably just pass the body as an argument).
There was a problem hiding this comment.
Thanks for the feedback. My usage of body_with_source_map was inspired by the validate_record_literal method, which uses it to build up the MissingField diagnostic (which contains an AstPtr). Just to clarify, do you mean the analysis shouldn't use the AST, or even the diagnostic shouldn't use the AST?
There was a problem hiding this comment.
Well, this is about two concerns mostly: 1. Using the abstractions of the HIR for analysis because that's what it's for, and 2. incrementality -- calling the body_with_source_map means this has to be recalculated whenever the AST changes in any way (i.e. if I type a space in one function, this would need to be rerun for all following functions in the file). validate_record_literal is probably fine because it only calls body_with_source_map if there actually is a diagnostic, which should not be the case for most functions. So using the source map in the end to provide the AST pointer is probably fine, but it should only be done if there are diagnostics.
There was a problem hiding this comment.
Using the abstractions of the HIR for analysis because that's what it's for
For example, consider macros in patterns. I'm actually not sure whether we expand them yet, but if we do, the HIR body will already contain the expanded form, so this code doesn't need to be concerned with them at all.
There was a problem hiding this comment.
I believe I am only using the AST in creating the diagnostic, to report the error. This is in line with other diagnostics as far as I can tell.
Has your concern here been resolved?
Yes, basically you can get the type of the |
a58f169 to
10b8e3a
Compare
|
Should |
|
We have a test for the missing field diagnostic here in hir_ty. Tests for the diagnostic should be in hir_ty; the quick fix will be calculated in ide, so the tests for that will need to be there. |
|
Also, I am super unhappy about current code organisation for diagnostics in hir, it is kindof bad that So, another related task here is to refactor all this to a single self-contained module (which shouldn't be done in the same PR that adds a new diagnostics). |
It seems to me perhaps this refactor would be best done before adding another diagnostic. Thoughts on this? If so, I can pivot to looking at that and I'd probably have another round of questions once I get a chance to dive in. On a different note, I think I found the place where rustc does exhastiveness checking of patterns. https://github.com/rust-lang/rust/blob/master/src/librustc_mir_build/hair/pattern/_match.rs edit - On second thought, after looking briefy at the refactoring task and realizing diagnostics cross several crates, I think that would require a broader scope of understanding than I currently have. I'm going to continue reviewing the rustc pattern matching source as a step toward implementing the missing match arm diagnostic. |
10b8e3a to
6eb07b5
Compare
My thoughts exactly! Generally, I prefer to add things to the current infra, and then apply refactoring ontop immediately after: that way, I make sure I have all the necessary context in the cache before changing code. |
6eb07b5 to
0020985
Compare
|
I've spent a little bit of time reading the documentation in rustc around pattern matching, and I think I'm starting to understand. The entry point for the interesting pattern match exhaustiveness checking work seems to be here. https://github.com/rust-lang/rust/blob/master/src/librustc_mir_build/hair/pattern/_match.rs#L1626 But it seems that method is able to assume that all of the patterns in all of the arms of the match statement are valid. By that I mean they are patterns which match the type of the match expression. So I assume there is some earlier pass of the compiler which checks for this? But I don't think that earlier pass exists (yet) in rust analyzer? Does this all sound correct? If so, I wonder if implementing a match arm validation diagnostic is a reasonable stepping stone on the way to an implementation of exhaustiveness checking? |
|
Hm. First of all, we don't have 'passes', i.e. steps that are executed one after the other and when one fails, analysis stops. If we don't want to do this analysis if there are type mismatches in the expression, we need to implement a check for that. We do type-check match expressions; that's what The last question is, how do we actually want to act if the match doesn't fully type-check. Just skipping the exhaustiveness check is an option, but I actually imagine there are lots of cases where we could still correctly do the analysis even if there are type errors. I think it might be a valid approach to start by always doing the analysis, and then see in which situations we absolutely cannot do it. It'd also be a possibility to skip it for now though. |
|
Sorry, wrong button 😬 |
aed009b to
080b6de
Compare
6d87b4d to
e0c9470
Compare
|
This is now ready for review. On documentation, I tried to find a balance between referencing the relevant rustc documentation and copying it over, leaning toward referencing it. I'm not sure what the standard practice is here for this project, so let me know if you'd like to see something different. I also tested this a small amount by running it against this repository. I also ran the CLI One thing I did notice while testing it though it that I will get duplicate warnings about missing match arms, since I have |
No, not at the moment.
Generally, the smaller target element the better. @flodiebold do you want to do a final review pass here (not necessary before today's relase, mind you). If not, I can take over the reviewing |
|
I was planning to review it this evening. |
|
Regarding the rustc diagnostic and target element, being able to deduplicate the diagnostic is probably also a good reason to choose the same target element as rustc 🤔 |
It doesn't yet, but it should! |
|
I've moved the diagnostic to the match expression (from the match arms) to align with rustc. This makes the duplication much less of a UX problem imo after a small amount of testing. |
flodiebold
left a comment
There was a problem hiding this comment.
I think it looks pretty good, I did have a bunch of comments, but it's a large PR 😉
a4af6e3 to
1871adc
Compare
1871adc to
9fc1f51
Compare
|
@flodiebold thanks for the review! I think I've addressed your comments, let me know if there is anything else you'd like to see before this merges. |
| cx: &MatchCheckCtx, | ||
| constructor: &Constructor, | ||
| ) -> MatchCheckResult<Option<PatStack>> { | ||
| let result = match (self.head().as_pat(cx), constructor) { |
There was a problem hiding this comment.
Yes, let's leave it as is for now.
I think it might make sense to separate the 'comparing the constructors' and 'expanding the pattern' parts. Comparing the constructors can then be done on Constructor alone, and expanding the pattern just needs to match on the pattern.
| } | ||
| (Pat::Wild, constructor) => Some(self.expand_wildcard(cx, constructor)?), | ||
| (Pat::Path(_), Constructor::Enum(constructor)) => { | ||
| // enums with no associated data become `Pat::Path` |
There was a problem hiding this comment.
btw, "enums with no associated data" are usually called "unit enum variants"
Build succeeded |
|
Somehow I am hitting a panic: This might be caused by this PR. use std::borrow::Cow;
use std::str::Utf8Error;
fn result() -> Result<Cow<'static, str>, Utf8Error> {
unimplemented!()
}
fn main() {
let location = match result() {
Ok(loc) => loc,
Err()
};
} |
|
Thanks @cynecx for the report. A minimal example of this is #[test]
fn malformed_match() {
let content = r"
fn test_fn() {
enum Either {
A,
B(u32),
}
match Either::A {
Either::A => (),
Either::B()
}
}
";
check_no_diagnostic(content);
}I'll submit a fix tonight. |
Following up on #3689 (comment), this PR creates a missing match arms diagnostic.
At the moment this is a very early draft, but I wanted to open it just to get some initial feedback.
Initial questions:
validate_matchfunction:match_expr? And when analyzingmatch_exprI should be looking at it as ahir_def::expr::Expr?Thanks for all the guidance.