From ea26069ee5dadd3463fa8099d3fa23e085e1e80c Mon Sep 17 00:00:00 2001 From: Tamay Eser Uysal Date: Mon, 19 Oct 2026 01:27:53 +0200 Subject: [PATCH 1/2] Accept a recursive Array wrapper in the Array own-key helpers (gate: gained object-keys-over-recursive-dictionary-or-array-union, 0 moved) --- scripts/emitted-baseline-runtime.txt | 1 + src/targets/cpp/runtime/gea_runtime.h | 29 +++++++++++++++++++ ...rsive-dictionary-or-array-union.runtime.ts | 16 ++++++++++ 3 files changed, 46 insertions(+) create mode 100644 test/runtime/object-keys-over-recursive-dictionary-or-array-union.runtime.ts diff --git a/scripts/emitted-baseline-runtime.txt b/scripts/emitted-baseline-runtime.txt index 7a4c385a..e881bc49 100644 --- a/scripts/emitted-baseline-runtime.txt +++ b/scripts/emitted-baseline-runtime.txt @@ -946,6 +946,7 @@ object-integrity-uses-physical-index-protocol.runtime.ts.cpp 23551c4185ed object-is-over-union-and-optional-carriers.runtime.ts.cpp 199c1035ac10 object-key-enumeration-requires-native-object.runtime.ts.cpp f6c110078688 object-keyed-map-index-matches-node.runtime.ts.cpp ab5c357ecdd6 +object-keys-over-recursive-dictionary-or-array-union.runtime.ts.cpp 061e846e55a7 object-literal-beside-class-in-or-keeps-its-own-layout.runtime.ts.cpp d86094099c83 object-literal-empty-array-filled-through-untyped-computed-read.runtime.js.cpp d13b67ac0d25 object-literal-es5-method-receiver.runtime.js.cpp 4c1c19bfe47a diff --git a/src/targets/cpp/runtime/gea_runtime.h b/src/targets/cpp/runtime/gea_runtime.h index ddf5d293..6de309da 100644 --- a/src/targets/cpp/runtime/gea_runtime.h +++ b/src/targets/cpp/runtime/gea_runtime.h @@ -15981,6 +15981,15 @@ struct ArrayObjectElementOf> { using type = Element; }; +namespace detail { +template +ArrayObject arrayObjectBaseProbe(const ArrayObject&); +template +using ArrayObjectBaseOf = decltype(arrayObjectBaseProbe(std::declval())); +template +concept DerivedArrayObject = !IsArrayObjectStruct::value && requires { typename ArrayObjectBaseOf; }; +} + namespace detail { template struct DynamicCarrier; @@ -32639,6 +32648,26 @@ bool assignOwnPropertiesUnorderedWith(const gea::Ref& source, const gea::Ref< } /** Array exotic own enumerable string keys, followed by enumerable expandos. */ +template +std::vector arrayOwnEnumerableKeys(const gea::Ref>& array); +template +bool arrayHasOwnEnumerableKey(const gea::Ref>& array, const std::string& key); +template +std::vector arrayOwnPropertyNames(const gea::Ref>& array); + +template +std::vector arrayOwnEnumerableKeys(const gea::Ref& array) { + return arrayOwnEnumerableKeys(gea::Ref>(array)); +} +template +bool arrayHasOwnEnumerableKey(const gea::Ref& array, const std::string& key) { + return arrayHasOwnEnumerableKey(gea::Ref>(array), key); +} +template +std::vector arrayOwnPropertyNames(const gea::Ref& array) { + return arrayOwnPropertyNames(gea::Ref>(array)); +} + template std::vector arrayOwnEnumerableKeys(const gea::Ref>& array) { std::vector keys; diff --git a/test/runtime/object-keys-over-recursive-dictionary-or-array-union.runtime.ts b/test/runtime/object-keys-over-recursive-dictionary-or-array-union.runtime.ts new file mode 100644 index 00000000..ab8e8e1e --- /dev/null +++ b/test/runtime/object-keys-over-recursive-dictionary-or-array-union.runtime.ts @@ -0,0 +1,16 @@ +type Leaf = string +type Tree = { [key: string]: Leaf | Tree | Branch } +type Branch = (Leaf | Tree | Branch | undefined)[] +type Container = Tree | Branch + +const keysOf = (container: Container): string => Object.keys(container).join(',') + +const tree: Tree = { a: 'x', b: ['y', { c: 'z' }] } +const branch: Branch = ['p', tree, undefined, 'q'] +tree['self'] = branch + +console.log(keysOf(tree)) +console.log(keysOf(branch)) + +//! expect: a,b,self +//! expect: 0,1,2,3 From 2dc9ee16087772bb5b70229bb6ec3fbb12aee723 Mon Sep 17 00:00:00 2001 From: Tamay Eser Uysal Date: Sun, 18 Oct 2026 22:03:29 +0200 Subject: [PATCH 2/2] Recognise a boxed instance of a class extending Error as an Error in instanceof, String() and the uncaught describer --- scripts/emitted-baseline-runtime.txt | 2 + src/targets/cpp/runtime/gea_runtime.h | 24 +++++++--- ...error-over-boxed-error-subclass.runtime.ts | 40 +++++++++++++++++ ...or-subclass-overriding-tostring.runtime.ts | 44 +++++++++++++++++++ 4 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 test/runtime/instanceof-error-over-boxed-error-subclass.runtime.ts create mode 100644 test/runtime/string-of-boxed-error-subclass-overriding-tostring.runtime.ts diff --git a/scripts/emitted-baseline-runtime.txt b/scripts/emitted-baseline-runtime.txt index e881bc49..f2e6ae54 100644 --- a/scripts/emitted-baseline-runtime.txt +++ b/scripts/emitted-baseline-runtime.txt @@ -621,6 +621,7 @@ instanceof-class-union-through-nested-record-views.runtime.ts.cpp 540bfb9a5f2f instanceof-date-over-a-union-of-native-carriers.runtime.ts.cpp 6543d6b77454 instanceof-derived-class.ts.cpp 526f685a24d1 instanceof-else-arm-narrowed-to-never-is-not-converted.runtime.ts.cpp 439d10677a1d +instanceof-error-over-boxed-error-subclass.runtime.ts.cpp 0a6f5f7e88c3 instanceof-error-over-error-subclass-union-arm.runtime.ts.cpp 3f5bdc34a912 instanceof-error-over-union-carrier.ts.cpp c0a862a2df14 instanceof-over-union-with-record-arm-after-a-boxed-callable.runtime.ts.cpp 61940b54d14f @@ -1230,6 +1231,7 @@ string-length-reuse-mutation.ts.cpp 0cd264bd8d0a string-length-reuse.ts.cpp 429819fd086c string-normalize-forms.ts.cpp 98c5bc66ab2f string-object-with-declared-fields.ts.cpp 8a8056fbe5ad +string-of-boxed-error-subclass-overriding-tostring.runtime.ts.cpp 5bc5135e23d8 string-of-symbol.runtime.js.cpp 234cbfea9b0d string-raw-tagged-template-into-regexp.runtime.ts.cpp b72805f3b232 string-shared-layout.ts.cpp 645b4b7ef4b6 diff --git a/src/targets/cpp/runtime/gea_runtime.h b/src/targets/cpp/runtime/gea_runtime.h index 6de309da..aa9e7de5 100644 --- a/src/targets/cpp/runtime/gea_runtime.h +++ b/src/targets/cpp/runtime/gea_runtime.h @@ -20256,6 +20256,8 @@ struct Error; namespace host { inline gea::Ref createRuntimeError(const char* kind, const gea::Optional& message); inline bool isRuntimeError(const gea::Value& value); +inline gea::Ref runtimeErrorOf(const gea::Value& value); +inline bool isErrorValue(const gea::Value& value); inline bool instanceOfRuntimeError(const gea::Value& value, const char* kind); inline std::string runtimeErrorString(const gea::Value& value); [[noreturn]] inline void throwRuntimeError(const char* kind, const std::string& message); @@ -43275,7 +43277,7 @@ inline std::string typeOf(const gea::Value &value) { // without that dynamic boundary still refuses by name inside `dynamicToString`: // manufacturing "[object Object]" would bypass a possible native `toString`. inline std::string toString(const gea::Value &value) { - if (gea::host::isRuntimeError(value)) return gea::host::runtimeErrorString(value); + if (gea::host::isErrorValue(value)) return gea::host::runtimeErrorString(value); switch (value.tag()) { case gea::Value::Tag::Undefined: return toStringUndefined(); @@ -44229,7 +44231,7 @@ inline void registerErrorRecordType(const char* name) { */ inline bool instanceOfError(const gea::Value& value, const char* name) { if (value.tag() != gea::Value::Tag::Object) return false; - if (gea::host::isRuntimeError(value)) return gea::host::instanceOfRuntimeError(value, name); + if (gea::host::isErrorValue(value)) return gea::host::instanceOfRuntimeError(value, name); const auto found = detail::errorPayloadTypes().find(name); if (found == detail::errorPayloadTypes().end()) return false; const void* payload = value.payloadType(); @@ -51944,11 +51946,23 @@ inline bool isRuntimeError(const gea::Value& value) { return value.tag() == gea::Value::Tag::Object && value.payloadType() == gea::detail::payloadTypeTagFor>(); } +inline gea::Ref runtimeErrorOf(const gea::Value& value) { + if (value.tag() != gea::Value::Tag::Object) return {}; + if (value.payloadType() == gea::detail::payloadTypeTagFor>()) return value.as>(); + if (value.classObject() && gea::detail::classIdentityExtends(value.classIdentity(), &gea::detail::RefOperationsFor::table)) + return value.classObject().template staticCast(); + return {}; +} +inline bool isErrorValue(const gea::Value& value) { return static_cast(runtimeErrorOf(value)); } inline bool instanceOfRuntimeError(const gea::Value& value, const char* kind) { - return isRuntimeError(value) && value.as>()->instanceOf(kind); + const auto error = runtimeErrorOf(value); + return error && error->instanceOf(kind); } inline std::string runtimeErrorString(const gea::Value& value) { - return gea::detail::unboxAs>(value, gea::Value::Tag::Object, "Error ToString")->toString(); + const auto error = runtimeErrorOf(value); + if (!error) return gea::detail::unboxAs>(value, gea::Value::Tag::Object, "Error ToString")->toString(); + if (value.classObject() && value.getProperty(gea::PropertyKey::string("toString")).tag() == gea::Value::Tag::Function) return gea::dynamicToString(value); + return error->toString(); } [[noreturn]] inline void throwRuntimeError(const char* kind, const std::string& message) { #if defined(GEA_RUNTIME_THROW_ENDS_PROGRAM) && GEA_RUNTIME_THROW_ENDS_PROGRAM @@ -51973,7 +51987,7 @@ inline const bool uncaughtDescriberInstalled = [] { std::rethrow_exception(current); } catch (const gea::Value& value) { try { - text = gea::host::isRuntimeError(value) ? gea::host::runtimeErrorString(value) : gea::dynamicToString(value); + text = gea::host::isErrorValue(value) ? gea::host::runtimeErrorString(value) : gea::dynamicToString(value); } catch (...) { text = ""; } diff --git a/test/runtime/instanceof-error-over-boxed-error-subclass.runtime.ts b/test/runtime/instanceof-error-over-boxed-error-subclass.runtime.ts new file mode 100644 index 00000000..e2edca37 --- /dev/null +++ b/test/runtime/instanceof-error-over-boxed-error-subclass.runtime.ts @@ -0,0 +1,40 @@ +class ParseFailure extends Error { + status: number + constructor(status: number, message: string, name = 'Error') { + super(message) + this.name = name + this.status = status + } +} + +class EntityTooLarge extends ParseFailure { + constructor(limit: number) { + super(413, `request entity too large: ${limit}`, 'PayloadTooLargeError') + } +} + +function fail(): never { + throw new ParseFailure(400, 'bad body', 'SyntaxError') +} + +const describe = (err: unknown): string => (err instanceof Error ? `${err.name}: ${err.message}` : `other:${String(err)}`) + +let caught: unknown = undefined +try { + fail() +} catch (err) { + caught = err +} + +//! expect: true true SyntaxError: bad body SyntaxError: bad body +console.log(caught instanceof Error, caught instanceof ParseFailure, describe(caught), String(caught)) + +const deep: unknown = new EntityTooLarge(1024) +//! expect: true true true PayloadTooLargeError: request entity too large: 1024 +console.log(deep instanceof Error, deep instanceof ParseFailure, deep instanceof EntityTooLarge, String(deep)) + +const plain: unknown = new Error('plain') +const typed: unknown = new TypeError('typed') +const record: unknown = { name: 'Error', message: 'shaped like one' } +//! expect: Error: plain TypeError: typed false other:[object Object] +console.log(describe(plain), describe(typed), record instanceof Error, describe(record)) diff --git a/test/runtime/string-of-boxed-error-subclass-overriding-tostring.runtime.ts b/test/runtime/string-of-boxed-error-subclass-overriding-tostring.runtime.ts new file mode 100644 index 00000000..d367fac9 --- /dev/null +++ b/test/runtime/string-of-boxed-error-subclass-overriding-tostring.runtime.ts @@ -0,0 +1,44 @@ +class HttpError extends Error { + status: number + constructor(status: number, message: string) { + super(message) + this.name = 'HttpError' + this.status = status + } + toString(): string { + return `HttpError(${this.status}): ${this.message}` + } +} + +class NotFound extends HttpError { + constructor(path: string) { + super(404, `no route for ${path}`) + } +} + +class PlainError extends Error { + constructor(message: string) { + super(message) + this.name = 'PlainError' + } +} + +const overriding: unknown = new HttpError(500, 'boom') +const inherited: unknown = new NotFound('/missing') +const plain: unknown = new PlainError('plain') + +//! expect: HttpError(500): boom HttpError(500): boom +console.log(String(overriding), `${overriding}`) +//! expect: HttpError(404): no route for /missing +console.log(String(inherited)) +//! expect: PlainError: plain +console.log(String(plain)) + +let caught: unknown = undefined +try { + throw overriding +} catch (err) { + caught = err +} +//! expect: true caught HttpError(500): boom +console.log(caught instanceof HttpError, 'caught', String(caught))