Skip to content

Show existential types as $a and name their constructor - #9080

Open
cknitt wants to merge 3 commits into
masterfrom
fix-9068-existential-names
Open

cknitt wants to merge 3 commits into
masterfrom
fix-9068-existential-names

Conversation

@cknitt

@cknitt cknitt commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

Fixes #9068.

Existential types of GADT constructors are printed $a instead of their internal name $Any_'a, in errors and in hover. An escaping one is explained by naming the constructor that binds it:

This has type: $a
But it's expected to have type: 'a
$a is an existential type bound by the constructor Any.
It is only known in the scope of the pattern that matches Any.

Existentials are named $a before the compiler makes the name unique, as in OCaml 5, so two existentials binding the same variable print as $a and $a1. The constructor binding each name is recorded for the explanation.

New fixtures gadt_existential_escape and gadt_existential_same_name and analysis test HoverExistential.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3

@github-actions

github-actions Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Changelog

💅 Polish

  • Existential types of GADT constructors are shown as $a instead of $Any_'a, and an escaping one is explained by naming the constructor that binds it.

Semantic Diff

3 commits · Full page

Show existential types as $a and name their constructor

bff79a6 · cknitt + Claude · Oct 27, 2026, 12:44 UTC

Why

An existential type of a GADT constructor is an abstract type named $Any_'a internally, and that name was printed as is in errors and hover. It is now printed $a, as in OCaml 5, and the escape error says which constructor binds it instead of calling it a type constructor.

Diagnostic modified (existential escape)

type rec any = Any('a): any

let get = (Any(x)) => x

diagnostic

-This has type: $Any_'a
+This has type: $a
 But it's expected to have type: 'a
-The type constructor $Any_'a would escape its scope
+$a is an existential type bound by the constructor Any.
+It is only known in the scope of the pattern that matches Any.

Review

  • Why decode the name instead of recording the constructor? The name already holds both parts, so printing needs no table that would have to be reset between compilations in one process.
Give distinct existential types distinct names

2294674 · cknitt + Claude · Oct 27, 2026, 20:25 UTC

Codex review on #9080: printing $C_'a as $a made existentials of different constructors that bind the same variable print alike, so a mismatch between them read $a vs $a. Existentials are now named $a before the compiler makes the name unique, as OCaml 5 does, so the second one is $a1; the constructor binding each name is recorded for the escape explanation instead of being decoded from the name.

Diagnostic modified (existentials of two constructors)

type rec any = First('a): any | Second('a): any

let same = (First(x), Second(y)) => x == y

diagnostic

-This has type: $a
+This has type: $a1
 But it's being compared to something of type: $a
Key existential constructors by identifier

f56b61c · cknitt + Claude · Oct 27, 2026, 20:38 UTC

Codex review on #9080: the constructor of each existential was recorded by its name, but the counter that makes names unique is reset for each binding, so a nested module's $a could overwrite the constructor of an outer $a. It is recorded by the identifier's stamp now.

@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.52%. Comparing base (beb423d) to head (f56b61c).

Files with missing lines Patch % Lines
compiler/ml/printtyp.ml 75.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9080      +/-   ##
==========================================
+ Coverage   81.45%   81.52%   +0.06%     
==========================================
  Files         460      460              
  Lines       64645    64659      +14     
==========================================
+ Hits        52657    52713      +56     
+ Misses      11988    11946      -42     
Files with missing lines Coverage Δ
compiler/ml/ctype.ml 67.80% <100.00%> (+0.43%) ⬆️
compiler/ml/printtyp.ml 72.06% <75.00%> (+0.07%) ⬆️

... and 4 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@pkg-pr-new

pkg-pr-new Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript@9080

@rescript/belt

npm i https://pkg.pr.new/@rescript/belt@9080

@rescript/darwin-arm64

npm i https://pkg.pr.new/@rescript/darwin-arm64@9080

@rescript/linux-arm64

npm i https://pkg.pr.new/@rescript/linux-arm64@9080

@rescript/linux-x64

npm i https://pkg.pr.new/@rescript/linux-x64@9080

@rescript/runtime

npm i https://pkg.pr.new/@rescript/runtime@9080

@rescript/win32-x64

npm i https://pkg.pr.new/@rescript/win32-x64@9080

commit: f56b61c

@github-actions

Copy link
Copy Markdown

@cknitt
cknitt marked this pull request as ready for review October 27, 2026 14:20
@cknitt

cknitt commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-27T20:40:26.519315Z 9569e25 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cknitt

cknitt commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bc31934fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/ml/printtyp.ml Outdated
cknitt added a commit that referenced this pull request Oct 27, 2026
Codex review on #9080: printing `$C_'a` as `$a` made existentials of
different constructors that bind the same variable print alike, so a
mismatch between them read `$a` vs `$a`. Existentials are now named `$a`
before the compiler makes the name unique, as OCaml 5 does, so the second
one is `$a1`; the constructor binding each name is recorded for the escape
explanation instead of being decoded from the name.

Diagnostic modified (existentials of two constructors):
Example:
    type rec any = First('a): any | Second('a): any

    let same = (First(x), Second(y)) => x == y
Before (diagnostic):
    This has type: $a
    But it's being compared to something of type: $a
After (diagnostic):
    This has type: $a1
    But it's being compared to something of type: $a

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt

cknitt commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a757919c44

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread compiler/ml/ctype.ml Outdated
cknitt added a commit that referenced this pull request Oct 27, 2026
Codex review on #9080: the constructor of each existential was recorded by
its name, but the counter that makes names unique is reset for each
binding, so a nested module's `$a` could overwrite the constructor of an
outer `$a`. It is recorded by the identifier's stamp now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt

cknitt commented Oct 27, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Another round soon, please!

Reviewed commit: 9569e25834

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

cknitt and others added 3 commits October 28, 2026 07:48
Why:
    An existential type of a GADT constructor is an abstract type named
    `$Any_'a` internally, and that name was printed as is in errors and
    hover. It is now printed `$a`, as in OCaml 5, and the escape error
    says which constructor binds it instead of calling it a type
    constructor.

Diagnostic modified (existential escape):
Example:
    type rec any = Any('a): any

    let get = (Any(x)) => x
Before (diagnostic):
    This has type: $Any_'a
    But it's expected to have type: 'a
    The type constructor $Any_'a would escape its scope
After (diagnostic):
    This has type: $a
    But it's expected to have type: 'a
    $a is an existential type bound by the constructor Any.
    It is only known in the scope of the pattern that matches Any.

Review: Why decode the name instead of recording the constructor?
    The name already holds both parts, so printing needs no table that
    would have to be reset between compilations in one process.

Fixes: #9068
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3
Signed-off-by: Christoph Knittel <ck@cca.io>
Codex review on #9080: printing `$C_'a` as `$a` made existentials of
different constructors that bind the same variable print alike, so a
mismatch between them read `$a` vs `$a`. Existentials are now named `$a`
before the compiler makes the name unique, as OCaml 5 does, so the second
one is `$a1`; the constructor binding each name is recorded for the escape
explanation instead of being decoded from the name.

Diagnostic modified (existentials of two constructors):
Example:
    type rec any = First('a): any | Second('a): any

    let same = (First(x), Second(y)) => x == y
Before (diagnostic):
    This has type: $a
    But it's being compared to something of type: $a
After (diagnostic):
    This has type: $a1
    But it's being compared to something of type: $a

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3
Signed-off-by: Christoph Knittel <ck@cca.io>
Codex review on #9080: the constructor of each existential was recorded by
its name, but the counter that makes names unique is reset for each
binding, so a nested module's `$a` could overwrite the constructor of an
outer `$a`. It is recorded by the identifier's stamp now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kbf3gLMawVgtmiKy3TMBc3
Signed-off-by: Christoph Knittel <ck@cca.io>
@cknitt
cknitt force-pushed the fix-9068-existential-names branch from 9569e25 to f56b61c Compare October 28, 2026 08:10

This branch has not been deployed

No deployments
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.

Existential types are shown as $Any_'a in errors and hover

1 participant