Repository navigation
Refuse coercing another type into a private record or variant - #9125
Conversation
Changelog💥 Breaking Change
💥 A changelog entry is a breaking change, but no commit says what it breaks ( Semantic Diff1 commit · Full page ✏️ Refuse coercing another type into a private record or variant052d3cc · cknitt + Claude · Oct 29, 2026, 10:11 UTC · edited
Typing modified (coercion into a private type) module Blob: { type t = private {size: float} } = { type t = {size: float} }
type plain = {size: float}
let fake = (p: plain) => (p :> Blob.t)
Diagnostic added (coercion into a private type) let fake = (p: plain) => (p :> Blob.t)
-none
+Type plain is not a subtype of Blob.t
+Blob.t is private: its values can only be built in the module that
+defines it, so another type cannot be coerced to it. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9125 +/- ##
=======================================
Coverage 81.48% 81.48%
=======================================
Files 460 460
Lines 64728 64740 +12
=======================================
+ Hits 52744 52756 +12
Misses 11984 11984
🚀 New features to boost your workflow:
|
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
|
Developer playground preview: https://rescript-lang.github.io/rescript/dev-playground/?version=pr-9125 |
Why:
A private type's values can only be built in the module that defines
it, but structural coercion built them anywhere: a plain record, another
module's private record with the same fields, a variant, a polymorphic
variant or a string coerced into a private record or variant (#9124).
`subtype_rec` refuses a coercion whose target's definition is a private
record or variant unless the source is that same type. Coercing out of a
private type is unchanged.
Typing modified (coercion into a private type):
Example:
module Blob: { type t = private {size: float} } = { type t = {size: float} }
type plain = {size: float}
let fake = (p: plain) => (p :> Blob.t)
Before (rule): a record coerces into a record with compatible fields,
private or not
After (rule): a record or variant coerces into a private record or variant
only from that same type
Diagnostic added (coercion into a private type):
Example:
let fake = (p: plain) => (p :> Blob.t)
Before (diagnostic): none
After (diagnostic):
Type plain is not a subtype of Blob.t
Blob.t is private: its values can only be built in the module that
defines it, so another type cannot be coerced to it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t
Claude-Session: https://claude.ai/code/session_01Kr4cjoUi2f5VpMvE6wT9bo
Signed-off-by: Christoph Knittel <ck@cca.io>
3f75b5f to
052d3cc
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
cristianoc
left a comment
There was a problem hiding this comment.
Looks good to me. Thanks for the changes.
Fixes #9124.
A private type's values can only be built in the module that defines it, but structural coercion built them anywhere. Each of these compiled before:
subtype_recnow refuses a coercion whose target's definition is a private record or variant, unless the source is that same type. One guard in front of the structural branches covers all routes. The new error says:Coercing out of a private type is unchanged (
(b :> plainBlob),(c :> string)).This is a prerequisite for
@instanceOfon private records (#9058), where a forged value would be dispatched as an instance of a class it isn't. It also means the upcast between two private records, e.g.(file :> blob), is refused until the RFC's@extendsdeclares that relationship.New tests:
super_errors/fixtures/coercion_into_private_*.res: one per route above.RecordCoercion.resandVariantCoercion.res: coercions out of a private record and variant.🤖 Generated with Claude Code
https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t