From cf19d26e403c1c1db4f66099492800600a28e561 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 25 Oct 2026 08:00:31 +0000 Subject: [PATCH 1/2] Make @unboxed apply to a whole variant only Why: `@unboxed` on a single constructor made that constructor untagged and left the others tagged. Such mixed variants missed the checks of untagged ones (#9020) and interface and coercion checks (#9038), and their matches crashed or picked the wrong case. Public code uses the attribute on every payload constructor or on the only one, which the type-level `@unboxed` says, but no code mixes tagged and untagged cases. `@unboxed` on a constructor is an error that says to write it on the type. The layout of a variant is untagged exactly when its type is `@unboxed`, so `check_invariant` no longer considers tagged and untagged blocks together, the type's `@unboxed` is no longer copied onto constructors, and `process_untagged`, a twin of `has_untagged`, is gone. Diagnostic added (@unboxed on a constructor): Example: type t = | @unboxed A(int) | B({x: int}) | C Before (diagnostic): none; `A(3)` is `3` and `B` is `{TAG: "B", x: 1}` After (diagnostic): @unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`, instead of on constructor A. Diagnostic added (@unboxed on a single-field inline record constructor): Example: type t = | @unboxed A({x: int}) Before (diagnostic): none; `A({x: 1})` is `{x: 1}` After (diagnostic): @unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`, instead of on constructor A. For a constructor whose payload is an inline record with one field, `@unboxed type t = A({...})` makes the value that field. To keep the record object, give the record type a name: `@unboxed type t = A(r)`. Breaking: `@unboxed` on a constructor no longer compiles. Moving it to the type keeps the representation, except for a constructor whose payload is an inline record with one field, whose value becomes the field; a named record type keeps the object. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel --- changes/mixed-variant-checks.fix.md | 1 - ...remove-unboxed-on-constructors.breaking.md | 1 + compiler/ml/UntaggedVariants.md | 33 ++-- compiler/ml/ast_untagged_variants.ml | 102 ++++------ compiler/ml/typedecl.ml | 25 ++- compiler/ml/variant_coercion.ml | 4 +- compiler/ml/variant_layout.ml | 4 +- ...ructorAttributeMoreThanOneArg.res.expected | 8 - .../unboxed_on_constructor.res.expected | 8 + ...ngle_field_record_constructor.res.expected | 8 + ...agged_mixed_object_and_tagged.res.expected | 9 - .../untagged_mixed_two_numbers.res.expected | 9 - ...ntagged_mixed_unknown_payload.res.expected | 9 - ...ggedConstructorAttributeMoreThanOneArg.res | 1 - .../fixtures/unboxed_on_constructor.res | 1 + ...xed_on_single_field_record_constructor.res | 1 + .../untagged_mixed_object_and_tagged.res | 8 - .../fixtures/untagged_mixed_two_numbers.res | 7 - .../untagged_mixed_unknown_payload.res | 7 - tests/tests/src/record_extension_test.res | 2 +- tests/tests/src/untagged_mixed_test.mjs | 35 ---- tests/tests/src/untagged_mixed_test.res | 24 --- .../tests/src/untagged_variants_spec_test.mjs | 183 +++++++++--------- .../tests/src/untagged_variants_spec_test.res | 27 ++- 24 files changed, 215 insertions(+), 302 deletions(-) delete mode 100644 changes/mixed-variant-checks.fix.md create mode 100644 changes/remove-unboxed-on-constructors.breaking.md delete mode 100644 tests/build_tests/super_errors/expected/UntaggedConstructorAttributeMoreThanOneArg.res.expected create mode 100644 tests/build_tests/super_errors/expected/unboxed_on_constructor.res.expected create mode 100644 tests/build_tests/super_errors/expected/unboxed_on_single_field_record_constructor.res.expected delete mode 100644 tests/build_tests/super_errors/expected/untagged_mixed_object_and_tagged.res.expected delete mode 100644 tests/build_tests/super_errors/expected/untagged_mixed_two_numbers.res.expected delete mode 100644 tests/build_tests/super_errors/expected/untagged_mixed_unknown_payload.res.expected delete mode 100644 tests/build_tests/super_errors/fixtures/UntaggedConstructorAttributeMoreThanOneArg.res create mode 100644 tests/build_tests/super_errors/fixtures/unboxed_on_constructor.res create mode 100644 tests/build_tests/super_errors/fixtures/unboxed_on_single_field_record_constructor.res delete mode 100644 tests/build_tests/super_errors/fixtures/untagged_mixed_object_and_tagged.res delete mode 100644 tests/build_tests/super_errors/fixtures/untagged_mixed_two_numbers.res delete mode 100644 tests/build_tests/super_errors/fixtures/untagged_mixed_unknown_payload.res delete mode 100644 tests/tests/src/untagged_mixed_test.mjs delete mode 100644 tests/tests/src/untagged_mixed_test.res diff --git a/changes/mixed-variant-checks.fix.md b/changes/mixed-variant-checks.fix.md deleted file mode 100644 index bcb7eec2cf2..00000000000 --- a/changes/mixed-variant-checks.fix.md +++ /dev/null @@ -1 +0,0 @@ -- Check variants with some `@unboxed` constructors like untagged variants, so that cases a match cannot tell apart are rejected, and an untagged case's `@as` no longer clashes with a tagged case's tag. diff --git a/changes/remove-unboxed-on-constructors.breaking.md b/changes/remove-unboxed-on-constructors.breaking.md new file mode 100644 index 00000000000..dfd4ff4babe --- /dev/null +++ b/changes/remove-unboxed-on-constructors.breaking.md @@ -0,0 +1 @@ +- `@unboxed` applies to a whole variant and can no longer be written on a constructor. Write `@unboxed type t = ...` instead. A constructor whose payload is an inline record with one field became that record object, while `@unboxed` on its type makes the value the field: give the record type a name to keep the object. diff --git a/compiler/ml/UntaggedVariants.md b/compiler/ml/UntaggedVariants.md index 26b89694519..0660e3cfea2 100644 --- a/compiler/ml/UntaggedVariants.md +++ b/compiler/ml/UntaggedVariants.md @@ -20,24 +20,35 @@ directory is named. ### 1. Declaration -`@unboxed` and `@tag(...)` on a variant type are copied onto every -constructor. `@unboxed` on a single constructor of an ordinary variant makes -that constructor untagged and leaves the others tagged: +`@unboxed` on a variant type makes the whole variant untagged: every case with +a payload is represented by its payload. `@tag(...)` on the type is copied +onto every constructor. A constructor cannot have its own `@unboxed`: it would +mix tagged and untagged cases in one variant, and is an error telling to write +it on the type. For a constructor whose payload is an inline record with one +field, `@unboxed` on the type makes the value that field (the legacy unboxed +representation), so the error also tells to give the record type a name to +keep the record object. | Declaration | Value | JavaScript | | --- | --- | --- | -| `type mixed = \| @unboxed MA(int) \| MB({x: int}) \| MC` | `MA(3)` | `3` | -| | `MB({x: 1})` | `{TAG: "MB", x: 1}` | -| | `MC` | `"MC"` | +| `@unboxed type whole = WA(int) \| WB({x: int}) \| WC` | `WA(3)` | `3` | +| | `WB({x: 1})` | `{x: 1}` | +| | `WC` | `"WC"` | + +| Declaration | Error | Fixture | +| --- | --- | --- | +| `type t = \| @unboxed A(int) \| B({x: int}) \| C` | @unboxed applies to a whole variant: write it on the type, ... | `unboxed_on_constructor.res` | +| `type t = \| @unboxed A({x: int})` | the same, and how to keep the record object | `unboxed_on_single_field_record_constructor.res` | The layout of a declaration is computed once, after its recursive group is in the environment, and stored on the declaration. -- Implementation: `typedecl.ml` (`copy_tag_attr_from_decl` in - `transl_declaration`; the layout pass in `transl_type_decl`), - `variant_layout.ml:layout_from_type_variant`. -- Tests: `spec_test` rule 1. -- Shaped by #7320 (recursive groups), #8579 (layout stored once). +- Implementation: `typedecl.ml` (the constructor check and + `copy_tag_attr_from_decl` in `transl_declaration`; the layout pass in + `transl_type_decl`), `variant_layout.ml:layout_from_type_variant`. +- Tests: `spec_test` rule 1; the fixtures above. +- Shaped by #7320 (recursive groups), #8579 (layout stored once), #9020 and + #9038 (constructor-level `@unboxed` removed). ### 2. Representation diff --git a/compiler/ml/ast_untagged_variants.ml b/compiler/ml/ast_untagged_variants.ml index fd3538bdc41..e1ed6db94dc 100644 --- a/compiler/ml/ast_untagged_variants.ml +++ b/compiler/ml/ast_untagged_variants.ml @@ -20,6 +20,7 @@ type error = | InvalidVariantTagAnnotation | InvalidUntaggedVariantDefinition of untagged_error | TagFieldNameConflict of string * string * string + | UnboxedOnConstructor of {constructor: string; single_field_record: bool} exception Error of Location.t * error let report_error ppf = @@ -71,6 +72,18 @@ let report_error ppf = value of inline record field \"%s\". Use a different @tag name or \ rename the field." constructor_name runtime_value field_name + | UnboxedOnConstructor {constructor; single_field_record} -> + fprintf ppf + "@@unboxed applies to a whole variant: write it on the type, as in \ + `@@unboxed type t = ...`, instead of on constructor %s." + constructor; + if single_field_record then + fprintf ppf + " For a constructor whose payload is an inline record with one field, \ + `@@unboxed type t = %s({...})` makes the value that field. To keep \ + the record object, give the record type a name: `@@unboxed type t = \ + %s(r)`." + constructor constructor (* Type of the runtime representation of a tag. @@ -91,14 +104,6 @@ let untagged = "unboxed" let has_untagged (attrs : Parsetree.attributes) = Ext_list.exists attrs (function {txt}, _ -> txt = untagged) -let process_untagged (attrs : Parsetree.attributes) = - let st = ref false in - Ext_list.iter attrs (fun ({txt}, _) -> - match txt with - | "unboxed" -> st := true - | _ -> ()); - !st - let runtime_tag_of_parsetree ~loc = function | Parsetree.Pct_string s -> String (String_literal.string_semantic s) | Pct_int source -> ( @@ -211,7 +216,7 @@ let is_nullary_variant (x : Types.constructor_arguments) = (* A literal's JavaScript value, which a [switch] compares with [===]. Ints are emitted as 32-bit numbers, so [1], [1.0] and [4294967297] are one number, while [1n] is a bigint. *) -let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list) +let check_invariant ~(consts : (Location.t * tag) list) ~(blocks : (Location.t * block) list) = let module String_set = Set.Make (String) in let module String_map = Map.Make (String) in @@ -297,61 +302,40 @@ let check_invariant ~is_untagged_def ~(consts : (Location.t * tag) list) Ext_list.rev_iter consts (fun (loc, literal) -> check_literal ~is_const:true ~loc literal); - (* A tagged block is told apart from the others by its tag; an untagged one - is its payload, whatever its [@as] *) + (* The blocks of a variant are all tagged or, when the type is [@unboxed], + all untagged. A tagged block is told apart from the others by its tag; an + untagged one by the kind of its payload, whatever its [@as]. *) Ext_list.rev_iter blocks (fun (loc, block) -> match block with | Tagged {tag} -> check_literal ~is_const:false ~loc tag - | Untagged _ -> ()); - (* The payload kinds are checked as soon as one case is untagged, even when - the type itself is not [@unboxed] *) - let has_untagged_block = - List.exists - (function - | _, Untagged _ -> true - | _, Tagged _ -> false) - blocks - in - if is_untagged_def || has_untagged_block then ( - (* Tagged blocks are objects with a tag field: together one object case *) - if - List.exists - (function - | _, Tagged _ -> true - | _, Untagged _ -> false) - blocks - then incr object_types; - Ext_list.rev_iter blocks (fun (loc, block) -> - match block with - | Untagged {tag; block_type} -> - (match block_type with - | UnknownType -> incr unknown_types - | ObjectType -> incr object_types - | InstanceType i -> - let count = - Hashtbl.find_opt instance_types i |> Option.value ~default:0 - in - Hashtbl.replace instance_types i (count + 1) - | FunctionType -> incr function_types - | IntType | FloatType -> incr number_types - | BigintType -> incr bigint_types - | BooleanType -> - (* Literal cases are matched before payload cases, so a boolean + | Untagged {tag; block_type} -> + (match block_type with + | UnknownType -> incr unknown_types + | ObjectType -> incr object_types + | InstanceType i -> + let count = + Hashtbl.find_opt instance_types i |> Option.value ~default:0 + in + Hashtbl.replace instance_types i (count + 1) + | FunctionType -> incr function_types + | IntType | FloatType -> incr number_types + | BigintType -> incr bigint_types + | BooleanType -> + (* Literal cases are matched before payload cases, so a boolean payload only receives the booleans that are not literal cases. A partial overlap is allowed, like for strings and numbers. *) - if - String_map.mem "true" !nonstring_literals_consts - && String_map.mem "false" !nonstring_literals_consts - then - raise - (Error - ( loc, - InvalidUntaggedVariantDefinition - (UnreachableBoolean tag.name) )); - incr boolean_types - | StringType -> incr string_types); - invariant loc tag.name - | Tagged _ -> ())) + if + String_map.mem "true" !nonstring_literals_consts + && String_map.mem "false" !nonstring_literals_consts + then + raise + (Error + ( loc, + InvalidUntaggedVariantDefinition + (UnreachableBoolean tag.name) )); + incr boolean_types + | StringType -> incr string_types); + invariant loc tag.name) let get_cstr_loc_tag (cstr : Types.constructor_declaration) = (cstr.cd_loc, {name = Ident.name cstr.cd_id; literal = cstr.cd_runtime_tag}) diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 82a86327294..4a3ec635241 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -472,10 +472,31 @@ let transl_declaration ~type_record_as_object env sdecl id = raise (Error (sdecl.ptype_loc, Duplicate_constructor name)); all_constrs := String_set.add name !all_constrs) scstrs; + (* [@unboxed] applies to the whole variant, so a constructor cannot have + its own *) + List.iter + (fun {pcd_name = {txt = constructor}; pcd_args; pcd_attributes} -> + match + Ext_list.find_first pcd_attributes (fun ({txt}, _) -> + txt = Ast_untagged_variants.untagged) + with + | Some ({loc}, _) -> + raise + (Ast_untagged_variants.Error + ( loc, + UnboxedOnConstructor + { + constructor; + single_field_record = + (match pcd_args with + | Pcstr_record [_] -> true + | Pcstr_record _ | Pcstr_tuple _ -> false); + } )) + | None -> ()) + scstrs; let copy_tag_attr_from_decl attr = let tag_attrs = - Ext_list.filter sdecl.ptype_attributes (fun ({txt}, _) -> - txt = "tag" || txt = Ast_untagged_variants.untagged) + Ext_list.filter sdecl.ptype_attributes (fun ({txt}, _) -> txt = "tag") in if tag_attrs = [] then attr else tag_attrs @ attr in diff --git a/compiler/ml/variant_coercion.ml b/compiler/ml/variant_coercion.ml index 0bd95ddbfd0..cac2b9b44d3 100644 --- a/compiler/ml/variant_coercion.ml +++ b/compiler/ml/variant_coercion.ml @@ -198,8 +198,8 @@ let variant_configuration_can_be_coerced_raises ~is_spread_context ~left_loc ~right_loc ~(left_attributes : Parsetree.attributes) ~(right_attributes : Parsetree.attributes) = (match - ( Ast_untagged_variants.process_untagged left_attributes, - Ast_untagged_variants.process_untagged right_attributes ) + ( Ast_untagged_variants.has_untagged left_attributes, + Ast_untagged_variants.has_untagged right_attributes ) with | true, true | false, false -> () | left, _right -> diff --git a/compiler/ml/variant_layout.ml b/compiler/ml/variant_layout.ml index f7cb3a3ad28..158be9a2794 100644 --- a/compiler/ml/variant_layout.ml +++ b/compiler/ml/variant_layout.ml @@ -61,7 +61,7 @@ let layout_from_type_variant ~(configuration : configuration) ~env let get_block (cstr : Types.constructor_declaration) : block = let tag = {name = Ident.name cstr.cd_id; literal = cstr.cd_runtime_tag} in let tag_name = process_tag_name cstr.cd_attributes in - if process_untagged cstr.cd_attributes then + if configuration.unboxed then Untagged {tag; block_type = get_block_type ~env cstr} else Tagged {tag; tag_name} in @@ -81,7 +81,7 @@ let layout_from_type_variant ~(configuration : configuration) ~env | Constant tag -> ((loc, tag) :: consts, blocks) | Block block -> (consts, (loc, block) :: blocks)) in - check_invariant ~is_untagged_def:configuration.unboxed ~consts ~blocks; + check_invariant ~consts ~blocks; let constructors = Array.of_list (List.map (fun (_, constructor) -> constructor) located_constructors) diff --git a/tests/build_tests/super_errors/expected/UntaggedConstructorAttributeMoreThanOneArg.res.expected b/tests/build_tests/super_errors/expected/UntaggedConstructorAttributeMoreThanOneArg.res.expected deleted file mode 100644 index 6e3d991a9b2..00000000000 --- a/tests/build_tests/super_errors/expected/UntaggedConstructorAttributeMoreThanOneArg.res.expected +++ /dev/null @@ -1,8 +0,0 @@ - - We've found a bug for you! - /.../fixtures/UntaggedConstructorAttributeMoreThanOneArg.res:1:10-31 - - 1 │ type t = | @unboxed A(int, int) | B - 2 │ - - This untagged variant definition is invalid: Constructor A has more than one argument. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/unboxed_on_constructor.res.expected b/tests/build_tests/super_errors/expected/unboxed_on_constructor.res.expected new file mode 100644 index 00000000000..743ee412b80 --- /dev/null +++ b/tests/build_tests/super_errors/expected/unboxed_on_constructor.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/unboxed_on_constructor.res:1:12-19 + + 1 │ type t = | @unboxed A(int) | B({x: int}) | C + 2 │ + + @unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`, instead of on constructor A. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/unboxed_on_single_field_record_constructor.res.expected b/tests/build_tests/super_errors/expected/unboxed_on_single_field_record_constructor.res.expected new file mode 100644 index 00000000000..c168c0d049d --- /dev/null +++ b/tests/build_tests/super_errors/expected/unboxed_on_single_field_record_constructor.res.expected @@ -0,0 +1,8 @@ + + We've found a bug for you! + /.../fixtures/unboxed_on_single_field_record_constructor.res:1:12-19 + + 1 │ type t = | @unboxed A({x: int}) + 2 │ + + @unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`, instead of on constructor A. For a constructor whose payload is an inline record with one field, `@unboxed type t = A({...})` makes the value that field. To keep the record object, give the record type a name: `@unboxed type t = A(r)`. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/untagged_mixed_object_and_tagged.res.expected b/tests/build_tests/super_errors/expected/untagged_mixed_object_and_tagged.res.expected deleted file mode 100644 index b8546679d29..00000000000 --- a/tests/build_tests/super_errors/expected/untagged_mixed_object_and_tagged.res.expected +++ /dev/null @@ -1,9 +0,0 @@ - - We've found a bug for you! - /.../fixtures/untagged_mixed_object_and_tagged.res:1:10-31 - - 1 │ type t = | @unboxed A({x: int}) | B({y: int}) | C - 2 │ let f = x => - 3 │ switch x { - - This untagged variant definition is invalid: At most one case can be an object type. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/untagged_mixed_two_numbers.res.expected b/tests/build_tests/super_errors/expected/untagged_mixed_two_numbers.res.expected deleted file mode 100644 index 0efbcc2da7f..00000000000 --- a/tests/build_tests/super_errors/expected/untagged_mixed_two_numbers.res.expected +++ /dev/null @@ -1,9 +0,0 @@ - - We've found a bug for you! - /.../fixtures/untagged_mixed_two_numbers.res:1:28-44 - - 1 │ type t = | @unboxed A(int) | @unboxed B(int) | C - 2 │ let f = x => - 3 │ switch x { - - This untagged variant definition is invalid: At most one case can be a number type (int or float). \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/untagged_mixed_unknown_payload.res.expected b/tests/build_tests/super_errors/expected/untagged_mixed_unknown_payload.res.expected deleted file mode 100644 index c004087eed7..00000000000 --- a/tests/build_tests/super_errors/expected/untagged_mixed_unknown_payload.res.expected +++ /dev/null @@ -1,9 +0,0 @@ - - We've found a bug for you! - /.../fixtures/untagged_mixed_unknown_payload.res:1:10-32 - - 1 │ type t = | @unboxed A(list) | B({x: int}) | C - 2 │ let f = x => - 3 │ switch x { - - This untagged variant definition is invalid: Case A has a payload that is not of one of the recognized shapes (object, array, etc). Then it must be the only case with payloads. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/UntaggedConstructorAttributeMoreThanOneArg.res b/tests/build_tests/super_errors/fixtures/UntaggedConstructorAttributeMoreThanOneArg.res deleted file mode 100644 index ddb2a6aebce..00000000000 --- a/tests/build_tests/super_errors/fixtures/UntaggedConstructorAttributeMoreThanOneArg.res +++ /dev/null @@ -1 +0,0 @@ -type t = | @unboxed A(int, int) | B diff --git a/tests/build_tests/super_errors/fixtures/unboxed_on_constructor.res b/tests/build_tests/super_errors/fixtures/unboxed_on_constructor.res new file mode 100644 index 00000000000..2549dd16613 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/unboxed_on_constructor.res @@ -0,0 +1 @@ +type t = | @unboxed A(int) | B({x: int}) | C diff --git a/tests/build_tests/super_errors/fixtures/unboxed_on_single_field_record_constructor.res b/tests/build_tests/super_errors/fixtures/unboxed_on_single_field_record_constructor.res new file mode 100644 index 00000000000..7aec966c82f --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/unboxed_on_single_field_record_constructor.res @@ -0,0 +1 @@ +type t = | @unboxed A({x: int}) diff --git a/tests/build_tests/super_errors/fixtures/untagged_mixed_object_and_tagged.res b/tests/build_tests/super_errors/fixtures/untagged_mixed_object_and_tagged.res deleted file mode 100644 index 3373b6b72a7..00000000000 --- a/tests/build_tests/super_errors/fixtures/untagged_mixed_object_and_tagged.res +++ /dev/null @@ -1,8 +0,0 @@ -type t = | @unboxed A({x: int}) | B({y: int}) | C -let f = x => - switch x { - | A(_) => "a" - | B(_) => "b" - | C => "c" - } -let r = f(A({x: 1})) diff --git a/tests/build_tests/super_errors/fixtures/untagged_mixed_two_numbers.res b/tests/build_tests/super_errors/fixtures/untagged_mixed_two_numbers.res deleted file mode 100644 index 4faca17526f..00000000000 --- a/tests/build_tests/super_errors/fixtures/untagged_mixed_two_numbers.res +++ /dev/null @@ -1,7 +0,0 @@ -type t = | @unboxed A(int) | @unboxed B(int) | C -let f = x => - switch x { - | A(_) => "a" - | B(_) => "b" - | C => "c" - } diff --git a/tests/build_tests/super_errors/fixtures/untagged_mixed_unknown_payload.res b/tests/build_tests/super_errors/fixtures/untagged_mixed_unknown_payload.res deleted file mode 100644 index 19ab89d449c..00000000000 --- a/tests/build_tests/super_errors/fixtures/untagged_mixed_unknown_payload.res +++ /dev/null @@ -1,7 +0,0 @@ -type t = | @unboxed A(list) | B({x: int}) | C -let f = x => - switch x { - | A(_) => "a" - | B(_) => "b" - | C => "c" - } diff --git a/tests/tests/src/record_extension_test.res b/tests/tests/src/record_extension_test.res index 18a11b99222..ef4688c25db 100644 --- a/tests/tests/src/record_extension_test.res +++ b/tests/tests/src/record_extension_test.res @@ -31,7 +31,7 @@ describe(__LOC__, () => { }) /* Record_unboxed */ -type t1 = | @unboxed A({x: int}) +@unboxed type t1 = A({x: int}) /* Record_inlined */ type t2 = diff --git a/tests/tests/src/untagged_mixed_test.mjs b/tests/tests/src/untagged_mixed_test.mjs deleted file mode 100644 index e6c6dc5ede1..00000000000 --- a/tests/tests/src/untagged_mixed_test.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// Generated by ReScript, PLEASE EDIT WITH CARE - -import * as Mocha from "mocha"; -import * as Test_utils from "./test_utils.mjs"; - -function kind(value) { - if (value === "C") { - return "c"; - } else if (typeof value === "number") { - return "a"; - } else { - return "b"; - } -} - -function id(x) { - return x; -} - -Mocha.describe("Untagged_mixed_test", () => { - Mocha.test("an untagged case's @as is not a tag", () => { - Test_utils.eq("File \"untagged_mixed_test.res\", line 20, characters 7-14", kind(id(1)), "a"); - Test_utils.eq("File \"untagged_mixed_test.res\", line 21, characters 7-14", kind(id({ - TAG: "x", - y: 1 - })), "b"); - Test_utils.eq("File \"untagged_mixed_test.res\", line 22, characters 7-14", kind(id("C")), "c"); - }); -}); - -export { - kind, - id, -} -/* Not a pure module */ diff --git a/tests/tests/src/untagged_mixed_test.res b/tests/tests/src/untagged_mixed_test.res deleted file mode 100644 index b92bb41f50f..00000000000 --- a/tests/tests/src/untagged_mixed_test.res +++ /dev/null @@ -1,24 +0,0 @@ -open Mocha -open Test_utils - -// In a variant with some @unboxed cases, the @as of an untagged case does not -// change its representation, so it does not clash with a tagged case's tag -// (#9020) -type t = | @unboxed @as("x") A(int) | @as("x") B({y: int}) | C - -let kind = value => - switch value { - | A(_) => "a" - | B(_) => "b" - | C => "c" - } - -@inline(never) let id = x => x - -describe(__MODULE__, () => { - test("an untagged case's @as is not a tag", () => { - eq(__LOC__, kind(id(A(1))), "a") - eq(__LOC__, kind(id(B({y: 1}))), "b") - eq(__LOC__, kind(id(C)), "c") - }) -}) diff --git a/tests/tests/src/untagged_variants_spec_test.mjs b/tests/tests/src/untagged_variants_spec_test.mjs index e71c04de949..48388be6934 100644 --- a/tests/tests/src/untagged_variants_spec_test.mjs +++ b/tests/tests/src/untagged_variants_spec_test.mjs @@ -8,13 +8,13 @@ function id(x) { return x; } -function mixedName(v) { - if (v === "MC") { - return "MC"; +function wholeName(v) { + if (v === "WC") { + return "WC"; } else if (typeof v === "number") { - return "MA"; + return "WA"; } else { - return "MB"; + return "WB"; } } @@ -229,171 +229,168 @@ function someWithDefined(v) { } Mocha.describe("Untagged_variants_spec_test", () => { - Mocha.test("rule 1: @unboxed on one constructor of a tagged variant", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 225, characters 7-14", id(3), 3); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 226, characters 7-14", id({ - TAG: "MB", + Mocha.test("rule 1: @unboxed on the type applies to every constructor", () => { + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 224, characters 7-14", id(3), 3); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 225, characters 7-14", id({ x: 1 }), { - TAG: "MB", x: 1 }); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 227, characters 7-14", id("MC"), "MC"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 228, characters 7-14", mixedName(id(3)), "MA"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 229, characters 7-14", mixedName(id({ - TAG: "MB", + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 226, characters 7-14", id("WC"), "WC"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 227, characters 7-14", wholeName(id(3)), "WA"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 228, characters 7-14", wholeName(id({ x: 1 - })), "MB"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 230, characters 7-14", mixedName(id("MC")), "MC"); + })), "WB"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 229, characters 7-14", wholeName(id("WC")), "WC"); }); Mocha.test("rule 2: representation of each case", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 234, characters 7-14", id("Named"), "Named"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 235, characters 7-14", id("s"), "s"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 236, characters 7-14", id(1), 1); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 237, characters 7-14", id(2.5), 2.5); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 238, characters 7-14", id(3n), 3n); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 239, characters 7-14", id(true), true); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 240, characters 7-14", id(null), null); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 241, characters 7-14", id(undefined), undefined); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 242, characters 7-14", id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 233, characters 7-14", id("Named"), "Named"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 234, characters 7-14", id("s"), "s"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 235, characters 7-14", id(1), 1); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 236, characters 7-14", id(2.5), 2.5); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 237, characters 7-14", id(3n), 3n); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 238, characters 7-14", id(true), true); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 239, characters 7-14", id(null), null); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 240, characters 7-14", id(undefined), undefined); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 241, characters 7-14", id({ x: 1 }), { x: 1 }); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 243, characters 7-14", id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 242, characters 7-14", id({ x: 1 }), { x: 1 }); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 244, characters 7-14", id("v"), "v"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 243, characters 7-14", id("v"), "v"); }); Mocha.test("rule 4: typeof kinds", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 248, characters 7-14", kindName(id("a")), "string"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 249, characters 7-14", kindName(id(1.5)), "number"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 250, characters 7-14", kindName(id(1n)), "bigint"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 251, characters 7-14", kindName(id(false)), "bool"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 252, characters 7-14", kindName(id(x => x)), "function"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 253, characters 7-14", kindName(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 247, characters 7-14", kindName(id("a")), "string"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 248, characters 7-14", kindName(id(1.5)), "number"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 249, characters 7-14", kindName(id(1n)), "bigint"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 250, characters 7-14", kindName(id(false)), "bool"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 251, characters 7-14", kindName(id(x => x)), "function"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 252, characters 7-14", kindName(id({ x: 1 })), "object"); }); Mocha.test("rule 4: tuples are array cases, dicts are object cases", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 257, characters 7-14", tupleName(id([ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 256, characters 7-14", tupleName(id([ 1, "a" ])), "array"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 258, characters 7-14", tupleName(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 257, characters 7-14", tupleName(id({ a: 1 })), "object"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 259, characters 7-14", aliasDictName(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 258, characters 7-14", aliasDictName(id({ a: 1 })), "object"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 260, characters 7-14", aliasDictName(id([1])), "array"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 259, characters 7-14", aliasDictName(id([1])), "array"); }); Mocha.test("rules 4 and 5: instance cases", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 264, characters 7-14", instanceName(id(new Date())), "Date"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 265, characters 7-14", instanceName(id(/a/)), "RegExp"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 266, characters 7-14", instanceName(id(Promise.resolve(1))), "Promise"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 267, characters 7-14", instanceName(id(new Map())), "Map"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 268, characters 7-14", instanceName(id(new Set())), "Set"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 269, characters 7-14", instanceName(id(new WeakMap())), "WeakMap"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 270, characters 7-14", instanceName(id(new WeakSet())), "WeakSet"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 263, characters 7-14", instanceName(id(new Date())), "Date"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 264, characters 7-14", instanceName(id(/a/)), "RegExp"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 265, characters 7-14", instanceName(id(Promise.resolve(1))), "Promise"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 266, characters 7-14", instanceName(id(new Map())), "Map"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 267, characters 7-14", instanceName(id(new Set())), "Set"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 268, characters 7-14", instanceName(id(new WeakMap())), "WeakMap"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 269, characters 7-14", instanceName(id(new WeakSet())), "WeakSet"); let buffer = new ArrayBuffer(8); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 272, characters 7-14", instanceName(id(buffer)), "ArrayBuffer"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 273, characters 7-14", instanceName(id(new DataView(buffer))), "DataView"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 274, characters 7-14", instanceName(id(new Float64Array([1.0]))), "Float64Array"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 275, characters 7-14", instanceName(id("a")), "string"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 271, characters 7-14", instanceName(id(buffer)), "ArrayBuffer"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 272, characters 7-14", instanceName(id(new DataView(buffer))), "DataView"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 273, characters 7-14", instanceName(id(new Float64Array([1.0]))), "Float64Array"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 274, characters 7-14", instanceName(id("a")), "string"); }); Mocha.test("rule 4: the unknown case receives every non-literal value", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 279, characters 7-14", unknownName(id(null)), "null"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 280, characters 7-14", unknownName(id("none")), "none"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 281, characters 7-14", unknownName(id({hd: 1, tl: /* [] */0})), "list"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 282, characters 7-14", unknownName(id(/* [] */0)), "list"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 278, characters 7-14", unknownName(id(null)), "null"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 279, characters 7-14", unknownName(id("none")), "none"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 280, characters 7-14", unknownName(id({hd: 1, tl: /* [] */0})), "list"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 281, characters 7-14", unknownName(id(/* [] */0)), "list"); }); Mocha.test("rule 7: a payload value equal to a literal is that literal's case", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 286, characters 7-14", overlapStringName(id("a")), "LitA"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 287, characters 7-14", overlapStringName(id("b")), "AnyString"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 288, characters 7-14", overlapNumberName(id(1)), "LitOne"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 289, characters 7-14", overlapNumberName(id(2)), "AnyInt"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 290, characters 7-14", overlapBoolName(id(true)), "LitTrue"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 291, characters 7-14", overlapBoolName(id(false)), "AnyBool"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 292, characters 7-14", overlapNullName(id(null)), "LitNull"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 293, characters 7-14", overlapNullName(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 285, characters 7-14", overlapStringName(id("a")), "LitA"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 286, characters 7-14", overlapStringName(id("b")), "AnyString"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 287, characters 7-14", overlapNumberName(id(1)), "LitOne"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 288, characters 7-14", overlapNumberName(id(2)), "AnyInt"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 289, characters 7-14", overlapBoolName(id(true)), "LitTrue"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 290, characters 7-14", overlapBoolName(id(false)), "AnyBool"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 291, characters 7-14", overlapNullName(id(null)), "LitNull"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 292, characters 7-14", overlapNullName(id({ x: 1 })), "AnyObject"); }); Mocha.test("rule 8: instance checks precede typeof checks", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 297, characters 7-14", objectFirstName(id(new Date())), "Date"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 298, characters 7-14", objectFirstName(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 296, characters 7-14", objectFirstName(id(new Date())), "Date"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 297, characters 7-14", objectFirstName(id({ x: 1 })), "object"); }); Mocha.test("rule 8: an int case receives any number", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 302, characters 7-14", intOrStringName(id(1)), "int"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 303, characters 7-14", intOrStringName(id(1.5)), "int"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 304, characters 7-14", intOrStringName(id("1")), "string"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 301, characters 7-14", intOrStringName(id(1)), "int"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 302, characters 7-14", intOrStringName(id(1.5)), "int"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 303, characters 7-14", intOrStringName(id("1")), "string"); }); Mocha.test("rule 9: null and arrays are not objects", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 308, characters 7-14", guardedName(id([])), "array"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 309, characters 7-14", guardedName(id(null)), "null"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 310, characters 7-14", guardedName(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 307, characters 7-14", guardedName(id([])), "array"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 308, characters 7-14", guardedName(id(null)), "null"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 309, characters 7-14", guardedName(id({ x: 1 })), "object"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 311, characters 7-14", guardedObjectOnly(id([])), "other"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 312, characters 7-14", guardedObjectOnly(id(null)), "other"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 313, characters 7-14", guardedObjectOnly(id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 310, characters 7-14", guardedObjectOnly(id([])), "other"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 311, characters 7-14", guardedObjectOnly(id(null)), "other"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 312, characters 7-14", guardedObjectOnly(id({ x: 1 })), "object"); }); Mocha.test("rule 10: a value outside every case lands in the unchecked branch", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 317, characters 7-14", intOrStringName(true), "string"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 318, characters 7-14", twoInstancesName({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 316, characters 7-14", intOrStringName(true), "string"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 317, characters 7-14", twoInstancesName({ x: 1 }), "RegExp"); let thenable = ({then: (resolve) => resolve(1)}); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 320, characters 7-14", promiseOrFloatName(thenable), "float"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 319, characters 7-14", promiseOrFloatName(thenable), "float"); }); Mocha.test("rule 12: option of an untagged variant", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 324, characters 7-14", someWithNull(null), null); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 325, characters 7-14", someWithNull(null) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 326, characters 7-14", someWithUndefined(id(undefined)) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 327, characters 7-14", someConstantUndefined === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 328, characters 7-14", someWithUndefined("a"), "a"); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 331, characters 7-14", someWithOpaque(undefined) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 332, characters 7-14", someWithVariable(undefined) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 333, characters 7-14", someWithBlob(undefined) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 334, characters 7-14", someWithDate(undefined) === undefined, true); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 335, characters 7-14", someWithRecord(undefined) === undefined, true); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 336, characters 7-14", someWithInlineRecord(undefined) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 337, characters 7-14", someWithUnboxedOption(undefined) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 338, characters 7-14", someWithUnboxedString(undefined) === undefined, true); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 323, characters 7-14", someWithNull(null), null); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 324, characters 7-14", someWithNull(null) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 325, characters 7-14", someWithUndefined(id(undefined)) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 326, characters 7-14", someConstantUndefined === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 327, characters 7-14", someWithUndefined("a"), "a"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 330, characters 7-14", someWithOpaque(undefined) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 331, characters 7-14", someWithVariable(undefined) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 332, characters 7-14", someWithBlob(undefined) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 333, characters 7-14", someWithDate(undefined) === undefined, true); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 334, characters 7-14", someWithRecord(undefined) === undefined, true); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 335, characters 7-14", someWithInlineRecord(undefined) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 336, characters 7-14", someWithUnboxedOption(undefined) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 337, characters 7-14", someWithUnboxedString(undefined) === undefined, true); }); Mocha.test("rule 13: @notUndefined unboxes option of a variant holding the type", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 342, characters 7-14", someWithDefined(undefined) === undefined, true); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 343, characters 7-14", someWithDefined(5), 5); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 341, characters 7-14", someWithDefined(undefined) === undefined, true); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 342, characters 7-14", someWithDefined(5), 5); }); Mocha.test("rule 14: @tag and @as on a tagged variant", () => { - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 347, characters 7-14", id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 346, characters 7-14", id({ kind: "TA", x: 1 }), { kind: "TA", x: 1 }); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 348, characters 7-14", id({ + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 347, characters 7-14", id({ kind: "bee", y: 1 }), { kind: "bee", y: 1 }); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 349, characters 7-14", id("TC"), "TC"); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 348, characters 7-14", id("TC"), "TC"); }); }); export { id, - mixedName, + wholeName, kindName, tupleName, aliasDictName, diff --git a/tests/tests/src/untagged_variants_spec_test.res b/tests/tests/src/untagged_variants_spec_test.res index 6ae8b9780d7..d9b903f16bd 100644 --- a/tests/tests/src/untagged_variants_spec_test.res +++ b/tests/tests/src/untagged_variants_spec_test.res @@ -12,14 +12,13 @@ those assertions show how recognition treats values outside every case. @inline(never) let id = x => x external raw: 'a => 'b = "%identity" -// Rule 1: `@unboxed` and `@tag` on the type apply to every constructor; -// `@unboxed` on one constructor of a tagged variant applies to that one. -type mixed = | @unboxed MA(int) | MB({x: int}) | MC -let mixedName = v => +// Rule 1: `@unboxed` and `@tag` on the type apply to every constructor. +@unboxed type whole = WA(int) | WB({x: int}) | WC +let wholeName = v => switch v { - | MA(_) => "MA" - | MB(_) => "MB" - | MC => "MC" + | WA(_) => "WA" + | WB(_) => "WB" + | WC => "WC" } // Rule 2: a nullary case is its literal; a payload case is its payload. @@ -221,13 +220,13 @@ let someWithUnboxedString = (v: withUnboxedString): option => @tag("kind") type tagged = TA({x: int}) | @as("bee") TB({y: int}) | TC describe(__MODULE__, () => { - test("rule 1: @unboxed on one constructor of a tagged variant", () => { - eq(__LOC__, raw(id(MA(3))), 3) - eq(__LOC__, raw(id(MB({x: 1}))), {"TAG": "MB", "x": 1}) - eq(__LOC__, raw(id(MC)), "MC") - eq(__LOC__, mixedName(id(MA(3))), "MA") - eq(__LOC__, mixedName(id(MB({x: 1}))), "MB") - eq(__LOC__, mixedName(id(MC)), "MC") + test("rule 1: @unboxed on the type applies to every constructor", () => { + eq(__LOC__, raw(id(WA(3))), 3) + eq(__LOC__, raw(id(WB({x: 1}))), {"x": 1}) + eq(__LOC__, raw(id(WC)), "WC") + eq(__LOC__, wholeName(id(WA(3))), "WA") + eq(__LOC__, wholeName(id(WB({x: 1}))), "WB") + eq(__LOC__, wholeName(id(WC)), "WC") }) test("rule 2: representation of each case", () => { From 6d9792a5fd54d1d6ee2d7c737f32c0364d69f69f Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 25 Oct 2026 08:38:41 +0000 Subject: [PATCH 2/2] Name the unboxed variant correctly in the coercion error The error for a coercion between variants with different `@unboxed` configurations said that the wrong one was unboxed. Diagnostic modified (unboxed configurations): Example: @unboxed type x = One(bool) | Two type y = One(bool) | Two Before (diagnostic): - Variant x is not unboxed. - Variant y is unboxed. After (diagnostic): - Variant x is unboxed. - Variant y is not unboxed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel --- compiler/ml/printtyp.ml | 2 +- .../expected/variant_to_variant_coercion_unboxed.res.expected | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/ml/printtyp.ml b/compiler/ml/printtyp.ml index a271e464b41..476d47ec156 100644 --- a/compiler/ml/printtyp.ml +++ b/compiler/ml/printtyp.ml @@ -1604,7 +1604,7 @@ let print_variant_configuration_issue ppf let print_unboxed_status ppf unboxed name = fprintf ppf "@ - Variant @{%s@} is @{%s@}unboxed." (Path.name name) - (if unboxed then "not " else "") + (if unboxed then "" else "not ") in print_unboxed_status ppf left_unboxed left_variant_name; print_unboxed_status ppf right_unboxed right_variant_name; diff --git a/tests/build_tests/super_errors/expected/variant_to_variant_coercion_unboxed.res.expected b/tests/build_tests/super_errors/expected/variant_to_variant_coercion_unboxed.res.expected index 399af95df07..848a05c80c0 100644 --- a/tests/build_tests/super_errors/expected/variant_to_variant_coercion_unboxed.res.expected +++ b/tests/build_tests/super_errors/expected/variant_to_variant_coercion_unboxed.res.expected @@ -10,7 +10,7 @@ Type x is not a subtype of y The variants have different @unboxed configurations. - - Variant x is not unboxed. - - Variant y is unboxed. + - Variant x is unboxed. + - Variant y is not unboxed. Fix this by making sure the variants either both have, or don't have, the @unboxed attribute. \ No newline at end of file