From 2266726cf21fe2376b19dc1b54cdc63e8227f510 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 26 Oct 2026 05:53:19 +0000 Subject: [PATCH 1/3] Deprecate @unboxed on a variant constructor Why: v13 rejects @unboxed on a constructor (#9040): @unboxed applies to a whole variant. 12.x users should hear about it before upgrading. `@unboxed` written on a constructor warns (warning 3), before the type's attributes are copied to its constructors, so a type-level `@unboxed` does not warn. Diagnostic added (@unboxed on a constructor): Example: type mixed = | @unboxed Text(string) | Count(int) @unboxed type whole = | @unboxed Name(string) | Age(int) Before (diagnostic): none After (diagnostic): Warning number 3 deprecated: @unboxed on constructor Text is deprecated and will be an error in v13, where @unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`. Warning number 3 deprecated: @unboxed on constructor Name is deprecated and will be an error in v13. The type is already @unboxed, so remove it from the constructor. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel --- CHANGELOG.md | 1 + compiler/ml/typedecl.ml | 21 +++++++++++++++++++ ...xed_on_constructor_deprecated.res.expected | 20 ++++++++++++++++++ .../unboxed_on_constructor_deprecated.res | 4 ++++ 4 files changed, 46 insertions(+) create mode 100644 tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res diff --git a/CHANGELOG.md b/CHANGELOG.md index 67a55168d3b..b422189b5d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Deprecate `Stdlib_Error` and `Stdlib_Exn` modules in favor of `JsError/JsExn`. https://github.com/rescript-lang/rescript/pull/8404 - Deprecate `List.has` in favor of `List.some`. https://github.com/rescript-lang/rescript/pull/8807 - Deprecate `@deriving(abstract)` and `@deriving(jsConverter)`, which will be removed in v13. https://github.com/rescript-lang/rescript/pull/8808 +- Deprecate `@unboxed` on a variant constructor, which is an error in v13: write `@unboxed` on the type. # 12.3.1 diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index 0f44d4595f4..d5537727f73 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -591,8 +591,29 @@ let transl_declaration ~type_record_as_object ~untagged_wfc env sdecl id = in Some (tcstr, cstr) in + (* [@unboxed] on a constructor, as written, before the type's + attributes are copied to its constructors. v13 rejects it. *) + let warn_unboxed_constructor scstr = + Ext_list.iter scstr.pcd_attributes (fun ({txt; loc}, _) -> + if txt = Ast_untagged_variants.untagged then + Location.deprecated loc + (if Ast_untagged_variants.has_untagged sdecl.ptype_attributes + then + Printf.sprintf + "@unboxed on constructor %s is deprecated and will be an \ + error in v13. The type is already @unboxed, so remove it \ + from the constructor." + scstr.pcd_name.txt + else + Printf.sprintf + "@unboxed on constructor %s is deprecated and will be an \ + error in v13, where @unboxed applies to a whole variant: \ + write it on the type, as in `@unboxed type t = ...`." + scstr.pcd_name.txt)) + in let make_cstr scstr = Builtin_attributes.warning_scope scstr.pcd_attributes (fun () -> + warn_unboxed_constructor scstr; make_cstr scstr) in let tcstrs, cstrs = List.split (List.filter_map make_cstr scstrs) in diff --git a/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected b/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected new file mode 100644 index 00000000000..fedc27a7016 --- /dev/null +++ b/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected @@ -0,0 +1,20 @@ + + Warning number 3 + /.../fixtures/unboxed_on_constructor_deprecated.res:1:16-23 + + 1 │ type mixed = | @unboxed Text(string) | Count(int) + 2 │ + 3 │ @unboxed + + deprecated: @unboxed on constructor Text is deprecated and will be an error in v13, where @unboxed applies to a whole variant: write it on the type, as in `@unboxed type t = ...`. + + + Warning number 3 + /.../fixtures/unboxed_on_constructor_deprecated.res:4:16-23 + + 2 │ + 3 │ @unboxed + 4 │ type whole = | @unboxed Name(string) | Age(int) + 5 │ + + deprecated: @unboxed on constructor Name is deprecated and will be an error in v13. The type is already @unboxed, so remove it from the constructor. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res b/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res new file mode 100644 index 00000000000..f13c2a0cd46 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res @@ -0,0 +1,4 @@ +type mixed = | @unboxed Text(string) | Count(int) + +@unboxed +type whole = | @unboxed Name(string) | Age(int) From 7e73b906af7098b40f767ee8064dec15d70ed456 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 26 Oct 2026 05:53:42 +0000 Subject: [PATCH 2/3] Add PR link to changelog entry Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b422189b5d0..26607be6989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ - Deprecate `Stdlib_Error` and `Stdlib_Exn` modules in favor of `JsError/JsExn`. https://github.com/rescript-lang/rescript/pull/8404 - Deprecate `List.has` in favor of `List.some`. https://github.com/rescript-lang/rescript/pull/8807 - Deprecate `@deriving(abstract)` and `@deriving(jsConverter)`, which will be removed in v13. https://github.com/rescript-lang/rescript/pull/8808 -- Deprecate `@unboxed` on a variant constructor, which is an error in v13: write `@unboxed` on the type. +- Deprecate `@unboxed` on a variant constructor, which is an error in v13: write `@unboxed` on the type. https://github.com/rescript-lang/rescript/pull/9049 # 12.3.1 From 6786938b41cb09b28d225aab99a2392f2131087e Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 26 Oct 2026 06:11:08 +0000 Subject: [PATCH 3/3] Skip spread constructors in the @unboxed constructor deprecation Why: A variant spread copies the source constructors with their attributes, including the source type's `@unboxed`, so the spread site warned about an annotation the user never wrote, or repeated the source's warning. The deprecation now skips constructors marked as coming from a spread; `@unboxed` written on a constructor is reported once, where it is written. Diagnostic removed (@unboxed reported at a spread): Example: @unboxed type a = A(string) @unboxed type b = | ...a | B(int) Before (diagnostic): Warning number 3 1:1-8 deprecated: @unboxed on constructor A is deprecated and will be an error in v13. The type is already @unboxed, so remove it from the constructor. After (diagnostic): none Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01CMkrYaktctdKc4M41WF36t Signed-off-by: Christoph Knittel --- compiler/ml/typedecl.ml | 9 +++++++-- .../unboxed_on_constructor_deprecated.res.expected | 1 + .../fixtures/unboxed_on_constructor_deprecated.res | 8 ++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/compiler/ml/typedecl.ml b/compiler/ml/typedecl.ml index d5537727f73..53f4ea33a24 100644 --- a/compiler/ml/typedecl.ml +++ b/compiler/ml/typedecl.ml @@ -592,10 +592,15 @@ let transl_declaration ~type_record_as_object ~untagged_wfc env sdecl id = Some (tcstr, cstr) in (* [@unboxed] on a constructor, as written, before the type's - attributes are copied to its constructors. v13 rejects it. *) + attributes are copied to its constructors. v13 rejects it. + Constructors copied by a variant spread carry the source type's + attributes, already reported at the source. *) let warn_unboxed_constructor scstr = + let from_spread = + Variant_type_spread.constructor_is_from_spread scstr.pcd_attributes + in Ext_list.iter scstr.pcd_attributes (fun ({txt; loc}, _) -> - if txt = Ast_untagged_variants.untagged then + if (not from_spread) && txt = Ast_untagged_variants.untagged then Location.deprecated loc (if Ast_untagged_variants.has_untagged sdecl.ptype_attributes then diff --git a/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected b/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected index fedc27a7016..2622fc3a2e5 100644 --- a/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected +++ b/tests/build_tests/super_errors/expected/unboxed_on_constructor_deprecated.res.expected @@ -16,5 +16,6 @@ 3 │ @unboxed 4 │ type whole = | @unboxed Name(string) | Age(int) 5 │ + 6 │ type spreadMixed = | ...mixed | Flag(bool) deprecated: @unboxed on constructor Name is deprecated and will be an error in v13. The type is already @unboxed, so remove it from the constructor. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res b/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res index f13c2a0cd46..dc8c855b30c 100644 --- a/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res +++ b/tests/build_tests/super_errors/fixtures/unboxed_on_constructor_deprecated.res @@ -2,3 +2,11 @@ type mixed = | @unboxed Text(string) | Count(int) @unboxed type whole = | @unboxed Name(string) | Age(int) + +type spreadMixed = | ...mixed | Flag(bool) + +@unboxed +type plain = Plain(string) + +@unboxed +type spreadPlain = | ...plain | Num(int)