@scriptc/compiler 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +13 -0
- package/dist/backend/cc.js +14 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +155 -6
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-island.js +9 -1
- package/dist/backend/emission/emit-island.js.map +1 -1
- package/dist/backend/emission/emit-stmts.js +3 -1
- package/dist/backend/emission/emit-stmts.js.map +1 -1
- package/dist/backend/emission/emit-types.d.ts +3 -0
- package/dist/backend/emission/emit-types.js +12 -0
- package/dist/backend/emission/emit-types.js.map +1 -1
- package/dist/backend/emission/emit-walkers.d.ts +7 -5
- package/dist/backend/emission/emit-walkers.js +45 -18
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -1
- package/dist/backend/emission/emitter.js +35 -10
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +4 -3
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/llvm/emitter.js +300 -18
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +98 -11
- package/dist/diagnostics/diagnostic.js +185 -25
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +24 -6
- package/dist/frontend/lib-contract.js +125 -44
- package/dist/frontend/lib-contract.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +16 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +11 -0
- package/dist/frontend/lowering/lower-builtins.js +127 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +77 -18
- package/dist/frontend/lowering/lower-calls.js +978 -44
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +14 -1
- package/dist/frontend/lowering/lower-classes.js +112 -16
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +4 -2
- package/dist/frontend/lowering/lower-containers.js +236 -44
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +55 -1
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +26 -3
- package/dist/frontend/lowering/lower-exprs.js +507 -47
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +7 -0
- package/dist/frontend/lowering/lower-island.js +71 -10
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.d.ts +1 -1
- package/dist/frontend/lowering/lower-mixins.js +1 -1
- package/dist/frontend/lowering/lower-modules.js +95 -4
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +866 -56
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +28 -2
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +85 -17
- package/dist/frontend/lowering/lowerer.js +459 -37
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +20 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.d.ts +7 -2
- package/dist/frontend/npm-static-rewrite.js +566 -138
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +11 -2
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/program.js +5 -5
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +6 -1
- package/dist/frontend/shared.js +46 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +304 -10
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +181 -26
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +159 -9
- package/dist/ir/nodes.js +76 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +93 -14
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +17 -3
- package/dist/library/profile.js +38 -9
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.js +162 -43
- package/dist/library/sidecar.js.map +1 -1
- package/dist/library/trap-teaching.d.ts +11 -0
- package/dist/library/trap-teaching.js +49 -0
- package/dist/library/trap-teaching.js.map +1 -0
- package/package.json +2 -2
- package/surface-manifest.json +46 -4
|
@@ -13,6 +13,7 @@ import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageD
|
|
|
13
13
|
import { PoisonError, dynUndefinedExpr, jsFuncNameOf, neverTaintedJsType, own } from "./lowerer.js";
|
|
14
14
|
import { lowerIndexMergeHelper, lowerNpmStaticSafeIndexRead, strCharsCall } from "./lower-containers.js";
|
|
15
15
|
import { npmStaticPackageOfPath } from "../npm-static.js";
|
|
16
|
+
import { unsupportedModuleFeatureOf } from "../shared.js";
|
|
16
17
|
import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
|
|
17
18
|
import { ambientNsRootOf, ambientUndefReadType, ambientUndefVarRootOf, ambientUndefinedFnSymbolOf, contextualUndefReadType, fenceEarlyAliasUse, fenceEarlyNsMemberRef, lowerNsIdentifierValue, nsMemberIdentOf, nsUndefRead, nsWritableTarget } from "./lower-namespaces.js";
|
|
18
19
|
import { expandoMemberRead, expandoWritableTarget } from "./lower-expando.js";
|
|
@@ -203,6 +204,45 @@ function lowerExprInner(L, expr) {
|
|
|
203
204
|
if (ts.isArrayLiteralExpression(expr) && expr.elements.length === 0 && underConstAssertion(expr)) {
|
|
204
205
|
return false;
|
|
205
206
|
}
|
|
207
|
+
// A JS variable INITIALIZER whose BINDING registered as a
|
|
208
|
+
// checked-dynamic module global (the typedef-annotated
|
|
209
|
+
// doc-builder consts whose JSDoc types degraded — DYN slots):
|
|
210
|
+
// the island build could never land (no engine→DOM crossing
|
|
211
|
+
// exists), so the literal takes its own world's path instead —
|
|
212
|
+
// the DOM literal, or a static record the slot converts.
|
|
213
|
+
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
214
|
+
ts.isVariableDeclaration(expr.parent) &&
|
|
215
|
+
expr.parent.initializer === expr &&
|
|
216
|
+
ts.isIdentifier(expr.parent.name)) {
|
|
217
|
+
const bindSym = L.checker.getSymbolAtLocation(expr.parent.name);
|
|
218
|
+
const g = bindSym ? L.globalsBySymbol.get(bindSym) : undefined;
|
|
219
|
+
if (g?.type.kind === "dyn")
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
// A PROJECT-DECLARED contextual type that only ABSORBED to the
|
|
223
|
+
// island (a doc-builder typedef whose field's JSDoc type
|
|
224
|
+
// reference degraded to checker-`any` — the tsgo multi-file
|
|
225
|
+
// value-as-type residue) is not an npm slot: when the literal's
|
|
226
|
+
// OWN type maps statically, it builds that way — the binding's
|
|
227
|
+
// checked-dynamic slot takes the DOM conversion. Genuinely
|
|
228
|
+
// island slots (npm .d.ts provenance, plain `any`) keep the
|
|
229
|
+
// native build.
|
|
230
|
+
if (ctxTs !== undefined && (ctxTs.flags & ts.TypeFlags.Any) === 0) {
|
|
231
|
+
// Intersection/tuple typedefs (`Line & {readonly soft: true}`)
|
|
232
|
+
// carry provenance on the ALIAS symbol.
|
|
233
|
+
const sym = ctxTs.getSymbol() ?? ctxTs.getAliasSymbol();
|
|
234
|
+
const decls = sym ? L.checker.declarationsOf(sym) : [];
|
|
235
|
+
const projectDeclared = decls.length > 0 &&
|
|
236
|
+
decls.every((d) => {
|
|
237
|
+
const sf = d.getSourceFile();
|
|
238
|
+
return !sf.isDeclarationFile && !sf.fileName.includes("/node_modules/");
|
|
239
|
+
});
|
|
240
|
+
if (projectDeclared) {
|
|
241
|
+
const own = L.mapTypeOf(L.typeOf(expr));
|
|
242
|
+
if (own?.kind === "record" || own?.kind === "array")
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
206
246
|
return true;
|
|
207
247
|
})()) {
|
|
208
248
|
if (ts.isObjectLiteralExpression(expr)) {
|
|
@@ -217,21 +257,73 @@ function lowerExprInner(L, expr) {
|
|
|
217
257
|
// (`"k" in o` is false), which the static record path can't say.
|
|
218
258
|
const argsWithout = [];
|
|
219
259
|
const argsWith = [];
|
|
260
|
+
const getters = [];
|
|
220
261
|
let spread = null;
|
|
262
|
+
// A MEMBER a JS file cannot lower or marshal (a generic function
|
|
263
|
+
// as a value, a signature with checked-dynamic parameters — the
|
|
264
|
+
// doc-builder public aggregate's degraded pieces): defer like a
|
|
265
|
+
// statement fence — the slot holds a host closure that THROWS the
|
|
266
|
+
// captured diagnostic when invoked, so building the aggregate
|
|
267
|
+
// compiles and only a CALL through the island stops the run.
|
|
268
|
+
const islandMemberFence = (diagsBefore, err, valueNode) => {
|
|
269
|
+
if (!(err instanceof PoisonError) ||
|
|
270
|
+
!isJsSourceFile(expr.getSourceFile()) ||
|
|
271
|
+
L.diagSink !== null ||
|
|
272
|
+
L.diags.length <= diagsBefore ||
|
|
273
|
+
L.diags.slice(diagsBefore).some((d) => d.code === "SC9001")) {
|
|
274
|
+
throw err;
|
|
275
|
+
}
|
|
276
|
+
const captured = L.diags.splice(diagsBefore);
|
|
277
|
+
L.runtimeFences.push(...captured);
|
|
278
|
+
const first = captured[0];
|
|
279
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? expr.getSourceFile(), first.loc.start);
|
|
280
|
+
const fnName = `%fn${L.lambdaCounter++}_islfence`;
|
|
281
|
+
L.liftedFns.push({
|
|
282
|
+
name: fnName,
|
|
283
|
+
params: [],
|
|
284
|
+
returnType: VOID,
|
|
285
|
+
locals: [],
|
|
286
|
+
captures: [],
|
|
287
|
+
body: [
|
|
288
|
+
{
|
|
289
|
+
kind: "runtimeFence",
|
|
290
|
+
code: first.code,
|
|
291
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
292
|
+
loc: locOf(valueNode),
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
loc: locOf(valueNode),
|
|
296
|
+
});
|
|
297
|
+
const fence = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(valueNode) };
|
|
298
|
+
return L.jsvalIn(fence, valueNode);
|
|
299
|
+
};
|
|
221
300
|
const pushProp = (name, value, valueNode, into) => {
|
|
301
|
+
const diagsBefore = L.diags.length;
|
|
302
|
+
let marshaled;
|
|
303
|
+
try {
|
|
304
|
+
marshaled = L.jsvalIn(value, valueNode);
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
marshaled = islandMemberFence(diagsBefore, err, valueNode);
|
|
308
|
+
}
|
|
222
309
|
for (const args of into) {
|
|
223
310
|
args.push({
|
|
224
311
|
kind: "jsMarshal",
|
|
225
312
|
value: { kind: "strLit", value: name.text, type: STRING, loc: locOf(name) },
|
|
226
313
|
type: JSVAL, loc: locOf(name),
|
|
227
314
|
});
|
|
228
|
-
args.push(
|
|
315
|
+
args.push(marshaled);
|
|
229
316
|
}
|
|
230
317
|
};
|
|
318
|
+
const spreadSrcs = [];
|
|
319
|
+
let sawPlainProp = false;
|
|
231
320
|
for (const prop of expr.properties) {
|
|
232
321
|
if (ts.isSpreadAssignment(prop)) {
|
|
233
322
|
const cs = conditionalSpreadOf(prop.expression);
|
|
234
323
|
if (cs && cs !== "unsupported" && !spread) {
|
|
324
|
+
if (spreadSrcs.length > 0) {
|
|
325
|
+
L.unsupported("SC1090", prop, "a conditional spread mixed with plain spreads in an 'any'-typed object literal");
|
|
326
|
+
}
|
|
235
327
|
const cond = L.lowerCondition(cs.cond);
|
|
236
328
|
if (!pureCondExpr(cond)) {
|
|
237
329
|
L.unsupported("SC1090", prop, "conditional spreads with effectful conditions in an 'any'-typed object literal (bind the condition to a const first)");
|
|
@@ -243,8 +335,23 @@ function lowerExprInner(L, expr) {
|
|
|
243
335
|
}
|
|
244
336
|
continue;
|
|
245
337
|
}
|
|
338
|
+
// A PLAIN spread (`{ ...options, plugins }` — the withPlugins
|
|
339
|
+
// argument-rebuild shape): the source copies into the fresh
|
|
340
|
+
// engine object through the spec's CopyDataProperties (the
|
|
341
|
+
// objSpread op). Spreads must precede explicit properties
|
|
342
|
+
// (the composition applies explicit keys AFTER the copies —
|
|
343
|
+
// JS's later-wins — which a spread after them would invert);
|
|
344
|
+
// mixing with a conditional spread keeps the fence.
|
|
345
|
+
if (cs === undefined || cs === null) {
|
|
346
|
+
if (sawPlainProp || spread) {
|
|
347
|
+
L.unsupported("SC1090", prop, "object spread after explicit properties (or mixed with a conditional spread) in an 'any'-typed object literal — spreads must come first");
|
|
348
|
+
}
|
|
349
|
+
spreadSrcs.push(L.jsvalIn(L.lowerExpr(prop.expression), prop.expression));
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
246
352
|
L.unsupported("SC1090", prop, "this spread form in an 'any'-typed object literal (one `...(c ? { k: v } : {})` conditional spread is supported — bind other shapes to a const and set keys explicitly)");
|
|
247
353
|
}
|
|
354
|
+
sawPlainProp = true;
|
|
248
355
|
// `name: value`, the shorthand `{ name }` (the value is the
|
|
249
356
|
// identifier itself, resolved through the shorthand VALUE symbol
|
|
250
357
|
// like the static record path), and METHODS — `{ load() {...} }`
|
|
@@ -254,22 +361,79 @@ function lowerExprInner(L, expr) {
|
|
|
254
361
|
// string-literal keys — engine property names have no
|
|
255
362
|
// identifier restriction ("content-type").
|
|
256
363
|
const name = prop.name;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
364
|
+
// A GET accessor (`get root() { return ROOT_INDENT; }` — the
|
|
365
|
+
// doc-printer's self-referential root-indent shape): the body
|
|
366
|
+
// lowers as a zero-param closure marshaled into the engine, and
|
|
367
|
+
// the property defines through the engine's own getter
|
|
368
|
+
// machinery AFTER the literal builds (the defineGetter op) —
|
|
369
|
+
// reads through the handle invoke it natively. `this` in the
|
|
370
|
+
// body keeps the object-method rejection; setters stay fenced.
|
|
371
|
+
if (ts.isGetAccessorDeclaration(prop) && prop.body && name && (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
|
|
372
|
+
L.rejectThisInObjectMethod(prop.body);
|
|
373
|
+
getters.push({ name: name.text, fn: L.jsvalIn(L.lowerLambda(prop), prop), loc: locOf(prop) });
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
// A member VALUE that cannot lower at all (a generic function
|
|
377
|
+
// referenced as a value — the aggregate's `align`): the same
|
|
378
|
+
// call-time fence deferral pushProp applies to marshal failures.
|
|
379
|
+
let value = null;
|
|
380
|
+
const valueDiagsBefore = L.diags.length;
|
|
381
|
+
try {
|
|
382
|
+
value = ts.isPropertyAssignment(prop)
|
|
383
|
+
? L.lowerExpr(prop.initializer)
|
|
384
|
+
: ts.isShorthandPropertyAssignment(prop)
|
|
385
|
+
? L.lowerShorthandValue(prop)
|
|
386
|
+
: ts.isMethodDeclaration(prop) && prop.body
|
|
387
|
+
? (L.rejectThisInObjectMethod(prop.body), L.lowerLambda(prop))
|
|
388
|
+
: null;
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
value = islandMemberFence(valueDiagsBefore, err, prop);
|
|
392
|
+
}
|
|
264
393
|
if (value && name && (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
|
|
265
394
|
pushProp(name, value, prop, [argsWithout, argsWith]);
|
|
266
395
|
}
|
|
267
396
|
else {
|
|
268
|
-
L.unsupported("SC1090", prop, "this property form in an 'any'-typed object literal (only `name: value
|
|
397
|
+
L.unsupported("SC1090", prop, "this property form in an 'any'-typed object literal (only `name: value`, shorthand names, methods, and get accessors are supported)");
|
|
269
398
|
}
|
|
270
399
|
}
|
|
271
|
-
|
|
272
|
-
|
|
400
|
+
const withGetters = (obj) => getters.reduce((acc, g) => ({
|
|
401
|
+
kind: "jsOp",
|
|
402
|
+
op: "defineGetter",
|
|
403
|
+
args: [
|
|
404
|
+
acc,
|
|
405
|
+
{ kind: "jsMarshal", value: { kind: "strLit", value: g.name, type: STRING, loc: g.loc }, type: JSVAL, loc: g.loc },
|
|
406
|
+
g.fn,
|
|
407
|
+
],
|
|
408
|
+
type: JSVAL,
|
|
409
|
+
loc: g.loc,
|
|
410
|
+
}), obj);
|
|
411
|
+
if (!spread) {
|
|
412
|
+
if (spreadSrcs.length === 0) {
|
|
413
|
+
return withGetters({ kind: "jsOp", op: "objLit", args: argsWithout, type: JSVAL, loc });
|
|
414
|
+
}
|
|
415
|
+
// Plain spreads compose left to right onto a fresh object, the
|
|
416
|
+
// explicit properties merging LAST (JS's later-wins): spread
|
|
417
|
+
// sources evaluate before later property values by nesting
|
|
418
|
+
// order, exactly the source order.
|
|
419
|
+
let acc = { kind: "jsOp", op: "objLit", args: [], type: JSVAL, loc };
|
|
420
|
+
for (const src of spreadSrcs) {
|
|
421
|
+
acc = { kind: "jsOp", op: "objSpread", args: [acc, src], type: JSVAL, loc };
|
|
422
|
+
}
|
|
423
|
+
if (argsWithout.length > 0) {
|
|
424
|
+
acc = {
|
|
425
|
+
kind: "jsOp",
|
|
426
|
+
op: "objSpread",
|
|
427
|
+
args: [acc, { kind: "jsOp", op: "objLit", args: argsWithout, type: JSVAL, loc }],
|
|
428
|
+
type: JSVAL,
|
|
429
|
+
loc,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
return withGetters(acc);
|
|
433
|
+
}
|
|
434
|
+
if (getters.length > 0) {
|
|
435
|
+
L.unsupported("SC1090", expr, "get accessors combined with conditional spreads in an 'any'-typed object literal");
|
|
436
|
+
}
|
|
273
437
|
const withLit = { kind: "jsOp", op: "objLit", args: argsWith, type: JSVAL, loc };
|
|
274
438
|
const withoutLit = { kind: "jsOp", op: "objLit", args: argsWithout, type: JSVAL, loc };
|
|
275
439
|
return {
|
|
@@ -529,7 +693,7 @@ function lowerExprInner(L, expr) {
|
|
|
529
693
|
{
|
|
530
694
|
const spec = L.fencedBuiltinImportOf(expr);
|
|
531
695
|
if (spec !== null) {
|
|
532
|
-
L.pushDiag(unsupportedDiag("SC1010", loc,
|
|
696
|
+
L.pushDiag(unsupportedDiag("SC1010", loc, unsupportedModuleFeatureOf(spec)));
|
|
533
697
|
throw new PoisonError();
|
|
534
698
|
}
|
|
535
699
|
}
|
|
@@ -585,8 +749,9 @@ function lowerExprInner(L, expr) {
|
|
|
585
749
|
BigInt: "arbitrary-precision integers have no static representation — f64 is the one number type (Number.MAX_SAFE_INTEGER bounds exact integers)",
|
|
586
750
|
Proxy: "property-access metaprogramming has no static lowering (every property read must resolve at compile time)",
|
|
587
751
|
Reflect: "reflective property access has no static lowering — read and call members directly",
|
|
588
|
-
Intl: "locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data)",
|
|
752
|
+
Intl: "locale- and ICU-backed behavior lives outside the static runtime (the localeCompare stance: code-unit order, no collation/locale data) — what lowers: the composed new Intl.NumberFormat(\"en-US\").format(x) and x.toLocaleString(\"en-US\") with default options",
|
|
589
753
|
SharedArrayBuffer: "no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
|
|
754
|
+
ArrayBuffer: "no free-standing ArrayBuffer value exists — typed arrays own their storage (new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)",
|
|
590
755
|
WeakRef: "deref()-after-collect exposes GC timing — genuinely dynamic; hold a strong reference instead",
|
|
591
756
|
FinalizationRegistry: "finalization callbacks expose GC timing — genuinely dynamic; release resources explicitly instead",
|
|
592
757
|
eval: "runtime code evaluation cannot be compiled ahead of time",
|
|
@@ -1538,7 +1703,7 @@ export function lowerTernary(L, expr, expected) {
|
|
|
1538
1703
|
// The ternary's IR type is normally the checker's own: it collapses
|
|
1539
1704
|
// same-kind literal unions ("a" | "b" → string) and forms tagged
|
|
1540
1705
|
// unions for mixed arms that map (`c ? okRec : errRec`); anything
|
|
1541
|
-
// left unmappable gets
|
|
1706
|
+
// left unmappable gets the type fence (badType) here. A record/union CONTEXTUAL type
|
|
1542
1707
|
// takes over exactly when the own type can't carry the value —
|
|
1543
1708
|
// unmappable, or a DIFFERENT union (branch literals omitting
|
|
1544
1709
|
// DIFFERENT optional subsets make the own type a union of the
|
|
@@ -1992,7 +2157,11 @@ export function lowerNullishCoalesce(L, expr, loc) {
|
|
|
1992
2157
|
L.unsupported("SC1100", expr, "nullish coalescing on 'unknown' values");
|
|
1993
2158
|
}
|
|
1994
2159
|
if (left.type.kind === "jsval") {
|
|
1995
|
-
|
|
2160
|
+
// `a ?? b` on an ISLAND value: the engine's own nullish test — the
|
|
2161
|
+
// left evaluates once, the right runs lazily in its branch and
|
|
2162
|
+
// marshals in (the emitter's jsval nullish arm).
|
|
2163
|
+
const right = L.jsvalIn(L.lowerExpr(expr.right), expr.right);
|
|
2164
|
+
return { kind: "nullish", left, right, type: JSVAL, loc };
|
|
1996
2165
|
}
|
|
1997
2166
|
if (left.type.kind !== "union")
|
|
1998
2167
|
return left;
|
|
@@ -2833,7 +3002,7 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2833
3002
|
/** `[a, b, c]`. The element type comes from the contextual type when tsc
|
|
2834
3003
|
* has one (`const a: number[] = []`, arguments, nested literals) and from
|
|
2835
3004
|
* the literal's own inferred type otherwise. A bare `[]` with no context
|
|
2836
|
-
* is `never[]` — unmappable, rejected with
|
|
3005
|
+
* is `never[]` — unmappable, rejected with the component fence (SC2009). `expected` overrides
|
|
2837
3006
|
* the contextual lookup where the caller knows the slot's array type and
|
|
2838
3007
|
* tsc's API doesn't surface it (ternary arms under an array context —
|
|
2839
3008
|
* tsc accepts the arm covariantly, but a tagged element representation
|
|
@@ -2887,6 +3056,36 @@ export function lowerArrayLiteral(L, expr, expected) {
|
|
|
2887
3056
|
if (arrayArms.length === 1)
|
|
2888
3057
|
mapped = arrayArms[0];
|
|
2889
3058
|
}
|
|
3059
|
+
// A NON-EMPTY literal whose own type has no static home (the JS dyn
|
|
3060
|
+
// fallback — a null/dyn mapping, or an inference that degraded to a
|
|
3061
|
+
// unit-only-element array over non-unit elements) under a union
|
|
3062
|
+
// with exactly ONE array-family arm — an array, or an
|
|
3063
|
+
// arity-matching tuple (the option-table `default: [{ value: [] }]`
|
|
3064
|
+
// shape): build AS that arm, exactly the empty-literal rule above;
|
|
3065
|
+
// the slot's coercion wraps it.
|
|
3066
|
+
if (ctxUnion && expr.elements.length > 0) {
|
|
3067
|
+
// A unit-only-element array (`(null | undefined)[]`) cannot hold
|
|
3068
|
+
// non-unit elements — neither as the literal's own mapping nor as
|
|
3069
|
+
// a competing union arm.
|
|
3070
|
+
const nonUnitElems = expr.elements.some((el) => !ts.isOmittedExpression(el) &&
|
|
3071
|
+
el.kind !== ts.SyntaxKind.NullKeyword &&
|
|
3072
|
+
!(ts.isIdentifier(el) && el.text === "undefined"));
|
|
3073
|
+
const ownUnhelpful = mapped === null || mapped.kind === "dyn" ||
|
|
3074
|
+
// The checker echoing the context union back as the literal's
|
|
3075
|
+
// own type decides nothing either — nor does an island ('any')
|
|
3076
|
+
// residue under a STATIC union slot.
|
|
3077
|
+
mapped.kind === "union" || mapped.kind === "jsval" ||
|
|
3078
|
+
(mapped.kind === "array" && nonUnitElems && L.unitOnlyElem(mapped.elem));
|
|
3079
|
+
if (ownUnhelpful) {
|
|
3080
|
+
const def = L.unions.get(ctxUnion.unionId);
|
|
3081
|
+
const arms = (def?.arms ?? []).filter((a) => (a.kind === "array" && !(nonUnitElems && L.unitOnlyElem(a.elem))) ||
|
|
3082
|
+
(a.kind === "record" &&
|
|
3083
|
+
!!L.shapes.get(a.shapeId)?.tuple &&
|
|
3084
|
+
L.shapes.get(a.shapeId).fields.length === expr.elements.length));
|
|
3085
|
+
if (arms.length === 1)
|
|
3086
|
+
mapped = arms[0];
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
2890
3089
|
}
|
|
2891
3090
|
// An EMPTY literal under a CONST ASSERTION whose type queries panicked
|
|
2892
3091
|
// (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
|
|
@@ -3245,7 +3444,7 @@ function propNameText(L, name) {
|
|
|
3245
3444
|
* (dynFrom's domain, functions box); a value with no dyn representation
|
|
3246
3445
|
* fences per property. Spreads, accessors, and methods stay fenced —
|
|
3247
3446
|
* none co-occur with the computed-key idiom in the wild. */
|
|
3248
|
-
function lowerDynObjectLiteral(L, expr) {
|
|
3447
|
+
export function lowerDynObjectLiteral(L, expr) {
|
|
3249
3448
|
const loc = locOf(expr);
|
|
3250
3449
|
const fields = [];
|
|
3251
3450
|
for (const prop of expr.properties) {
|
|
@@ -3283,11 +3482,92 @@ function lowerDynObjectLiteral(L, expr) {
|
|
|
3283
3482
|
// Lambda values that fail to lower become trap closures (the
|
|
3284
3483
|
// per-call fence granularity — fenceClosureProbe) so the object
|
|
3285
3484
|
// still builds; everything else keeps the per-property fence below.
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3485
|
+
let raw;
|
|
3486
|
+
const propDiagsBefore = L.diags.length;
|
|
3487
|
+
try {
|
|
3488
|
+
raw =
|
|
3489
|
+
fenceClosureProbe(L, valueExpr, undefined, () => L.lowerExpr(valueExpr)) ??
|
|
3490
|
+
L.lowerExpr(valueExpr);
|
|
3491
|
+
}
|
|
3492
|
+
catch (err) {
|
|
3493
|
+
// A PURE member read a JS file cannot lower (a namespace object
|
|
3494
|
+
// in an export aggregate): the slot takes a boxed fence closure —
|
|
3495
|
+
// the diagnostics defer to the runtime-fence ledger, the object
|
|
3496
|
+
// builds, and only USING the member stops the run.
|
|
3497
|
+
const pureMember = ts.isIdentifier(valueExpr);
|
|
3498
|
+
if (!(err instanceof PoisonError) ||
|
|
3499
|
+
!pureMember ||
|
|
3500
|
+
L.diagSink !== null ||
|
|
3501
|
+
L.diags.length <= propDiagsBefore ||
|
|
3502
|
+
L.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
3503
|
+
throw err;
|
|
3504
|
+
}
|
|
3505
|
+
const captured = L.diags.splice(propDiagsBefore);
|
|
3506
|
+
L.runtimeFences.push(...captured);
|
|
3507
|
+
const first = captured[0];
|
|
3508
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? expr.getSourceFile(), first.loc.start);
|
|
3509
|
+
const fnName = `%fn${L.lambdaCounter++}_dynfence`;
|
|
3510
|
+
L.liftedFns.push({
|
|
3511
|
+
name: fnName,
|
|
3512
|
+
params: [],
|
|
3513
|
+
returnType: VOID,
|
|
3514
|
+
locals: [],
|
|
3515
|
+
captures: [],
|
|
3516
|
+
body: [
|
|
3517
|
+
{
|
|
3518
|
+
kind: "runtimeFence",
|
|
3519
|
+
code: first.code,
|
|
3520
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
3521
|
+
loc: locOf(prop),
|
|
3522
|
+
},
|
|
3523
|
+
],
|
|
3524
|
+
loc: locOf(prop),
|
|
3525
|
+
});
|
|
3526
|
+
raw = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(prop) };
|
|
3527
|
+
}
|
|
3528
|
+
let v = L.coerceToExpected(raw, DYN);
|
|
3289
3529
|
if (v.type.kind !== "dyn") {
|
|
3290
|
-
|
|
3530
|
+
const convDiagsBefore = L.diags.length;
|
|
3531
|
+
try {
|
|
3532
|
+
L.unsupported("SC1101", valueExpr, `holding '${L.fmt(v.type)}' values in a runtime-keyed (computed-key) object literal`);
|
|
3533
|
+
}
|
|
3534
|
+
catch (err) {
|
|
3535
|
+
// A member value the DOM cannot hold — a func whose signature
|
|
3536
|
+
// cannot box, an island ('any') handle, a record carrying func
|
|
3537
|
+
// fields (the export aggregate's typed utilities and npm-handle
|
|
3538
|
+
// members): the same call-time fence deferral as an unlowerable
|
|
3539
|
+
// member — the slot takes a boxed fence closure; only USING it
|
|
3540
|
+
// stops the run. Probe mode and ICEs keep the poison.
|
|
3541
|
+
if (!(err instanceof PoisonError) ||
|
|
3542
|
+
L.diagSink !== null ||
|
|
3543
|
+
L.diags.length <= convDiagsBefore ||
|
|
3544
|
+
L.diags.slice(convDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
3545
|
+
throw err;
|
|
3546
|
+
}
|
|
3547
|
+
const captured = L.diags.splice(convDiagsBefore);
|
|
3548
|
+
L.runtimeFences.push(...captured);
|
|
3549
|
+
const first = captured[0];
|
|
3550
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? expr.getSourceFile(), first.loc.start);
|
|
3551
|
+
const fnName = `%fn${L.lambdaCounter++}_dynfence`;
|
|
3552
|
+
L.liftedFns.push({
|
|
3553
|
+
name: fnName,
|
|
3554
|
+
params: [],
|
|
3555
|
+
returnType: VOID,
|
|
3556
|
+
locals: [],
|
|
3557
|
+
captures: [],
|
|
3558
|
+
body: [
|
|
3559
|
+
{
|
|
3560
|
+
kind: "runtimeFence",
|
|
3561
|
+
code: first.code,
|
|
3562
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
3563
|
+
loc: locOf(prop),
|
|
3564
|
+
},
|
|
3565
|
+
],
|
|
3566
|
+
loc: locOf(prop),
|
|
3567
|
+
});
|
|
3568
|
+
const fence = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(prop) };
|
|
3569
|
+
v = L.coerceToExpected(fence, DYN);
|
|
3570
|
+
}
|
|
3291
3571
|
}
|
|
3292
3572
|
fields.push({ key, value: v });
|
|
3293
3573
|
}
|
|
@@ -3480,7 +3760,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3480
3760
|
// Syntax fence FIRST: unsupported member forms get their specific
|
|
3481
3761
|
// message even when they also make the literal's type unmappable
|
|
3482
3762
|
// (a getter or a `this`-returning method would otherwise surface as an
|
|
3483
|
-
// opaque
|
|
3763
|
+
// opaque type fence on the whole literal).
|
|
3484
3764
|
for (const prop of expr.properties) {
|
|
3485
3765
|
if (ts.isSpreadAssignment(prop)) {
|
|
3486
3766
|
// Supported shapes: FULL spreads of known record shapes read as
|
|
@@ -3625,7 +3905,11 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3625
3905
|
// the slot's width coercion constructs through the trivial
|
|
3626
3906
|
// parameter-property constructor (recordToClassPlan), or fences with
|
|
3627
3907
|
// the record-shape story.
|
|
3628
|
-
|
|
3908
|
+
// A jsval-mapped context reaches here only when lowerExpr's island
|
|
3909
|
+
// gate DECLINED it (a project-declared typedef that absorbed to the
|
|
3910
|
+
// island through checker-`any` field residue): the literal builds at
|
|
3911
|
+
// its own type like every unmappable context.
|
|
3912
|
+
if (mapped === null || mapped.kind === "union" || mapped.kind === "dyn" || mapped.kind === "object" || mapped.kind === "jsval") {
|
|
3629
3913
|
const ctxUnion = mapped?.kind === "union" ? mapped : null;
|
|
3630
3914
|
mapped = L.mapTypeOf(L.typeOf(expr)) ?? mapped;
|
|
3631
3915
|
// A literal whose own shape re-tags into NO arm of the contextual
|
|
@@ -3876,7 +4160,9 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3876
4160
|
}
|
|
3877
4161
|
const shapeMismatch = (node) => {
|
|
3878
4162
|
const own = L.mapTypeOf(L.typeOf(expr));
|
|
3879
|
-
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), own ? L.fmt(own) : L.checker.typeToString(L.typeOf(expr)), locOf(node)
|
|
4163
|
+
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), own ? L.fmt(own) : L.checker.typeToString(L.typeOf(expr)), locOf(node), own?.kind === "record"
|
|
4164
|
+
? (L.describeRecordWidthBlocker(own.shapeId, type.shapeId) ?? undefined)
|
|
4165
|
+
: undefined));
|
|
3880
4166
|
throw new PoisonError();
|
|
3881
4167
|
};
|
|
3882
4168
|
const fields = [];
|
|
@@ -3885,6 +4171,9 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3885
4171
|
// contributor overriding one would silently drop that evaluation —
|
|
3886
4172
|
// collisions fence in both directions.
|
|
3887
4173
|
const conditionalNames = new Set();
|
|
4174
|
+
// Member names DROPPED from the shape (JS deferral — unlowerable pure
|
|
4175
|
+
// member reads narrow away; see the catch in the property loop).
|
|
4176
|
+
const droppedNames = new Set();
|
|
3888
4177
|
for (const prop of expr.properties) {
|
|
3889
4178
|
if (ts.isSpreadAssignment(prop)) {
|
|
3890
4179
|
const cs = conditionalSpreadOf(prop.expression);
|
|
@@ -4055,7 +4344,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4055
4344
|
if (targetType.kind === "union") {
|
|
4056
4345
|
const retag = L.unionRetagHelper(f.type.unionId, targetType.unionId, locOf(prop));
|
|
4057
4346
|
if (!retag) {
|
|
4058
|
-
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(recArm), locOf(prop)));
|
|
4347
|
+
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(recArm), locOf(prop), `spread field '${f.name}': '${L.fmt(f.type)}' cannot re-tag into '${L.fmt(targetType)}' behind the present-test`));
|
|
4059
4348
|
throw new PoisonError();
|
|
4060
4349
|
}
|
|
4061
4350
|
thenVal = { kind: "call", callee: retag, args: [fRead()], type: targetType, loc: locOf(prop) };
|
|
@@ -4064,7 +4353,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4064
4353
|
thenVal = { kind: "unionNarrow", unionId: f.type.unionId, tag: L.armTag(f.type.unionId, targetType), value: fRead(), type: targetType, loc: locOf(prop) };
|
|
4065
4354
|
}
|
|
4066
4355
|
else {
|
|
4067
|
-
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(recArm), locOf(prop)));
|
|
4356
|
+
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(recArm), locOf(prop), `spread field '${f.name}': '${L.fmt(f.type)}' cannot narrow into '${L.fmt(targetType)}' behind the present-test`));
|
|
4068
4357
|
throw new PoisonError();
|
|
4069
4358
|
}
|
|
4070
4359
|
}
|
|
@@ -4076,7 +4365,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4076
4365
|
// undefined trap must not shadow.
|
|
4077
4366
|
const lift = L.widthLiftPlan(f.type, targetType);
|
|
4078
4367
|
if (!lift) {
|
|
4079
|
-
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(recArm), locOf(prop)));
|
|
4368
|
+
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(recArm), locOf(prop), `spread field '${f.name}': '${L.fmt(f.type)}' does not lift into '${L.fmt(targetType)}'`));
|
|
4080
4369
|
throw new PoisonError();
|
|
4081
4370
|
}
|
|
4082
4371
|
thenVal = L.applyWidthLift(lift, fRead(), targetType, locOf(prop));
|
|
@@ -4156,7 +4445,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4156
4445
|
// it can render identically to the target while the spread
|
|
4157
4446
|
// source (the thing that actually mismatches) differs — an
|
|
4158
4447
|
// invisible difference is a diagnostics bug.
|
|
4159
|
-
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(srcType), locOf(prop)));
|
|
4448
|
+
L.pushDiag(recordShapeMismatchDiag(L.fmt(type), L.fmt(srcType), locOf(prop), `spread field '${f.name}': '${L.fmt(f.type)}' does not lift into '${L.fmt(targetType)}'`));
|
|
4160
4449
|
throw new PoisonError();
|
|
4161
4450
|
}
|
|
4162
4451
|
const obj = srcLowered ?? L.lowerExpr(srcNode);
|
|
@@ -4303,21 +4592,56 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4303
4592
|
}
|
|
4304
4593
|
let value;
|
|
4305
4594
|
let valueNode = prop;
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4595
|
+
const propDiagsBefore = L.diags.length;
|
|
4596
|
+
try {
|
|
4597
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
4598
|
+
valueNode = prop.initializer;
|
|
4599
|
+
// ARRAY-LITERAL initializers route through the expected-type
|
|
4600
|
+
// lowering: a union field with one array-family arm builds the
|
|
4601
|
+
// literal AS that arm (lowerExprExpecting's IR-directed rule —
|
|
4602
|
+
// the option-table `default: [{ value: [] }]` shape), where the
|
|
4603
|
+
// bare lowering would take the JS DOM fallback and fence.
|
|
4604
|
+
let init = prop.initializer;
|
|
4605
|
+
while (ts.isParenthesizedExpression(init))
|
|
4606
|
+
init = init.expression;
|
|
4607
|
+
value =
|
|
4608
|
+
fenceClosureProbe(L, prop.initializer, fieldType, () => L.lowerExpr(prop.initializer)) ??
|
|
4609
|
+
(fieldType !== undefined && ts.isArrayLiteralExpression(init)
|
|
4610
|
+
? L.lowerExprExpecting(prop.initializer, fieldType)
|
|
4611
|
+
: L.lowerExpr(prop.initializer));
|
|
4612
|
+
}
|
|
4613
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4614
|
+
value = L.lowerShorthandValue(prop);
|
|
4615
|
+
}
|
|
4616
|
+
else if (ts.isMethodDeclaration(prop)) {
|
|
4617
|
+
value =
|
|
4618
|
+
fenceClosureProbe(L, prop, fieldType, () => L.lowerLambda(prop)) ?? L.lowerLambda(prop);
|
|
4619
|
+
}
|
|
4620
|
+
else {
|
|
4621
|
+
L.unsupported("SC1090", prop, `syntax '${ts.SyntaxKind[prop.kind]}'`);
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
catch (err) {
|
|
4625
|
+
// A member VALUE a JS file cannot lower (a namespace object in an
|
|
4626
|
+
// export aggregate — the sharedWithCli `errors` member): the
|
|
4627
|
+
// member NARROWS AWAY like a CJS export-table accessor entry —
|
|
4628
|
+
// the diagnostics defer to the runtime-fence ledger, the shape
|
|
4629
|
+
// drops the field, and each READ of it meets its own per-site
|
|
4630
|
+
// fence. Pure member forms only (identifier/shorthand reads);
|
|
4631
|
+
// TypeScript, probe mode, and ICEs keep the poison.
|
|
4632
|
+
const pureMember = ts.isShorthandPropertyAssignment(prop) ||
|
|
4633
|
+
(ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.initializer));
|
|
4634
|
+
if (!(err instanceof PoisonError) ||
|
|
4635
|
+
!pureMember ||
|
|
4636
|
+
!isJsSourceFile(expr.getSourceFile()) ||
|
|
4637
|
+
L.diagSink !== null ||
|
|
4638
|
+
L.diags.length <= propDiagsBefore ||
|
|
4639
|
+
L.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
4640
|
+
throw err;
|
|
4641
|
+
}
|
|
4642
|
+
L.runtimeFences.push(...L.diags.splice(propDiagsBefore));
|
|
4643
|
+
droppedNames.add(name);
|
|
4644
|
+
continue;
|
|
4321
4645
|
}
|
|
4322
4646
|
if (!fieldType) {
|
|
4323
4647
|
// Overflow entry: the value flows into the index signature's value
|
|
@@ -4352,6 +4676,12 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4352
4676
|
// omission of required fields before lowering, undefined-armed or not).
|
|
4353
4677
|
// A REQUIRED missing field keeps the shape-mismatch rejection (possible
|
|
4354
4678
|
// through `as`: the cast smuggles a narrower literal past freshness).
|
|
4679
|
+
if (droppedNames.size > 0) {
|
|
4680
|
+
const narrowed = shape.fields.filter((f) => !droppedNames.has(f.name));
|
|
4681
|
+
const narrowedId = L.shapes.intern(narrowed.map((f) => ({ name: f.name, type: f.type })), false, shape.indexValue);
|
|
4682
|
+
type = { kind: "record", shapeId: narrowedId };
|
|
4683
|
+
shape = L.shapes.get(narrowedId);
|
|
4684
|
+
}
|
|
4355
4685
|
if (fields.filter((f) => !f.overflow).length !== shape.fields.length) {
|
|
4356
4686
|
const provided = new Set(fields.filter((f) => !f.overflow).map((f) => f.name));
|
|
4357
4687
|
for (const f of shape.fields) {
|
|
@@ -4982,7 +5312,11 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
|
|
|
4982
5312
|
// NUMERIC literals are their canonical string spelling — JS object keys
|
|
4983
5313
|
// ARE strings (`r[1]` reads `r["1"]`), so `b[1]` hits a declared field
|
|
4984
5314
|
// named "1" (a mapped type over a numeric enum) exactly like `b["1"]`.
|
|
4985
|
-
|
|
5315
|
+
// A key IDENTIFIER whose type proves one literal (a literal-typed
|
|
5316
|
+
// const, a keyof-constrained type parameter bound to a literal inside
|
|
5317
|
+
// a generic instance) is the same static read — identifier evaluation
|
|
5318
|
+
// is pure, so skipping it matches JS exactly.
|
|
5319
|
+
const litKey = recordKeyLiteralText(keyNode) ?? recordKeyTypeLiteralText(L, keyNode);
|
|
4986
5320
|
// The receiver lowers FIRST (both branches below read it, and JS
|
|
4987
5321
|
// evaluates the receiver before the key).
|
|
4988
5322
|
const obj = L.lowerExpr(expr.expression);
|
|
@@ -5069,6 +5403,32 @@ export function recordKeyLiteralText(node) {
|
|
|
5069
5403
|
return String(Number(node.text));
|
|
5070
5404
|
return null;
|
|
5071
5405
|
}
|
|
5406
|
+
/** The compile-time key an IDENTIFIER's TYPE proves: a key variable whose
|
|
5407
|
+
* (narrowed) type is one string/number literal — `const k: "a" = …; o[k]`,
|
|
5408
|
+
* and the generic-body form where k's type is a keyof-constrained
|
|
5409
|
+
* parameter bound to a literal (`pick(o, "a")`'s instance reads `o[k]`
|
|
5410
|
+
* with K = "a", resolved through typeParamTsBindings). Identifier reads
|
|
5411
|
+
* are pure, so the static field read may skip evaluating the key exactly
|
|
5412
|
+
* like a syntactic literal. Null anywhere the type keeps more than one
|
|
5413
|
+
* key. */
|
|
5414
|
+
export function recordKeyTypeLiteralText(L, node) {
|
|
5415
|
+
if (!ts.isIdentifier(node))
|
|
5416
|
+
return null;
|
|
5417
|
+
let t = L.typeOf(node);
|
|
5418
|
+
if (t.flags & ts.TypeFlags.TypeParameter) {
|
|
5419
|
+
const bound = L.typeParamTsResolver(t);
|
|
5420
|
+
if (!bound)
|
|
5421
|
+
return null;
|
|
5422
|
+
t = bound;
|
|
5423
|
+
}
|
|
5424
|
+
if (t.isStringLiteralType())
|
|
5425
|
+
return t.value;
|
|
5426
|
+
if (t.isNumberLiteralType()) {
|
|
5427
|
+
const n = t.value;
|
|
5428
|
+
return Number.isFinite(n) && n >= 0 ? String(n) : null;
|
|
5429
|
+
}
|
|
5430
|
+
return null;
|
|
5431
|
+
}
|
|
5072
5432
|
/** Can every value a dynamic key can reach (declared fields + the
|
|
5073
5433
|
* overflow) surface as the read's result type? dyn results need
|
|
5074
5434
|
* DOM-convertible fields (JSON-safe, with the undefined arm allowed at
|
|
@@ -5232,8 +5592,13 @@ export function lowerElementWrite(L, expr) {
|
|
|
5232
5592
|
if (shape && !shape.tuple) {
|
|
5233
5593
|
// Literal keys — string literals AND numeric literals in their
|
|
5234
5594
|
// canonical string spelling (`b[1] = v` writes field/key "1", JS's
|
|
5235
|
-
// own key derivation) — name declared fields directly.
|
|
5236
|
-
|
|
5595
|
+
// own key derivation) — name declared fields directly. A key
|
|
5596
|
+
// IDENTIFIER whose TYPE proves one literal (a literal-typed const,
|
|
5597
|
+
// a keyof-constrained type parameter bound to a literal inside a
|
|
5598
|
+
// generic instance — `set(o, "a", v)`'s body writing `o[k] = v`)
|
|
5599
|
+
// is the same static field write.
|
|
5600
|
+
const litKey = recordKeyLiteralText(target.argumentExpression) ??
|
|
5601
|
+
recordKeyTypeLiteralText(L, target.argumentExpression);
|
|
5237
5602
|
if (litKey !== null) {
|
|
5238
5603
|
const field = shape.fields.find((f) => f.name === litKey);
|
|
5239
5604
|
if (field) {
|
|
@@ -5243,7 +5608,40 @@ export function lowerElementWrite(L, expr) {
|
|
|
5243
5608
|
}
|
|
5244
5609
|
}
|
|
5245
5610
|
if (!shape.indexValue) {
|
|
5246
|
-
|
|
5611
|
+
// A SIGNATURE-FREE shape whose declared fields share ONE type
|
|
5612
|
+
// writes through the same per-shape dispatch (the mockable-clock
|
|
5613
|
+
// module shape: `mocked[functionality] = implementation` over a
|
|
5614
|
+
// table of same-signature closures). A key naming no declared
|
|
5615
|
+
// field throws the catchable TypeError — JS would ADD the
|
|
5616
|
+
// property, which a monomorphic struct cannot (the SEMANTICS.md
|
|
5617
|
+
// keyed-write-miss divergence). Mixed-type and accessor-carrying
|
|
5618
|
+
// shapes keep the fence.
|
|
5619
|
+
const common = shape.fields.length > 0 &&
|
|
5620
|
+
!shapeHasAccessorSlots(shape) &&
|
|
5621
|
+
shape.fields.every((f) => typeEquals(f.type, shape.fields[0].type))
|
|
5622
|
+
? shape.fields[0].type
|
|
5623
|
+
: null;
|
|
5624
|
+
if (!common) {
|
|
5625
|
+
L.unsupported("SC1090", target, "dynamic keyed writes to records without an index signature (declared fields of ONE shared type dispatch by key — spell the field name otherwise)");
|
|
5626
|
+
}
|
|
5627
|
+
const obj = L.lowerExpr(target.expression);
|
|
5628
|
+
let key = litKey !== null
|
|
5629
|
+
? { kind: "strLit", value: litKey, type: STRING, loc: locOf(target.argumentExpression) }
|
|
5630
|
+
: L.lowerExpr(target.argumentExpression);
|
|
5631
|
+
// Runtime number/boolean/dyn keys stringify — ToPropertyKey,
|
|
5632
|
+
// exactly the index-signature path's rule.
|
|
5633
|
+
if (key.type.kind === "f64")
|
|
5634
|
+
key = L.ensureString(key, target.argumentExpression);
|
|
5635
|
+
if (key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
5636
|
+
key = { kind: "toString", operand: key, type: STRING, loc: locOf(target.argumentExpression) };
|
|
5637
|
+
}
|
|
5638
|
+
if (key.type.kind !== "string") {
|
|
5639
|
+
L.unsupported("SC1090", target.argumentExpression, "indexing records with non-string or non-number keys");
|
|
5640
|
+
}
|
|
5641
|
+
const value = L.coerceInto(expr.right, L.lowerExpr(expr.right), common);
|
|
5642
|
+
if (!typeEquals(value.type, common))
|
|
5643
|
+
L.badType(expr.right, L.typeOf(expr.right));
|
|
5644
|
+
return { kind: "recordKeySet", obj, shapeId: receiverIr.shapeId, key, value, loc: locOf(expr) };
|
|
5247
5645
|
}
|
|
5248
5646
|
// A LITERAL key naming no declared field is a pure overflow insert
|
|
5249
5647
|
// — no collision to validate. Runtime keys must be able to collide
|
|
@@ -5732,6 +6130,46 @@ export function lowerBinary(L, expr) {
|
|
|
5732
6130
|
const value = L.lowerExprExpecting(expr.right, target.type);
|
|
5733
6131
|
return { kind: "assignExpr", localId: target.id, value, type: target.type, loc };
|
|
5734
6132
|
}
|
|
6133
|
+
// `events.defaultMaxListeners = v` — the module-property write
|
|
6134
|
+
// Node validates (validateNumber(n, 'defaultMaxListeners', 0)):
|
|
6135
|
+
// the value crosses into the DOM and the runtime ladder throws
|
|
6136
|
+
// ERR_INVALID_ARG_TYPE / ERR_OUT_OF_RANGE with Node's exact slot
|
|
6137
|
+
// name; valid numbers apply. The expression's value is the RHS
|
|
6138
|
+
// (JS's assignment yield).
|
|
6139
|
+
if (op === ts.SyntaxKind.EqualsToken &&
|
|
6140
|
+
ts.isPropertyAccessExpression(expr.left) &&
|
|
6141
|
+
!expr.left.questionDotToken &&
|
|
6142
|
+
expr.left.name.text === "defaultMaxListeners") {
|
|
6143
|
+
const bi = L.builtinMemberOf(expr.left);
|
|
6144
|
+
if (bi && bi.module === "events" && bi.member === "defaultMaxListeners") {
|
|
6145
|
+
const rhs = L.lowerExpr(expr.right);
|
|
6146
|
+
if (rhs.type.kind === "dyn" || rhs.kind === "unitLit" || L.dynConvertible(rhs.type)) {
|
|
6147
|
+
const valTmp = L.declareHiddenLocal("%setVal", rhs.type);
|
|
6148
|
+
const valRef = () => ({ kind: "varRef", localId: valTmp.id, type: rhs.type, loc });
|
|
6149
|
+
const dynVal = rhs.type.kind === "dyn" ? valRef() : { kind: "dynFrom", value: valRef(), type: DYN, loc };
|
|
6150
|
+
return {
|
|
6151
|
+
kind: "seqExpr",
|
|
6152
|
+
stmts: [
|
|
6153
|
+
{ kind: "varDecl", localId: valTmp.id, init: rhs, loc },
|
|
6154
|
+
{
|
|
6155
|
+
kind: "exprStmt",
|
|
6156
|
+
expr: {
|
|
6157
|
+
kind: "libCall",
|
|
6158
|
+
fn: "emitter.setDefaultMaxChk",
|
|
6159
|
+
args: [dynVal, { kind: "strLit", value: "defaultMaxListeners", type: STRING, loc }],
|
|
6160
|
+
type: VOID,
|
|
6161
|
+
loc,
|
|
6162
|
+
},
|
|
6163
|
+
loc,
|
|
6164
|
+
},
|
|
6165
|
+
],
|
|
6166
|
+
result: valRef(),
|
|
6167
|
+
type: rhs.type,
|
|
6168
|
+
loc,
|
|
6169
|
+
};
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
5735
6173
|
// Member targets whose storage IS a module global — namespace
|
|
5736
6174
|
// members (`N.x`) and expando function members (`Foo.baz`): the
|
|
5737
6175
|
// same assignExpr, since the "member" is a variable.
|
|
@@ -6217,6 +6655,7 @@ export function lowerBinary(L, expr) {
|
|
|
6217
6655
|
kind: "unionEq",
|
|
6218
6656
|
unionId: ut.unionId,
|
|
6219
6657
|
negated,
|
|
6658
|
+
sameValue: false,
|
|
6220
6659
|
left: L.coerceInto(expr.left, left, ut),
|
|
6221
6660
|
right: L.coerceInto(expr.right, right, ut),
|
|
6222
6661
|
type: BOOL,
|
|
@@ -7043,6 +7482,27 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
7043
7482
|
return { kind: "dynTest", test: "bytes", value: left, type: BOOL, loc };
|
|
7044
7483
|
}
|
|
7045
7484
|
}
|
|
7485
|
+
// `x instanceof RegExp` over a union with a regex arm (the
|
|
7486
|
+
// skip-utility `string | RegExp` dispatch): a union tag test — the
|
|
7487
|
+
// socket-narrowing shape; the checker types the branches and
|
|
7488
|
+
// maybeNarrow bridges the reads. A plain regex-typed LHS folds
|
|
7489
|
+
// true; the fold keeps the operand-purity rule of the class folds
|
|
7490
|
+
// (identifier reads only).
|
|
7491
|
+
if (ts.isIdentifier(expr.right) &&
|
|
7492
|
+
L.isStdlibGlobal(expr.right, "RegExp") &&
|
|
7493
|
+
!L.caughtLocalOf(expr.left)) {
|
|
7494
|
+
const left = L.lowerExpr(expr.left);
|
|
7495
|
+
if (left.type.kind === "union") {
|
|
7496
|
+
const def = L.unions.get(left.type.unionId);
|
|
7497
|
+
const tag = def ? def.arms.findIndex((a) => a.kind === "regex") : -1;
|
|
7498
|
+
if (tag >= 0) {
|
|
7499
|
+
return { kind: "unionIsTag", unionId: left.type.unionId, tag, negated: false, value: left, type: BOOL, loc };
|
|
7500
|
+
}
|
|
7501
|
+
}
|
|
7502
|
+
if (left.type.kind === "regex" && left.kind === "varRef" && ts.isIdentifier(expr.left)) {
|
|
7503
|
+
return { kind: "boolLit", value: true, type: BOOL, loc };
|
|
7504
|
+
}
|
|
7505
|
+
}
|
|
7046
7506
|
// `x instanceof X` where X is a class VALUE (a classval-typed
|
|
7047
7507
|
// binding): the target is DYNAMIC — a hierarchy target reads its
|
|
7048
7508
|
// interval from the class object at runtime (instanceOfValue); a
|