Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e99e3ae
Match untagged variants with a literal switch and one typeof dispatch
cknitt Oct 24, 2026
356ef58
Drop literal arms that a complete payload dispatch runs anyway
cknitt Oct 24, 2026
77e7004
Update the gentype example for the untagged dispatch
cknitt Oct 24, 2026
2271e3e
Let builtin types state their runtime shapes
cknitt Oct 24, 2026
d1446fb
Resolve the literal of a constructor without @as once
cknitt Oct 24, 2026
7fb8da3
Allow @notUndefined on type abbreviations
cknitt Oct 24, 2026
d42a132
Fold matches of constructors by their runtime literals
cknitt Oct 24, 2026
f42bd93
Fold matches of numeric constants by their runtime value
cknitt Oct 24, 2026
a2a824c
Warn about untagged payload patterns shadowed by literal cases
cknitt Oct 19, 2026
fc353d3
Report shadowed payload alternatives when only warning 12 is on
cknitt Oct 20, 2026
c49c11e
Describe the shadowed payload warnings in the spec
cknitt Oct 24, 2026
59739f7
Say in rule 6 that variants with some @unboxed constructors are checked
cknitt Oct 24, 2026
bb944d8
Require a private abbreviation for every subclass
cknitt Oct 24, 2026
ab924c6
Say precisely when literals use an if and what folds
cknitt Oct 24, 2026
ad2cc33
Emit untagged checks directly instead of through Dynamic_checks
cknitt Oct 24, 2026
7139b27
Share which literal passes which payload check
cknitt Oct 24, 2026
18bb267
Move the variant invariant check next to the layout
cknitt Oct 24, 2026
19cd7e9
Compare every runtime shape claim of an interface in one place
cknitt Oct 24, 2026
be31af5
Merge the @notUndefined rule into the rule on declared shapes
cknitt Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/untagged-declarations.polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Allow `@notUndefined` on type abbreviations, and `@typeOf` restating a builtin type's kind, as in `@typeOf("number") type count = int`; a constructor `A` and `@as("A") A` now agree across interfaces and coercions.
1 change: 1 addition & 0 deletions changes/untagged-dispatch.polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Match an untagged variant with one `switch` over its literals and one `switch (typeof x)` over its primitive cases before any class check, so that strings and numbers skip `instanceof` and a class missing at run time no longer throws for them.
1 change: 1 addition & 0 deletions changes/untagged-literal-overlap-warnings.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Warn about untagged variant payload patterns that can never match because a literal case has the same value, like `S("a")` with `@as("a") A | S(string)`, and treat `@as(true) Yes | Ans(bool)` as fully covered by `Yes | Ans(false)`.
1 change: 1 addition & 0 deletions changes/untagged-private-subclasses.polish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Require `= private` for every subclass declared with `@instanceOf`, also in phantom type hierarchies, and fold `null` into an untagged variant's object case as it is matched at run time.
8 changes: 2 additions & 6 deletions compiler/core/js_dump.ml
Original file line number Diff line number Diff line change
Expand Up @@ -897,9 +897,7 @@ and expression_desc cxt ~(level : int) f x : cxt =
| Tagged {tag; tag_name} ->
( Js_op.Lit (Option.value tag_name ~default:L.tag),
(* TAG:xx for inline records *)
match tag.literal with
| None -> E.str p.name
| Some t -> E.literal_tag t )
E.literal_tag tag.literal )
:: tails
in
expression_desc cxt ~level f (Object (E.object_entries objs))
Expand Down Expand Up @@ -936,9 +934,7 @@ and expression_desc cxt ~(level : int) f x : cxt =
else
( Js_op.Lit tag_name,
(* TAG:xx *)
match literal with
| None -> E.str p.name
| Some t -> E.literal_tag t )
E.literal_tag literal )
:: tails
in
expression_desc cxt ~level f (J.Object (E.object_entries objs))
Expand Down
72 changes: 32 additions & 40 deletions compiler/core/js_exp_make.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1395,52 +1395,44 @@ let class_constructor (instance : Variant_runtime.Instance.t) =
| [] -> assert false
| global :: properties -> List.fold_left dot (js_global global) properties

(* The [typeof] string an untagged payload answers to. *)
let block_type_name = function
| Variant_runtime.InstanceType i -> class_constructor i
| block_type -> (
match Variant_runtime.typeof_name block_type with
| Some name -> str name
| None ->
(* An unknown payload has no check: it is the only payload case *)
assert false)
(* The [typeof] string an untagged payload answers to, as a [switch] key.
Instances are told apart by [is_instance_of], an unknown payload is not
checked *)
let block_type_name block_type =
match Variant_runtime.typeof_name block_type with
| Some name -> str name
| None -> assert false

let tag_type = function
| Variant_runtime.Literal d -> literal_tag d
| Payload_shape b -> block_type_name b

let rec emit_check (check : t Ast_untagged_variants.Dynamic_checks.t) =
match check with
| TagType t -> tag_type t
| BinOp (op, x, y) ->
let op =
match op with
| EqEqEq -> Js_op.EqEqEq
| NotEqEq -> NotEqEq
| And -> And
| Or -> Or
in
bin op (emit_check x) (emit_check y)
| TypeOf x -> typeof (emit_check x)
| IsInstanceOf (Array, x) -> is_array (emit_check x)
| IsInstanceOf (instance, x) ->
instanceof (emit_check x) (class_constructor instance)
| Not x -> not (emit_check x)
| Expr x -> x

let is_a_literal_case ~literal_cases ~block_cases (e : t) =
let check =
Ast_untagged_variants.Dynamic_checks.is_a_literal_case ~literal_cases
~block_cases (Expr e)
in
emit_check check

let is_int_tag ?has_null_undefined_other e =
let check =
Ast_untagged_variants.Dynamic_checks.is_int_tag ?has_null_undefined_other
(Expr e)
let is_instance_of (instance : Variant_runtime.Instance.t) e =
match instance with
| Array -> is_array e
| Class _ -> instanceof e (class_constructor instance)

(* Whether a value of a tagged variant is a constant rather than a block,
given the literals of its constants *)
let is_int_tag ~(literal_tags : Variant_runtime.literal_tag list) e =
let has_null = List.mem Variant_runtime.Null literal_tags in
let has_undefined = List.mem Variant_runtime.Undefined literal_tags in
let has_other =
List.exists
(function
| Variant_runtime.Null | Undefined -> false
| String _ | Int _ | Float _ | BigInt _ | Bool _ -> true)
literal_tags
in
emit_check check
let is_null = bin EqEqEq e nil in
let is_undefined = bin EqEqEq e undefined in
let not_an_object = bin NotEqEq (typeof e) (str "object") in
match (has_null, has_undefined, has_other) with
| true, false, false -> is_null
| true, true, false -> bin Or is_null is_undefined
| false, true, false -> is_undefined
| true, _, true -> bin Or is_null not_an_object
| false, _, _ -> not_an_object

let tag ?comment ?(name = Js_dump_lit.tag) e : t =
{expression_desc = Caml_block_tag (e, name); comment; source_loc = None}
Expand Down
11 changes: 3 additions & 8 deletions compiler/core/js_exp_make.mli
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ val assign : ?comment:string -> t -> t -> t
val literal_tag : Variant_runtime.literal_tag -> t
val tag_type : Variant_runtime.tag_type -> t

val emit_check : t Ast_untagged_variants.Dynamic_checks.t -> t
val is_instance_of : Variant_runtime.Instance.t -> t -> t
(** [Array.isArray(e)] or [e instanceof C] *)

val triple_equal : ?comment:string -> t -> t -> t
(* TODO: reduce [triple_equal] use *)
Expand All @@ -179,13 +180,7 @@ val eq_null_undefined_boolean : ?comment:string -> t -> t -> t

val neq_null_undefined_boolean : ?comment:string -> t -> t -> t

val is_int_tag : ?has_null_undefined_other:bool * bool * bool -> t -> t

val is_a_literal_case :
literal_cases:Variant_runtime.literal_tag list ->
block_cases:Variant_runtime.block_type list ->
t ->
t
val is_int_tag : literal_tags:Variant_runtime.literal_tag list -> t -> t

val is_type_object : t -> t

Expand Down
3 changes: 3 additions & 0 deletions compiler/core/js_stmt_make.mli
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ val string_switch :
(Variant_runtime.tag_type * J.case_clause) list ->
t

val block_last_is_return_throw_or_continue : J.block -> bool
(** Whether control never falls out of the end of the block *)

val declare_variable :
?comment:string ->
?ident_info:J.ident_info ->
Expand Down
Loading
Loading