@scriptc/compiler 0.0.6 → 0.0.8
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/dist/backend/emission/emit-exprs.js +47 -1
- 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-walkers.js +22 -8
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.js +12 -1
- 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 +105 -9
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +49 -1
- package/dist/diagnostics/diagnostic.js +119 -5
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-contract.d.ts +114 -0
- package/dist/frontend/lib-contract.js +372 -0
- package/dist/frontend/lib-contract.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +16 -1
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +47 -7
- package/dist/frontend/lowering/lower-calls.js +560 -36
- 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 +85 -15
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +43 -1
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.js +1 -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 +474 -44
- 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 +430 -44
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +19 -3
- package/dist/frontend/lowering/lowerer.js +188 -11
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/types.d.ts +28 -1
- package/dist/frontend/types.js +300 -8
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +96 -7
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +48 -2
- package/dist/ir/nodes.js +103 -3
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +35 -6
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +53 -3
- package/dist/library/profile.js +143 -12
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar-validate.d.ts +4 -0
- package/dist/library/sidecar-validate.js +506 -0
- package/dist/library/sidecar-validate.js.map +1 -0
- package/dist/library/sidecar.d.ts +191 -0
- package/dist/library/sidecar.js +687 -0
- package/dist/library/sidecar.js.map +1 -0
- 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/dist/library/wyhash.d.ts +12 -0
- package/dist/library/wyhash.js +116 -0
- package/dist/library/wyhash.js.map +1 -0
- package/package.json +2 -2
- package/surface-manifest.json +37 -2
|
@@ -203,6 +203,45 @@ function lowerExprInner(L, expr) {
|
|
|
203
203
|
if (ts.isArrayLiteralExpression(expr) && expr.elements.length === 0 && underConstAssertion(expr)) {
|
|
204
204
|
return false;
|
|
205
205
|
}
|
|
206
|
+
// A JS variable INITIALIZER whose BINDING registered as a
|
|
207
|
+
// checked-dynamic module global (the typedef-annotated
|
|
208
|
+
// doc-builder consts whose JSDoc types degraded — DYN slots):
|
|
209
|
+
// the island build could never land (no engine→DOM crossing
|
|
210
|
+
// exists), so the literal takes its own world's path instead —
|
|
211
|
+
// the DOM literal, or a static record the slot converts.
|
|
212
|
+
if (isJsSourceFile(expr.getSourceFile()) &&
|
|
213
|
+
ts.isVariableDeclaration(expr.parent) &&
|
|
214
|
+
expr.parent.initializer === expr &&
|
|
215
|
+
ts.isIdentifier(expr.parent.name)) {
|
|
216
|
+
const bindSym = L.checker.getSymbolAtLocation(expr.parent.name);
|
|
217
|
+
const g = bindSym ? L.globalsBySymbol.get(bindSym) : undefined;
|
|
218
|
+
if (g?.type.kind === "dyn")
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
// A PROJECT-DECLARED contextual type that only ABSORBED to the
|
|
222
|
+
// island (a doc-builder typedef whose field's JSDoc type
|
|
223
|
+
// reference degraded to checker-`any` — the tsgo multi-file
|
|
224
|
+
// value-as-type residue) is not an npm slot: when the literal's
|
|
225
|
+
// OWN type maps statically, it builds that way — the binding's
|
|
226
|
+
// checked-dynamic slot takes the DOM conversion. Genuinely
|
|
227
|
+
// island slots (npm .d.ts provenance, plain `any`) keep the
|
|
228
|
+
// native build.
|
|
229
|
+
if (ctxTs !== undefined && (ctxTs.flags & ts.TypeFlags.Any) === 0) {
|
|
230
|
+
// Intersection/tuple typedefs (`Line & {readonly soft: true}`)
|
|
231
|
+
// carry provenance on the ALIAS symbol.
|
|
232
|
+
const sym = ctxTs.getSymbol() ?? ctxTs.getAliasSymbol();
|
|
233
|
+
const decls = sym ? L.checker.declarationsOf(sym) : [];
|
|
234
|
+
const projectDeclared = decls.length > 0 &&
|
|
235
|
+
decls.every((d) => {
|
|
236
|
+
const sf = d.getSourceFile();
|
|
237
|
+
return !sf.isDeclarationFile && !sf.fileName.includes("/node_modules/");
|
|
238
|
+
});
|
|
239
|
+
if (projectDeclared) {
|
|
240
|
+
const own = L.mapTypeOf(L.typeOf(expr));
|
|
241
|
+
if (own?.kind === "record" || own?.kind === "array")
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
206
245
|
return true;
|
|
207
246
|
})()) {
|
|
208
247
|
if (ts.isObjectLiteralExpression(expr)) {
|
|
@@ -217,21 +256,73 @@ function lowerExprInner(L, expr) {
|
|
|
217
256
|
// (`"k" in o` is false), which the static record path can't say.
|
|
218
257
|
const argsWithout = [];
|
|
219
258
|
const argsWith = [];
|
|
259
|
+
const getters = [];
|
|
220
260
|
let spread = null;
|
|
261
|
+
// A MEMBER a JS file cannot lower or marshal (a generic function
|
|
262
|
+
// as a value, a signature with checked-dynamic parameters — the
|
|
263
|
+
// doc-builder public aggregate's degraded pieces): defer like a
|
|
264
|
+
// statement fence — the slot holds a host closure that THROWS the
|
|
265
|
+
// captured diagnostic when invoked, so building the aggregate
|
|
266
|
+
// compiles and only a CALL through the island stops the run.
|
|
267
|
+
const islandMemberFence = (diagsBefore, err, valueNode) => {
|
|
268
|
+
if (!(err instanceof PoisonError) ||
|
|
269
|
+
!isJsSourceFile(expr.getSourceFile()) ||
|
|
270
|
+
L.diagSink !== null ||
|
|
271
|
+
L.diags.length <= diagsBefore ||
|
|
272
|
+
L.diags.slice(diagsBefore).some((d) => d.code === "SC9001")) {
|
|
273
|
+
throw err;
|
|
274
|
+
}
|
|
275
|
+
const captured = L.diags.splice(diagsBefore);
|
|
276
|
+
L.runtimeFences.push(...captured);
|
|
277
|
+
const first = captured[0];
|
|
278
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? expr.getSourceFile(), first.loc.start);
|
|
279
|
+
const fnName = `%fn${L.lambdaCounter++}_islfence`;
|
|
280
|
+
L.liftedFns.push({
|
|
281
|
+
name: fnName,
|
|
282
|
+
params: [],
|
|
283
|
+
returnType: VOID,
|
|
284
|
+
locals: [],
|
|
285
|
+
captures: [],
|
|
286
|
+
body: [
|
|
287
|
+
{
|
|
288
|
+
kind: "runtimeFence",
|
|
289
|
+
code: first.code,
|
|
290
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
291
|
+
loc: locOf(valueNode),
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
loc: locOf(valueNode),
|
|
295
|
+
});
|
|
296
|
+
const fence = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(valueNode) };
|
|
297
|
+
return L.jsvalIn(fence, valueNode);
|
|
298
|
+
};
|
|
221
299
|
const pushProp = (name, value, valueNode, into) => {
|
|
300
|
+
const diagsBefore = L.diags.length;
|
|
301
|
+
let marshaled;
|
|
302
|
+
try {
|
|
303
|
+
marshaled = L.jsvalIn(value, valueNode);
|
|
304
|
+
}
|
|
305
|
+
catch (err) {
|
|
306
|
+
marshaled = islandMemberFence(diagsBefore, err, valueNode);
|
|
307
|
+
}
|
|
222
308
|
for (const args of into) {
|
|
223
309
|
args.push({
|
|
224
310
|
kind: "jsMarshal",
|
|
225
311
|
value: { kind: "strLit", value: name.text, type: STRING, loc: locOf(name) },
|
|
226
312
|
type: JSVAL, loc: locOf(name),
|
|
227
313
|
});
|
|
228
|
-
args.push(
|
|
314
|
+
args.push(marshaled);
|
|
229
315
|
}
|
|
230
316
|
};
|
|
317
|
+
const spreadSrcs = [];
|
|
318
|
+
let sawPlainProp = false;
|
|
231
319
|
for (const prop of expr.properties) {
|
|
232
320
|
if (ts.isSpreadAssignment(prop)) {
|
|
233
321
|
const cs = conditionalSpreadOf(prop.expression);
|
|
234
322
|
if (cs && cs !== "unsupported" && !spread) {
|
|
323
|
+
if (spreadSrcs.length > 0) {
|
|
324
|
+
L.unsupported("SC1090", prop, "a conditional spread mixed with plain spreads in an 'any'-typed object literal");
|
|
325
|
+
}
|
|
235
326
|
const cond = L.lowerCondition(cs.cond);
|
|
236
327
|
if (!pureCondExpr(cond)) {
|
|
237
328
|
L.unsupported("SC1090", prop, "conditional spreads with effectful conditions in an 'any'-typed object literal (bind the condition to a const first)");
|
|
@@ -243,8 +334,23 @@ function lowerExprInner(L, expr) {
|
|
|
243
334
|
}
|
|
244
335
|
continue;
|
|
245
336
|
}
|
|
337
|
+
// A PLAIN spread (`{ ...options, plugins }` — the withPlugins
|
|
338
|
+
// argument-rebuild shape): the source copies into the fresh
|
|
339
|
+
// engine object through the spec's CopyDataProperties (the
|
|
340
|
+
// objSpread op). Spreads must precede explicit properties
|
|
341
|
+
// (the composition applies explicit keys AFTER the copies —
|
|
342
|
+
// JS's later-wins — which a spread after them would invert);
|
|
343
|
+
// mixing with a conditional spread keeps the fence.
|
|
344
|
+
if (cs === undefined || cs === null) {
|
|
345
|
+
if (sawPlainProp || spread) {
|
|
346
|
+
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");
|
|
347
|
+
}
|
|
348
|
+
spreadSrcs.push(L.jsvalIn(L.lowerExpr(prop.expression), prop.expression));
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
246
351
|
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
352
|
}
|
|
353
|
+
sawPlainProp = true;
|
|
248
354
|
// `name: value`, the shorthand `{ name }` (the value is the
|
|
249
355
|
// identifier itself, resolved through the shorthand VALUE symbol
|
|
250
356
|
// like the static record path), and METHODS — `{ load() {...} }`
|
|
@@ -254,22 +360,79 @@ function lowerExprInner(L, expr) {
|
|
|
254
360
|
// string-literal keys — engine property names have no
|
|
255
361
|
// identifier restriction ("content-type").
|
|
256
362
|
const name = prop.name;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
363
|
+
// A GET accessor (`get root() { return ROOT_INDENT; }` — the
|
|
364
|
+
// doc-printer's self-referential root-indent shape): the body
|
|
365
|
+
// lowers as a zero-param closure marshaled into the engine, and
|
|
366
|
+
// the property defines through the engine's own getter
|
|
367
|
+
// machinery AFTER the literal builds (the defineGetter op) —
|
|
368
|
+
// reads through the handle invoke it natively. `this` in the
|
|
369
|
+
// body keeps the object-method rejection; setters stay fenced.
|
|
370
|
+
if (ts.isGetAccessorDeclaration(prop) && prop.body && name && (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
|
|
371
|
+
L.rejectThisInObjectMethod(prop.body);
|
|
372
|
+
getters.push({ name: name.text, fn: L.jsvalIn(L.lowerLambda(prop), prop), loc: locOf(prop) });
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
// A member VALUE that cannot lower at all (a generic function
|
|
376
|
+
// referenced as a value — the aggregate's `align`): the same
|
|
377
|
+
// call-time fence deferral pushProp applies to marshal failures.
|
|
378
|
+
let value = null;
|
|
379
|
+
const valueDiagsBefore = L.diags.length;
|
|
380
|
+
try {
|
|
381
|
+
value = ts.isPropertyAssignment(prop)
|
|
382
|
+
? L.lowerExpr(prop.initializer)
|
|
383
|
+
: ts.isShorthandPropertyAssignment(prop)
|
|
384
|
+
? L.lowerShorthandValue(prop)
|
|
385
|
+
: ts.isMethodDeclaration(prop) && prop.body
|
|
386
|
+
? (L.rejectThisInObjectMethod(prop.body), L.lowerLambda(prop))
|
|
387
|
+
: null;
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
value = islandMemberFence(valueDiagsBefore, err, prop);
|
|
391
|
+
}
|
|
264
392
|
if (value && name && (ts.isIdentifier(name) || ts.isStringLiteral(name))) {
|
|
265
393
|
pushProp(name, value, prop, [argsWithout, argsWith]);
|
|
266
394
|
}
|
|
267
395
|
else {
|
|
268
|
-
L.unsupported("SC1090", prop, "this property form in an 'any'-typed object literal (only `name: value
|
|
396
|
+
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
397
|
}
|
|
270
398
|
}
|
|
271
|
-
|
|
272
|
-
|
|
399
|
+
const withGetters = (obj) => getters.reduce((acc, g) => ({
|
|
400
|
+
kind: "jsOp",
|
|
401
|
+
op: "defineGetter",
|
|
402
|
+
args: [
|
|
403
|
+
acc,
|
|
404
|
+
{ kind: "jsMarshal", value: { kind: "strLit", value: g.name, type: STRING, loc: g.loc }, type: JSVAL, loc: g.loc },
|
|
405
|
+
g.fn,
|
|
406
|
+
],
|
|
407
|
+
type: JSVAL,
|
|
408
|
+
loc: g.loc,
|
|
409
|
+
}), obj);
|
|
410
|
+
if (!spread) {
|
|
411
|
+
if (spreadSrcs.length === 0) {
|
|
412
|
+
return withGetters({ kind: "jsOp", op: "objLit", args: argsWithout, type: JSVAL, loc });
|
|
413
|
+
}
|
|
414
|
+
// Plain spreads compose left to right onto a fresh object, the
|
|
415
|
+
// explicit properties merging LAST (JS's later-wins): spread
|
|
416
|
+
// sources evaluate before later property values by nesting
|
|
417
|
+
// order, exactly the source order.
|
|
418
|
+
let acc = { kind: "jsOp", op: "objLit", args: [], type: JSVAL, loc };
|
|
419
|
+
for (const src of spreadSrcs) {
|
|
420
|
+
acc = { kind: "jsOp", op: "objSpread", args: [acc, src], type: JSVAL, loc };
|
|
421
|
+
}
|
|
422
|
+
if (argsWithout.length > 0) {
|
|
423
|
+
acc = {
|
|
424
|
+
kind: "jsOp",
|
|
425
|
+
op: "objSpread",
|
|
426
|
+
args: [acc, { kind: "jsOp", op: "objLit", args: argsWithout, type: JSVAL, loc }],
|
|
427
|
+
type: JSVAL,
|
|
428
|
+
loc,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return withGetters(acc);
|
|
432
|
+
}
|
|
433
|
+
if (getters.length > 0) {
|
|
434
|
+
L.unsupported("SC1090", expr, "get accessors combined with conditional spreads in an 'any'-typed object literal");
|
|
435
|
+
}
|
|
273
436
|
const withLit = { kind: "jsOp", op: "objLit", args: argsWith, type: JSVAL, loc };
|
|
274
437
|
const withoutLit = { kind: "jsOp", op: "objLit", args: argsWithout, type: JSVAL, loc };
|
|
275
438
|
return {
|
|
@@ -1538,7 +1701,7 @@ export function lowerTernary(L, expr, expected) {
|
|
|
1538
1701
|
// The ternary's IR type is normally the checker's own: it collapses
|
|
1539
1702
|
// same-kind literal unions ("a" | "b" → string) and forms tagged
|
|
1540
1703
|
// unions for mixed arms that map (`c ? okRec : errRec`); anything
|
|
1541
|
-
// left unmappable gets
|
|
1704
|
+
// left unmappable gets the type fence (badType) here. A record/union CONTEXTUAL type
|
|
1542
1705
|
// takes over exactly when the own type can't carry the value —
|
|
1543
1706
|
// unmappable, or a DIFFERENT union (branch literals omitting
|
|
1544
1707
|
// DIFFERENT optional subsets make the own type a union of the
|
|
@@ -1744,10 +1907,25 @@ export function lowerUnitComparison(L, left, right, negated, loc) {
|
|
|
1744
1907
|
}
|
|
1745
1908
|
if (other.type.kind === "union") {
|
|
1746
1909
|
const tag = L.armTag(other.type.unionId, unit.type);
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1910
|
+
if (tag < 0) {
|
|
1911
|
+
// A union WITHOUT that unit arm: legal TS (`miss === null` on a
|
|
1912
|
+
// `number | undefined` — null/undefined guard comparisons are
|
|
1913
|
+
// permitted against nullable-adjacent types), and === never
|
|
1914
|
+
// coerces, so the answer is the constant `negated`. The literal
|
|
1915
|
+
// must NOT flow into the union representation (the unionEq
|
|
1916
|
+
// fallback would coerce it through the stranded-arm trap and
|
|
1917
|
+
// throw where Node answers false). JS still evaluates the
|
|
1918
|
+
// operand, so a non-droppable one (`xs.find(f) === null` — the
|
|
1919
|
+
// callback's effects are observable) rides one throwaway tag
|
|
1920
|
+
// test; droppable reads fold to the bare literal.
|
|
1921
|
+
const answer = { kind: "boolLit", value: negated, type: BOOL, loc };
|
|
1922
|
+
if (droppableStatic(other))
|
|
1923
|
+
return answer;
|
|
1924
|
+
const evalOnce = {
|
|
1925
|
+
kind: "unionIsTag", unionId: other.type.unionId, tag: 0, negated: false, value: other, type: BOOL, loc,
|
|
1926
|
+
};
|
|
1927
|
+
return { kind: "logical", op: negated ? "||" : "&&", left: evalOnce, right: answer, type: BOOL, loc };
|
|
1928
|
+
}
|
|
1751
1929
|
return {
|
|
1752
1930
|
kind: "unionIsTag",
|
|
1753
1931
|
unionId: other.type.unionId,
|
|
@@ -1977,7 +2155,11 @@ export function lowerNullishCoalesce(L, expr, loc) {
|
|
|
1977
2155
|
L.unsupported("SC1100", expr, "nullish coalescing on 'unknown' values");
|
|
1978
2156
|
}
|
|
1979
2157
|
if (left.type.kind === "jsval") {
|
|
1980
|
-
|
|
2158
|
+
// `a ?? b` on an ISLAND value: the engine's own nullish test — the
|
|
2159
|
+
// left evaluates once, the right runs lazily in its branch and
|
|
2160
|
+
// marshals in (the emitter's jsval nullish arm).
|
|
2161
|
+
const right = L.jsvalIn(L.lowerExpr(expr.right), expr.right);
|
|
2162
|
+
return { kind: "nullish", left, right, type: JSVAL, loc };
|
|
1981
2163
|
}
|
|
1982
2164
|
if (left.type.kind !== "union")
|
|
1983
2165
|
return left;
|
|
@@ -2818,7 +3000,7 @@ export function lowerIntrinsicProperty(L, expr) {
|
|
|
2818
3000
|
/** `[a, b, c]`. The element type comes from the contextual type when tsc
|
|
2819
3001
|
* has one (`const a: number[] = []`, arguments, nested literals) and from
|
|
2820
3002
|
* the literal's own inferred type otherwise. A bare `[]` with no context
|
|
2821
|
-
* is `never[]` — unmappable, rejected with
|
|
3003
|
+
* is `never[]` — unmappable, rejected with the component fence (SC2009). `expected` overrides
|
|
2822
3004
|
* the contextual lookup where the caller knows the slot's array type and
|
|
2823
3005
|
* tsc's API doesn't surface it (ternary arms under an array context —
|
|
2824
3006
|
* tsc accepts the arm covariantly, but a tagged element representation
|
|
@@ -2872,6 +3054,36 @@ export function lowerArrayLiteral(L, expr, expected) {
|
|
|
2872
3054
|
if (arrayArms.length === 1)
|
|
2873
3055
|
mapped = arrayArms[0];
|
|
2874
3056
|
}
|
|
3057
|
+
// A NON-EMPTY literal whose own type has no static home (the JS dyn
|
|
3058
|
+
// fallback — a null/dyn mapping, or an inference that degraded to a
|
|
3059
|
+
// unit-only-element array over non-unit elements) under a union
|
|
3060
|
+
// with exactly ONE array-family arm — an array, or an
|
|
3061
|
+
// arity-matching tuple (the option-table `default: [{ value: [] }]`
|
|
3062
|
+
// shape): build AS that arm, exactly the empty-literal rule above;
|
|
3063
|
+
// the slot's coercion wraps it.
|
|
3064
|
+
if (ctxUnion && expr.elements.length > 0) {
|
|
3065
|
+
// A unit-only-element array (`(null | undefined)[]`) cannot hold
|
|
3066
|
+
// non-unit elements — neither as the literal's own mapping nor as
|
|
3067
|
+
// a competing union arm.
|
|
3068
|
+
const nonUnitElems = expr.elements.some((el) => !ts.isOmittedExpression(el) &&
|
|
3069
|
+
el.kind !== ts.SyntaxKind.NullKeyword &&
|
|
3070
|
+
!(ts.isIdentifier(el) && el.text === "undefined"));
|
|
3071
|
+
const ownUnhelpful = mapped === null || mapped.kind === "dyn" ||
|
|
3072
|
+
// The checker echoing the context union back as the literal's
|
|
3073
|
+
// own type decides nothing either — nor does an island ('any')
|
|
3074
|
+
// residue under a STATIC union slot.
|
|
3075
|
+
mapped.kind === "union" || mapped.kind === "jsval" ||
|
|
3076
|
+
(mapped.kind === "array" && nonUnitElems && L.unitOnlyElem(mapped.elem));
|
|
3077
|
+
if (ownUnhelpful) {
|
|
3078
|
+
const def = L.unions.get(ctxUnion.unionId);
|
|
3079
|
+
const arms = (def?.arms ?? []).filter((a) => (a.kind === "array" && !(nonUnitElems && L.unitOnlyElem(a.elem))) ||
|
|
3080
|
+
(a.kind === "record" &&
|
|
3081
|
+
!!L.shapes.get(a.shapeId)?.tuple &&
|
|
3082
|
+
L.shapes.get(a.shapeId).fields.length === expr.elements.length));
|
|
3083
|
+
if (arms.length === 1)
|
|
3084
|
+
mapped = arms[0];
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
2875
3087
|
}
|
|
2876
3088
|
// An EMPTY literal under a CONST ASSERTION whose type queries panicked
|
|
2877
3089
|
// (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
|
|
@@ -3230,7 +3442,7 @@ function propNameText(L, name) {
|
|
|
3230
3442
|
* (dynFrom's domain, functions box); a value with no dyn representation
|
|
3231
3443
|
* fences per property. Spreads, accessors, and methods stay fenced —
|
|
3232
3444
|
* none co-occur with the computed-key idiom in the wild. */
|
|
3233
|
-
function lowerDynObjectLiteral(L, expr) {
|
|
3445
|
+
export function lowerDynObjectLiteral(L, expr) {
|
|
3234
3446
|
const loc = locOf(expr);
|
|
3235
3447
|
const fields = [];
|
|
3236
3448
|
for (const prop of expr.properties) {
|
|
@@ -3268,11 +3480,92 @@ function lowerDynObjectLiteral(L, expr) {
|
|
|
3268
3480
|
// Lambda values that fail to lower become trap closures (the
|
|
3269
3481
|
// per-call fence granularity — fenceClosureProbe) so the object
|
|
3270
3482
|
// still builds; everything else keeps the per-property fence below.
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3483
|
+
let raw;
|
|
3484
|
+
const propDiagsBefore = L.diags.length;
|
|
3485
|
+
try {
|
|
3486
|
+
raw =
|
|
3487
|
+
fenceClosureProbe(L, valueExpr, undefined, () => L.lowerExpr(valueExpr)) ??
|
|
3488
|
+
L.lowerExpr(valueExpr);
|
|
3489
|
+
}
|
|
3490
|
+
catch (err) {
|
|
3491
|
+
// A PURE member read a JS file cannot lower (a namespace object
|
|
3492
|
+
// in an export aggregate): the slot takes a boxed fence closure —
|
|
3493
|
+
// the diagnostics defer to the runtime-fence ledger, the object
|
|
3494
|
+
// builds, and only USING the member stops the run.
|
|
3495
|
+
const pureMember = ts.isIdentifier(valueExpr);
|
|
3496
|
+
if (!(err instanceof PoisonError) ||
|
|
3497
|
+
!pureMember ||
|
|
3498
|
+
L.diagSink !== null ||
|
|
3499
|
+
L.diags.length <= propDiagsBefore ||
|
|
3500
|
+
L.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
3501
|
+
throw err;
|
|
3502
|
+
}
|
|
3503
|
+
const captured = L.diags.splice(propDiagsBefore);
|
|
3504
|
+
L.runtimeFences.push(...captured);
|
|
3505
|
+
const first = captured[0];
|
|
3506
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? expr.getSourceFile(), first.loc.start);
|
|
3507
|
+
const fnName = `%fn${L.lambdaCounter++}_dynfence`;
|
|
3508
|
+
L.liftedFns.push({
|
|
3509
|
+
name: fnName,
|
|
3510
|
+
params: [],
|
|
3511
|
+
returnType: VOID,
|
|
3512
|
+
locals: [],
|
|
3513
|
+
captures: [],
|
|
3514
|
+
body: [
|
|
3515
|
+
{
|
|
3516
|
+
kind: "runtimeFence",
|
|
3517
|
+
code: first.code,
|
|
3518
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
3519
|
+
loc: locOf(prop),
|
|
3520
|
+
},
|
|
3521
|
+
],
|
|
3522
|
+
loc: locOf(prop),
|
|
3523
|
+
});
|
|
3524
|
+
raw = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(prop) };
|
|
3525
|
+
}
|
|
3526
|
+
let v = L.coerceToExpected(raw, DYN);
|
|
3274
3527
|
if (v.type.kind !== "dyn") {
|
|
3275
|
-
|
|
3528
|
+
const convDiagsBefore = L.diags.length;
|
|
3529
|
+
try {
|
|
3530
|
+
L.unsupported("SC1101", valueExpr, `holding '${L.fmt(v.type)}' values in a runtime-keyed (computed-key) object literal`);
|
|
3531
|
+
}
|
|
3532
|
+
catch (err) {
|
|
3533
|
+
// A member value the DOM cannot hold — a func whose signature
|
|
3534
|
+
// cannot box, an island ('any') handle, a record carrying func
|
|
3535
|
+
// fields (the export aggregate's typed utilities and npm-handle
|
|
3536
|
+
// members): the same call-time fence deferral as an unlowerable
|
|
3537
|
+
// member — the slot takes a boxed fence closure; only USING it
|
|
3538
|
+
// stops the run. Probe mode and ICEs keep the poison.
|
|
3539
|
+
if (!(err instanceof PoisonError) ||
|
|
3540
|
+
L.diagSink !== null ||
|
|
3541
|
+
L.diags.length <= convDiagsBefore ||
|
|
3542
|
+
L.diags.slice(convDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
3543
|
+
throw err;
|
|
3544
|
+
}
|
|
3545
|
+
const captured = L.diags.splice(convDiagsBefore);
|
|
3546
|
+
L.runtimeFences.push(...captured);
|
|
3547
|
+
const first = captured[0];
|
|
3548
|
+
const pos = ts.getLineAndCharacterOfPosition(L.program.getSourceFile(first.loc.file) ?? expr.getSourceFile(), first.loc.start);
|
|
3549
|
+
const fnName = `%fn${L.lambdaCounter++}_dynfence`;
|
|
3550
|
+
L.liftedFns.push({
|
|
3551
|
+
name: fnName,
|
|
3552
|
+
params: [],
|
|
3553
|
+
returnType: VOID,
|
|
3554
|
+
locals: [],
|
|
3555
|
+
captures: [],
|
|
3556
|
+
body: [
|
|
3557
|
+
{
|
|
3558
|
+
kind: "runtimeFence",
|
|
3559
|
+
code: first.code,
|
|
3560
|
+
message: `${first.message} [${first.code} at ${first.loc.file}:${pos.line + 1}]`,
|
|
3561
|
+
loc: locOf(prop),
|
|
3562
|
+
},
|
|
3563
|
+
],
|
|
3564
|
+
loc: locOf(prop),
|
|
3565
|
+
});
|
|
3566
|
+
const fence = { kind: "closure", fnName, captures: [], type: funcOf([], VOID), loc: locOf(prop) };
|
|
3567
|
+
v = L.coerceToExpected(fence, DYN);
|
|
3568
|
+
}
|
|
3276
3569
|
}
|
|
3277
3570
|
fields.push({ key, value: v });
|
|
3278
3571
|
}
|
|
@@ -3465,7 +3758,7 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3465
3758
|
// Syntax fence FIRST: unsupported member forms get their specific
|
|
3466
3759
|
// message even when they also make the literal's type unmappable
|
|
3467
3760
|
// (a getter or a `this`-returning method would otherwise surface as an
|
|
3468
|
-
// opaque
|
|
3761
|
+
// opaque type fence on the whole literal).
|
|
3469
3762
|
for (const prop of expr.properties) {
|
|
3470
3763
|
if (ts.isSpreadAssignment(prop)) {
|
|
3471
3764
|
// Supported shapes: FULL spreads of known record shapes read as
|
|
@@ -3610,7 +3903,11 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3610
3903
|
// the slot's width coercion constructs through the trivial
|
|
3611
3904
|
// parameter-property constructor (recordToClassPlan), or fences with
|
|
3612
3905
|
// the record-shape story.
|
|
3613
|
-
|
|
3906
|
+
// A jsval-mapped context reaches here only when lowerExpr's island
|
|
3907
|
+
// gate DECLINED it (a project-declared typedef that absorbed to the
|
|
3908
|
+
// island through checker-`any` field residue): the literal builds at
|
|
3909
|
+
// its own type like every unmappable context.
|
|
3910
|
+
if (mapped === null || mapped.kind === "union" || mapped.kind === "dyn" || mapped.kind === "object" || mapped.kind === "jsval") {
|
|
3614
3911
|
const ctxUnion = mapped?.kind === "union" ? mapped : null;
|
|
3615
3912
|
mapped = L.mapTypeOf(L.typeOf(expr)) ?? mapped;
|
|
3616
3913
|
// A literal whose own shape re-tags into NO arm of the contextual
|
|
@@ -3870,6 +4167,9 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3870
4167
|
// contributor overriding one would silently drop that evaluation —
|
|
3871
4168
|
// collisions fence in both directions.
|
|
3872
4169
|
const conditionalNames = new Set();
|
|
4170
|
+
// Member names DROPPED from the shape (JS deferral — unlowerable pure
|
|
4171
|
+
// member reads narrow away; see the catch in the property loop).
|
|
4172
|
+
const droppedNames = new Set();
|
|
3873
4173
|
for (const prop of expr.properties) {
|
|
3874
4174
|
if (ts.isSpreadAssignment(prop)) {
|
|
3875
4175
|
const cs = conditionalSpreadOf(prop.expression);
|
|
@@ -4288,21 +4588,56 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4288
4588
|
}
|
|
4289
4589
|
let value;
|
|
4290
4590
|
let valueNode = prop;
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4591
|
+
const propDiagsBefore = L.diags.length;
|
|
4592
|
+
try {
|
|
4593
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
4594
|
+
valueNode = prop.initializer;
|
|
4595
|
+
// ARRAY-LITERAL initializers route through the expected-type
|
|
4596
|
+
// lowering: a union field with one array-family arm builds the
|
|
4597
|
+
// literal AS that arm (lowerExprExpecting's IR-directed rule —
|
|
4598
|
+
// the option-table `default: [{ value: [] }]` shape), where the
|
|
4599
|
+
// bare lowering would take the JS DOM fallback and fence.
|
|
4600
|
+
let init = prop.initializer;
|
|
4601
|
+
while (ts.isParenthesizedExpression(init))
|
|
4602
|
+
init = init.expression;
|
|
4603
|
+
value =
|
|
4604
|
+
fenceClosureProbe(L, prop.initializer, fieldType, () => L.lowerExpr(prop.initializer)) ??
|
|
4605
|
+
(fieldType !== undefined && ts.isArrayLiteralExpression(init)
|
|
4606
|
+
? L.lowerExprExpecting(prop.initializer, fieldType)
|
|
4607
|
+
: L.lowerExpr(prop.initializer));
|
|
4608
|
+
}
|
|
4609
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
4610
|
+
value = L.lowerShorthandValue(prop);
|
|
4611
|
+
}
|
|
4612
|
+
else if (ts.isMethodDeclaration(prop)) {
|
|
4613
|
+
value =
|
|
4614
|
+
fenceClosureProbe(L, prop, fieldType, () => L.lowerLambda(prop)) ?? L.lowerLambda(prop);
|
|
4615
|
+
}
|
|
4616
|
+
else {
|
|
4617
|
+
L.unsupported("SC1090", prop, `syntax '${ts.SyntaxKind[prop.kind]}'`);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
catch (err) {
|
|
4621
|
+
// A member VALUE a JS file cannot lower (a namespace object in an
|
|
4622
|
+
// export aggregate — the sharedWithCli `errors` member): the
|
|
4623
|
+
// member NARROWS AWAY like a CJS export-table accessor entry —
|
|
4624
|
+
// the diagnostics defer to the runtime-fence ledger, the shape
|
|
4625
|
+
// drops the field, and each READ of it meets its own per-site
|
|
4626
|
+
// fence. Pure member forms only (identifier/shorthand reads);
|
|
4627
|
+
// TypeScript, probe mode, and ICEs keep the poison.
|
|
4628
|
+
const pureMember = ts.isShorthandPropertyAssignment(prop) ||
|
|
4629
|
+
(ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.initializer));
|
|
4630
|
+
if (!(err instanceof PoisonError) ||
|
|
4631
|
+
!pureMember ||
|
|
4632
|
+
!isJsSourceFile(expr.getSourceFile()) ||
|
|
4633
|
+
L.diagSink !== null ||
|
|
4634
|
+
L.diags.length <= propDiagsBefore ||
|
|
4635
|
+
L.diags.slice(propDiagsBefore).some((d) => d.code === "SC9001")) {
|
|
4636
|
+
throw err;
|
|
4637
|
+
}
|
|
4638
|
+
L.runtimeFences.push(...L.diags.splice(propDiagsBefore));
|
|
4639
|
+
droppedNames.add(name);
|
|
4640
|
+
continue;
|
|
4306
4641
|
}
|
|
4307
4642
|
if (!fieldType) {
|
|
4308
4643
|
// Overflow entry: the value flows into the index signature's value
|
|
@@ -4337,6 +4672,12 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4337
4672
|
// omission of required fields before lowering, undefined-armed or not).
|
|
4338
4673
|
// A REQUIRED missing field keeps the shape-mismatch rejection (possible
|
|
4339
4674
|
// through `as`: the cast smuggles a narrower literal past freshness).
|
|
4675
|
+
if (droppedNames.size > 0) {
|
|
4676
|
+
const narrowed = shape.fields.filter((f) => !droppedNames.has(f.name));
|
|
4677
|
+
const narrowedId = L.shapes.intern(narrowed.map((f) => ({ name: f.name, type: f.type })), false, shape.indexValue);
|
|
4678
|
+
type = { kind: "record", shapeId: narrowedId };
|
|
4679
|
+
shape = L.shapes.get(narrowedId);
|
|
4680
|
+
}
|
|
4340
4681
|
if (fields.filter((f) => !f.overflow).length !== shape.fields.length) {
|
|
4341
4682
|
const provided = new Set(fields.filter((f) => !f.overflow).map((f) => f.name));
|
|
4342
4683
|
for (const f of shape.fields) {
|
|
@@ -4967,7 +5308,11 @@ export function lowerRecordKeyRead(L, expr, shapeId, shape) {
|
|
|
4967
5308
|
// NUMERIC literals are their canonical string spelling — JS object keys
|
|
4968
5309
|
// ARE strings (`r[1]` reads `r["1"]`), so `b[1]` hits a declared field
|
|
4969
5310
|
// named "1" (a mapped type over a numeric enum) exactly like `b["1"]`.
|
|
4970
|
-
|
|
5311
|
+
// A key IDENTIFIER whose type proves one literal (a literal-typed
|
|
5312
|
+
// const, a keyof-constrained type parameter bound to a literal inside
|
|
5313
|
+
// a generic instance) is the same static read — identifier evaluation
|
|
5314
|
+
// is pure, so skipping it matches JS exactly.
|
|
5315
|
+
const litKey = recordKeyLiteralText(keyNode) ?? recordKeyTypeLiteralText(L, keyNode);
|
|
4971
5316
|
// The receiver lowers FIRST (both branches below read it, and JS
|
|
4972
5317
|
// evaluates the receiver before the key).
|
|
4973
5318
|
const obj = L.lowerExpr(expr.expression);
|
|
@@ -5054,6 +5399,32 @@ export function recordKeyLiteralText(node) {
|
|
|
5054
5399
|
return String(Number(node.text));
|
|
5055
5400
|
return null;
|
|
5056
5401
|
}
|
|
5402
|
+
/** The compile-time key an IDENTIFIER's TYPE proves: a key variable whose
|
|
5403
|
+
* (narrowed) type is one string/number literal — `const k: "a" = …; o[k]`,
|
|
5404
|
+
* and the generic-body form where k's type is a keyof-constrained
|
|
5405
|
+
* parameter bound to a literal (`pick(o, "a")`'s instance reads `o[k]`
|
|
5406
|
+
* with K = "a", resolved through typeParamTsBindings). Identifier reads
|
|
5407
|
+
* are pure, so the static field read may skip evaluating the key exactly
|
|
5408
|
+
* like a syntactic literal. Null anywhere the type keeps more than one
|
|
5409
|
+
* key. */
|
|
5410
|
+
export function recordKeyTypeLiteralText(L, node) {
|
|
5411
|
+
if (!ts.isIdentifier(node))
|
|
5412
|
+
return null;
|
|
5413
|
+
let t = L.typeOf(node);
|
|
5414
|
+
if (t.flags & ts.TypeFlags.TypeParameter) {
|
|
5415
|
+
const bound = L.typeParamTsResolver(t);
|
|
5416
|
+
if (!bound)
|
|
5417
|
+
return null;
|
|
5418
|
+
t = bound;
|
|
5419
|
+
}
|
|
5420
|
+
if (t.isStringLiteralType())
|
|
5421
|
+
return t.value;
|
|
5422
|
+
if (t.isNumberLiteralType()) {
|
|
5423
|
+
const n = t.value;
|
|
5424
|
+
return Number.isFinite(n) && n >= 0 ? String(n) : null;
|
|
5425
|
+
}
|
|
5426
|
+
return null;
|
|
5427
|
+
}
|
|
5057
5428
|
/** Can every value a dynamic key can reach (declared fields + the
|
|
5058
5429
|
* overflow) surface as the read's result type? dyn results need
|
|
5059
5430
|
* DOM-convertible fields (JSON-safe, with the undefined arm allowed at
|
|
@@ -5217,8 +5588,13 @@ export function lowerElementWrite(L, expr) {
|
|
|
5217
5588
|
if (shape && !shape.tuple) {
|
|
5218
5589
|
// Literal keys — string literals AND numeric literals in their
|
|
5219
5590
|
// canonical string spelling (`b[1] = v` writes field/key "1", JS's
|
|
5220
|
-
// own key derivation) — name declared fields directly.
|
|
5221
|
-
|
|
5591
|
+
// own key derivation) — name declared fields directly. A key
|
|
5592
|
+
// IDENTIFIER whose TYPE proves one literal (a literal-typed const,
|
|
5593
|
+
// a keyof-constrained type parameter bound to a literal inside a
|
|
5594
|
+
// generic instance — `set(o, "a", v)`'s body writing `o[k] = v`)
|
|
5595
|
+
// is the same static field write.
|
|
5596
|
+
const litKey = recordKeyLiteralText(target.argumentExpression) ??
|
|
5597
|
+
recordKeyTypeLiteralText(L, target.argumentExpression);
|
|
5222
5598
|
if (litKey !== null) {
|
|
5223
5599
|
const field = shape.fields.find((f) => f.name === litKey);
|
|
5224
5600
|
if (field) {
|
|
@@ -5228,7 +5604,40 @@ export function lowerElementWrite(L, expr) {
|
|
|
5228
5604
|
}
|
|
5229
5605
|
}
|
|
5230
5606
|
if (!shape.indexValue) {
|
|
5231
|
-
|
|
5607
|
+
// A SIGNATURE-FREE shape whose declared fields share ONE type
|
|
5608
|
+
// writes through the same per-shape dispatch (the mockable-clock
|
|
5609
|
+
// module shape: `mocked[functionality] = implementation` over a
|
|
5610
|
+
// table of same-signature closures). A key naming no declared
|
|
5611
|
+
// field throws the catchable TypeError — JS would ADD the
|
|
5612
|
+
// property, which a monomorphic struct cannot (the SEMANTICS.md
|
|
5613
|
+
// keyed-write-miss divergence). Mixed-type and accessor-carrying
|
|
5614
|
+
// shapes keep the fence.
|
|
5615
|
+
const common = shape.fields.length > 0 &&
|
|
5616
|
+
!shapeHasAccessorSlots(shape) &&
|
|
5617
|
+
shape.fields.every((f) => typeEquals(f.type, shape.fields[0].type))
|
|
5618
|
+
? shape.fields[0].type
|
|
5619
|
+
: null;
|
|
5620
|
+
if (!common) {
|
|
5621
|
+
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)");
|
|
5622
|
+
}
|
|
5623
|
+
const obj = L.lowerExpr(target.expression);
|
|
5624
|
+
let key = litKey !== null
|
|
5625
|
+
? { kind: "strLit", value: litKey, type: STRING, loc: locOf(target.argumentExpression) }
|
|
5626
|
+
: L.lowerExpr(target.argumentExpression);
|
|
5627
|
+
// Runtime number/boolean/dyn keys stringify — ToPropertyKey,
|
|
5628
|
+
// exactly the index-signature path's rule.
|
|
5629
|
+
if (key.type.kind === "f64")
|
|
5630
|
+
key = L.ensureString(key, target.argumentExpression);
|
|
5631
|
+
if (key.type.kind === "bool" || key.type.kind === "dyn") {
|
|
5632
|
+
key = { kind: "toString", operand: key, type: STRING, loc: locOf(target.argumentExpression) };
|
|
5633
|
+
}
|
|
5634
|
+
if (key.type.kind !== "string") {
|
|
5635
|
+
L.unsupported("SC1090", target.argumentExpression, "indexing records with non-string or non-number keys");
|
|
5636
|
+
}
|
|
5637
|
+
const value = L.coerceInto(expr.right, L.lowerExpr(expr.right), common);
|
|
5638
|
+
if (!typeEquals(value.type, common))
|
|
5639
|
+
L.badType(expr.right, L.typeOf(expr.right));
|
|
5640
|
+
return { kind: "recordKeySet", obj, shapeId: receiverIr.shapeId, key, value, loc: locOf(expr) };
|
|
5232
5641
|
}
|
|
5233
5642
|
// A LITERAL key naming no declared field is a pure overflow insert
|
|
5234
5643
|
// — no collision to validate. Runtime keys must be able to collide
|
|
@@ -7028,6 +7437,27 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
7028
7437
|
return { kind: "dynTest", test: "bytes", value: left, type: BOOL, loc };
|
|
7029
7438
|
}
|
|
7030
7439
|
}
|
|
7440
|
+
// `x instanceof RegExp` over a union with a regex arm (the
|
|
7441
|
+
// skip-utility `string | RegExp` dispatch): a union tag test — the
|
|
7442
|
+
// socket-narrowing shape; the checker types the branches and
|
|
7443
|
+
// maybeNarrow bridges the reads. A plain regex-typed LHS folds
|
|
7444
|
+
// true; the fold keeps the operand-purity rule of the class folds
|
|
7445
|
+
// (identifier reads only).
|
|
7446
|
+
if (ts.isIdentifier(expr.right) &&
|
|
7447
|
+
L.isStdlibGlobal(expr.right, "RegExp") &&
|
|
7448
|
+
!L.caughtLocalOf(expr.left)) {
|
|
7449
|
+
const left = L.lowerExpr(expr.left);
|
|
7450
|
+
if (left.type.kind === "union") {
|
|
7451
|
+
const def = L.unions.get(left.type.unionId);
|
|
7452
|
+
const tag = def ? def.arms.findIndex((a) => a.kind === "regex") : -1;
|
|
7453
|
+
if (tag >= 0) {
|
|
7454
|
+
return { kind: "unionIsTag", unionId: left.type.unionId, tag, negated: false, value: left, type: BOOL, loc };
|
|
7455
|
+
}
|
|
7456
|
+
}
|
|
7457
|
+
if (left.type.kind === "regex" && left.kind === "varRef" && ts.isIdentifier(expr.left)) {
|
|
7458
|
+
return { kind: "boolLit", value: true, type: BOOL, loc };
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7031
7461
|
// `x instanceof X` where X is a class VALUE (a classval-typed
|
|
7032
7462
|
// binding): the target is DYNAMIC — a hierarchy target reads its
|
|
7033
7463
|
// interval from the class object at runtime (instanceOfValue); a
|