Skip to content

Commit 35b1e48

Browse files
cknittclaude
andauthored
Box Some of a constant case represented by undefined (#9008)
Why: `Some(U)` with `@as(undefined) U` compiled to plain `undefined` when `U` was a constant, so it was `None`, while `Some(v)` with `v = U` passed at run time stayed distinct (#8954). A constant constructor whose literal is `undefined` is now a none for `const_some_none_depth`, so `Some` of it is boxed like `Some(None)`. This holds for untagged and tagged variants alike. The spec's known bug is gone, and rule 12 states the case. JS modified: Example: @unboxed type u = | @as(undefined) U | V(string) let x: option<u> = Some(U) Before (js): let x = undefined; After (js): let x = Primitive_option.some(undefined); Fixes: #8954 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel <ck@cca.io> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent e6b89eb commit 35b1e48

5 files changed

Lines changed: 95 additions & 99 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Keep `Some(U)` distinct from `None` when `U` is a constant case represented by `undefined` (`@as(undefined)`).

‎compiler/ml/UntaggedVariants.md‎

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ as `Primitive_option.some(v)` otherwise. An untagged variant cannot hold
291291
| an abstract type without it, a type variable, `option<_>` | yes |
292292
| a type whose declaration is not on the load path | yes |
293293

294-
An `@as(null)` case does not prevent unboxing; `Some(N)` is `null`.
294+
An `@as(null)` case does not prevent unboxing; `Some(N)` is `null`. `Some(U)` of an `@as(undefined)` case is boxed, as a constant
295+
too, so it stays distinct from `None`.
295296

296297
| Declaration | `Some(v)` |
297298
| --- | --- |
@@ -313,7 +314,7 @@ abstract types `@notUndefined`; `Blob.t` and `File.t` are not marked.
313314
- Tests: `spec_test` rule 12; `UntaggedVariants.res`
314315
(`OptionUnboxingHeuristic`); `tests/tests/src/option_wrapping_test.res`;
315316
`tests/build_tests/option_transitive_dependency`.
316-
- Shaped by #6222, #6233, #8719.
317+
- Shaped by #6222, #6233, #8719, #8954.
317318

318319
### 13. `@notUndefined`
319320

@@ -356,21 +357,6 @@ cases with a payload (see Known bugs).
356357
Each was observed with the compiler built from this tree; the programs are
357358
compiled with `bsc` and run in Node 25.
358359

359-
**`Some` of a constant `undefined` case is `None`.** Rule 12 boxes `Some(v)`
360-
for a variant with an `@as(undefined)` case, but a constant argument skips
361-
the boxing.
362-
363-
```rescript
364-
@unboxed type u = | @as(undefined) U | V(string)
365-
let x: option<u> = Some(U) // let x = undefined;
366-
@inline(never) let mk = (v: u): option<u> => Some(v)
367-
Console.log2(x == None, mk(U) == None) // true false
368-
```
369-
370-
`lambda.ml:const_some_none_depth` returns "not a none" for every
371-
`Const_constructor`, including one whose literal is `undefined`; that this is
372-
the cause is conjectured, not established.
373-
374360
**Overlapping instance classes follow declaration order** (#8938).
375361

376362
```rescript

‎compiler/ml/lambda.ml‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,10 @@ let rec const_some_none_depth (c : structured_constant) : int =
586586
| Const_some v ->
587587
let depth = const_some_none_depth v in
588588
if depth < 0 then depth else depth + 1
589-
| Const_module_alias | Const_js_undefined _ -> 0
589+
| Const_module_alias | Const_js_undefined _
590+
(* A constructor represented by [undefined], [@as(undefined)] *)
591+
| Const_constructor {literal = Some Undefined} ->
592+
0
590593
| _ -> -1
591594

592595
(* A constant that has to be built at run time rather than shared. A [Some]

0 commit comments

Comments
 (0)