From 95019ca537f26e3ed8a9477e354870459d78c749 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Wed, 21 Oct 2026 18:01:54 +0000 Subject: [PATCH 1/2] Annotate the stdlib's runtime classes Why: Untagged variants recognized stdlib classes only by type name, with no relation between them, so a `Blob` case declared before a `File` case also caught files (#8938). Types whose values are instances of a global class now say so with `@instanceOf` instead of `@notUndefined`, `Symbol.t` with `@typeOf("symbol")`, and `File.t` is a private abbreviation of `Blob.t`. `JsError.t`, `Symbol.t` and the `Intl` classes can be untagged variant cases too. Fixes #8938. JS modified (Blob and File cases): Example: @unboxed type upload = Blob(Blob.t) | File(File.t) let kind = upload => switch upload { | Blob(_) => "blob" | File(_) => "file" } Before (js): if (upload instanceof Blob) { return "blob"; } else { return "file"; } After (js): if (upload instanceof Blob && !(upload instanceof File)) { return "blob"; } else { return "file"; } Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF Signed-off-by: Christoph Knittel --- changes/stdlib-instanceof-annotations.fix.md | 1 + .../@rescript/runtime/Stdlib_ArrayBuffer.res | 2 +- .../@rescript/runtime/Stdlib_ArrayBuffer.resi | 2 +- .../runtime/Stdlib_BigInt64Array.res | 2 +- .../runtime/Stdlib_BigUint64Array.res | 2 +- packages/@rescript/runtime/Stdlib_Blob.res | 1 + .../@rescript/runtime/Stdlib_DataView.res | 2 +- .../@rescript/runtime/Stdlib_DataView.resi | 2 +- packages/@rescript/runtime/Stdlib_Date.res | 2 +- packages/@rescript/runtime/Stdlib_Date.resi | 2 +- packages/@rescript/runtime/Stdlib_File.res | 5 ++- .../@rescript/runtime/Stdlib_Float32Array.res | 2 +- .../@rescript/runtime/Stdlib_Float64Array.res | 2 +- .../@rescript/runtime/Stdlib_Int16Array.res | 2 +- .../@rescript/runtime/Stdlib_Int32Array.res | 2 +- .../@rescript/runtime/Stdlib_Int8Array.res | 2 +- .../runtime/Stdlib_Intl_Collator.res | 2 +- .../runtime/Stdlib_Intl_DateTimeFormat.res | 2 +- .../runtime/Stdlib_Intl_ListFormat.res | 2 +- .../@rescript/runtime/Stdlib_Intl_Locale.res | 2 +- .../runtime/Stdlib_Intl_NumberFormat.res | 2 +- .../runtime/Stdlib_Intl_PluralRules.res | 2 +- .../Stdlib_Intl_RelativeTimeFormat.res | 2 +- .../runtime/Stdlib_Intl_Segmenter.res | 2 +- packages/@rescript/runtime/Stdlib_JsError.res | 2 +- .../@rescript/runtime/Stdlib_JsError.resi | 2 +- packages/@rescript/runtime/Stdlib_Map.res | 2 +- packages/@rescript/runtime/Stdlib_Map.resi | 2 +- packages/@rescript/runtime/Stdlib_RegExp.res | 2 +- packages/@rescript/runtime/Stdlib_RegExp.resi | 2 +- packages/@rescript/runtime/Stdlib_Set.res | 2 +- packages/@rescript/runtime/Stdlib_Set.resi | 2 +- packages/@rescript/runtime/Stdlib_Symbol.res | 2 +- packages/@rescript/runtime/Stdlib_Symbol.resi | 2 +- .../@rescript/runtime/Stdlib_Uint16Array.res | 2 +- .../@rescript/runtime/Stdlib_Uint32Array.res | 2 +- .../@rescript/runtime/Stdlib_Uint8Array.res | 2 +- .../runtime/Stdlib_Uint8ClampedArray.res | 2 +- packages/@rescript/runtime/Stdlib_WeakMap.res | 2 +- packages/@rescript/runtime/Stdlib_WeakSet.res | 2 +- tests/tests/src/UntaggedVariants.mjs | 2 +- tests/tests/src/untagged_file_blob_test.mjs | 41 +++++++++++++++++++ tests/tests/src/untagged_file_blob_test.res | 38 +++++++++++++++++ 43 files changed, 122 insertions(+), 40 deletions(-) create mode 100644 changes/stdlib-instanceof-annotations.fix.md create mode 100644 tests/tests/src/untagged_file_blob_test.mjs create mode 100644 tests/tests/src/untagged_file_blob_test.res diff --git a/changes/stdlib-instanceof-annotations.fix.md b/changes/stdlib-instanceof-annotations.fix.md new file mode 100644 index 00000000000..672e8786031 --- /dev/null +++ b/changes/stdlib-instanceof-annotations.fix.md @@ -0,0 +1 @@ +- An untagged variant with both `Blob` and `File` cases matches files as `File`, whatever the order of its cases. `File.t` is now a private abbreviation of `Blob.t`, and `JsError.t`, `Symbol.t` and the `Intl` classes can be untagged variant cases. diff --git a/packages/@rescript/runtime/Stdlib_ArrayBuffer.res b/packages/@rescript/runtime/Stdlib_ArrayBuffer.res index 4983aff64d8..24773b6bce8 100644 --- a/packages/@rescript/runtime/Stdlib_ArrayBuffer.res +++ b/packages/@rescript/runtime/Stdlib_ArrayBuffer.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("ArrayBuffer") type t @new external make: int => t = "ArrayBuffer" diff --git a/packages/@rescript/runtime/Stdlib_ArrayBuffer.resi b/packages/@rescript/runtime/Stdlib_ArrayBuffer.resi index e74dd6667f1..f45360bb879 100644 --- a/packages/@rescript/runtime/Stdlib_ArrayBuffer.resi +++ b/packages/@rescript/runtime/Stdlib_ArrayBuffer.resi @@ -6,7 +6,7 @@ See: [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Ref /** Type representing an ArrayBuffer object used to represent a generic raw binary data buffer. */ -@notUndefined +@instanceOf("ArrayBuffer") type t /** diff --git a/packages/@rescript/runtime/Stdlib_BigInt64Array.res b/packages/@rescript/runtime/Stdlib_BigInt64Array.res index e0545460c07..adfafe3275f 100644 --- a/packages/@rescript/runtime/Stdlib_BigInt64Array.res +++ b/packages/@rescript/runtime/Stdlib_BigInt64Array.res @@ -1,6 +1,6 @@ /** The `BigInt64Array` typed array represents an array of 64-bit signed integers in platform byte order. See [BigInt64Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("BigInt64Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_BigUint64Array.res b/packages/@rescript/runtime/Stdlib_BigUint64Array.res index 3c8aab184bc..d1df29b60f3 100644 --- a/packages/@rescript/runtime/Stdlib_BigUint64Array.res +++ b/packages/@rescript/runtime/Stdlib_BigUint64Array.res @@ -1,6 +1,6 @@ /** The `BigUint64Array` typed array represents an array of 64-bit unsigned integers in platform byte order. See [BigUint64Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("BigUint64Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Blob.res b/packages/@rescript/runtime/Stdlib_Blob.res index 78df7b6c531..30b2132c82b 100644 --- a/packages/@rescript/runtime/Stdlib_Blob.res +++ b/packages/@rescript/runtime/Stdlib_Blob.res @@ -1,2 +1,3 @@ /** JavaScript Blob API */ +@instanceOf("Blob") type t diff --git a/packages/@rescript/runtime/Stdlib_DataView.res b/packages/@rescript/runtime/Stdlib_DataView.res index e1aae2469f9..b9533aebfe4 100644 --- a/packages/@rescript/runtime/Stdlib_DataView.res +++ b/packages/@rescript/runtime/Stdlib_DataView.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("DataView") type t @new diff --git a/packages/@rescript/runtime/Stdlib_DataView.resi b/packages/@rescript/runtime/Stdlib_DataView.resi index b557557c188..79bf937c87a 100644 --- a/packages/@rescript/runtime/Stdlib_DataView.resi +++ b/packages/@rescript/runtime/Stdlib_DataView.resi @@ -13,7 +13,7 @@ See [`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Referen /** Type representing a DataView. */ -@notUndefined +@instanceOf("DataView") type t /** diff --git a/packages/@rescript/runtime/Stdlib_Date.res b/packages/@rescript/runtime/Stdlib_Date.res index 95216e9a38c..4d77a320fbb 100644 --- a/packages/@rescript/runtime/Stdlib_Date.res +++ b/packages/@rescript/runtime/Stdlib_Date.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Date") type t type msSinceEpoch = float diff --git a/packages/@rescript/runtime/Stdlib_Date.resi b/packages/@rescript/runtime/Stdlib_Date.resi index c0e90397c11..1efa181161b 100644 --- a/packages/@rescript/runtime/Stdlib_Date.resi +++ b/packages/@rescript/runtime/Stdlib_Date.resi @@ -5,7 +5,7 @@ /** A type representing a JavaScript date. */ -@notUndefined +@instanceOf("Date") type t /** diff --git a/packages/@rescript/runtime/Stdlib_File.res b/packages/@rescript/runtime/Stdlib_File.res index 7eaf8e67f77..f600d1809d0 100644 --- a/packages/@rescript/runtime/Stdlib_File.res +++ b/packages/@rescript/runtime/Stdlib_File.res @@ -1,2 +1,3 @@ -/** JavaScript File API */ -type t +/** JavaScript File API. A file is a blob. */ +@instanceOf("File") +type t = private Stdlib_Blob.t diff --git a/packages/@rescript/runtime/Stdlib_Float32Array.res b/packages/@rescript/runtime/Stdlib_Float32Array.res index f916dce88b6..4b87ad539c2 100644 --- a/packages/@rescript/runtime/Stdlib_Float32Array.res +++ b/packages/@rescript/runtime/Stdlib_Float32Array.res @@ -1,6 +1,6 @@ /** The `Float32Array` typed array represents an array of 32-bit floating point numbers in platform byte order. See [Float32Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Float32Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Float64Array.res b/packages/@rescript/runtime/Stdlib_Float64Array.res index fffb73704cb..29cdd62dae6 100644 --- a/packages/@rescript/runtime/Stdlib_Float64Array.res +++ b/packages/@rescript/runtime/Stdlib_Float64Array.res @@ -1,6 +1,6 @@ /** The `Float64Array` typed array represents an array of 64-bit floating point numbers in platform byte order. See [Float64Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Float64Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Int16Array.res b/packages/@rescript/runtime/Stdlib_Int16Array.res index 72aa31ea66b..8a07291437d 100644 --- a/packages/@rescript/runtime/Stdlib_Int16Array.res +++ b/packages/@rescript/runtime/Stdlib_Int16Array.res @@ -1,6 +1,6 @@ /** The `Int16Array` typed array represents an array of twos-complement 16-bit signed integers in platform byte order. See [Int16Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Int16Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Int32Array.res b/packages/@rescript/runtime/Stdlib_Int32Array.res index f537411e29d..70ac9e3089e 100644 --- a/packages/@rescript/runtime/Stdlib_Int32Array.res +++ b/packages/@rescript/runtime/Stdlib_Int32Array.res @@ -1,6 +1,6 @@ /** The `Int32Array` typed array represents an array of twos-complemenet 32-bit signed integers in platform byte order. See [Int32Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Int32Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Int8Array.res b/packages/@rescript/runtime/Stdlib_Int8Array.res index d129824acd2..b03b30369c7 100644 --- a/packages/@rescript/runtime/Stdlib_Int8Array.res +++ b/packages/@rescript/runtime/Stdlib_Int8Array.res @@ -1,6 +1,6 @@ /** The `Int8Array` typed array represents an array of twos-complement 8-bit signed integers. See [Int8Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Int8Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Intl_Collator.res b/packages/@rescript/runtime/Stdlib_Intl_Collator.res index a6d0e4a6d7b..6b3ac1b8b06 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_Collator.res +++ b/packages/@rescript/runtime/Stdlib_Intl_Collator.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Intl.Collator") type t type usage = [#sort | #search] diff --git a/packages/@rescript/runtime/Stdlib_Intl_DateTimeFormat.res b/packages/@rescript/runtime/Stdlib_Intl_DateTimeFormat.res index d7904bf02b8..94c8225d3f7 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_DateTimeFormat.res +++ b/packages/@rescript/runtime/Stdlib_Intl_DateTimeFormat.res @@ -2,7 +2,7 @@ Bindings to JavaScript's `Intl.DateTimeFormat`. */ -@notUndefined +@instanceOf("Intl.DateTimeFormat") type t type dateStyle = [#full | #long | #medium | #short] diff --git a/packages/@rescript/runtime/Stdlib_Intl_ListFormat.res b/packages/@rescript/runtime/Stdlib_Intl_ListFormat.res index 86715d40121..03b4e45c584 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_ListFormat.res +++ b/packages/@rescript/runtime/Stdlib_Intl_ListFormat.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Intl.ListFormat") type t type listType = [ diff --git a/packages/@rescript/runtime/Stdlib_Intl_Locale.res b/packages/@rescript/runtime/Stdlib_Intl_Locale.res index 0e4bf97f75d..d5b37053076 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_Locale.res +++ b/packages/@rescript/runtime/Stdlib_Intl_Locale.res @@ -2,7 +2,7 @@ Bindings to JavaScript's `Intl.Locale`. */ -@notUndefined +@instanceOf("Intl.Locale") type t type options = { diff --git a/packages/@rescript/runtime/Stdlib_Intl_NumberFormat.res b/packages/@rescript/runtime/Stdlib_Intl_NumberFormat.res index bd9d53da77b..a26a3f8d128 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_NumberFormat.res +++ b/packages/@rescript/runtime/Stdlib_Intl_NumberFormat.res @@ -1,6 +1,6 @@ module Grouping = Stdlib_Intl_NumberFormat_Grouping -@notUndefined +@instanceOf("Intl.NumberFormat") type t /** diff --git a/packages/@rescript/runtime/Stdlib_Intl_PluralRules.res b/packages/@rescript/runtime/Stdlib_Intl_PluralRules.res index c94c534c76a..688ad7f1b2b 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_PluralRules.res +++ b/packages/@rescript/runtime/Stdlib_Intl_PluralRules.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Intl.PluralRules") type t type localeType = [#cardinal | #ordinal] diff --git a/packages/@rescript/runtime/Stdlib_Intl_RelativeTimeFormat.res b/packages/@rescript/runtime/Stdlib_Intl_RelativeTimeFormat.res index 37fa2f5ae90..5d1da8af913 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_RelativeTimeFormat.res +++ b/packages/@rescript/runtime/Stdlib_Intl_RelativeTimeFormat.res @@ -3,7 +3,7 @@ Bindings to JavaScript's `Intl.RelativeTimeFormat`. See [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat) for API details. */ -@notUndefined +@instanceOf("Intl.RelativeTimeFormat") type t type numeric = [#always | #auto] diff --git a/packages/@rescript/runtime/Stdlib_Intl_Segmenter.res b/packages/@rescript/runtime/Stdlib_Intl_Segmenter.res index ff00a53c644..63a14731fe0 100644 --- a/packages/@rescript/runtime/Stdlib_Intl_Segmenter.res +++ b/packages/@rescript/runtime/Stdlib_Intl_Segmenter.res @@ -3,7 +3,7 @@ Bindings to JavaScript's `Intl.Segmenter`. See [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter) for API details. */ -@notUndefined +@instanceOf("Intl.Segmenter") type t type granularity = [#grapheme | #word | #sentence] diff --git a/packages/@rescript/runtime/Stdlib_JsError.res b/packages/@rescript/runtime/Stdlib_JsError.res index 06229731789..d80274e282f 100644 --- a/packages/@rescript/runtime/Stdlib_JsError.res +++ b/packages/@rescript/runtime/Stdlib_JsError.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Error") type t @get external stack: t => option = "stack" diff --git a/packages/@rescript/runtime/Stdlib_JsError.resi b/packages/@rescript/runtime/Stdlib_JsError.resi index 854d19c5d73..1580f3ac784 100644 --- a/packages/@rescript/runtime/Stdlib_JsError.resi +++ b/packages/@rescript/runtime/Stdlib_JsError.resi @@ -5,7 +5,7 @@ See [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ */ /** Represents a JavaScript error. */ -@notUndefined +@instanceOf("Error") type t /** diff --git a/packages/@rescript/runtime/Stdlib_Map.res b/packages/@rescript/runtime/Stdlib_Map.res index 706132cc007..b7321c2249f 100644 --- a/packages/@rescript/runtime/Stdlib_Map.res +++ b/packages/@rescript/runtime/Stdlib_Map.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Map") type t<'k, 'v> external asIterable: t<'k, 'v> => Stdlib_Iterable.t<('k, 'v)> = "%identity" diff --git a/packages/@rescript/runtime/Stdlib_Map.resi b/packages/@rescript/runtime/Stdlib_Map.resi index 28975651ee1..54ff66bc4f9 100644 --- a/packages/@rescript/runtime/Stdlib_Map.resi +++ b/packages/@rescript/runtime/Stdlib_Map.resi @@ -7,7 +7,7 @@ See [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Gl /** Type representing an instance of `Map`. */ -@notUndefined +@instanceOf("Map") type t<'k, 'v> /** diff --git a/packages/@rescript/runtime/Stdlib_RegExp.res b/packages/@rescript/runtime/Stdlib_RegExp.res index 9f1b5736a20..3f5c9269a6d 100644 --- a/packages/@rescript/runtime/Stdlib_RegExp.res +++ b/packages/@rescript/runtime/Stdlib_RegExp.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("RegExp") type t module Result = { diff --git a/packages/@rescript/runtime/Stdlib_RegExp.resi b/packages/@rescript/runtime/Stdlib_RegExp.resi index f6822678973..0d5b95e2f36 100644 --- a/packages/@rescript/runtime/Stdlib_RegExp.resi +++ b/packages/@rescript/runtime/Stdlib_RegExp.resi @@ -7,7 +7,7 @@ See [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference /** Type representing an instantiated `RegExp`. */ -@notUndefined +@instanceOf("RegExp") type t module Result: { diff --git a/packages/@rescript/runtime/Stdlib_Set.res b/packages/@rescript/runtime/Stdlib_Set.res index 3a2beee3ef3..968900ae366 100644 --- a/packages/@rescript/runtime/Stdlib_Set.res +++ b/packages/@rescript/runtime/Stdlib_Set.res @@ -1,4 +1,4 @@ -@notUndefined +@instanceOf("Set") type t<'a> external asIterable: t<'a> => Stdlib_Iterable.t<'a> = "%identity" diff --git a/packages/@rescript/runtime/Stdlib_Set.resi b/packages/@rescript/runtime/Stdlib_Set.resi index 8d32617608f..2a6967ef705 100644 --- a/packages/@rescript/runtime/Stdlib_Set.resi +++ b/packages/@rescript/runtime/Stdlib_Set.resi @@ -7,7 +7,7 @@ See [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Gl /** Type representing an instance of `Set`. */ -@notUndefined +@instanceOf("Set") type t<'a> /** diff --git a/packages/@rescript/runtime/Stdlib_Symbol.res b/packages/@rescript/runtime/Stdlib_Symbol.res index bbdb9783135..af37431e7fe 100644 --- a/packages/@rescript/runtime/Stdlib_Symbol.res +++ b/packages/@rescript/runtime/Stdlib_Symbol.res @@ -1,4 +1,4 @@ -@notUndefined +@typeOf("symbol") type t @val external make: string => t = "Symbol" diff --git a/packages/@rescript/runtime/Stdlib_Symbol.resi b/packages/@rescript/runtime/Stdlib_Symbol.resi index b9810319639..509fd90c6f5 100644 --- a/packages/@rescript/runtime/Stdlib_Symbol.resi +++ b/packages/@rescript/runtime/Stdlib_Symbol.resi @@ -7,7 +7,7 @@ Compiles to a regular JavaScript Symbol. /** Type representing a Symbol. */ -@notUndefined +@typeOf("symbol") type t /** diff --git a/packages/@rescript/runtime/Stdlib_Uint16Array.res b/packages/@rescript/runtime/Stdlib_Uint16Array.res index 7f0dcbf8441..feb4eae0c69 100644 --- a/packages/@rescript/runtime/Stdlib_Uint16Array.res +++ b/packages/@rescript/runtime/Stdlib_Uint16Array.res @@ -1,6 +1,6 @@ /** The `Uint16Array` typed array represents an array of 16-bit unsigned integers in platform byte order. See [Uint16Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Uint16Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Uint32Array.res b/packages/@rescript/runtime/Stdlib_Uint32Array.res index b18795a8b4f..41ca7c40ae9 100644 --- a/packages/@rescript/runtime/Stdlib_Uint32Array.res +++ b/packages/@rescript/runtime/Stdlib_Uint32Array.res @@ -1,6 +1,6 @@ /** The `Uint32Array` typed array represents an array of 32-bit unsigned integers in platform byte order. See [Uint32Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Uint32Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Uint8Array.res b/packages/@rescript/runtime/Stdlib_Uint8Array.res index 4866d45956b..66964e5a527 100644 --- a/packages/@rescript/runtime/Stdlib_Uint8Array.res +++ b/packages/@rescript/runtime/Stdlib_Uint8Array.res @@ -1,6 +1,6 @@ /** The `Uint8Array` typed array represents an array of 8-bit unsigned integers. See [Uint8Array on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Uint8Array") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_Uint8ClampedArray.res b/packages/@rescript/runtime/Stdlib_Uint8ClampedArray.res index 769267c019b..79959c9d05c 100644 --- a/packages/@rescript/runtime/Stdlib_Uint8ClampedArray.res +++ b/packages/@rescript/runtime/Stdlib_Uint8ClampedArray.res @@ -1,6 +1,6 @@ /** The `Uint8ClampedArray` typed array represents an array of 8-bit unsigned integers clamped to 0-255. See [Uint8ClampedArray on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) */ -@editor.completeFrom(Stdlib.TypedArray) +@editor.completeFrom(Stdlib.TypedArray) @instanceOf("Uint8ClampedArray") type t = Stdlib_TypedArray.t module Constants = { diff --git a/packages/@rescript/runtime/Stdlib_WeakMap.res b/packages/@rescript/runtime/Stdlib_WeakMap.res index 4ac50a49301..7ad60562d51 100644 --- a/packages/@rescript/runtime/Stdlib_WeakMap.res +++ b/packages/@rescript/runtime/Stdlib_WeakMap.res @@ -5,7 +5,7 @@ Weak maps keep key/value pairs where keys must be objects and the references do */ /** Mutable weak map storing values of type `'v` with object keys `'k`. */ -@notUndefined +@instanceOf("WeakMap") type t<'k, 'v> /** diff --git a/packages/@rescript/runtime/Stdlib_WeakSet.res b/packages/@rescript/runtime/Stdlib_WeakSet.res index 51ff206270f..705ec6ae0f3 100644 --- a/packages/@rescript/runtime/Stdlib_WeakSet.res +++ b/packages/@rescript/runtime/Stdlib_WeakSet.res @@ -5,7 +5,7 @@ Weak sets store references to objects without preventing those objects from bein */ /** Mutable weak set storing object references of type `'a`. */ -@notUndefined +@instanceOf("WeakSet") type t<'a> /** diff --git a/tests/tests/src/UntaggedVariants.mjs b/tests/tests/src/UntaggedVariants.mjs index a9d410ad5ab..df8f7b89be7 100644 --- a/tests/tests/src/UntaggedVariants.mjs +++ b/tests/tests/src/UntaggedVariants.mjs @@ -517,7 +517,7 @@ async function classifyAll(t) { console.log(t.name); return; } - if (t instanceof Blob) { + if (t instanceof Blob && !(t instanceof File)) { console.log(t.size); return; } diff --git a/tests/tests/src/untagged_file_blob_test.mjs b/tests/tests/src/untagged_file_blob_test.mjs new file mode 100644 index 00000000000..6fb2f5a1c5d --- /dev/null +++ b/tests/tests/src/untagged_file_blob_test.mjs @@ -0,0 +1,41 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +function kind(upload) { + if (upload instanceof Blob && !(upload instanceof File)) { + return "blob"; + } else { + return "file"; + } +} + +function onlyBlob(upload) { + if (upload instanceof Blob && !(upload instanceof File)) { + return "blob"; + } else { + return "other"; + } +} + +Mocha.describe("Untagged_file_blob_test", () => { + Mocha.test("a file is not taken for a blob", () => { + Test_utils.eq("File \"untagged_file_blob_test.res\", line 25, characters 7-14", "blob", kind(new Blob(["x"]))); + Test_utils.eq("File \"untagged_file_blob_test.res\", line 26, characters 7-14", "file", kind(new File(["x"], "a.txt"))); + }); + Mocha.test("a blob case alone excludes files", () => { + Test_utils.eq("File \"untagged_file_blob_test.res\", line 30, characters 7-14", "blob", onlyBlob(new Blob(["x"]))); + Test_utils.eq("File \"untagged_file_blob_test.res\", line 31, characters 7-14", "other", onlyBlob(new File(["x"], "a.txt"))); + }); + Mocha.test("a file upcasts to a blob", () => { + let blob = new File(["x"], "a.txt"); + Test_utils.eq("File \"untagged_file_blob_test.res\", line 36, characters 7-14", "file", kind(blob)); + }); +}); + +export { + kind, + onlyBlob, +} +/* Not a pure module */ diff --git a/tests/tests/src/untagged_file_blob_test.res b/tests/tests/src/untagged_file_blob_test.res new file mode 100644 index 00000000000..62ec91c6d2f --- /dev/null +++ b/tests/tests/src/untagged_file_blob_test.res @@ -0,0 +1,38 @@ +open Mocha +open Test_utils + +@new external makeBlob: array => Blob.t = "Blob" +@new external makeFile: (array, string) => File.t = "File" + +// Blob is declared before its subclass File (#8938) +@unboxed +type upload = Blob(Blob.t) | File(File.t) + +let kind = upload => + switch upload { + | Blob(_) => "blob" + | File(_) => "file" + } + +let onlyBlob = upload => + switch upload { + | Blob(_) => "blob" + | _ => "other" + } + +describe(__MODULE__, () => { + test("a file is not taken for a blob", () => { + eq(__LOC__, "blob", kind(Blob(makeBlob(["x"])))) + eq(__LOC__, "file", kind(File(makeFile(["x"], "a.txt")))) + }) + + test("a blob case alone excludes files", () => { + eq(__LOC__, "blob", onlyBlob(Blob(makeBlob(["x"])))) + eq(__LOC__, "other", onlyBlob(File(makeFile(["x"], "a.txt")))) + }) + + test("a file upcasts to a blob", () => { + let blob: Blob.t = (makeFile(["x"], "a.txt") :> Blob.t) + eq(__LOC__, "file", kind(Obj.magic(blob))) + }) +}) From 25a5ddc34aff905837befb8e5b89142ed54c3386 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Thu, 22 Oct 2026 16:53:58 +0000 Subject: [PATCH 2/2] Document the stdlib's runtime annotations in the untagged variants spec Why: With the stdlib annotated, `Blob.t` and `File.t` are never undefined and #8938 is fixed, which the spec and its test still stated the other way round. Rule 5 lists the stdlib's annotations, rule 12 unboxes `option` of a variant with a `Blob.t` payload, and the known bug of overlapping classes is gone. The typed-array bug stays, filed as #8955. Representation modified (option of a variant with a Blob payload): Example: @unboxed type withBlob = BBlob(Blob.t) | BString(string) let someWithBlob = (v: withBlob): option => Some(v) Before (js): return Primitive_option.some(v); After (js): return v; Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Fc7LEuyEfynjaKCVEBPmTF Signed-off-by: Christoph Knittel --- compiler/ml/UntaggedVariants.md | 48 +++++++++++-------- .../tests/src/untagged_variants_spec_test.mjs | 4 +- .../tests/src/untagged_variants_spec_test.res | 2 +- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/compiler/ml/UntaggedVariants.md b/compiler/ml/UntaggedVariants.md index fc74226b24d..20337b2b901 100644 --- a/compiler/ml/UntaggedVariants.md +++ b/compiler/ml/UntaggedVariants.md @@ -170,8 +170,21 @@ through `globalThis` (#8785). The compiler trusts the annotations, as it trusts the type of an external: a value that is not what its type declares is recognized by what it is (rule 10). +The stdlib annotates the types of the classes it binds: + +| Type | Annotation | +| --- | --- | +| `Date.t`, `RegExp.t`, `ArrayBuffer.t`, `DataView.t`, `Map.t`, `Set.t`, `WeakMap.t`, `WeakSet.t`, `Blob.t` | `@instanceOf` the class of the same name | +| `File.t`, a private abbreviation of `Blob.t` | `@instanceOf("File")`, a subclass of `Blob` | +| `JsError.t` | `@instanceOf("Error")` | +| `Intl.Collator.t`, `Intl.DateTimeFormat.t`, `Intl.ListFormat.t`, `Intl.Locale.t`, `Intl.NumberFormat.t`, `Intl.PluralRules.t`, `Intl.RelativeTimeFormat.t`, `Intl.Segmenter.t` | `@instanceOf("Intl.Collator")` and so on | +| `Int8Array.t` and the other typed arrays, public abbreviations of `TypedArray.t<_>` | `@instanceOf` the class of the same name | +| `Symbol.t` | `@typeOf("symbol")` | + Types without annotations are recognized from a fixed list of type names in -`ast_untagged_variants.ml:type_to_instanceof_backed_obj`. The predefined +`ast_untagged_variants.ml:type_to_instanceof_backed_obj`. Every listed stdlib +type is annotated, so the list only covers other types printed with one of +these paths. The predefined `promise<_>` and `array<_>` are matched by path; the others by the printed path of the type: @@ -184,8 +197,8 @@ path of the type: | `Int8Array.t`, `Uint8Array.t`, `Uint8ClampedArray.t`, `Int16Array.t`, `Uint16Array.t`, `Int32Array.t`, `Uint32Array.t`, `Float32Array.t`, `Float64Array.t`, `BigInt64Array.t`, `BigUint64Array.t` | the class of the same name | The listed classes have no superclasses. The typed-array types are -abbreviations of one type, `TypedArray.t<_>`, and are told apart only by the -name as written. +abbreviations of one type, `TypedArray.t<_>`, and are told apart only by +their annotations, which are read on the name as written (Known bugs). - Implementation: `variant_layout.ml:runtime_annotations`, `declared_block_type`, `check_manifest_annotation`; @@ -195,9 +208,10 @@ name as written. `variant_runtime.ml:Instance`; `js_exp_make.ml:class_constructor`. - Tests: `spec_test` rules 4 and 5; `tests/tests/src/untagged_runtime_annotations_test.res`, - `untagged_runtime_annotations_shadow_test.res`; the fixtures above, + `untagged_runtime_annotations_shadow_test.res`, + `untagged_file_blob_test.res`; the fixtures above, `UntaggedAtMostOneInstance.res`, `UntaggedTupleAndArray.res`. -- Shaped by #6296, #6383, #6396, #7788, #8009, #8950. Related: #7147. +- Shaped by #6296, #6383, #6396, #7788, #8009, #8950, #8948. Related: #7147. ### 6. Well-formedness @@ -378,12 +392,13 @@ An `@as(null)` case does not prevent unboxing; `Some(N)` is `null`. | `@as(null) WNull \| WString(string)` | `v` | | `@as(undefined) WUndefined \| WUString(string)` | `Primitive_option.some(v)` | | `@as("n") ONone \| OPaque(opaque)` | `Primitive_option.some(v)` | -| `BBlob(Blob.t) \| BString(string)` | `Primitive_option.some(v)` | +| `BBlob(Blob.t) \| BString(string)` | `v` | | `DDate(Date.t) \| DString(string)` | `v` | -The stdlib marks `Date.t`, `RegExp.t`, `Map.t`, `Set.t`, `WeakMap.t`, -`WeakSet.t`, `ArrayBuffer.t`, `DataView.t`, the typed arrays and other -abstract types `@notUndefined`; `Blob.t` and `File.t` are not marked. +The stdlib's annotated types (rule 5) cannot hold `undefined`. It marks the +JSX element and component types, `Iterator.t`, `AsyncIterator.t`, +`TypedArray.t`, `Intl.Segments.t`, `Intl.NumberFormat.Grouping.t` and +`Lazy.t` with `@notUndefined`. Rule 5 keeps an annotated abbreviation from claiming this of a type that can hold `undefined`. @@ -476,18 +491,9 @@ Console.log2(x == None, mk(U) == None) // true false `Const_constructor`, including one whose literal is `undefined`; that this is the cause is conjectured, not established. -**Overlapping instance classes follow declaration order** (#8938). - -```rescript -@unboxed type b = B(Blob.t) | F(File.t) -let f = v => switch v { | B(_) => "blob" | F(_) => "file" } -Console.log(f(F(%raw(`new File(["x"], "a.txt")`)))) // blob -``` - -A `File` is a `Blob`, and the `Blob` check runs first. - -**Typed arrays are classified by the name as written.** The typed-array types -are one type, so a value of one can be wrapped in the case of another. +**Typed arrays share one type** (#8955). The typed-array types are public +abbreviations of `TypedArray.t<_>`, each annotated with its class, so a value +of one can be wrapped in the case of another. ```rescript @unboxed type ta = I8(Int8Array.t) | U8(Uint8Array.t) diff --git a/tests/tests/src/untagged_variants_spec_test.mjs b/tests/tests/src/untagged_variants_spec_test.mjs index d31df24cabe..10f532cbf0c 100644 --- a/tests/tests/src/untagged_variants_spec_test.mjs +++ b/tests/tests/src/untagged_variants_spec_test.mjs @@ -199,7 +199,7 @@ function someWithVariable(v) { } function someWithBlob(v) { - return Primitive_option.some(v); + return v; } function someWithDate(v) { @@ -342,7 +342,7 @@ Mocha.describe("Untagged_variants_spec_test", () => { Test_utils.eq("File \"untagged_variants_spec_test.res\", line 310, characters 7-14", someWithUndefined("a"), "a"); Test_utils.eq("File \"untagged_variants_spec_test.res\", line 313, characters 7-14", someWithOpaque(undefined) === undefined, false); Test_utils.eq("File \"untagged_variants_spec_test.res\", line 314, characters 7-14", someWithVariable(undefined) === undefined, false); - Test_utils.eq("File \"untagged_variants_spec_test.res\", line 315, characters 7-14", someWithBlob(undefined) === undefined, false); + Test_utils.eq("File \"untagged_variants_spec_test.res\", line 315, characters 7-14", someWithBlob(undefined) === undefined, true); Test_utils.eq("File \"untagged_variants_spec_test.res\", line 316, characters 7-14", someWithDate(undefined) === undefined, true); }); Mocha.test("rule 13: @notUndefined unboxes option of a variant holding the type", () => { diff --git a/tests/tests/src/untagged_variants_spec_test.res b/tests/tests/src/untagged_variants_spec_test.res index d2fcf8b9590..32c9242a6a3 100644 --- a/tests/tests/src/untagged_variants_spec_test.res +++ b/tests/tests/src/untagged_variants_spec_test.res @@ -312,7 +312,7 @@ describe(__MODULE__, () => { // distinct from `None`, an unboxed one does not. eq(__LOC__, someWithOpaque(OPaque(raw(Nullable.undefined))) == None, false) eq(__LOC__, someWithVariable(Var(raw(Nullable.undefined))) == None, false) - eq(__LOC__, someWithBlob(BBlob(raw(Nullable.undefined))) == None, false) + eq(__LOC__, someWithBlob(BBlob(raw(Nullable.undefined))) == None, true) eq(__LOC__, someWithDate(DDate(raw(Nullable.undefined))) == None, true) })