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
Treat empty variants as GADTs for exhaustiveness check #9309
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a preliminary commit which introduces the test before your patch would have been useful, I initially scratched my head thinking "well that test looks fine, what is he complaining about?".
Apart from that the patch looks correct to me (though it's a bit unfortunate that the variable we look at is called must_backtrack_on_gadt
), but I'll let a second pair of eyes (@gasche, @garrigue ?) confirm this before merging.
872fd8a
to
5d41ade
Compare
Thanks! I updated the commits to first introduce the test, and then fix it. |
I think that this matter is delicate and warrants a bit more thought. (The empty type is not a GADT and so it is not clear why it must be treated as one; why are there no issues with a non-empty non-GADT type used in the same way?) On the other hand, I am trying to take a break from compiler contributions right now, so I will not be the one giving more thought in the short future. |
The issue is quite apparent in the documentation of
The above text fails to consider what happens if we pick an empty branch (and it should be updated). The answer with the current code is that as soon as there is one empty branch, we raise Morally, we should probably raise an |
|
||
type nothing = | | ||
type ('a, 'b, 'c) t = | A of 'a | B of 'b | C of 'c | ||
module Runner : sig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concerning the test, a simpler
type nothing = |
type 'b t = A | B of 'b | C
let g (x:nothing t) = match x with A -> ()
triggers this issue. Did you have a specific reason in mind for the current test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a simplified version of the error that we observed in our code base -- that's how I derived it. No preference on whether we should use the simpler one or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think keep at least adding the simpler error case is useful, since it makes it clearer what is the intend of the test.
I agree with @Octachron's assessment. I'm fine with the behavior proposed in this PR; but I'd like to see the doc / variable names updated, as this case is indeed unrelated to GADTs. |
For comparison's sake, I have a version with a split exception: Octachron@8b8f9e0 . The main advantage might be that it removes the |
I added the changes from @trefis. |
Yeah, except « possibly ill typed branch » doesn't quite match what's explained in the docs IIUC. The branch itself might type fine, but introduce constraints that make later parts of the pattern ill-typed. |
One possibility for the name might be to focus on the task at hand: |
Happy with either. |
Hi @smuenzel-js, in the end because of the social pressure of people who are taking a break from compiler contributions, I just pushed (in #9318) a slightly updated version of @Octachron's proposal. |
I have looked at both the present PR and @Octachron's approach, and I believe that @Octachron's approach is better. The reason why we give up on empty types is not the same as the reason we give up on GADT constraints, and the way we handle these situations is different: on GADT constraints we go to the topmost non-split or-pattern and retry, empty types do not require backtracking and may be raised to the caller. Having two exceptions to distinguish these two different situations is the right approach. |
I discussed this extensively with @Octachron today. Actually, the following example (not involving GADTs) may be faster with the alternative approach, but there should be a better way to improve performance than this very specific hack: type empty = |
type t = A | B
let f = function (A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(A|B),(_ : empty) -> . |
@Octachron's approach is not a hack, it handles separately two things that are different; I believe that it is the right design, and in particular his approach is a natural suggestion of any attempt to update the documentation in a way that is correct (unlike the documentation changes in the present PR that tend to be incorrect). Either approaches could be reviewed carefully and merged into the 4.10 branch, but do we really need to hold the release for a bug was reported a couple weeks after we started the release-candidate process, and is present in many existing releases? It is stressful to hold the presses at the last minute. Note that the bug is also present in 4.07, 4.08 and 4.09: delaying a release that was planned this week is not solving the soundness issue for users in the wild, it is just adding more work to the release process. (On the other hand, we could plan for a bugfix release for 4.10, just as we can have bugfix releases of 4.09 and possibly 4.08, including whichever approach we think is the best one.) I'm not the one making the decision, but if I was I would release without hurrying to fix this new report. It can go in 4.10.1. |
Another point is that there is a workaround for the issue that works on every version: use a GADT-based empty type. Since the bug was only discovered after the beta, and it is not widespread, this seems like a good reason to wait for a version later than 4.10 . |
If it were to me I would release with the 1 line fix, without changing the documentation. The discussion about the difference in meaning between GADT constructors and empty types is moot to me, as one could make exactly the same distinction on GADTs. I.e., when expanding a GADT, we could immediately check whether all its cases are impossible, and it is empty as a result, avoiding going back to the englobing or-pattern. |
We agree that the difference is not between GADTs and empty types and that the right distinction is between empty branches and branches adding local constraints. The fact that the first case only happens with empty sum type is indeed contingent. Rather than an optimisation, I see it as decoupling two different concerns in order to make it easier to understand the code locally. |
Is that true? I would have thought matching on say |
I went ahead, did a last review of the better implementation in #9318, and merged it. The bug reported in this PR is now fixed in trunk. Thanks @smuenzel-js for the report, the patch proposal, and the fairly enlightening repro case. |
(We can of course continue discussing whether one or the other fix should be backported in 4.10 at the last minute.) |
Concerning backporting, this seems like a prime candidate for 4.10.1; but not 4.10 . |
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Treat empty variants as GADTs for exhaustiveness check
Currently, the empty variant is not treated as a GADT when we're typing a pattern. This means that if we have an or pattern, and one case contains the empty variant, the other case may not be examined (example enclosed in the patch).
This patch attempts to treat the empty variant as a GADT, to fix this.
(Note: this is the first time I'm doing anything in /typing, so I expect that I may have gotten something wrong here).