Repository navigation
TScert: print nested options by depth and polymorphic option code as overloads under -bs-emit-ts - #9213
TScert: print nested options by depth and polymorphic option code as overloads under -bs-emit-ts#9213cristianoc wants to merge 3 commits into
Conversation
Why:
A nested match and a polymorphic function at a nested option failed
tsc under -bs-emit-ts. The marker arm printed every depth as `number`,
`{ readonly BS_PRIVATE_NESTED_SOME_NONE: number }`, so no true
declaration of `valFromOption` gave the payload back: at depth 0 it
returns `undefined`, at depth d > 0 the marker of depth d - 1. And a
function whose type parameter is an option's payload was bounded to the
primitives, so `getOr` at `'a = option<int>` (NestMatch.useNested) and
`Stdlib_Option.getOr` at that instance (Memo.cached) had no signature.
Representation modified (option under -bs-emit-ts):
Before (representation):
option<option<int>> ↦ undefined | number | { readonly BS_PRIVATE_NESTED_SOME_NONE: number }
option<unit> ↦ undefined | void | { readonly BS_PRIVATE_NESTED_SOME_NONE: number }
After (representation):
option<option<int>> ↦ undefined | number | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }
option<option<option<int>>> ↦ undefined | number | { …: 0 } | { …: 1 }
option<unit> ↦ undefined | void | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }
option<'a> in a type declaration, or at an abstract payload ↦ undefined | a | { …: number }
rule: Some(v) is the marker of depth 0 when v is undefined, of depth d + 1 when v is the marker of depth d, and v otherwise (Primitive_option.some); the depths are read off the payload's encoding, an option's, unit's, or a type that cannot hold undefined (no marker)
JS modified (-bs-emit-ts polymorphic option code):
Example:
let getOr = (o: option<'a>, d: 'a) =>
switch o {
| Some(v) => v
| None => d
}
Before (js):
export function getOr<a extends string | number | boolean | bigint>(o: (undefined | a), d: a): a {
After (js):
export function getOr<a extends string | number | boolean | bigint>(o: (undefined | a), d: a): a;
export function getOr<a extends string | number | boolean | bigint>(o: (undefined | (
(undefined | a)
| { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 })), d: (undefined | a)): (undefined | a);
export function getOr<a extends string | number | boolean | bigint>(o: (undefined | (
(undefined | a)
| { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 })), d: (undefined | a)): (undefined | a) {
JS modified (-bs-emit-ts a function returning undefined alone):
Example:
let useNestedFolded = () => getOr(Some(None), Some(3))
Before (js):
export function useNestedFolded(): (undefined | number) {
}
After (js):
export function useNestedFolded(): (undefined | number) {
return;
}
Review: Why is each overload true? It is the function's signature at the
instance `'a := option^k<a>`, k = 0 or 1, with `a` bounded to the primitives
as before, printed by the same rule: the function's code is the code of
every instance. The signature the body carries is the deepest instance's,
which no caller sees; tsc checks the body there.
Review: Why no conditional type for `Some<A>`? `Some<A>` at an unbounded
`A` is true only if no value but the runtime's markers owns the key
`BS_PRIVATE_NESTED_SOME_NONE`; a record type admits values owning further
keys. The overloads hold at primitive payloads with no assumption.
Review: What do the runtime declarations state? `Primitive_option.some` and
`valFromOption`, and `Stdlib_Option.getOr`, one signature per nesting of a
primitive payload, up to an option of an option; each line audited against
Primitive_option.res and Stdlib_Option.res. `Stdlib_Option.map` keeps its
primitive signature.
Review: Does this change genType's output? No: the marker hook answers "no
marker" outside -bs-emit-ts, and the gentype tests' outputs are unchanged.
The examples NestMatch (nested matches, `getOr` at a nested instance) and
Memo (`Stdlib_Option.getOr` at `'a = option<int>`) join the build test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Cristiano Calcagno <ccrisccris@gmail.com>
Why:
The depth rule read "cannot hold undefined" as "is no marker". A record
naming a field `BS_PRIVATE_NESTED_SOME_NONE` at run time satisfies the
first and owns the key `Primitive_option.some` reads: with
`x = {BS_PRIVATE_NESTED_SOME_NONE: 0, tag: "payload"}`, `Some(x)` at
`option<option<keyed>>` is the marker of depth 1, which the printed
`undefined | keyed | { …: 0 }` excludes (a review found it and ran it).
Representation modified (option under -bs-emit-ts, payloads that may own the key):
Before (representation):
option<option<keyed>> ↦ undefined | keyed | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }
After (representation):
option<option<keyed>> ↦ undefined | keyed | { readonly BS_PRIVATE_NESTED_SOME_NONE: number }
rule: a payload is no marker only where it cannot hold undefined and may own no key BS_PRIVATE_NESTED_SOME_NONE: a record or an inline record naming that field at run time, an object type, and a variant case whose payload may, may own it
Review: Does the example fail tsc? `keepKeyed` passes the value through and
type-checks; wrapping such a payload calls `Primitive_option.some`, which no
runtime declaration states at a non-primitive payload, and fails tsc.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Cristiano Calcagno <ccrisccris@gmail.com>
Why:
Printing nested options by depth and polymorphic option code as
overloads gives NestMatch's nested match and its getOr at option<int>
a signature tsc accepts, so the four errors at the primitive bound are
gone.
Tests modified (tests/build_tests/emit_ts/tsc.txt):
empty
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ChangelogNo changelog entry. Semantic Diff3 commits · Full page Print nested options by depth and polymorphic option code as overloads7c88be8 · cristianoc + Claude · Nov 04, 2026, 06:01 UTC
Representation modified (option under -bs-emit-ts)
-option<option<int>> ↦ undefined | number | { readonly BS_PRIVATE_NESTED_SOME_NONE: number }
-option<unit> ↦ undefined | void | { readonly BS_PRIVATE_NESTED_SOME_NONE: number }
+option<option<int>> ↦ undefined | number | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }
+option<option<option<int>>> ↦ undefined | number | { …: 0 } | { …: 1 }
+option<unit> ↦ undefined | void | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }
+option<'a> in a type declaration, or at an abstract payload ↦ undefined | a | { …: number }
+rule: Some(v) is the marker of depth 0 when v is undefined, of depth d + 1 when v is the marker of depth d, and v otherwise (Primitive_option.some); the depths are read off the payload's encoding, an option's, unit's, or a type that cannot hold undefined (no marker)JS modified (-bs-emit-ts polymorphic option code) let getOr = (o: option<'a>, d: 'a) =>
switch o {
| Some(v) => v
| None => d
}
-export function getOr<a extends string | number | boolean | bigint>(o: (undefined | a), d: a): a {
+export function getOr<a extends string | number | boolean | bigint>(o: (undefined | a), d: a): a;
+export function getOr<a extends string | number | boolean | bigint>(o: (undefined | (
+ (undefined | a)
+ | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 })), d: (undefined | a)): (undefined | a);
+export function getOr<a extends string | number | boolean | bigint>(o: (undefined | (
+ (undefined | a)
+ | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 })), d: (undefined | a)): (undefined | a) {JS modified (-bs-emit-ts a function returning undefined alone) let useNestedFolded = () => getOr(Some(None), Some(3))
export function useNestedFolded(): (undefined | number) {
+ return;
}The examples NestMatch (nested matches, Review
Print a marker of any depth at a payload that may own the marker keybd978bd · cristianoc + Claude · Nov 04, 2026, 06:11 UTC
Representation modified (option under -bs-emit-ts, payloads that may own the key)
-option<option<keyed>> ↦ undefined | keyed | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }
+option<option<keyed>> ↦ undefined | keyed | { readonly BS_PRIVATE_NESTED_SOME_NONE: number }
+rule: a payload is no marker only where it cannot hold undefined and may own no key BS_PRIVATE_NESTED_SOME_NONE: a record or an inline record naming that field at run time, an object type, and a variant case whose payload may, may own itReview
Record tsc's diagnostics for the emit_ts examples: none with overloadsdc30f4e · cristianoc + Claude · Nov 04, 2026, 08:16 UTC
Tests modified (tests/build_tests/emit_ts/tsc.txt) empty |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## exp/emit-ts-2 #9213 +/- ##
=================================================
- Coverage 81.76% 81.75% -0.01%
=================================================
Files 459 459
Lines 65480 65613 +133
=================================================
+ Hits 53539 53645 +106
- Misses 11941 11968 +27
🚀 New features to boost your workflow:
|
rescript
@rescript/belt
@rescript/darwin-arm64
@rescript/linux-arm64
@rescript/linux-x64
@rescript/runtime
@rescript/win32-x64
commit: |
Stacked on #9211. It prints nested options and polymorphic option code under
-bs-emit-tsso that tsc and TScert accept the nested matches #9211 lists as open.What is printed:
option<t>printstbeside the marker of each depthSome(v)can take. For example,option<option<int>>printsundefined | number | { readonly BS_PRIVATE_NESTED_SOME_NONE: 0 }, and each further level adds the next depth.numberat abstract types, at a type declaration's parameter, and at a payload that may own the marker key. That covers a record with a field@as("BS_PRIVATE_NESTED_SOME_NONE"), an object type, and a variant whose payload may own the key.'a := option^k<a>, witha extends string | number | boolean | bigint. The implementation's signature is the deepest instance.Runtime declarations (
runtime-dts/):Primitive_option.someandvalFromOption, andStdlib_Option.getOr, are stated at depths 0–2. Each line is checked againstPrimitive_option.resorStdlib_Option.res, and none assumes anything about who owns the marker key.Tests:
NestMatch(inner,useNested) andMemo(cached).tsc.txtis now empty: the four errors at the primitive bound are gone.make test-gentypepasses with its outputs unchanged.TScert: with its generic-overloads change, all 10
emit_tsmodules verify end to end. TScert is not released yet, so a reviewer can't reproduce that run.Limit: polymorphic option code at a record payload, or nested deeper than the printed overloads, still fails tsc and is refused, never falsely accepted. Covering every payload needs either an assumption that only the runtime's markers own the key, or conditional types.
🤖 Generated with Claude Code