@scriptc/compiler 0.0.2 → 0.0.4
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 +145 -9
- package/dist/backend/cc.d.ts +7 -0
- package/dist/backend/cc.js +1 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +35 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-shapes.js +1 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -1
- package/dist/backend/emission/emitter.js +15 -3
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.js +65 -11
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.js +1 -0
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/coverage/surface-manifest.d.ts +22 -0
- package/dist/coverage/surface-manifest.js +295 -0
- package/dist/coverage/surface-manifest.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +16 -0
- package/dist/diagnostics/diagnostic.js +26 -2
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +76 -6
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +37 -4
- package/dist/frontend/lowering/lower-builtins.js +240 -6
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +122 -32
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +34 -2
- package/dist/frontend/lowering/lower-classes.js +256 -33
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +25 -3
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +10 -0
- package/dist/frontend/lowering/lower-exprs.js +410 -9
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.d.ts +16 -1
- package/dist/frontend/lowering/lower-inspect.js +132 -5
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +24 -3
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.d.ts +6 -5
- package/dist/frontend/lowering/lower-modules.js +56 -18
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +56 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +57 -1
- package/dist/frontend/lowering/lowerer.js +291 -10
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +9 -2
- package/dist/frontend/lowering/surfaces.js +44 -7
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm.js +2 -2
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.js +59 -8
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.js +120 -5
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/shared.d.ts +8 -2
- package/dist/frontend/shared.js +22 -6
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/ts7/checker.js +1 -1
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/types.d.ts +25 -1
- package/dist/frontend/types.js +121 -7
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +43 -1
- package/dist/ir/nodes.js +41 -0
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +38 -6
- package/dist/ir/validate.js.map +1 -1
- package/package.json +6 -4
- package/surface-manifest.json +2117 -0
|
@@ -522,3 +522,13 @@ export declare function lowerPromiseAllTupleCall(L: Lowerer, call: ts.CallExpres
|
|
|
522
522
|
* Restricted to side-effect-free receivers (identifier or `this`)
|
|
523
523
|
* because the desugar evaluates the receiver twice. */
|
|
524
524
|
export declare function lowerFieldCompound(L: Lowerer, access: ts.PropertyAccessExpression | ts.ElementAccessExpression, op: CompoundOp, rhsNode: ts.Expression | null, loc: SrcLoc): IrStmt;
|
|
525
|
+
/** The primitive-constructor closure (`String`/`Number`/`Boolean` as a
|
|
526
|
+
* VALUE): interns one synthesized module function per constructor —
|
|
527
|
+
* `%builtin.String` et al. — and returns the zero-capture closure over it.
|
|
528
|
+
* The body IS the string-coercion the type mapping promised
|
|
529
|
+
* (`(value: string) => primitive`): String is identity, Number the ECMA
|
|
530
|
+
* StringToNumber (num.fromString — the same runtime call the direct
|
|
531
|
+
* `Number(s)` lowering emits), Boolean the emptiness test. Interning makes
|
|
532
|
+
* every reference the SAME immortal closure, so `opt.type === String`
|
|
533
|
+
* compares like JS function identity. */
|
|
534
|
+
export declare function primitiveCtorClosure(L: Lowerer, name: "String" | "Number" | "Boolean", loc: SrcLoc): IrExpr;
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* (FieldTarget). */
|
|
7
7
|
import * as ts from "../ts7/adapter.js";
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
|
-
import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, canDynCheckTo, isJsonSafeType, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey } from "../../ir/nodes.js";
|
|
9
|
+
import { BOOL, CAUGHT, DYN, DYN_HANDLE_KINDS, F64, JSVAL, NULL_T, REF_TRUTHY_KINDS, REGEX, RUNTIME_ERROR_CLASSES, SEARCH_PARAMS_T, STRING, UNDEFINED_T, VOID, arrayOf, canAdaptDynFuncTo, canBoxFuncIntoDyn, canDynCheckTo, funcOf, isJsonSafeType, isUnitType, jsOpResultKind, shapeHasAccessorSlots, typeEquals, typeKey, unionFuncSetArmsOk } from "../../ir/nodes.js";
|
|
10
10
|
import { cjsClassExprWholeExportOf, cjsExportAssignmentOf, cjsExportDiscardReason, isCjsExportTableLiteral, isCjsJsFile, isJsSourceFile, isModuleExportsAccess, isNodeEsmFile, locOf } from "../program.js";
|
|
11
11
|
import { ARRAY_METHODS, builtinConstLit, builtinFenceHintOf, builtinModuleConstOf, builtinModuleFnOf, ISLAND_SURFACE, isChildSurfaceMember, MAP_METHODS, NARROW_FIRST, SET_METHODS, STR_METHODS, UNSUPPORTED_EXPR, sideEffectFreeOptionValue, stdlibGlobalNameOf } from "./surfaces.js";
|
|
12
12
|
import { blockedBindingUseDiag, recordShapeMismatchDiag, requiresDynamicPackageDiag, unsupportedDiag } from "../../diagnostics/diagnostic.js";
|
|
13
|
-
import { PoisonError, jsFuncNameOf, neverTaintedJsType, own } from "./lowerer.js";
|
|
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
16
|
import { fenceEnumObjectValue, lowerEnumAccess } from "./lower-enums.js";
|
|
@@ -20,7 +20,7 @@ import { lowerSocketInstanceOf } from "./lower-server.js";
|
|
|
20
20
|
import { findGenericMethodOn, lowerStaticFieldRead } from "./lower-classes.js";
|
|
21
21
|
import { bindingNeverReassigned, implicitMonoFile, lowerTaggedTemplate, objLitGenericFnInfoOf, objLitGenericFnNodeOf, requireObjLitGenericReceiver } from "./lower-calls.js";
|
|
22
22
|
import { mixinFnOfCallee } from "./lower-mixins.js";
|
|
23
|
-
import { isGenericCallableMemberType, unitOnlyUnion } from "../types.js";
|
|
23
|
+
import { isConstAssertionTypeNode, isGenericCallableMemberType, underConstAssertion, unitOnlyUnion } from "../types.js";
|
|
24
24
|
import { lowerYield } from "./lower-generators.js";
|
|
25
25
|
import { lowerStreamProperty, lowerStreamStateProperty, streamSidesOf } from "./lower-stream.js";
|
|
26
26
|
/** A template piece's RAW text (String.raw's contract: escapes stay
|
|
@@ -195,7 +195,15 @@ function lowerExprInner(L, expr) {
|
|
|
195
195
|
(() => {
|
|
196
196
|
const ctxTs = L.checker.getContextualType(expr);
|
|
197
197
|
const mapped = (ctxTs ? L.mapTypeOf(ctxTs) : null) ?? L.mapTypeOf(L.typeOf(expr));
|
|
198
|
-
|
|
198
|
+
if (mapped?.kind !== "jsval")
|
|
199
|
+
return false;
|
|
200
|
+
// The tsgo readonly-[] panic repair (see lowerArrayLiteral): an
|
|
201
|
+
// EMPTY array literal under a const assertion is the empty tuple —
|
|
202
|
+
// its `any` answer is a panicked query, not an island slot.
|
|
203
|
+
if (ts.isArrayLiteralExpression(expr) && expr.elements.length === 0 && underConstAssertion(expr)) {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
199
207
|
})()) {
|
|
200
208
|
if (ts.isObjectLiteralExpression(expr)) {
|
|
201
209
|
// Two engine literals when a CONDITIONAL spread participates
|
|
@@ -537,6 +545,20 @@ function lowerExprInner(L, expr) {
|
|
|
537
545
|
return { kind: "numLit", value: expr.text === "NaN" ? NaN : Infinity, type: F64, loc };
|
|
538
546
|
}
|
|
539
547
|
}
|
|
548
|
+
// The primitive constructors as VALUES (`const f = String`, an
|
|
549
|
+
// option table's `type: Boolean` field, `opt.type === Number`): the
|
|
550
|
+
// interned coercion closure — one synthesized module function per
|
|
551
|
+
// constructor per program, so every reference is the SAME zero-
|
|
552
|
+
// capture closure and `===` is JS identity (the type mapping in
|
|
553
|
+
// types.ts pins the one concrete signature `(value: string) =>
|
|
554
|
+
// primitive`; direct calls `String(x)` never reach here — the call
|
|
555
|
+
// lowering intercepts them with the wider static coercions).
|
|
556
|
+
// JavaScript sources keep the identity-token path below.
|
|
557
|
+
if ((expr.text === "String" || expr.text === "Number" || expr.text === "Boolean") &&
|
|
558
|
+
!isJsSourceFile(expr.getSourceFile()) &&
|
|
559
|
+
L.isStdlibSymbol(L.checker.getSymbolAtLocation(expr))) {
|
|
560
|
+
return primitiveCtorClosure(L, expr.text, loc);
|
|
561
|
+
}
|
|
540
562
|
// The lib fence's IDENTIFIER chokepoint: the real standard library
|
|
541
563
|
// resolves names the old minimal ambient world never declared
|
|
542
564
|
// (Symbol, Reflect, Infinity, Date, ...) — and the adopted
|
|
@@ -938,6 +960,22 @@ function lowerExprInner(L, expr) {
|
|
|
938
960
|
// bound handle).
|
|
939
961
|
if (L.isIslandExpr(expr.expression)) {
|
|
940
962
|
const receiver = L.lowerExpr(expr.expression);
|
|
963
|
+
// The checker said 'any' but the VALUE lives in the DOM (`this`
|
|
964
|
+
// in a plain JS function — dyn.this — or a checked-dynamic local
|
|
965
|
+
// behind an any-typed spelling): the property read is the DOM's
|
|
966
|
+
// own keyed read, dyn results and dyn chains exactly like every
|
|
967
|
+
// checked-dynamic member access (a nullish receiver throws V8's
|
|
968
|
+
// catchable TypeError, exactly Node). Never a jsOp over a dyn —
|
|
969
|
+
// the two dynamic worlds don't share a value representation.
|
|
970
|
+
if (receiver.type.kind === "dyn") {
|
|
971
|
+
return {
|
|
972
|
+
kind: "dynKeyGet",
|
|
973
|
+
key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
|
|
974
|
+
value: receiver,
|
|
975
|
+
type: DYN,
|
|
976
|
+
loc,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
941
979
|
const read = { kind: "jsOp", op: "getProp", name: expr.name.text, args: [receiver], type: JSVAL, loc };
|
|
942
980
|
// A member the .d.ts DECLARES as a primitive exits eagerly to that
|
|
943
981
|
// static type (`f.mediaType` on a package handle IS a string):
|
|
@@ -2835,6 +2873,17 @@ export function lowerArrayLiteral(L, expr, expected) {
|
|
|
2835
2873
|
mapped = arrayArms[0];
|
|
2836
2874
|
}
|
|
2837
2875
|
}
|
|
2876
|
+
// An EMPTY literal under a CONST ASSERTION whose type queries panicked
|
|
2877
|
+
// (tsgo's readonly-[] TupleType conversion — the facade answers `any`,
|
|
2878
|
+
// which maps to nothing statically and to an island value under
|
|
2879
|
+
// --dynamic): the value is provably the empty tuple; ride the
|
|
2880
|
+
// unit-element array, mapType's own `[] as const` rule.
|
|
2881
|
+
if ((mapped === null || mapped.kind === "jsval") &&
|
|
2882
|
+
expr.elements.length === 0 &&
|
|
2883
|
+
(tsType.flags & ts.TypeFlags.Any) !== 0 &&
|
|
2884
|
+
underConstAssertion(expr)) {
|
|
2885
|
+
mapped = arrayOf(unitOnlyUnion(L.unions));
|
|
2886
|
+
}
|
|
2838
2887
|
// A TUPLE-typed slot (`const t: [string, number] = ["a", 1]`): the
|
|
2839
2888
|
// literal constructs the tuple's record shape — one positional field
|
|
2840
2889
|
// per element, source order (which IS index order, so evaluation order
|
|
@@ -3301,6 +3350,102 @@ function fenceClosureProbe(L, node, slotType, attempt) {
|
|
|
3301
3350
|
return { kind: "closure", fnName: name, captures: [], type: fieldType, loc };
|
|
3302
3351
|
}
|
|
3303
3352
|
}
|
|
3353
|
+
/** The union arm an object LITERAL inhabits when its fields must widen
|
|
3354
|
+
* PER FIELD into exactly one record arm — the reducer-action pattern
|
|
3355
|
+
* (`{ kind: "a", parsed: 1 }` into `{ kind: "a"; parsed: number | null }
|
|
3356
|
+
* | { kind: "b" }`). The IR shapes erased the literal types tsc
|
|
3357
|
+
* discriminated on, so the probe runs against the contextual union's
|
|
3358
|
+
* CHECKER members: every literal field must exist on the member
|
|
3359
|
+
* (excess-property freshness — tsc already enforced it), every arm field
|
|
3360
|
+
* missing from the literal must be optional-flavored (an undefined-armed
|
|
3361
|
+
* union, or a dyn slot — the absent-completion rule), and each present
|
|
3362
|
+
* field's LITERAL type must fit the member's field type — literal against
|
|
3363
|
+
* literal decides by VALUE (the discriminant), everything else by the
|
|
3364
|
+
* widened IR pair under the width-lift relation. Exactly ONE fitting arm
|
|
3365
|
+
* answers it; zero or several answer null and the caller keeps its
|
|
3366
|
+
* fences. Plain property-assignment/shorthand literals only — spreads,
|
|
3367
|
+
* accessors, methods, and unfoldable computed keys keep their own paths. */
|
|
3368
|
+
function literalUnionArmOf(L, expr, tsType, recordArms) {
|
|
3369
|
+
if (!tsType.isUnionType())
|
|
3370
|
+
return null;
|
|
3371
|
+
const props = [];
|
|
3372
|
+
for (const p of expr.properties) {
|
|
3373
|
+
if (ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name)) {
|
|
3374
|
+
props.push({ name: propNameText(L, p.name), node: p.initializer });
|
|
3375
|
+
}
|
|
3376
|
+
else if (ts.isShorthandPropertyAssignment(p) && ts.isIdentifier(p.name)) {
|
|
3377
|
+
props.push({ name: p.name.text, node: p.name });
|
|
3378
|
+
}
|
|
3379
|
+
else {
|
|
3380
|
+
return null;
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
/** litT fits ftT: unions per arm; literal-vs-literal by value; unit
|
|
3384
|
+
* types only into their own unit; otherwise the widened IR pair must be
|
|
3385
|
+
* equal or width-liftable. */
|
|
3386
|
+
const fits = (litT, ftT) => {
|
|
3387
|
+
if (ftT.isUnionType())
|
|
3388
|
+
return ftT.getTypes().some((a) => fits(litT, a));
|
|
3389
|
+
if (ftT.isStringLiteralType())
|
|
3390
|
+
return litT.isStringLiteralType() && litT.value === ftT.value;
|
|
3391
|
+
if (ftT.isNumberLiteralType())
|
|
3392
|
+
return litT.isNumberLiteralType() && litT.value === ftT.value;
|
|
3393
|
+
if (ftT.flags & ts.TypeFlags.BooleanLiteral) {
|
|
3394
|
+
return (litT.flags & ts.TypeFlags.BooleanLiteral) !== 0 && L.checker.typeToString(litT) === L.checker.typeToString(ftT);
|
|
3395
|
+
}
|
|
3396
|
+
if (ftT.flags & ts.TypeFlags.Null)
|
|
3397
|
+
return (litT.flags & ts.TypeFlags.Null) !== 0;
|
|
3398
|
+
if (ftT.flags & ts.TypeFlags.Undefined)
|
|
3399
|
+
return (litT.flags & ts.TypeFlags.Undefined) !== 0;
|
|
3400
|
+
if (litT.flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined))
|
|
3401
|
+
return false;
|
|
3402
|
+
const li = L.mapTypeOf(L.checker.getBaseTypeOfLiteralType(litT));
|
|
3403
|
+
const fi = L.mapTypeOf(ftT);
|
|
3404
|
+
if (!li || !fi)
|
|
3405
|
+
return false;
|
|
3406
|
+
return typeEquals(li, fi) || L.widthLiftPlan(li, fi) !== null;
|
|
3407
|
+
};
|
|
3408
|
+
const armShapeIds = new Set(recordArms.map((a) => a.shapeId));
|
|
3409
|
+
const candidates = new Set();
|
|
3410
|
+
for (const member of tsType.getTypes()) {
|
|
3411
|
+
const mMapped = L.mapTypeOf(member);
|
|
3412
|
+
if (mMapped?.kind !== "record" || !armShapeIds.has(mMapped.shapeId) || candidates.has(mMapped.shapeId))
|
|
3413
|
+
continue;
|
|
3414
|
+
const shape = L.shapes.get(mMapped.shapeId);
|
|
3415
|
+
if (!shape || shape.tuple || shape.indexValue)
|
|
3416
|
+
continue;
|
|
3417
|
+
// Excess-property freshness: every literal field must exist on the
|
|
3418
|
+
// member (tsc rejected the others for a fresh literal).
|
|
3419
|
+
if (!props.every((p) => L.checker.getPropertyOfType(member, p.name) !== undefined))
|
|
3420
|
+
continue;
|
|
3421
|
+
// Arm fields the literal leaves unset must be optional-flavored (the
|
|
3422
|
+
// absent-completion rule: an undefined-armed union or a dyn slot).
|
|
3423
|
+
const names = new Set(props.map((p) => p.name));
|
|
3424
|
+
const absentOk = shape.fields.every((f) => {
|
|
3425
|
+
if (names.has(f.name))
|
|
3426
|
+
return true;
|
|
3427
|
+
if (f.type.kind === "dyn")
|
|
3428
|
+
return true;
|
|
3429
|
+
if (f.type.kind !== "union")
|
|
3430
|
+
return false;
|
|
3431
|
+
return L.unions.get(f.type.unionId)?.arms.some((a) => a.kind === "undefinedT") ?? false;
|
|
3432
|
+
});
|
|
3433
|
+
if (!absentOk)
|
|
3434
|
+
continue;
|
|
3435
|
+
const fieldsFit = props.every((p) => {
|
|
3436
|
+
const sym = L.checker.getPropertyOfType(member, p.name);
|
|
3437
|
+
if (!sym)
|
|
3438
|
+
return false;
|
|
3439
|
+
return fits(L.typeOf(p.node), L.checker.getTypeOfSymbol(sym));
|
|
3440
|
+
});
|
|
3441
|
+
if (fieldsFit)
|
|
3442
|
+
candidates.add(mMapped.shapeId);
|
|
3443
|
+
}
|
|
3444
|
+
if (candidates.size !== 1)
|
|
3445
|
+
return null;
|
|
3446
|
+
const [only] = candidates;
|
|
3447
|
+
return recordArms.find((a) => a.shapeId === only) ?? null;
|
|
3448
|
+
}
|
|
3304
3449
|
export function lowerObjectLiteral(L, expr) {
|
|
3305
3450
|
const loc = locOf(expr);
|
|
3306
3451
|
// The RUNTIME-KEYED literal (JS): a computed key that doesn't fold to a
|
|
@@ -3491,6 +3636,26 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
3491
3636
|
mapped = recordArms[0];
|
|
3492
3637
|
}
|
|
3493
3638
|
}
|
|
3639
|
+
else if (recordArms.length > 1) {
|
|
3640
|
+
const ownShapeId = mapped?.kind === "record" ? mapped.shapeId : null;
|
|
3641
|
+
// SEVERAL record arms (the reducer-action / discriminated-message
|
|
3642
|
+
// pattern): the literal's own inferred shape re-tags into no arm
|
|
3643
|
+
// — its field types widened per field (`parsed: 1` against
|
|
3644
|
+
// `parsed: number | null`), so the IR-level candidate probe is
|
|
3645
|
+
// ambiguous (a narrower arm also admits the literal by dropping
|
|
3646
|
+
// fields). The LITERAL types tsc checked carry the discriminant
|
|
3647
|
+
// the shapes erased: match the literal's fields against each
|
|
3648
|
+
// union member's CHECKER types (literal-vs-literal field pairs
|
|
3649
|
+
// decide by value — the `kind: "a"` discriminant), and when
|
|
3650
|
+
// exactly ONE member fits, build AS that arm — its field types
|
|
3651
|
+
// drive every property's coercion, exactly the single-record-arm
|
|
3652
|
+
// rule above. Ambiguous literals keep the SC2003 fence.
|
|
3653
|
+
if (ownShapeId === null || !recordArms.some((a) => a.shapeId === ownShapeId)) {
|
|
3654
|
+
const arm = literalUnionArmOf(L, expr, tsType, recordArms);
|
|
3655
|
+
if (arm)
|
|
3656
|
+
mapped = arm;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3494
3659
|
}
|
|
3495
3660
|
}
|
|
3496
3661
|
// The CJS EXPORT-TABLE literal in VALUE position (JS): importers reach
|
|
@@ -4177,9 +4342,13 @@ export function lowerObjectLiteral(L, expr) {
|
|
|
4177
4342
|
for (const f of shape.fields) {
|
|
4178
4343
|
if (provided.has(f.name))
|
|
4179
4344
|
continue;
|
|
4180
|
-
|
|
4345
|
+
// 'unknown' fields complete with the DOM undefined — the absent
|
|
4346
|
+
// property reads as undefined in Node, and a dyn slot holds
|
|
4347
|
+
// exactly that (the options-record call shape against
|
|
4348
|
+
// `{ plugins: unknown, ... }` — a JS caller the checker admits).
|
|
4349
|
+
const absent = L.wrappedUndefined(f.type, loc) ?? (f.type.kind === "dyn" ? dynUndefinedExpr(loc) : null);
|
|
4181
4350
|
if (!absent)
|
|
4182
|
-
throw shapeMismatch(expr); // only optional (undefined-armed) fields may be omitted
|
|
4351
|
+
throw shapeMismatch(expr); // only optional (undefined-armed) and 'unknown' fields may be omitted
|
|
4183
4352
|
fields.push({ name: f.name, value: absent });
|
|
4184
4353
|
}
|
|
4185
4354
|
}
|
|
@@ -5257,6 +5426,17 @@ export function lowerTemplate(L, expr) {
|
|
|
5257
5426
|
/** `e as T` and the old-style assertion `<T>e` — one node shape (both
|
|
5258
5427
|
* carry `.type` and `.expression`), one lowering. */
|
|
5259
5428
|
export function lowerAsExpression(L, expr) {
|
|
5429
|
+
// `[] as const` — tsgo panics computing the expression's `readonly []`
|
|
5430
|
+
// type (the facade's fence answers `any`), but the syntax pins the
|
|
5431
|
+
// value exactly: the empty tuple, ridden as the unit-element array
|
|
5432
|
+
// (mapType's empty-tuple rule). Lower it directly; enclosing slots
|
|
5433
|
+
// coerce like any other empty-array source.
|
|
5434
|
+
if (ts.isAsExpression(expr) &&
|
|
5435
|
+
isConstAssertionTypeNode(expr.type) &&
|
|
5436
|
+
ts.isArrayLiteralExpression(expr.expression) &&
|
|
5437
|
+
expr.expression.elements.length === 0) {
|
|
5438
|
+
return { kind: "arrayLit", elems: [], type: arrayOf(unitOnlyUnion(L.unions)), loc: locOf(expr) };
|
|
5439
|
+
}
|
|
5260
5440
|
// `e as C` on a CATCH BINDING (`(err as Error).message`): the checked
|
|
5261
5441
|
// extraction — an instanceof match extracts the payload, anything else
|
|
5262
5442
|
// throws the catchable TypeError (dynCheck's trust-but-verify stance,
|
|
@@ -5635,6 +5815,22 @@ export function lowerBinary(L, expr) {
|
|
|
5635
5815
|
const nullTest = lowerLooseNullCompare(L, expr, loc);
|
|
5636
5816
|
if (nullTest)
|
|
5637
5817
|
return nullTest;
|
|
5818
|
+
// SAME-KIND loose equality IS strict equality (the spec's ==
|
|
5819
|
+
// dispatches to === when both operands share a type): `typeof v ==
|
|
5820
|
+
// 'object'`, `n != 0`, `flag == true` all lower exactly. Mixed
|
|
5821
|
+
// kinds (where == coerces) keep the fence.
|
|
5822
|
+
{
|
|
5823
|
+
const negated = op === ts.SyntaxKind.ExclamationEqualsToken;
|
|
5824
|
+
const left = L.lowerExpr(expr.left);
|
|
5825
|
+
const right = L.lowerExpr(expr.right);
|
|
5826
|
+
if (left.type.kind === "string" && right.type.kind === "string") {
|
|
5827
|
+
return { kind: "strEq", negated, left, right, type: BOOL, loc };
|
|
5828
|
+
}
|
|
5829
|
+
if ((left.type.kind === "f64" && right.type.kind === "f64") ||
|
|
5830
|
+
(left.type.kind === "bool" && right.type.kind === "bool")) {
|
|
5831
|
+
return { kind: "bin", op: negated ? "!==" : "===", left, right, type: BOOL, loc };
|
|
5832
|
+
}
|
|
5833
|
+
}
|
|
5638
5834
|
L.unsupported("SC1040", expr);
|
|
5639
5835
|
}
|
|
5640
5836
|
if (op === ts.SyntaxKind.AmpersandAmpersandToken || op === ts.SyntaxKind.BarBarToken) {
|
|
@@ -6940,6 +7136,147 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
6940
7136
|
}
|
|
6941
7137
|
L.unsupported("SC1090", expr, "statically-decided 'instanceof' on computed operands (bind the value to a variable first)");
|
|
6942
7138
|
}
|
|
7139
|
+
/** `#name in obj` — the ergonomic brand check. In this closed world a
|
|
7140
|
+
* brand is held by exactly the instances of the declaring class
|
|
7141
|
+
* (subclasses included — construction always runs the declaring class's
|
|
7142
|
+
* own initializers), so the test IS `obj instanceof <declaring class>`:
|
|
7143
|
+
* statically decided when the receiver's class sits at/below the
|
|
7144
|
+
* declarer (true) or in a disjoint subtree (false) — both folds under
|
|
7145
|
+
* the instanceof purity rule — and a runtime interval test when the
|
|
7146
|
+
* declarer sits strictly BELOW the receiver's static class (the
|
|
7147
|
+
* narrowing use; tsc types the true branch at the class, and reads
|
|
7148
|
+
* bridge through maybeNarrow's downcast exactly like instanceof). tsc
|
|
7149
|
+
* confines the spelling to the declaring class's body and rejects
|
|
7150
|
+
* primitive/unknown receivers, so Node's in-operator TypeError is
|
|
7151
|
+
* unreachable in compilable programs. Timing residue: JS installs
|
|
7152
|
+
* brands DURING construction, so a check reachable from a base
|
|
7153
|
+
* constructor can observe false mid-construction where this answers
|
|
7154
|
+
* true (SEMANTICS.md). */
|
|
7155
|
+
function lowerPrivateIn(L, expr, priv, loc) {
|
|
7156
|
+
const pname = priv.text;
|
|
7157
|
+
// The declaring class: the nearest enclosing class declaring the name
|
|
7158
|
+
// (JS scoping — an inner class's spelling shadows an outer one's).
|
|
7159
|
+
let classDecl = null;
|
|
7160
|
+
let staticBrand = false;
|
|
7161
|
+
for (let n = priv.parent; n; n = n.parent) {
|
|
7162
|
+
if (ts.isClassDeclaration(n) || ts.isClassExpression(n)) {
|
|
7163
|
+
const owner = n.members.find((m) => {
|
|
7164
|
+
const name = m.name;
|
|
7165
|
+
return name !== undefined && ts.isPrivateIdentifier(name) && name.text === pname;
|
|
7166
|
+
});
|
|
7167
|
+
if (owner) {
|
|
7168
|
+
classDecl = n;
|
|
7169
|
+
staticBrand =
|
|
7170
|
+
ts.canHaveModifiers(owner) &&
|
|
7171
|
+
ts.getModifiers(owner)?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) === true;
|
|
7172
|
+
break;
|
|
7173
|
+
}
|
|
7174
|
+
}
|
|
7175
|
+
}
|
|
7176
|
+
// tsc rejects the spelling outside a declaring class body; defensive.
|
|
7177
|
+
if (!classDecl)
|
|
7178
|
+
L.unsupported("SC1090", expr, `'${pname} in …' outside a class declaring '${pname}'`);
|
|
7179
|
+
if (staticBrand) {
|
|
7180
|
+
L.unsupported("SC1090", expr, `'${pname} in …' brand checks for private STATICS (JS brands the declaring class OBJECT, not instances — compare against the class value directly)`);
|
|
7181
|
+
}
|
|
7182
|
+
const info = L.currentClass?.decl === classDecl
|
|
7183
|
+
? L.currentClass
|
|
7184
|
+
: (() => {
|
|
7185
|
+
const sym = classDecl.name ? L.checker.getSymbolAtLocation(classDecl.name) : undefined;
|
|
7186
|
+
return sym ? L.classBySymbol.get(sym) : undefined;
|
|
7187
|
+
})();
|
|
7188
|
+
if (!info) {
|
|
7189
|
+
L.unsupported("SC1090", expr, `'${pname} in …' where the declaring class has no lowering (see the class declaration's own diagnostic)`);
|
|
7190
|
+
}
|
|
7191
|
+
// A GENERIC family: JS has ONE runtime class and ONE brand for every
|
|
7192
|
+
// instantiation, while these layouts mint one class per instantiation
|
|
7193
|
+
// — a cross-instantiation check would answer false where Node says
|
|
7194
|
+
// true, so the family fences rather than silently splitting the brand.
|
|
7195
|
+
if (info.generic || info.genericInstance) {
|
|
7196
|
+
L.unsupported("SC1090", expr, `'${pname} in …' inside a generic class (JS shares one brand across every instantiation; these layouts mint one class per instantiation)`);
|
|
7197
|
+
}
|
|
7198
|
+
const recv = L.lowerExpr(expr.right);
|
|
7199
|
+
const declName = info.def.name;
|
|
7200
|
+
// A UNION of class instances (`o: Counter | Helper` — the
|
|
7201
|
+
// discriminating use): every object arm answers membership STATICALLY
|
|
7202
|
+
// (at/below the declarer → true, disjoint subtree → false), so the
|
|
7203
|
+
// whole test collapses to runtime TAG tests — the record-shape 'in'
|
|
7204
|
+
// discrimination, brand form. An arm ABOVE the declarer answers per
|
|
7205
|
+
// VALUE (its slot can hold branded and unbranded instances), and
|
|
7206
|
+
// non-object arms have no brand story — both fence.
|
|
7207
|
+
if (recv.type.kind === "union") {
|
|
7208
|
+
const unionId = recv.type.unionId;
|
|
7209
|
+
const arms = L.unions.get(unionId)?.arms ?? [];
|
|
7210
|
+
const answers = [];
|
|
7211
|
+
let staticAnswers = arms.length > 0;
|
|
7212
|
+
for (const arm of arms) {
|
|
7213
|
+
const tag = L.armTag(unionId, arm);
|
|
7214
|
+
if (tag < 0 || arm.kind !== "object" || L.isSubclassOf(declName, arm.className)) {
|
|
7215
|
+
staticAnswers = false;
|
|
7216
|
+
break;
|
|
7217
|
+
}
|
|
7218
|
+
answers.push({ tag, has: arm.className === declName || L.isSubclassOf(arm.className, declName) });
|
|
7219
|
+
}
|
|
7220
|
+
if (staticAnswers) {
|
|
7221
|
+
const pureRecv = recv.kind === "varRef" || recv.kind === "recordGet" || recv.kind === "fieldGet";
|
|
7222
|
+
const isTag = (tag, negated) => ({
|
|
7223
|
+
kind: "unionIsTag",
|
|
7224
|
+
unionId,
|
|
7225
|
+
tag,
|
|
7226
|
+
negated,
|
|
7227
|
+
value: recv,
|
|
7228
|
+
type: BOOL,
|
|
7229
|
+
loc,
|
|
7230
|
+
});
|
|
7231
|
+
const trues = answers.filter((a) => a.has);
|
|
7232
|
+
if (trues.length === answers.length || trues.length === 0) {
|
|
7233
|
+
const ans = trues.length !== 0;
|
|
7234
|
+
if (pureRecv)
|
|
7235
|
+
return { kind: "boolLit", value: ans, type: BOOL, loc };
|
|
7236
|
+
// Constant either way, receiver still evaluates once: one tag
|
|
7237
|
+
// test whose branches agree (the record-'in' rule verbatim).
|
|
7238
|
+
return {
|
|
7239
|
+
kind: "ternary",
|
|
7240
|
+
cond: isTag(answers[0].tag, false),
|
|
7241
|
+
then: { kind: "boolLit", value: ans, type: BOOL, loc },
|
|
7242
|
+
else_: { kind: "boolLit", value: ans, type: BOOL, loc },
|
|
7243
|
+
type: BOOL,
|
|
7244
|
+
loc,
|
|
7245
|
+
};
|
|
7246
|
+
}
|
|
7247
|
+
if (trues.length === 1)
|
|
7248
|
+
return isTag(trues[0].tag, false);
|
|
7249
|
+
const falses = answers.filter((a) => !a.has);
|
|
7250
|
+
if (falses.length === 1)
|
|
7251
|
+
return isTag(falses[0].tag, true);
|
|
7252
|
+
if (pureRecv) {
|
|
7253
|
+
let out = isTag(trues[0].tag, false);
|
|
7254
|
+
for (const t of trues.slice(1)) {
|
|
7255
|
+
out = { kind: "logical", op: "||", left: out, right: isTag(t.tag, false), type: BOOL, loc };
|
|
7256
|
+
}
|
|
7257
|
+
return out;
|
|
7258
|
+
}
|
|
7259
|
+
L.unsupported("SC1090", expr, `statically-decided '${pname} in …' on computed receivers (bind the value to a variable first)`);
|
|
7260
|
+
}
|
|
7261
|
+
}
|
|
7262
|
+
if (recv.type.kind !== "object") {
|
|
7263
|
+
L.unsupported("SC1090", expr, `'${pname} in …' on '${L.fmt(recv.type)}' receivers (only class-instance receivers — and unions of classes below or beside the declarer — have a static brand answer; narrow first)`);
|
|
7264
|
+
}
|
|
7265
|
+
const lhsName = recv.type.className;
|
|
7266
|
+
const lhsInfo = L.classes.get(lhsName);
|
|
7267
|
+
if (!lhsInfo)
|
|
7268
|
+
throw new Error(`lowerer bug: unknown class ${lhsName}`);
|
|
7269
|
+
if (L.isSubclassOf(declName, lhsName)) {
|
|
7270
|
+
// The narrowing direction: the declarer strictly below the
|
|
7271
|
+
// receiver's static class — a strict subclass relation puts both in
|
|
7272
|
+
// a hierarchy, so the vtable interval test always exists.
|
|
7273
|
+
return { kind: "instanceOf", value: recv, className: declName, type: BOOL, loc };
|
|
7274
|
+
}
|
|
7275
|
+
const value = lhsName === declName || L.isSubclassOf(lhsName, declName);
|
|
7276
|
+
if (recv.kind === "varRef")
|
|
7277
|
+
return { kind: "boolLit", value, type: BOOL, loc };
|
|
7278
|
+
L.unsupported("SC1090", expr, `statically-decided '${pname} in …' on computed operands (bind the value to a variable first)`);
|
|
7279
|
+
}
|
|
6943
7280
|
/** `"key" in v` — the key-presence test. Three lowered receivers:
|
|
6944
7281
|
* process.env (getenv(3) presence — Node-exact, an empty value still
|
|
6945
7282
|
* counts as present), and monomorphic record shapes, where the answer is
|
|
@@ -6955,6 +7292,12 @@ export function lowerInstanceOf(L, expr, loc) {
|
|
|
6955
7292
|
* and dyn/unknown stay fenced. Keys are literal strings — a computed key
|
|
6956
7293
|
* over a shape would need the runtime key table. */
|
|
6957
7294
|
export function lowerInExpression(L, expr, loc) {
|
|
7295
|
+
// `#name in obj` — the ergonomic brand check (ES2022) — resolves
|
|
7296
|
+
// before any string-key machinery: the left operand is a private
|
|
7297
|
+
// NAME, not a value.
|
|
7298
|
+
if (ts.isPrivateIdentifier(expr.left)) {
|
|
7299
|
+
return lowerPrivateIn(L, expr, expr.left, loc);
|
|
7300
|
+
}
|
|
6958
7301
|
// Compile-time-known STRING keys fold — literals, and the same
|
|
6959
7302
|
// const/enum-literal and template folding computed property keys get
|
|
6960
7303
|
// (foldedStringKeyOf); runtime-valued keys keep the fence.
|
|
@@ -7522,6 +7865,16 @@ function lowerUnionKeyedRead(L, expr, unionId, value, key, literalField) {
|
|
|
7522
7865
|
}
|
|
7523
7866
|
if (joinArms.length === 0)
|
|
7524
7867
|
return null;
|
|
7868
|
+
// The join must be a BUILDABLE union: arm kinds the union invariants
|
|
7869
|
+
// admit (no map/dyn/jsval/generator arms; func arms only beside
|
|
7870
|
+
// func/unit siblings — unionFuncSetArmsOk, the validator's rule). A
|
|
7871
|
+
// join mixing, say, a func answer with data answers has no union to
|
|
7872
|
+
// surface as; the caller owns the fence message.
|
|
7873
|
+
if (joinArms.length > 1 &&
|
|
7874
|
+
(!unionFuncSetArmsOk(joinArms) ||
|
|
7875
|
+
joinArms.some((a) => a.kind === "map" || a.kind === "dyn" || a.kind === "jsval" || a.kind === "generator" || a.kind === "caught"))) {
|
|
7876
|
+
return null;
|
|
7877
|
+
}
|
|
7525
7878
|
joinArms.sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
|
7526
7879
|
const type = joinArms.length === 1 ? joinArms[0] : { kind: "union", unionId: L.unions.intern(joinArms) };
|
|
7527
7880
|
// Pass 2: every arm's answer must SURFACE as the join, and index arms
|
|
@@ -7824,9 +8177,23 @@ export function fieldGetExpr(L, target, loc, blame) {
|
|
|
7824
8177
|
loc,
|
|
7825
8178
|
};
|
|
7826
8179
|
}
|
|
7827
|
-
|
|
7828
|
-
|
|
7829
|
-
|
|
8180
|
+
if (target.container === "class") {
|
|
8181
|
+
const read = { kind: "fieldGet", obj: target.obj, className: target.className, field: target.field, type: target.fieldType, loc };
|
|
8182
|
+
// DEFERRED-INIT fields (`stream!: T` assigned past the constructor's
|
|
8183
|
+
// top level — ClassInfo.deferredInitFields): the SLOT is the
|
|
8184
|
+
// undefined-armed union; the read CHECKED-extracts the declared type
|
|
8185
|
+
// — a genuinely unassigned read throws the catchable TypeError
|
|
8186
|
+
// where Node reads an undefined the declared type cannot hold.
|
|
8187
|
+
if (L.classes.get(target.className)?.deferredInitFields?.has(target.field) === true &&
|
|
8188
|
+
target.fieldType.kind === "union") {
|
|
8189
|
+
const inner = L.stripUndefinedArm(target.fieldType);
|
|
8190
|
+
const helper = L.deferredReadHelper(target.fieldType.unionId, inner, loc);
|
|
8191
|
+
if (helper)
|
|
8192
|
+
return { kind: "call", callee: helper, args: [read], type: inner, loc };
|
|
8193
|
+
}
|
|
8194
|
+
return read;
|
|
8195
|
+
}
|
|
8196
|
+
return { kind: "recordGet", obj: target.obj, shapeId: target.shapeId, field: target.field, type: target.fieldType, loc };
|
|
7830
8197
|
}
|
|
7831
8198
|
/** The write statement for a field target (fieldSet / recordSet / setter
|
|
7832
8199
|
* call). A write to a getter-only property never gets here in a clean
|
|
@@ -8108,4 +8475,38 @@ function lowerStreamObjectProperty(L, expr) {
|
|
|
8108
8475
|
return null;
|
|
8109
8476
|
return lowerStreamProperty(L, expr, info);
|
|
8110
8477
|
}
|
|
8478
|
+
/** The primitive-constructor closure (`String`/`Number`/`Boolean` as a
|
|
8479
|
+
* VALUE): interns one synthesized module function per constructor —
|
|
8480
|
+
* `%builtin.String` et al. — and returns the zero-capture closure over it.
|
|
8481
|
+
* The body IS the string-coercion the type mapping promised
|
|
8482
|
+
* (`(value: string) => primitive`): String is identity, Number the ECMA
|
|
8483
|
+
* StringToNumber (num.fromString — the same runtime call the direct
|
|
8484
|
+
* `Number(s)` lowering emits), Boolean the emptiness test. Interning makes
|
|
8485
|
+
* every reference the SAME immortal closure, so `opt.type === String`
|
|
8486
|
+
* compares like JS function identity. */
|
|
8487
|
+
export function primitiveCtorClosure(L, name, loc) {
|
|
8488
|
+
const ret = name === "String" ? STRING : name === "Number" ? F64 : BOOL;
|
|
8489
|
+
const fnT = funcOf([STRING], ret);
|
|
8490
|
+
let fnName = L.primitiveCtorFns.get(name);
|
|
8491
|
+
if (!fnName) {
|
|
8492
|
+
fnName = `%builtin.${name}`;
|
|
8493
|
+
L.primitiveCtorFns.set(name, fnName);
|
|
8494
|
+
const s = { kind: "varRef", localId: "v.0", type: STRING, loc };
|
|
8495
|
+
const value = name === "String"
|
|
8496
|
+
? s
|
|
8497
|
+
: name === "Number"
|
|
8498
|
+
? { kind: "libCall", fn: "num.fromString", args: [s], type: F64, loc }
|
|
8499
|
+
: { kind: "strEq", negated: true, left: s, right: { kind: "strLit", value: "", type: STRING, loc }, type: BOOL, loc };
|
|
8500
|
+
const fn = {
|
|
8501
|
+
name: fnName,
|
|
8502
|
+
params: [{ localId: "v.0", name: "value", type: STRING }],
|
|
8503
|
+
returnType: ret,
|
|
8504
|
+
locals: [{ id: "v.0", name: "value", type: STRING, mutable: false }],
|
|
8505
|
+
body: [{ kind: "return", value, loc }],
|
|
8506
|
+
loc,
|
|
8507
|
+
};
|
|
8508
|
+
L.liftedFns.push(fn);
|
|
8509
|
+
}
|
|
8510
|
+
return { kind: "closure", fnName, captures: [], type: fnT, loc };
|
|
8511
|
+
}
|
|
8111
8512
|
//# sourceMappingURL=lower-exprs.js.map
|