diff --git a/packages/compiler/src/frontend/lowering/builtin-namespaces.ts b/packages/compiler/src/frontend/lowering/builtin-namespaces.ts index 07c6bda5b..991e40657 100644 --- a/packages/compiler/src/frontend/lowering/builtin-namespaces.ts +++ b/packages/compiler/src/frontend/lowering/builtin-namespaces.ts @@ -1,4 +1,4 @@ -/** The standard namespace objects (`Math`, `JSON`, `Reflect`) read as +/** The standard namespace objects (`Math`, `JSON`, `Reflect`, `Intl`) read as * JavaScript values. Each is one object per program, built on first use, * with Node's own properties in Node's order: the functions are writable, * configurable and non-enumerable, the Math constants are read-only and @@ -106,6 +106,21 @@ function namespaceMembers(name: string): readonly NamespaceMember[] | null { { key: "set", length: 3 }, { key: "setPrototypeOf", length: 2 }, ]; + case "Intl": + return [ + { key: "getCanonicalLocales", length: 1 }, + { key: "supportedValuesOf", length: 1 }, + { key: "DateTimeFormat", length: 0 }, + { key: "NumberFormat", length: 0 }, + { key: "Collator", length: 0 }, + { key: "PluralRules", length: 0 }, + { key: "RelativeTimeFormat", length: 0 }, + { key: "ListFormat", length: 0 }, + { key: "Locale", length: 1 }, + { key: "DisplayNames", length: 2 }, + { key: "Segmenter", length: 0 }, + { key: "DurationFormat", length: 0 }, + ]; default: return null; } @@ -270,7 +285,9 @@ function functionValue(lowerer: Lowerer, canonical: string, key: string, loc: Sr ? mathMemberValue(lowerer, key, loc) : canonical === "JSON" ? jsonMemberValue(lowerer, key, loc) - : reflectMemberValue(lowerer, key, loc); + : canonical === "Reflect" + ? reflectMemberValue(lowerer, key, loc) + : null; if (value !== null) return value.type.kind === "dyn" ? value diff --git a/packages/compiler/src/frontend/lowering/expressions/property-access.ts b/packages/compiler/src/frontend/lowering/expressions/property-access.ts index 3b617c9fe..9daeaf83a 100644 --- a/packages/compiler/src/frontend/lowering/expressions/property-access.ts +++ b/packages/compiler/src/frontend/lowering/expressions/property-access.ts @@ -4993,7 +4993,10 @@ const ERROR_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ * constructor, and dynamic values answer `.constructor` with the same one, * so `o.constructor === Object` and `err.constructor === TypeError` are * exact identity tests, and `err.constructor.name` is the constructor's - * name. */ + * name. SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array and + * Proxy have values with Node's name, length and instanceof answers; + * calling one throws Node's TypeError, and constructing through the value + * is fenced. */ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ "Object", "Function", @@ -5005,6 +5008,11 @@ const GLOBAL_CONSTRUCTOR_VALUES: ReadonlySet = new Set([ "WeakMap", "WeakSet", "FinalizationRegistry", + "SharedArrayBuffer", + "DataView", + "BigInt64Array", + "BigUint64Array", + "Proxy", ...ERROR_CONSTRUCTOR_VALUES, ]); diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/builtin-values-proxy-intl-views.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/builtin-values-proxy-intl-views.cjs.json new file mode 100644 index 000000000..c4a1cf629 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/builtin-values-proxy-intl-views.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/builtin-values-proxy-intl-views.cjs" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_dyn_invoke.c b/packages/runtime/src/scr_dyn_invoke.c index 0279ccab1..8ac5bfc2d 100644 --- a/packages/runtime/src/scr_dyn_invoke.c +++ b/packages/runtime/src/scr_dyn_invoke.c @@ -1959,7 +1959,9 @@ ScrDyn *scr_dyn_builtin_method(const ScrStr *prototype, const ScrStr *method) { * answer `.constructor` on the matching dynamic values, so * `o.constructor === Object` and `a.constructor === b.constructor` are * exact identity tests. `Object(v)` works through the stored value; calling - * the others through it is fenced. */ + * the others through it is fenced. SharedArrayBuffer, DataView, + * BigInt64Array, BigUint64Array and Proxy are interned the same way; + * calling one throws Node's "Constructor X requires 'new'" TypeError. */ /* String constants live in named arrays in their own section rather than * in the unit's merged string pool (the explicit section also keeps the * compiler from folding the unit's existing literals into them), so @@ -2012,11 +2014,23 @@ static const char dyn_s38[] SCR_DYN_COMPAT_STR = "structuredClone"; static const char dyn_s39[] SCR_DYN_COMPAT_STR = "The \"input\" argument must be specified"; static const char dyn_s40[] SCR_DYN_COMPAT_STR = "The \"The value argument must be specified\" argument must be specified"; static const char dyn_s41[] SCR_DYN_COMPAT_STR = "ERR_MISSING_ARGS"; +static const char dyn_s42[] SCR_DYN_COMPAT_STR = "SharedArrayBuffer"; +static const char dyn_s43[] SCR_DYN_COMPAT_STR = "DataView"; +static const char dyn_s44[] SCR_DYN_COMPAT_STR = "BigInt64Array"; +static const char dyn_s45[] SCR_DYN_COMPAT_STR = "BigUint64Array"; +static const char dyn_s46[] SCR_DYN_COMPAT_STR = "Proxy"; +static const char dyn_s47[] SCR_DYN_COMPAT_STR = "Constructor %s requires 'new'"; static const char *const dyn_global_ctor_names[] = { dyn_s26, dyn_s29, dyn_s25, dyn_s30, dyn_s31, dyn_s21, dyn_s22, dyn_s24, dyn_s35, + dyn_s42, dyn_s43, dyn_s44, dyn_s45, dyn_s46, }; #define DYN_GLOBAL_CTORS (sizeof dyn_global_ctor_names / sizeof dyn_global_ctor_names[0]) +/* Each constructor's own length, indexed like dyn_global_ctor_names. */ +static const uint8_t dyn_global_ctor_lengths[DYN_GLOBAL_CTORS] = {1, 7, 2, 0, 0, 1, 0, 0, 1, 1, 1, 3, 3, 2}; +/* The first of the constructors that only `new` builds + * (SharedArrayBuffer onward): calling one throws Node's TypeError. */ +#define DYN_GLOBAL_NEW_ONLY 9 static SCR_TL ScrDyn *dyn_global_ctors[DYN_GLOBAL_CTORS]; static SCR_TL bool dyn_global_ctors_registered; @@ -2029,6 +2043,13 @@ static void dyn_global_ctors_cleanup(void) { static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, size_t argc) { const char *name = closure->fn; + for (size_t i = DYN_GLOBAL_NEW_ONLY; i < DYN_GLOBAL_CTORS; i++) { + if (name != dyn_global_ctor_names[i]) continue; + char message[64]; + int length = snprintf(message, sizeof message, dyn_s47, name); + scr_throw_error_msg(SCR_ERR_TYPE, message, (size_t)length); + return NULL; + } bool object = strcmp(name, dyn_s26) == 0; if (object) { const ScrDyn *value = argc ? args[0] : scr_dyn_undefined(); @@ -2046,8 +2067,8 @@ static ScrDyn *dyn_global_ctor_call(ScrClosure *closure, ScrDyn *const *args, si } /* `new C(...args)` through a stored Object, Date, Map, Set, WeakMap or - * WeakSet builds what the `new` expression builds. RegExp, Promise, and - * Object over a primitive (a wrapper object) are fenced. */ + * WeakSet builds what the `new` expression builds. The other constructors, + * and Object over a primitive (a wrapper object), are fenced. */ static bool dyn_global_construct(const ScrDyn *callee, const ScrDyn *args, ScrDyn **result) { if (callee->kind != SCR_DYN_FUNC || callee->v.fn.thunk != dyn_global_ctor_call) return false; const char *name = callee->v.fn.clo->fn; @@ -2083,11 +2104,8 @@ static ScrDyn *dyn_global_ctor(const char *name) { scr_dyn_global_construct_fn = dyn_global_construct; } if (!dyn_global_ctors[i]) { - /* Each constructor's own length: Object 1, Date 7, RegExp 2, Promise 1, - * the collections 0, FinalizationRegistry 1. */ - uint32_t length = i == 0 || i == 5 || i == 8 ? 1 : i == 1 ? 7 : i == 2 ? 2 : 0; dyn_global_ctors[i] = scr_dyn_new_func(scr_closure_new((void *)dyn_global_ctor_names[i], 0), - dyn_global_ctor_call, length, dyn_s6, + dyn_global_ctor_call, dyn_global_ctor_lengths[i], dyn_s6, dyn_global_ctor_names[i]); /* `o.constructor.prototype` of a plain object is Object.prototype * (zod's isPlainObject). The other prototypes are not modeled. */ diff --git a/packages/runtime/src/scr_json.c b/packages/runtime/src/scr_json.c index 311596597..2668278c5 100644 --- a/packages/runtime/src/scr_json.c +++ b/packages/runtime/src/scr_json.c @@ -5538,10 +5538,26 @@ static int scr_dyn_global_constructor_instanceof(const ScrDyn *value, const ScrD return -1; /* A native Date, or an instance of a class declared `extends Date`. */ if (strcmp(callee->v.fn.name, "Date") == 0) return scr_dyn_date_instance_is(value); + if (strcmp(callee->v.fn.name, "DataView") == 0) return scr_dyn_data_view_is(value); + /* No value is a BigInt64Array or BigUint64Array: neither is constructed. */ + if (strcmp(callee->v.fn.name, "BigInt64Array") == 0 || strcmp(callee->v.fn.name, "BigUint64Array") == 0) + return 0; + /* Proxy has no prototype: false for a primitive, Node's TypeError for an + * object. */ + if (strcmp(callee->v.fn.name, "Proxy") == 0) { + if (value->kind == SCR_DYN_UNDEF || value->kind == SCR_DYN_NULL || value->kind == SCR_DYN_BOOL || + value->kind == SCR_DYN_NUM || value->kind == SCR_DYN_STR || value->kind == SCR_DYN_BIGINT || + value->kind == SCR_DYN_SYMBOL) + return 0; + static const char message[] = "Function has non-object prototype 'undefined' in instanceof check"; + scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1); + return 0; + } static const struct { const char *name; int tag; } kinds[] = { {"RegExp", SCR_DYNH_REGEXP}, {"Map", SCR_DYNH_MAP}, {"Set", SCR_DYNH_SET}, {"WeakMap", SCR_DYNH_WEAK_MAP}, {"WeakSet", SCR_DYNH_WEAK_SET}, {"FinalizationRegistry", SCR_DYNH_FINALIZATION_REGISTRY}, + {"SharedArrayBuffer", SCR_DYNH_SHARED_ARRAY_BUFFER}, }; bool promise = strcmp(callee->v.fn.name, "Promise") == 0; int tag = -1; diff --git a/tests/corpus/builtin-values-proxy-intl-views.cjs b/tests/corpus/builtin-values-proxy-intl-views.cjs new file mode 100644 index 000000000..2cde73bb4 --- /dev/null +++ b/tests/corpus/builtin-values-proxy-intl-views.cjs @@ -0,0 +1,33 @@ +// SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array, Proxy and Intl +// read as values hold Node's functions and namespace object. +function attempt(label, run) { + try { + console.log(label, run()); + } catch (error) { + console.log(label, error.name, error.message); + } +} + +const constructors = { SharedArrayBuffer, DataView, BigInt64Array, BigUint64Array, Proxy }; +for (const key of Object.keys(constructors)) { + const value = constructors[key]; + console.log(key, typeof value, value.name, value.length, value === globalThis[key]); + attempt(`call ${key}`, () => value()); +} +console.log(constructors); +console.log(String(DataView), Object.prototype.toString.call(constructors.Proxy)); + +const shared = new SharedArrayBuffer(8); +const view = new DataView(new ArrayBuffer(4)); +const stored = { shared: SharedArrayBuffer, view: DataView, proxy: Proxy, big: BigInt64Array }; +console.log(shared instanceof stored.shared, new ArrayBuffer(1) instanceof stored.shared); +console.log(view instanceof stored.view, new Uint8Array(4) instanceof stored.view); +console.log(view instanceof stored.big, JSON.parse("1") instanceof stored.proxy); +attempt("instanceof Proxy", () => ({}) instanceof stored.proxy); + +const intl = Intl; +console.log(typeof intl, intl === globalThis.Intl, Object.keys(intl).length, "NumberFormat" in intl); +console.log(Object.getOwnPropertyNames(intl).join(",")); +console.log(Object.prototype.toString.call(intl), intl[Symbol.toStringTag]); +console.log(typeof intl.DateTimeFormat, intl.Locale.length, intl.DisplayNames.length); +console.log(new Intl.NumberFormat("en-US").format(1234.5)); diff --git a/tests/corpus/builtin-values-untyped-slots.ts b/tests/corpus/builtin-values-untyped-slots.ts index 4185a524e..c32857ef6 100644 --- a/tests/corpus/builtin-values-untyped-slots.ts +++ b/tests/corpus/builtin-values-untyped-slots.ts @@ -51,3 +51,14 @@ console.log( Object.keys(math).length, Object.getOwnPropertyNames(json).join(","), ); + +const views: any = { intl: Intl, proxy: Proxy, view: DataView, shared: SharedArrayBuffer }; +console.log(typeof views.intl, Object.getOwnPropertyNames(views.intl).length); +console.log(Object.prototype.toString.call(views.intl), views.intl.Locale.length); +console.log(views.proxy.name, views.proxy.length, views.view.name, views.shared.length); +try { + views.proxy(); +} catch (error) { + console.log((error as Error).message); +} +console.log(views); diff --git a/tests/diagnostics/builtin-values-without-lowering.ts b/tests/diagnostics/builtin-values-without-lowering.ts index b8b5c90a1..6ea93c949 100644 --- a/tests/diagnostics/builtin-values-without-lowering.ts +++ b/tests/diagnostics/builtin-values-without-lowering.ts @@ -5,7 +5,7 @@ function take(x: unknown): unknown { } const inferred = { json: JSON }; -const loose: any = { intl: Intl, proxy: Proxy }; +const loose: any = { atomics: Atomics }; const timers: Record = { setTimeout }; const list: any[] = [Array, parseInt]; console.log(inferred, loose, timers, list, take(process)); diff --git a/tests/harness/__snapshots__/builtin-values-without-lowering.ts.txt b/tests/harness/__snapshots__/builtin-values-without-lowering.ts.txt index 418eaedef..466015d88 100644 --- a/tests/harness/__snapshots__/builtin-values-without-lowering.ts.txt +++ b/tests/harness/__snapshots__/builtin-values-without-lowering.ts.txt @@ -3,7 +3,7 @@ builtin-values-without-lowering.ts:7:7 - error SC2009: values of type '{ json: J 6 | 7 | const inferred = { json: JSON }; | ^~~~~~~~ - 8 | const loose: any = { intl: Intl, proxy: Proxy }; + 8 | const loose: any = { atomics: Atomics }; hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') @@ -12,22 +12,22 @@ builtin-values-without-lowering.ts:7:18 - error SC2009: values of type '{ json: 6 | 7 | const inferred = { json: JSON }; | ^~~~~~~~~~~~~~ - 8 | const loose: any = { intl: Intl, proxy: Proxy }; + 8 | const loose: any = { atomics: Atomics }; hint: the outer shape is supported — the named component is the blocker; restate it inside the compilable set (number, string, boolean, arrays, Maps, Sets, RegExp, functions, classes, records, unions of those, and 'unknown') -builtin-values-without-lowering.ts:8:28 - error SC2020: 'Intl' is part of the standard library types but has no scriptc lowering yet +builtin-values-without-lowering.ts:8:31 - error SC2020: 'Atomics' is part of the standard library types but has no scriptc lowering yet 7 | const inferred = { json: JSON }; - 8 | const loose: any = { intl: Intl, proxy: Proxy }; - | ^~~~ + 8 | const loose: any = { atomics: Atomics }; + | ^~~~~~~ 9 | const timers: Record = { setTimeout }; - hint: native Intl supports Intl.Segmenter with grapheme or word granularity and the composed new Intl.NumberFormat("en-US").format(x), x.toLocaleString("en-US") and x.toLocaleString(); formatter objects, locale negotiation and the other Intl constructors remain unsupported + hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations) builtin-values-without-lowering.ts:9:43 - error SC2020: 'setTimeout' is part of the standard library types but has no scriptc lowering yet - 8 | const loose: any = { intl: Intl, proxy: Proxy }; + 8 | const loose: any = { atomics: Atomics }; 9 | const timers: Record = { setTimeout }; | ^~~~~~~~~~ 10 | const list: any[] = [Array, parseInt]; diff --git a/tests/harness/corpus-costs/builtin-values-proxy-intl-views.cjs.json b/tests/harness/corpus-costs/builtin-values-proxy-intl-views.cjs.json new file mode 100644 index 000000000..5c169787d --- /dev/null +++ b/tests/harness/corpus-costs/builtin-values-proxy-intl-views.cjs.json @@ -0,0 +1 @@ +{ "plain": 4800, "san": 7200 }