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

typing: fix a try_expand_once forgotten from #10170 #12974

Merged
merged 2 commits into from
Feb 15, 2024

Conversation

gasche
Copy link
Member

@gasche gasche commented Feb 12, 2024

(In #10170, almost all callsites of try_expand_once were replaced by calls to try_expand_safe which catches the Escape exception and returns None instead, except a few places that were already catching Escape... and the one occurrence in the diff here, which should also have been replaced.)

On OCaml versions 5.1 and older, this glitch causes a Ctype.Escape(_) uncaught exception on the included test case. See #12971.

With the fix in the present PR (which applies cleanly to 4.14, trunk and everything in between), the included test case results in a proper error on OCaml 5.1 and older.

In OCaml 5.2 and trunk, the included test case does not raise an uncaught exception, and it does not fail with an error instead, the program is accepted with a (recursive) type. This is due to #12691 (included in 5.2), which improved the definition of some expansion functions to not raise Escape and do something more sensible instead. (Edit: not more sensible, it was in fact a regression, see discussion below.)

The summary of my understanding of the issue:

  • The present PR is a trivial (obvious, one-line) fix to an uncaught-exception bug from 4.14 to 5.1, and even in trunk/5.2 it makes the code strictly more consistent.
  • The behavior inherited from Cleanup expand_abbrev_gen #12691 is even better than the one-line fix (Edit: wrong). But backporting it to older versions (as @Octachron suggested) would be sensibly more work -- I would not take the initiative to do it.

I therefore propose to merge the present PR in trunk, and backport it in 4.14 for now.

@Octachron
Copy link
Member

Accepting the code in your test without -rectypes is a bug, not a bugfix.

@gasche
Copy link
Member Author

gasche commented Feb 13, 2024

Hmmm. I agree, and I wonder why I didn't notice this earlier -- I guess I had convinced myself that the test somehow used a feature that allows equi-recursive types.

This suggests that something is slightly wrong with #12691 that we want to fix before the 5.2 release.

I think that this also strengthens my point that this one-line PR is good to have, if only to then backport it to older versions. What do you think?

@Octachron
Copy link
Member

Sorry to forgot to mention it before, but #12691 is not enough to replicate the bug in isolation.
I am not sure why we would want to backport a bug to 4.14 ?
I would rather suggest that we first fix what is going wrong in 5.2 before thinking of backporting to 4.14 .

@gasche
Copy link
Member Author

gasche commented Feb 13, 2024

I am not sure why we would want to backport a bug to 4.14 ?

Sorry for being unclear. I propose to include in 4.14 a fix for the uncaught Escape exception, and to start by including the fix in trunk, even though the uncaught exception does not happen anymore (something else does that is weird), because it fixes an inconsistency in the ctype.ml codebase.

@Octachron
Copy link
Member

Hard disagree: that would be backporting a bug to 4.14 to erase a bad error message.

@gasche
Copy link
Member Author

gasche commented Feb 13, 2024

I'm confused. If I apply my change to 4.14, the repro case behavior changes from

Fatal error: exception Ctype.Escape(_)

to

19 | ..let T x = xt in
20 |   Seq.cons Seq.empty x
Error: This expression has type ('a Seq.t as 'a) Seq.t Seq.t
       but an expression was expected of type 'b
       The type variable 'b occurs inside ('a Seq.t as 'a) Seq.t Seq.t

Are you saying that the latter output is buggy? (The error message looks a bit odd, but who doesn't?)

@gasche
Copy link
Member Author

gasche commented Feb 13, 2024

For reference, the behavior of the testcase on 4.12 and older (the bug was introduced in 4.13) is as follows:

File "test.ml", lines 4-5, characters 2-22:
4 | ..let T x = xt in
5 |   Seq.cons Seq.empty x
Error: This expression has type
         ('a Seq.t as 'a) Seq.t Seq.t = unit -> 'a Seq.t Seq.node
       but an expression was expected of type 'b
       Type 'c is not compatible with type 'a = unit -> 'c Seq.node 
       The type variable 'c occurs inside 'a

@Octachron
Copy link
Member

Ah, I was mislead by your test case and missed the fact that the change resulted in a error message on 4.14. Sorry!

However, the wording of the error message might hint to a deeper problem with error trace, and I would still prefer to understand and fix the issue on 5.2 before backporting to 4.14.

@gasche
Copy link
Member Author

gasche commented Feb 13, 2024

I disagree with your analysis (the bug has been around since 4.13, so I don't understand why figuring out what went wrong with an independent change in 5.2 should be taken as a prerequisite to fixing it), but I will wait to see you in-person to discuss this further.

@gasche
Copy link
Member Author

gasche commented Feb 14, 2024

After an in-person discussion, Florian remains unconvinced that this is the right first move. (I remain convinced.) I am closing for now, I guess he volunteered to look at this himself.

@gasche gasche closed this Feb 14, 2024
let to_seq (xt : 'a t) : 'a Seq.t =
let T x = xt in
Seq.cons Seq.empty x

Copy link
Member

@Octachron Octachron Feb 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative test which leads to the same error on 5.2:

let strange x = Seq.[cons x empty; cons empty x]

@Octachron
Copy link
Member

After reminding me of the context, I think that this is a correct partial fix.

In particular, the "Escape" error during expansion might be a nested recursive occurrence. In this case, we need to forget all the unifications and abbreviations expansions done in this branch of the occur check and possibly retry at a parent node. And this is the semantics of your try_expand_safe version.

This PR doesn't solve the general bug described in #10170 (that it is possible to escape from the scrutiny of occur_rec and creates recursive types) but it prevents naked error messages while being more principled than the current implementation.

@Octachron Octachron reopened this Feb 14, 2024
@gasche
Copy link
Member Author

gasche commented Feb 14, 2024

Would you like to see further changes to the PR (besides writing a Changes entry) before approving?

(Should I replace the test with your alternative proposal? Or maybe include both?)

@Octachron
Copy link
Member

The current state is fine now that I agree that the state after this PR is clearer.
Having both test seems like a good option since the behavior in the two case has diverged on trunk.

@gasche
Copy link
Member Author

gasche commented Feb 14, 2024

I added a Changes entry, a second test as you suggested, and wrote a comment in the first test explaining that the current output is not correct.

@Octachron
Copy link
Member

Sorry I forgot to mention it, but there is already a test for recursive occurrence in tests/typing-misc/occur_rec.ml, I would prefer to have the two test version here rather than multiplying the number of test file.

@gasche
Copy link
Member Author

gasche commented Feb 15, 2024

Ok. I initially did not use an expect test because I wanted to reproduce the uncaught exception error on 4.x, but this is less relevant now so I will move the tests there.

@gasche
Copy link
Member Author

gasche commented Feb 15, 2024

The behavior of your testcase on trunk is interesting:

let strange x = Seq.[cons x empty; cons empty x];;
[%%expect{|
Uncaught exception: Ctype.Escape(_)

|}, Principal{|
val strange : 'a Seq.t Seq.t -> 'a Seq.t Seq.t Seq.t list = <fun>
|}];;

@gasche
Copy link
Member Author

gasche commented Feb 15, 2024

@Octachron I have rebased the PR with the test as you suggested. The first commit adds the test on trunk (no type-checker change), so we can see the before-PR beahvior; the second commit has the fix and updates the testsuite.

Uncaught exception: Ctype.Escape(_)

|}, Principal{|
val strange : 'a Seq.t Seq.t -> 'a Seq.t Seq.t Seq.t list = <fun>
Copy link
Member

@Octachron Octachron Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Self-comment: this unsound part is somehow dependent on the previous test, the compiler does not give this unsound type to strange by itself.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(This makes me strictly less confident in except tests for this kind of bugs, but oh well.)

@Octachron Octachron merged commit 166c91f into ocaml:trunk Feb 15, 2024
11 checks passed
Octachron added a commit that referenced this pull request Feb 15, 2024
typing: fix a try_expand_once forgotten from #10170
(cherry picked from commit 166c91f)
Octachron added a commit that referenced this pull request Feb 15, 2024
typing: fix a try_expand_once forgotten from #10170
(cherry picked from commit 166c91f)
@Octachron
Copy link
Member

Cherry-picked to 4.14, 5.2, and changes entry duplicated to the 5.2 section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants