@scriptc/compiler 0.2.1 → 0.2.2
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 +59 -4
- package/dist/backend/llvm/dyn.d.ts +3 -0
- package/dist/backend/llvm/dyn.js +84 -12
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +4 -1
- package/dist/backend/llvm/emitter.js +20 -5
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +9 -1
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +5 -0
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +30 -5
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +70 -9
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-callbacks.d.ts +2 -2
- package/dist/backend/llvm/expr-stream-callbacks.js +7 -57
- package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +41 -3
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +12 -9
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +35 -1
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/may-throw.js +4 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +1 -1
- package/dist/backend/native-codegen.js +4 -3
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +11 -0
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/target-diagnostics.d.ts +5 -0
- package/dist/backend/target-diagnostics.js +72 -1
- package/dist/backend/target-diagnostics.js.map +1 -1
- package/dist/backend/target-diagnostics.test.d.ts +1 -0
- package/dist/backend/target-diagnostics.test.js +68 -0
- package/dist/backend/target-diagnostics.test.js.map +1 -0
- package/dist/coverage/surface-manifest.js +5 -3
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +7 -3
- package/dist/executable/executable-cache.js +47 -11
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +109 -2
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/executable/prepare.js +2 -1
- package/dist/executable/prepare.js.map +1 -1
- package/dist/frontend/builtin-modules.d.ts +1 -1
- package/dist/frontend/builtin-modules.js +1 -1
- package/dist/frontend/builtin-modules.js.map +1 -1
- package/dist/frontend/function-completion.d.ts +6 -0
- package/dist/frontend/function-completion.js +50 -0
- package/dist/frontend/function-completion.js.map +1 -0
- package/dist/frontend/lowering/abstract-equality.js +5 -1
- package/dist/frontend/lowering/abstract-equality.js.map +1 -1
- package/dist/frontend/lowering/checked-iterable-spread.d.ts +4 -0
- package/dist/frontend/lowering/checked-iterable-spread.js +30 -0
- package/dist/frontend/lowering/checked-iterable-spread.js.map +1 -0
- package/dist/frontend/lowering/checked-promise-all.d.ts +5 -0
- package/dist/frontend/lowering/checked-promise-all.js +31 -0
- package/dist/frontend/lowering/checked-promise-all.js.map +1 -0
- package/dist/frontend/lowering/class-callbacks.d.ts +1 -1
- package/dist/frontend/lowering/class-callbacks.js +7 -6
- package/dist/frontend/lowering/class-callbacks.js.map +1 -1
- package/dist/frontend/lowering/class-construction.d.ts +2 -0
- package/dist/frontend/lowering/class-construction.js +34 -5
- package/dist/frontend/lowering/class-construction.js.map +1 -1
- package/dist/frontend/lowering/class-descriptors.d.ts +1 -1
- package/dist/frontend/lowering/class-descriptors.js +12 -5
- package/dist/frontend/lowering/class-descriptors.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +25 -0
- package/dist/frontend/lowering/class-dynamic-dispatch.js +597 -78
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js +64 -2
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -1
- package/dist/frontend/lowering/class-instance-constructor.d.ts +1 -1
- package/dist/frontend/lowering/class-instance-constructor.js +11 -41
- package/dist/frontend/lowering/class-instance-constructor.js.map +1 -1
- package/dist/frontend/lowering/class-method-values.d.ts +16 -0
- package/dist/frontend/lowering/class-method-values.js +60 -29
- package/dist/frontend/lowering/class-method-values.js.map +1 -1
- package/dist/frontend/lowering/class-prototypes.d.ts +5 -1
- package/dist/frontend/lowering/class-prototypes.js +69 -10
- package/dist/frontend/lowering/class-prototypes.js.map +1 -1
- package/dist/frontend/lowering/class-runtime-statics.d.ts +4 -4
- package/dist/frontend/lowering/class-runtime-statics.js +26 -8
- package/dist/frontend/lowering/class-runtime-statics.js.map +1 -1
- package/dist/frontend/lowering/class-static-data.d.ts +1 -0
- package/dist/frontend/lowering/class-static-data.js +4 -2
- package/dist/frontend/lowering/class-static-data.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +17 -4
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.js +6 -1
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/deferred-module-initializers.d.ts +13 -0
- package/dist/frontend/lowering/deferred-module-initializers.js +410 -0
- package/dist/frontend/lowering/deferred-module-initializers.js.map +1 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.d.ts +1 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.js +92 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.js.map +1 -0
- package/dist/frontend/lowering/expressions/object-literals.js +31 -1
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/expressions/optional-chains.js +3 -3
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +4 -9
- package/dist/frontend/lowering/lower-builtins.js +291 -51
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +283 -73
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +5 -0
- package/dist/frontend/lowering/lower-classes.js +439 -25
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +1 -1
- package/dist/frontend/lowering/lower-containers.js +39 -17
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +10 -6
- package/dist/frontend/lowering/lower-exprs.js +294 -129
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.js +5 -1
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +15 -1
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +4 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-island.test.d.ts +1 -0
- package/dist/frontend/lowering/lower-island.test.js +51 -0
- package/dist/frontend/lowering/lower-island.test.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +1 -3
- package/dist/frontend/lowering/lower-modules.js +186 -82
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.test.js +69 -1
- package/dist/frontend/lowering/lower-modules.test.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +100 -35
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.test.d.ts +1 -0
- package/dist/frontend/lowering/lower-stmts.test.js +77 -0
- package/dist/frontend/lowering/lower-stmts.test.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.js +2 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-url.d.ts +2 -1
- package/dist/frontend/lowering/lower-url.js +32 -9
- package/dist/frontend/lowering/lower-url.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +26 -9
- package/dist/frontend/lowering/lowerer.js +218 -27
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/lowerer.test.js +38 -0
- package/dist/frontend/lowering/lowerer.test.js.map +1 -1
- package/dist/frontend/lowering/module-namespace-elements.d.ts +7 -0
- package/dist/frontend/lowering/module-namespace-elements.js +54 -0
- package/dist/frontend/lowering/module-namespace-elements.js.map +1 -0
- package/dist/frontend/lowering/object-factory-new.d.ts +4 -3
- package/dist/frontend/lowering/object-factory-new.js +27 -14
- package/dist/frontend/lowering/object-factory-new.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +1 -0
- package/dist/frontend/lowering/surfaces.js +24 -12
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/text-decoder-encoding.js +3 -0
- package/dist/frontend/lowering/text-decoder-encoding.js.map +1 -1
- package/dist/frontend/lowering/text-decoder-encoding.test.js +2 -1
- package/dist/frontend/lowering/text-decoder-encoding.test.js.map +1 -1
- package/dist/frontend/program.js +4 -2
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/type-mapper.js +83 -14
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/frontend/union-discriminants.js +22 -7
- package/dist/frontend/union-discriminants.js.map +1 -1
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/ir/ir.d.ts +27 -13
- package/dist/ir/ir.js +26 -2
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.js +21 -1
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/serialize.test.js +21 -0
- package/dist/ir/serialize.test.js.map +1 -1
- package/dist/ir/traverse.d.ts +0 -3
- package/dist/ir/traverse.js +33 -20
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +56 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +17 -1
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/compiler-self-identity.js +1 -1
- package/dist/library/compiler-self-identity.test.js +2 -2
- package/dist/library/compiler-self-identity.test.js.map +1 -1
- package/dist/library/int-infer.d.ts +2 -2
- package/dist/library/int-infer.js +10 -7
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +44 -0
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/prepare.js +2 -1
- package/dist/library/prepare.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +115 -13
|
@@ -31,8 +31,9 @@ import { lowerObjectFactoryNew } from "./object-factory-new.js";
|
|
|
31
31
|
import { tryLowerExpression } from "./expressions/try-lower-expression.js";
|
|
32
32
|
import { lowerInstanceConstructorNew } from "./class-instance-constructor.js";
|
|
33
33
|
import { classPrototypeData } from "./class-prototypes.js";
|
|
34
|
-
import { checkedClassConstruction } from "./class-construction.js";
|
|
34
|
+
import { checkedClassConstruction, checkedClassConstructionPacked } from "./class-construction.js";
|
|
35
35
|
import { initializeRuntimeStatics, hasRuntimeStatics } from "./class-runtime-statics.js";
|
|
36
|
+
import { reflectedClassStaticMethodValue } from "./class-method-values.js";
|
|
36
37
|
export function returnsOnlyThis(member) {
|
|
37
38
|
const body = member.body;
|
|
38
39
|
const last = body?.statements.at(-1);
|
|
@@ -492,9 +493,83 @@ export function builtinStreamInfoOf(lowerer, symbol) {
|
|
|
492
493
|
/** A dictionary lookup may be absent even when its checker type is a
|
|
493
494
|
* required reference. Keep literal nullish fallbacks in inferred JS storage. */
|
|
494
495
|
function jsFieldInitializerType(lowerer, type, expression) {
|
|
496
|
+
if (ts.isIdentifier(expression)) {
|
|
497
|
+
const symbol = lowerer.checker.getSymbolAtLocation(expression);
|
|
498
|
+
if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => ts.isParameter(decl) && (ts.isConstructorDeclaration(decl.parent) ||
|
|
499
|
+
(lowerer.typeOf(expression).flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) !== 0)))
|
|
500
|
+
return DYN;
|
|
501
|
+
}
|
|
502
|
+
if (type.kind === "array" || type.kind === "record")
|
|
503
|
+
return DYN;
|
|
504
|
+
// JavaScript documentation cannot guarantee that a call or an indexed
|
|
505
|
+
// read returns a present number. Numeric fields receiving those values
|
|
506
|
+
// must retain undefined, so later arithmetic performs JS coercion (NaN).
|
|
507
|
+
if (type.kind === "f64" && isJsSourceFile(expression.getSourceFile())) {
|
|
508
|
+
const assignment = expression.parent;
|
|
509
|
+
const left = ts.isBinaryExpression(assignment) ? assignment.left : null;
|
|
510
|
+
const field = left && ts.isPropertyAccessExpression(left) && left.expression.kind === ts.SyntaxKind.ThisKeyword ? left.name.text : null;
|
|
511
|
+
let owner = expression.parent;
|
|
512
|
+
while (owner && !ts.isClassDeclaration(owner) && !ts.isClassExpression(owner))
|
|
513
|
+
owner = owner.parent;
|
|
514
|
+
if (field && owner) {
|
|
515
|
+
let acceptsUnchecked = false;
|
|
516
|
+
const scan = (node) => {
|
|
517
|
+
if (acceptsUnchecked || node !== owner && (ts.isClassDeclaration(node) || ts.isClassExpression(node)))
|
|
518
|
+
return;
|
|
519
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
520
|
+
ts.isPropertyAccessExpression(node.left) && node.left.expression.kind === ts.SyntaxKind.ThisKeyword && node.left.name.text === field) {
|
|
521
|
+
let rhs = node.right;
|
|
522
|
+
while (ts.isParenthesizedExpression(rhs))
|
|
523
|
+
rhs = rhs.expression;
|
|
524
|
+
if (ts.isCallExpression(rhs) || ts.isElementAccessExpression(rhs))
|
|
525
|
+
acceptsUnchecked = true;
|
|
526
|
+
}
|
|
527
|
+
node.forEachChild(scan);
|
|
528
|
+
};
|
|
529
|
+
scan(owner);
|
|
530
|
+
if (acceptsUnchecked)
|
|
531
|
+
return DYN;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
495
534
|
let node = expression;
|
|
496
535
|
while (ts.isParenthesizedExpression(node))
|
|
497
536
|
node = node.expression;
|
|
537
|
+
if (ts.isCallExpression(node) && type.kind === "bytes") {
|
|
538
|
+
let prototypeArgument = false;
|
|
539
|
+
for (const argument of node.arguments)
|
|
540
|
+
ts.walkPreorder(argument, (part) => {
|
|
541
|
+
if (!ts.isPropertyAccessExpression(part) || part.expression.kind !== ts.SyntaxKind.ThisKeyword)
|
|
542
|
+
return;
|
|
543
|
+
const symbol = lowerer.checker.getSymbolAtLocation(part.name);
|
|
544
|
+
if (symbol && lowerer.checker.declarationsOf(symbol).some((decl) => {
|
|
545
|
+
const member = ts.isBinaryExpression(decl) ? decl.left : decl;
|
|
546
|
+
return ts.isPropertyAccessExpression(member) && ts.isPropertyAccessExpression(member.expression) &&
|
|
547
|
+
member.expression.name.text === "prototype";
|
|
548
|
+
}))
|
|
549
|
+
prototypeArgument = true;
|
|
550
|
+
// JS checkers can synthesize a property declaration for prototype
|
|
551
|
+
// assignments. Keep the source ownership proof when that happens.
|
|
552
|
+
let owner = node.parent;
|
|
553
|
+
while (owner && !(ts.isClassDeclaration(owner) || ts.isClassExpression(owner)))
|
|
554
|
+
owner = owner.parent;
|
|
555
|
+
if (owner && (ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) && owner.name) {
|
|
556
|
+
const className = owner.name.text;
|
|
557
|
+
for (const statement of owner.getSourceFile().statements) {
|
|
558
|
+
if (!ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression))
|
|
559
|
+
continue;
|
|
560
|
+
const left = statement.expression.left;
|
|
561
|
+
if (statement.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isPropertyAccessExpression(left) &&
|
|
562
|
+
left.name.text === part.name.text && ts.isPropertyAccessExpression(left.expression) && left.expression.name.text === "prototype" &&
|
|
563
|
+
ts.isIdentifier(left.expression.expression) && left.expression.expression.text === className)
|
|
564
|
+
prototypeArgument = true;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
// A prototype-selected conversion can return different container kinds
|
|
569
|
+
// in subclasses even when JSDoc describes the base class's default.
|
|
570
|
+
if (prototypeArgument)
|
|
571
|
+
return DYN;
|
|
572
|
+
}
|
|
498
573
|
if (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) {
|
|
499
574
|
const ret = lowerer.declaredReturnType(node, node);
|
|
500
575
|
const arms = type.kind === "union" ? lowerer.unions.get(type.unionId)?.arms ?? [] : [type];
|
|
@@ -506,6 +581,14 @@ function jsFieldInitializerType(lowerer, type, expression) {
|
|
|
506
581
|
if (node.kind === ts.SyntaxKind.NullKeyword)
|
|
507
582
|
return withUnitArm(type, "nullT", lowerer.unions) ?? type;
|
|
508
583
|
const inferred = lowerer.typeOf(node);
|
|
584
|
+
const scalar = (inferred.flags & ts.TypeFlags.BooleanLike) !== 0 ? "bool"
|
|
585
|
+
: (inferred.flags & ts.TypeFlags.NumberLike) !== 0 ? "f64"
|
|
586
|
+
: (inferred.flags & ts.TypeFlags.StringLike) !== 0 ? "string"
|
|
587
|
+
: (inferred.flags & ts.TypeFlags.BigIntLike) !== 0 ? "bigint"
|
|
588
|
+
: (inferred.flags & ts.TypeFlags.ESSymbolLike) !== 0 ? "symbol" : null;
|
|
589
|
+
if (scalar && type.kind !== "dyn" && type.kind !== scalar &&
|
|
590
|
+
!(type.kind === "union" && lowerer.unions.get(type.unionId)?.arms.some((arm) => arm.kind === scalar)))
|
|
591
|
+
return DYN;
|
|
509
592
|
for (const arm of inferred.isUnionType() ? ts.constituentTypes(inferred) : [inferred]) {
|
|
510
593
|
if ((arm.flags & ts.TypeFlags.Undefined) !== 0)
|
|
511
594
|
type = lowerer.withUndefinedArmOf(type) ?? type;
|
|
@@ -525,6 +608,56 @@ function jsFieldInitializerType(lowerer, type, expression) {
|
|
|
525
608
|
}
|
|
526
609
|
return type;
|
|
527
610
|
}
|
|
611
|
+
function inheritedJsFieldType(lowerer, decl, name, type) {
|
|
612
|
+
for (const expression of lowerer.inheritedJsFieldWrites.get(decl)?.get(name) ?? []) {
|
|
613
|
+
// Keep one shared layout, including sibling subclasses. A descendant
|
|
614
|
+
// initializer that exceeds the base slot needs checked JS storage.
|
|
615
|
+
if (!typeEquals(type, jsFieldInitializerType(lowerer, type, expression)))
|
|
616
|
+
return DYN;
|
|
617
|
+
}
|
|
618
|
+
return type;
|
|
619
|
+
}
|
|
620
|
+
function returnsWidenedJsField(lowerer, decl, member, fields, ret) {
|
|
621
|
+
if (!isJsSourceFile(member.getSourceFile()) || member.type || member.asteriskToken || hasExplicitJsDocReturn(member))
|
|
622
|
+
return false;
|
|
623
|
+
const widened = (expression) => {
|
|
624
|
+
if (ts.isParenthesizedExpression(expression))
|
|
625
|
+
return widened(expression.expression);
|
|
626
|
+
if (ts.isConditionalExpression(expression))
|
|
627
|
+
return widened(expression.whenTrue) || widened(expression.whenFalse);
|
|
628
|
+
if (ts.isCallExpression(expression)) {
|
|
629
|
+
const callee = expression.expression;
|
|
630
|
+
const symbol = ts.isPropertyAccessExpression(callee) ? lowerer.checker.getSymbolAtLocation(callee.name)
|
|
631
|
+
: ts.isElementAccessExpression(callee) ? lowerer.checker.getPropertyOfType(lowerer.typeOf(callee.expression), lowerer.foldedStringKeyOf(callee.argumentExpression) ?? "")
|
|
632
|
+
: undefined;
|
|
633
|
+
if (symbol && lowerer.checker.declarationsOf(symbol).some((declaration) => ts.isMethodDeclaration(declaration) && lowerer.checkedVirtualJsMethods.has(declaration)))
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
const name = ts.isPropertyAccessExpression(expression) ? expression.name.text
|
|
637
|
+
: ts.isElementAccessExpression(expression) ? lowerer.foldedStringKeyOf(expression.argumentExpression) : null;
|
|
638
|
+
if (ts.isPropertyAccessExpression(expression)) {
|
|
639
|
+
const symbol = lowerer.checker.getSymbolAtLocation(expression.name);
|
|
640
|
+
if (symbol && lowerer.checker.declarationsOf(symbol).some((declaration) => (ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && lowerer.checkedVirtualJsAccessors.has(declaration)))
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
if (name !== null && (ts.isPropertyAccessExpression(expression) || ts.isElementAccessExpression(expression)) &&
|
|
644
|
+
expression.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
645
|
+
return fields.get(name)?.kind === "dyn" || inheritedJsFieldType(lowerer, decl, name, ret).kind === "dyn";
|
|
646
|
+
}
|
|
647
|
+
return false;
|
|
648
|
+
};
|
|
649
|
+
let result = false;
|
|
650
|
+
const scan = (node) => {
|
|
651
|
+
if (result || ts.isFunctionLike(node))
|
|
652
|
+
return;
|
|
653
|
+
if (ts.isReturnStatement(node) && node.expression && widened(node.expression))
|
|
654
|
+
result = true;
|
|
655
|
+
node.forEachChild(scan);
|
|
656
|
+
};
|
|
657
|
+
if (member.body)
|
|
658
|
+
scan(member.body);
|
|
659
|
+
return result;
|
|
660
|
+
}
|
|
528
661
|
/** The undefined-armed union of a JS class property's inferred type — the
|
|
529
662
|
* honest slot for a field first assigned outside the constructor's top
|
|
530
663
|
* level (undefined until the write runs, Node-exact). Null when the
|
|
@@ -1262,6 +1395,12 @@ mixin) {
|
|
|
1262
1395
|
const staticBlocks = [];
|
|
1263
1396
|
const runtimeStatics = [];
|
|
1264
1397
|
const runtimePrototypeMembers = [];
|
|
1398
|
+
// Public JS accessors need live constructor descriptors, including
|
|
1399
|
+
// receiver-sensitive getters and setters inherited by subclasses.
|
|
1400
|
+
const checkedStatics = isJsSourceFile(decl.getSourceFile()) && (base && hasRuntimeStatics(base) || decl.members.some((member) => ts.isAccessor(member) && !ts.isPrivateIdentifier(member.name) &&
|
|
1401
|
+
member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword))) &&
|
|
1402
|
+
!decl.members.some((member) => member.name && ts.isPrivateIdentifier(member.name) &&
|
|
1403
|
+
ts.getModifiers(member)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword));
|
|
1265
1404
|
// GENERIC methods (own type parameters), instance and static: only
|
|
1266
1405
|
// the SYNTAX is checked here — parameter/return types mention the
|
|
1267
1406
|
// type parameters and cannot map yet; bodies lower per call-site
|
|
@@ -1310,7 +1449,7 @@ mixin) {
|
|
|
1310
1449
|
});
|
|
1311
1450
|
};
|
|
1312
1451
|
for (const member of decl.members) {
|
|
1313
|
-
if (functionLocalClass(decl) && isJsSourceFile(decl.getSourceFile()) &&
|
|
1452
|
+
if ((functionLocalClass(decl) || checkedStatics) && isJsSourceFile(decl.getSourceFile()) &&
|
|
1314
1453
|
(ts.isClassStaticBlockDeclaration(member) || ts.getModifiers(member)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword))) {
|
|
1315
1454
|
runtimeStatics.push(member);
|
|
1316
1455
|
continue;
|
|
@@ -1368,6 +1507,10 @@ mixin) {
|
|
|
1368
1507
|
(ts.isIdentifier(member.name) || ts.isPrivateIdentifier(member.name)) &&
|
|
1369
1508
|
member.initializer &&
|
|
1370
1509
|
member.postfixToken?.kind !== ts.SyntaxKind.QuestionToken) {
|
|
1510
|
+
const initializerSymbol = ts.isIdentifier(member.initializer) ? lowerer.resolveValueSymbol(member.initializer) : null;
|
|
1511
|
+
// Enum namespace aliases are erased; member reads fold through the checker.
|
|
1512
|
+
if (initializerSymbol && (initializerSymbol.flags & ts.SymbolFlags.Enum) !== 0)
|
|
1513
|
+
continue;
|
|
1371
1514
|
const type = lowerer.irTypeOf(member.name);
|
|
1372
1515
|
if (type.kind === "void")
|
|
1373
1516
|
lowerer.badType(member.name, lowerer.typeOf(member.name));
|
|
@@ -1566,10 +1709,17 @@ mixin) {
|
|
|
1566
1709
|
const declared = lowerer.typeOf(member);
|
|
1567
1710
|
const inferred = member.type || !member.initializer ? declared : lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(member.initializer));
|
|
1568
1711
|
const mapped = member.initializer ? lowerer.mapTypeOf(inferred) ?? dynFallbackType(lowerer, member, inferred) : DYN;
|
|
1569
|
-
|
|
1712
|
+
let type = mapped?.kind === "record" && isJsSourceFile(member.getSourceFile()) && !member.type
|
|
1570
1713
|
? DYN : mapped ? jsOpenObjectType(member, mapped, lowerer.shapes, lowerer.unions) : null;
|
|
1571
1714
|
if (!type || type.kind === "void" || (type.kind === "dyn" && !isJsSourceFile(member.getSourceFile())))
|
|
1572
1715
|
lowerer.badType(member, inferred);
|
|
1716
|
+
if (isJsSourceFile(member.getSourceFile())) {
|
|
1717
|
+
type = inheritedJsFieldType(lowerer, decl, fieldName, type);
|
|
1718
|
+
if (base?.fields.get(fieldName)?.kind === "dyn")
|
|
1719
|
+
type = DYN;
|
|
1720
|
+
if (base && !fields.has(fieldName) && lowerer.dynConvertible(type))
|
|
1721
|
+
type = DYN;
|
|
1722
|
+
}
|
|
1573
1723
|
const undefinedInitializer = !member.initializer ? locOf(member) : undefined;
|
|
1574
1724
|
const previous = fields.get(fieldName);
|
|
1575
1725
|
if (previous) {
|
|
@@ -1635,6 +1785,18 @@ mixin) {
|
|
|
1635
1785
|
// as undefined on construction, verified), and reads/writes ride
|
|
1636
1786
|
// the ordinary undefined-armed union machinery.
|
|
1637
1787
|
let type = lowerer.irTypeOf(member.name);
|
|
1788
|
+
if (isJsSourceFile(member.getSourceFile()) && !member.initializer) {
|
|
1789
|
+
const constructor = decl.members.find((part) => ts.isConstructorDeclaration(part) && !!part.body);
|
|
1790
|
+
for (const statement of constructor?.body?.statements ?? []) {
|
|
1791
|
+
if (!ts.isExpressionStatement(statement) || !ts.isBinaryExpression(statement.expression) ||
|
|
1792
|
+
statement.expression.operatorToken.kind !== ts.SyntaxKind.EqualsToken)
|
|
1793
|
+
continue;
|
|
1794
|
+
const left = statement.expression.left;
|
|
1795
|
+
if (!ts.isPropertyAccessExpression(left) || left.expression.kind !== ts.SyntaxKind.ThisKeyword || left.name.text !== member.name.text)
|
|
1796
|
+
continue;
|
|
1797
|
+
type = statement.expression.right.kind === ts.SyntaxKind.NullKeyword ? DYN : jsFieldInitializerType(lowerer, type, statement.expression.right);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1638
1800
|
// Unannotated JavaScript callback slots retain the original
|
|
1639
1801
|
// callable and its receiver instead of adapting its signature.
|
|
1640
1802
|
if (isJsSourceFile(member.getSourceFile()) && !member.type &&
|
|
@@ -1646,6 +1808,13 @@ mixin) {
|
|
|
1646
1808
|
if (isJsSourceFile(member.getSourceFile()) && !member.initializer && type.kind !== "dyn") {
|
|
1647
1809
|
type = lowerer.withUndefinedArmOf(type) ?? DYN;
|
|
1648
1810
|
}
|
|
1811
|
+
if (isJsSourceFile(member.getSourceFile())) {
|
|
1812
|
+
type = inheritedJsFieldType(lowerer, decl, member.name.text, type);
|
|
1813
|
+
if (base?.fields.get(member.name.text)?.kind === "dyn")
|
|
1814
|
+
type = DYN;
|
|
1815
|
+
if (base && !fields.has(member.name.text) && lowerer.dynConvertible(type))
|
|
1816
|
+
type = DYN;
|
|
1817
|
+
}
|
|
1649
1818
|
if (type.kind === "void")
|
|
1650
1819
|
lowerer.badType(member.name, lowerer.typeOf(member.name));
|
|
1651
1820
|
// Bundled JS declares fields without annotations before assigning
|
|
@@ -1884,6 +2053,20 @@ mixin) {
|
|
|
1884
2053
|
}
|
|
1885
2054
|
}
|
|
1886
2055
|
const { shapes, funcType: ft } = lowerer.lambdaSignature(member);
|
|
2056
|
+
const fieldRet = ft.ret.kind === "promise" ? ft.ret.inner : ft.ret;
|
|
2057
|
+
if (returnsWidenedJsField(lowerer, decl, member, fields, fieldRet)) {
|
|
2058
|
+
ft.ret = ft.ret.kind === "promise" ? { ...ft.ret, inner: DYN } : DYN;
|
|
2059
|
+
}
|
|
2060
|
+
if (lowerer.checkedVirtualJsMethods.has(member)) {
|
|
2061
|
+
for (const shape of shapes) {
|
|
2062
|
+
shape.type = DYN;
|
|
2063
|
+
if (shape.bodyType)
|
|
2064
|
+
shape.bodyType = DYN;
|
|
2065
|
+
}
|
|
2066
|
+
ft.params = shapes.map((shape) => shape.type);
|
|
2067
|
+
if (ft.ret.kind !== "void" || lowerer.checkedVirtualJsValueReturns.has(member))
|
|
2068
|
+
ft.ret = DYN;
|
|
2069
|
+
}
|
|
1887
2070
|
// JS inference may retain only a symbol sentinel while a method
|
|
1888
2071
|
// also returns values from an untyped mutable field or local.
|
|
1889
2072
|
if (isJsSourceFile(member.getSourceFile()) && !member.type && !hasExplicitJsDocReturn(member) && ft.ret.kind === "symbol")
|
|
@@ -1972,14 +2155,13 @@ mixin) {
|
|
|
1972
2155
|
shapes.push(...overridden.sig.params.slice(shapes.length).map((p) => ({ type: p.type, mode: "omittable" })));
|
|
1973
2156
|
}
|
|
1974
2157
|
if (overridden &&
|
|
2158
|
+
member.asteriskToken === undefined &&
|
|
1975
2159
|
(overridden.sig.params.length !== shapes.length ||
|
|
1976
2160
|
!overridden.sig.params.every((p, i) => typeEquals(p.type, shapes[i].type)) ||
|
|
1977
2161
|
!typeEquals(overridden.sig.ret, ft.ret))) {
|
|
1978
2162
|
const fencedJsOverride = isJsSourceFile(member.getSourceFile()) &&
|
|
1979
2163
|
overridden.declarer.decl !== null && isJsSourceFile(overridden.declarer.decl.getSourceFile()) &&
|
|
1980
|
-
|
|
1981
|
-
overridden.sig.async !== true &&
|
|
1982
|
-
member.asteriskToken === undefined && overridden.sig.gen === undefined;
|
|
2164
|
+
overridden.sig.gen === undefined;
|
|
1983
2165
|
if (fencedJsOverride) {
|
|
1984
2166
|
// A dormant JS method must not poison the whole class. Keep
|
|
1985
2167
|
// the inherited call/dispatch ABI and refuse at method entry;
|
|
@@ -2013,10 +2195,18 @@ mixin) {
|
|
|
2013
2195
|
// an override chain touching an async method on either end would
|
|
2014
2196
|
// put a spawn wrapper behind a virtual slot, so it fences.
|
|
2015
2197
|
if (overridden && (asyncMember || overridden.sig.async === true)) {
|
|
2016
|
-
|
|
2198
|
+
const message = `overriding ${overridden.sig.async === true ? "the async method" : "a method with an async method"} '${mName}' (async methods dispatch statically — the vtable slot machinery has no fiber-spawn story)`;
|
|
2199
|
+
if (isJsSourceFile(member.getSourceFile()))
|
|
2200
|
+
methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message));
|
|
2201
|
+
else
|
|
2202
|
+
lowerer.unsupported("SC1090", member.name, message);
|
|
2017
2203
|
}
|
|
2018
2204
|
if (overridden && (member.asteriskToken !== undefined || overridden.sig.gen !== undefined)) {
|
|
2019
|
-
|
|
2205
|
+
const message = `overriding the generator method '${mName}' (generator methods dispatch statically — a virtual slot cannot enter their spawn wrapper)`;
|
|
2206
|
+
if (isJsSourceFile(member.getSourceFile()))
|
|
2207
|
+
methodEntryFences.set(mName, unsupportedDiag("SC1090", locOf(member.name), message));
|
|
2208
|
+
else
|
|
2209
|
+
lowerer.unsupported("SC1090", member.name, message);
|
|
2020
2210
|
}
|
|
2021
2211
|
// A GENERATOR method carries its channels on the sig:
|
|
2022
2212
|
// the body lowers as a generator IrFunction (`this` as param 0),
|
|
@@ -2072,6 +2262,13 @@ mixin) {
|
|
|
2072
2262
|
if (!member.body && !abstractAccessor)
|
|
2073
2263
|
lowerer.unsupported("SC1090", member, "bodyless accessors");
|
|
2074
2264
|
const mName = `${isGet ? "get" : "set"}:${prop}`;
|
|
2265
|
+
if (fields.has(prop) && isJsSourceFile(decl.getSourceFile()) && base?.fields.has(prop) &&
|
|
2266
|
+
!fieldOrder.some((field) => field.name === prop)) {
|
|
2267
|
+
// JS may override an inherited data field with a prototype
|
|
2268
|
+
// accessor. Retain the base storage prefix for its constructor;
|
|
2269
|
+
// property dispatch on this subclass selects the accessor.
|
|
2270
|
+
fields.delete(prop);
|
|
2271
|
+
}
|
|
2075
2272
|
if (fields.has(prop) && !((prop === "message" || prop === "name") && base && (base.def.name === "%Error" || lowerer.isSubclassOf(base.def.name, "%Error")))) {
|
|
2076
2273
|
// tsc rejects field/accessor mixing (TS2610/2611); defensive.
|
|
2077
2274
|
lowerer.unsupported("SC1090", member.name, "accessors sharing a name with a field");
|
|
@@ -2087,6 +2284,13 @@ mixin) {
|
|
|
2087
2284
|
// tsc rejects optional/default/rest setter params (TS1051-53).
|
|
2088
2285
|
sig = { params: lowerer.paramShapes(member.parameters), ret: VOID };
|
|
2089
2286
|
}
|
|
2287
|
+
if (lowerer.checkedVirtualJsAccessors.has(member)) {
|
|
2288
|
+
if (isGet)
|
|
2289
|
+
sig.ret = DYN;
|
|
2290
|
+
else
|
|
2291
|
+
for (const parameter of sig.params)
|
|
2292
|
+
parameter.type = DYN;
|
|
2293
|
+
}
|
|
2090
2294
|
// One property, ONE type: tsc (5.1+) admits get/set pairs with
|
|
2091
2295
|
// unrelated annotated types; a property slot here has a single
|
|
2092
2296
|
// IR type, so the pair must agree exactly.
|
|
@@ -2273,7 +2477,11 @@ mixin) {
|
|
|
2273
2477
|
if (fields.has(name) || hasAccessor(name))
|
|
2274
2478
|
continue;
|
|
2275
2479
|
if (methods.has(name) || lowerer.findMethodOn(base, name)) {
|
|
2276
|
-
|
|
2480
|
+
// An own JS callable shadows its prototype declaration.
|
|
2481
|
+
// Keep the method in the prototype and store the assigned
|
|
2482
|
+
// value in the instance property bag.
|
|
2483
|
+
lowerer.prototypeMethodAccesses.set(name, assign);
|
|
2484
|
+
continue;
|
|
2277
2485
|
}
|
|
2278
2486
|
const sym = lowerer.checker.getSymbolAtLocation(assign);
|
|
2279
2487
|
const t = sym ? lowerer.checker.getTypeOfSymbol(sym) : undefined;
|
|
@@ -2314,6 +2522,11 @@ mixin) {
|
|
|
2314
2522
|
type = widened;
|
|
2315
2523
|
}
|
|
2316
2524
|
}
|
|
2525
|
+
type = inheritedJsFieldType(lowerer, decl, name, type);
|
|
2526
|
+
// super() can invoke a subclass setter or method before its
|
|
2527
|
+
// constructor assignments run. These slots start undefined.
|
|
2528
|
+
if (base && lowerer.dynConvertible(type))
|
|
2529
|
+
type = DYN;
|
|
2317
2530
|
fields.set(name, type);
|
|
2318
2531
|
fieldOrder.push({ name, type, initializer: undefined });
|
|
2319
2532
|
continue;
|
|
@@ -2341,7 +2554,8 @@ mixin) {
|
|
|
2341
2554
|
if (!key && (fields.has(fieldName) || hasAccessor(fieldName)))
|
|
2342
2555
|
continue;
|
|
2343
2556
|
if (!key && (methods.has(fieldName) || lowerer.findMethodOn(base, fieldName))) {
|
|
2344
|
-
lowerer.
|
|
2557
|
+
lowerer.prototypeMethodAccesses.set(fieldName, lhs);
|
|
2558
|
+
continue;
|
|
2345
2559
|
}
|
|
2346
2560
|
// A key already declared (own or inherited) makes later
|
|
2347
2561
|
// assignments writes, not declarations.
|
|
@@ -2372,6 +2586,9 @@ mixin) {
|
|
|
2372
2586
|
type = jsOpenObjectType(lhs, type, lowerer.shapes, lowerer.unions);
|
|
2373
2587
|
if (!type || type.kind === "void")
|
|
2374
2588
|
lowerer.badType(lhs, t ?? lowerer.typeOf(lhs));
|
|
2589
|
+
type = inheritedJsFieldType(lowerer, decl, fieldName, type);
|
|
2590
|
+
if (base && lowerer.dynConvertible(type))
|
|
2591
|
+
type = DYN;
|
|
2375
2592
|
fields.set(fieldName, type);
|
|
2376
2593
|
if (key)
|
|
2377
2594
|
symbolFields.set(key.identity, fieldName);
|
|
@@ -2421,7 +2638,8 @@ mixin) {
|
|
|
2421
2638
|
// TypeScript classes keep the loud fence too: an annotated
|
|
2422
2639
|
// program can spell `T | undefined` itself.
|
|
2423
2640
|
if (isJsSourceFile(decl.getSourceFile())) {
|
|
2424
|
-
const
|
|
2641
|
+
const original = undefArmedFieldType(lowerer, p);
|
|
2642
|
+
const armed = original === null ? null : inheritedJsFieldType(lowerer, decl, p.name, original);
|
|
2425
2643
|
if (armed !== null) {
|
|
2426
2644
|
// Unknown JS properties use the instance bag: they do not
|
|
2427
2645
|
// exist until assigned, and a subclass may declare the same
|
|
@@ -2471,7 +2689,7 @@ mixin) {
|
|
|
2471
2689
|
ctorParams.push({ type: DYN, mode: "arguments" });
|
|
2472
2690
|
}
|
|
2473
2691
|
const info = {
|
|
2474
|
-
...(runtimeStatics.length ? { runtimeStatics } : {}),
|
|
2692
|
+
...(runtimeStatics.length || checkedStatics ? { runtimeStatics } : {}),
|
|
2475
2693
|
...(runtimePrototypeMembers.length ? { runtimePrototypeMembers } : {}),
|
|
2476
2694
|
def: {
|
|
2477
2695
|
name: className,
|
|
@@ -2833,6 +3051,9 @@ function lowerStaticFieldInitsInner(lowerer, info) {
|
|
|
2833
3051
|
{ kind: "varRef", localId: constructorId, type: DYN, loc },
|
|
2834
3052
|
], type: DYN, loc }, loc });
|
|
2835
3053
|
}
|
|
3054
|
+
if (info.runtimeStatics && !info.localClass && info.decl) {
|
|
3055
|
+
out.push({ kind: "exprStmt", expr: initializeRuntimeStatics(lowerer, info, classValueRef(lowerer, info, info.decl)), loc: locOf(info.decl) });
|
|
3056
|
+
}
|
|
2836
3057
|
const items = [
|
|
2837
3058
|
...info.staticFields.map((f) => ({ pos: f.initializer.getStart(), kind: "field", f })),
|
|
2838
3059
|
...(info.staticBlocks ?? []).map((b) => ({ pos: b.getStart(), kind: "block", b })),
|
|
@@ -3130,6 +3351,9 @@ export function findGenericStaticOn(lowerer, info, name) {
|
|
|
3130
3351
|
* module function — the declared-function-as-value rule verbatim
|
|
3131
3352
|
* (interned by the backend, so `C.m === C.m` holds). */
|
|
3132
3353
|
function staticMethodValue(lowerer, declarer, name, sig, blame, loc) {
|
|
3354
|
+
const reflected = reflectedClassStaticMethodValue(lowerer, declarer, name);
|
|
3355
|
+
if (reflected)
|
|
3356
|
+
return reflected;
|
|
3133
3357
|
const fnName = `%${declarer.def.name}.static:${name}`;
|
|
3134
3358
|
lowerer.noteEdge(fnName);
|
|
3135
3359
|
const funcType = {
|
|
@@ -3225,7 +3449,8 @@ export function lowerStaticFieldRead(lowerer, expr) {
|
|
|
3225
3449
|
return null;
|
|
3226
3450
|
if (expr.questionDotToken)
|
|
3227
3451
|
return null;
|
|
3228
|
-
if (!ts.isIdentifier(expr.expression) && expr.expression.kind !== ts.SyntaxKind.ThisKeyword
|
|
3452
|
+
if (!ts.isIdentifier(expr.expression) && expr.expression.kind !== ts.SyntaxKind.ThisKeyword &&
|
|
3453
|
+
exactClassOfReceiver(lowerer, expr.expression) === null)
|
|
3229
3454
|
return null;
|
|
3230
3455
|
const symbol = ts.isIdentifier(expr.expression) ? lowerer.resolveValueSymbol(expr.expression) : null;
|
|
3231
3456
|
const info = (symbol ? lowerer.classBySymbol.get(symbol) : undefined) ??
|
|
@@ -3281,8 +3506,14 @@ export function lowerStaticFieldRead(lowerer, expr) {
|
|
|
3281
3506
|
return { kind: "dynKeyGet", value: lowerer.coerceToExpected(classValueRef(lowerer, info, expr.expression), DYN),
|
|
3282
3507
|
key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc };
|
|
3283
3508
|
const data = classStaticDataFor(lowerer, info, expr.name.text, loc);
|
|
3284
|
-
if (data)
|
|
3285
|
-
|
|
3509
|
+
if (data) {
|
|
3510
|
+
const value = { kind: "dynKeyGet", value: data, key: { kind: "strLit", value: expr.name.text, type: STRING, loc }, type: DYN, loc };
|
|
3511
|
+
// JavaScript often assigns public statics after the class declaration.
|
|
3512
|
+
// Their storage stays mutable and checked, while reads retain the
|
|
3513
|
+
// checker's concrete type instead of losing the method receiver.
|
|
3514
|
+
const type = isJsSourceFile(expr.getSourceFile()) ? null : lowerer.mapTypeOf(lowerer.typeOf(expr));
|
|
3515
|
+
return type ? lowerer.coerceToExpected(value, type) : value;
|
|
3516
|
+
}
|
|
3286
3517
|
return null;
|
|
3287
3518
|
}
|
|
3288
3519
|
/** NamedEvaluation's answer for a class expression's `.name`: its own
|
|
@@ -3388,7 +3619,7 @@ export function lowerClassExpressionInfo(lowerer, expr) {
|
|
|
3388
3619
|
// drains it), which is JS's order for whole-initializer positions.
|
|
3389
3620
|
// Anything subtler (multi-declarator statements, arguments evaluated
|
|
3390
3621
|
// after other side effects) is a named fence, never a reordering.
|
|
3391
|
-
if (!local && (info.callableBase || info.def.baseValueGlobal || info.staticFields.length > 0 || (info.staticBlocks?.length ?? 0) > 0)) {
|
|
3622
|
+
if (!local && (info.callableBase || info.def.baseValueGlobal || info.runtimeStatics || info.staticFields.length > 0 || (info.staticBlocks?.length ?? 0) > 0)) {
|
|
3392
3623
|
let holder = expr.parent;
|
|
3393
3624
|
while (ts.isParenthesizedExpression(holder) || ts.isClassExpression(holder) ||
|
|
3394
3625
|
ts.isHeritageClause(holder) || ts.isExpressionWithTypeArguments(holder)) {
|
|
@@ -3434,7 +3665,7 @@ export function lowerClassExpression(lowerer, expr) {
|
|
|
3434
3665
|
}
|
|
3435
3666
|
const info = lowerClassExpressionInfo(lowerer, expr);
|
|
3436
3667
|
if (!info.localClass && !info.expressionInitializationEmitted &&
|
|
3437
|
-
(info.callableBase || info.def.baseValueGlobal || info.staticFields.length > 0 || (info.staticBlocks?.length ?? 0) > 0)) {
|
|
3668
|
+
(info.callableBase || info.def.baseValueGlobal || info.runtimeStatics || info.staticFields.length > 0 || (info.staticBlocks?.length ?? 0) > 0)) {
|
|
3438
3669
|
info.expressionInitializationEmitted = true;
|
|
3439
3670
|
lowerer.pendingClassExprInits.push(...lowerer.lowerStaticFieldInits(info));
|
|
3440
3671
|
}
|
|
@@ -3500,9 +3731,8 @@ export function lowerClassExpression(lowerer, expr) {
|
|
|
3500
3731
|
], type: DYN, loc }, loc },
|
|
3501
3732
|
], result: clsRef, type: value.type, loc });
|
|
3502
3733
|
}
|
|
3503
|
-
return initializeRuntimeStatics(lowerer, info,
|
|
3504
|
-
? { ...value, captures: info.localClass.captureSources }
|
|
3505
|
-
: value);
|
|
3734
|
+
return info.localClass ? initializeRuntimeStatics(lowerer, info, value.kind === "classRef"
|
|
3735
|
+
? { ...value, captures: info.localClass.captureSources } : value) : value;
|
|
3506
3736
|
}
|
|
3507
3737
|
/** The EXACT class a receiver expression is statically known to BE (not
|
|
3508
3738
|
* merely be typed by): the class name itself, or a `const` binding
|
|
@@ -3511,6 +3741,11 @@ export function lowerClassExpression(lowerer, expr) {
|
|
|
3511
3741
|
* hit the declaring class's storage exactly (the shadowing hazards of
|
|
3512
3742
|
* general class values don't arise). Null for everything else. */
|
|
3513
3743
|
export function exactClassOfReceiver(lowerer, expr) {
|
|
3744
|
+
if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
|
|
3745
|
+
const member = nsMemberIdentOf(lowerer, expr);
|
|
3746
|
+
if (member)
|
|
3747
|
+
return exactClassOfReceiver(lowerer, member);
|
|
3748
|
+
}
|
|
3514
3749
|
// A class-valued lexical this is introduced only for an exact static
|
|
3515
3750
|
// receiver specialization; ordinary instance this remains an object.
|
|
3516
3751
|
if (expr.kind === ts.SyntaxKind.ThisKeyword) {
|
|
@@ -3667,7 +3902,7 @@ export function lowerStaticMethodCall(lowerer, call, access) {
|
|
|
3667
3902
|
if (!ts.isIdentifier(access.expression)) {
|
|
3668
3903
|
if (!isModuleExportsAccess(access.expression) || !isCjsJsFile(access.getSourceFile(), lowerer.program)) {
|
|
3669
3904
|
let receiverType = lowerer.mapTypeOf(lowerer.typeOf(access.expression));
|
|
3670
|
-
if (receiverType?.kind !== "classval" && isJsSourceFile(call.getSourceFile())
|
|
3905
|
+
if (receiverType?.kind !== "classval" && isJsSourceFile(call.getSourceFile())) {
|
|
3671
3906
|
const receiver = tryLowerExpression(lowerer, access.expression);
|
|
3672
3907
|
if (receiver?.type.kind === "classval")
|
|
3673
3908
|
receiverType = receiver.type;
|
|
@@ -3958,11 +4193,98 @@ export function findGenericMethodOn(lowerer, info, name) {
|
|
|
3958
4193
|
export function genericOverrideBelow(lowerer, info, name) {
|
|
3959
4194
|
return info.subclasses.some((s) => s.genericMethods?.has(name) === true || genericOverrideBelow(lowerer, s, name));
|
|
3960
4195
|
}
|
|
4196
|
+
/** Discover inherited data writes before any base storage ABI is chosen.
|
|
4197
|
+
* The checker does not widen a base field for assignments in JS subclasses. */
|
|
4198
|
+
export function collectInheritedJsFieldWrites(lowerer, files) {
|
|
4199
|
+
for (const file of files) {
|
|
4200
|
+
if (!isJsSourceFile(file))
|
|
4201
|
+
continue;
|
|
4202
|
+
ts.walkPreorder(file, (node) => {
|
|
4203
|
+
if ((!ts.isClassDeclaration(node) && !ts.isClassExpression(node)) || !node.heritageClauses?.length)
|
|
4204
|
+
return;
|
|
4205
|
+
const writes = new Map();
|
|
4206
|
+
const addWrite = (name, expression) => {
|
|
4207
|
+
const existing = writes.get(name);
|
|
4208
|
+
if (existing)
|
|
4209
|
+
existing.push(expression);
|
|
4210
|
+
else
|
|
4211
|
+
writes.set(name, [expression]);
|
|
4212
|
+
};
|
|
4213
|
+
const scan = (part) => {
|
|
4214
|
+
if (ts.isClassDeclaration(part) || ts.isClassExpression(part) ||
|
|
4215
|
+
ts.isFunctionLike(part) && !ts.isArrowFunction(part))
|
|
4216
|
+
return;
|
|
4217
|
+
if (ts.isBinaryExpression(part) && part.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
4218
|
+
const left = part.left;
|
|
4219
|
+
if (ts.isPropertyAccessExpression(left) && left.expression.kind === ts.SyntaxKind.ThisKeyword)
|
|
4220
|
+
addWrite(left.name.text, part.right);
|
|
4221
|
+
else if (ts.isElementAccessExpression(left) && left.expression.kind === ts.SyntaxKind.ThisKeyword) {
|
|
4222
|
+
const name = lowerer.foldedStringKeyOf(left.argumentExpression);
|
|
4223
|
+
if (name !== null)
|
|
4224
|
+
addWrite(name, part.right);
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
part.forEachChild(scan);
|
|
4228
|
+
};
|
|
4229
|
+
for (const member of node.members) {
|
|
4230
|
+
if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword))
|
|
4231
|
+
continue;
|
|
4232
|
+
if (ts.isPropertyDeclaration(member) && member.initializer) {
|
|
4233
|
+
const name = ts.isPrivateIdentifier(member.name) ? null : classMemberNameOf(lowerer, member.name);
|
|
4234
|
+
if (name !== null)
|
|
4235
|
+
addWrite(name, member.initializer);
|
|
4236
|
+
scan(member.initializer);
|
|
4237
|
+
}
|
|
4238
|
+
if (ts.isConstructorDeclaration(member) || ts.isMethodDeclaration(member) || ts.isAccessor(member)) {
|
|
4239
|
+
if (member.body)
|
|
4240
|
+
scan(member.body);
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
if (writes.size === 0)
|
|
4244
|
+
return;
|
|
4245
|
+
const symbol = lowerer.typeOf(node).getSymbol();
|
|
4246
|
+
const instance = symbol ? lowerer.checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
4247
|
+
const seen = new Set();
|
|
4248
|
+
const markBases = (type) => {
|
|
4249
|
+
const target = type.isTypeReference() ? type.getTarget() : type;
|
|
4250
|
+
if (!target?.isClassOrInterface() || seen.has(target))
|
|
4251
|
+
return;
|
|
4252
|
+
seen.add(target);
|
|
4253
|
+
for (const base of lowerer.checker.getBaseTypes(target)) {
|
|
4254
|
+
for (const [name, expressions] of writes) {
|
|
4255
|
+
const property = lowerer.checker.getPropertyOfType(base, name);
|
|
4256
|
+
for (const declaration of property ? lowerer.checker.declarationsOf(property) : []) {
|
|
4257
|
+
if (ts.isMethodDeclaration(declaration) || ts.isAccessor(declaration))
|
|
4258
|
+
continue;
|
|
4259
|
+
let owner = declaration.parent;
|
|
4260
|
+
while (owner && !ts.isClassDeclaration(owner) && !ts.isClassExpression(owner))
|
|
4261
|
+
owner = owner.parent;
|
|
4262
|
+
if (!owner || (!ts.isClassDeclaration(owner) && !ts.isClassExpression(owner)) || !isJsSourceFile(owner.getSourceFile()))
|
|
4263
|
+
continue;
|
|
4264
|
+
let fields = lowerer.inheritedJsFieldWrites.get(owner);
|
|
4265
|
+
if (!fields)
|
|
4266
|
+
lowerer.inheritedJsFieldWrites.set(owner, fields = new Map());
|
|
4267
|
+
const existing = fields.get(name);
|
|
4268
|
+
if (existing)
|
|
4269
|
+
existing.push(...expressions);
|
|
4270
|
+
else
|
|
4271
|
+
fields.set(name, [...expressions]);
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
markBases(base);
|
|
4275
|
+
}
|
|
4276
|
+
};
|
|
4277
|
+
if (instance)
|
|
4278
|
+
markBases(instance);
|
|
4279
|
+
});
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
3961
4282
|
/** Discover overrides before choosing inferred method ABIs. Class collection
|
|
3962
4283
|
* visits bases first, including imported bases and class expressions, so the
|
|
3963
4284
|
* collected subclass graph cannot answer this question yet. The checker
|
|
3964
4285
|
* hierarchy identifies the original declarations through aliases as well. */
|
|
3965
4286
|
export function collectVirtualJsMethods(lowerer, files) {
|
|
4287
|
+
const families = [];
|
|
3966
4288
|
// Record named prototype accesses before collecting method signatures.
|
|
3967
4289
|
// Those slots need a callable ABI even when JavaScript would otherwise
|
|
3968
4290
|
// specialize each call independently.
|
|
@@ -3985,6 +4307,39 @@ export function collectVirtualJsMethods(lowerer, files) {
|
|
|
3985
4307
|
});
|
|
3986
4308
|
const visit = (node) => {
|
|
3987
4309
|
if (ts.isClassDeclaration(node) || ts.isClassExpression(node)) {
|
|
4310
|
+
const accessors = node.members.filter((member) => (ts.isGetAccessorDeclaration(member) || ts.isSetAccessorDeclaration(member)) && ts.isIdentifier(member.name) &&
|
|
4311
|
+
!member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword));
|
|
4312
|
+
if (node.heritageClauses?.length && accessors.length && isJsSourceFile(node.getSourceFile())) {
|
|
4313
|
+
const symbol = lowerer.typeOf(node).getSymbol();
|
|
4314
|
+
const instance = symbol ? lowerer.checker.getDeclaredTypeOfSymbol(symbol) : null;
|
|
4315
|
+
const seen = new Set();
|
|
4316
|
+
const markAccessors = (type) => {
|
|
4317
|
+
const target = type.isTypeReference() ? type.getTarget() : type;
|
|
4318
|
+
if (!target?.isClassOrInterface() || seen.has(target))
|
|
4319
|
+
return;
|
|
4320
|
+
seen.add(target);
|
|
4321
|
+
for (const base of lowerer.checker.getBaseTypes(target)) {
|
|
4322
|
+
for (const member of accessors) {
|
|
4323
|
+
if (!ts.isIdentifier(member.name))
|
|
4324
|
+
continue;
|
|
4325
|
+
const property = lowerer.checker.getPropertyOfType(base, member.name.text);
|
|
4326
|
+
for (const declaration of property ? lowerer.checker.declarationsOf(property) : []) {
|
|
4327
|
+
if ((ts.isGetAccessorDeclaration(declaration) || ts.isSetAccessorDeclaration(declaration)) && isJsSourceFile(declaration.getSourceFile())) {
|
|
4328
|
+
lowerer.checkedVirtualJsAccessors.add(declaration);
|
|
4329
|
+
lowerer.checkedVirtualJsAccessors.add(member);
|
|
4330
|
+
for (const twin of node.members)
|
|
4331
|
+
if ((ts.isGetAccessorDeclaration(twin) || ts.isSetAccessorDeclaration(twin)) &&
|
|
4332
|
+
ts.isIdentifier(twin.name) && twin.name.text === member.name.text)
|
|
4333
|
+
lowerer.checkedVirtualJsAccessors.add(twin);
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
markAccessors(base);
|
|
4338
|
+
}
|
|
4339
|
+
};
|
|
4340
|
+
if (instance)
|
|
4341
|
+
markAccessors(instance);
|
|
4342
|
+
}
|
|
3988
4343
|
const members = node.members.filter((member) => ts.isMethodDeclaration(member) && ts.isIdentifier(member.name) &&
|
|
3989
4344
|
!member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword));
|
|
3990
4345
|
if (node.heritageClauses?.length && members.length > 0) {
|
|
@@ -4007,8 +4362,17 @@ export function collectVirtualJsMethods(lowerer, files) {
|
|
|
4007
4362
|
if (isJsSourceFile(member.getSourceFile()) && !member.typeParameters &&
|
|
4008
4363
|
!member.asteriskToken && !member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) &&
|
|
4009
4364
|
!bodyReadsArguments(member) &&
|
|
4010
|
-
member.parameters.every((p) => !p.
|
|
4365
|
+
member.parameters.every((p) => !p.dotDotDotToken)) {
|
|
4011
4366
|
lowerer.virtualJsMethodArity.set(declaration, Math.max(lowerer.virtualJsMethodArity.get(declaration) ?? 0, member.parameters.length));
|
|
4367
|
+
const checked = !declaration.typeParameters && !declaration.asteriskToken &&
|
|
4368
|
+
!declaration.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) &&
|
|
4369
|
+
!bodyReadsArguments(declaration) &&
|
|
4370
|
+
[...member.parameters, ...declaration.parameters].every((p) => !p.dotDotDotToken);
|
|
4371
|
+
if (checked) {
|
|
4372
|
+
lowerer.checkedVirtualJsMethods.add(declaration);
|
|
4373
|
+
lowerer.checkedVirtualJsMethods.add(member);
|
|
4374
|
+
families.push([declaration, member]);
|
|
4375
|
+
}
|
|
4012
4376
|
}
|
|
4013
4377
|
}
|
|
4014
4378
|
}
|
|
@@ -4023,6 +4387,29 @@ export function collectVirtualJsMethods(lowerer, files) {
|
|
|
4023
4387
|
};
|
|
4024
4388
|
for (const file of files)
|
|
4025
4389
|
ts.walkPreorder(file, visit);
|
|
4390
|
+
for (const member of lowerer.checkedVirtualJsMethods) {
|
|
4391
|
+
const scan = (node) => {
|
|
4392
|
+
if (node !== member && ts.isFunctionLike(node))
|
|
4393
|
+
return;
|
|
4394
|
+
if (ts.isReturnStatement(node) && node.expression)
|
|
4395
|
+
lowerer.checkedVirtualJsValueReturns.add(member);
|
|
4396
|
+
node.forEachChild(scan);
|
|
4397
|
+
};
|
|
4398
|
+
scan(member);
|
|
4399
|
+
}
|
|
4400
|
+
let changed = true;
|
|
4401
|
+
while (changed) {
|
|
4402
|
+
changed = false;
|
|
4403
|
+
for (const [base, derived] of families) {
|
|
4404
|
+
if (!lowerer.checkedVirtualJsValueReturns.has(base) && !lowerer.checkedVirtualJsValueReturns.has(derived))
|
|
4405
|
+
continue;
|
|
4406
|
+
for (const member of [base, derived])
|
|
4407
|
+
if (!lowerer.checkedVirtualJsValueReturns.has(member)) {
|
|
4408
|
+
lowerer.checkedVirtualJsValueReturns.add(member);
|
|
4409
|
+
changed = true;
|
|
4410
|
+
}
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4026
4413
|
}
|
|
4027
4414
|
/** The receiver's EXACT runtime class, when the expression proves it: a
|
|
4028
4415
|
* `new C(...)` expression directly, or a const binding initialized with
|
|
@@ -4382,6 +4769,10 @@ function lowerClassCtorInner(lowerer, info) {
|
|
|
4382
4769
|
export function classMemberNameOf(lowerer, name) {
|
|
4383
4770
|
if (ts.isIdentifier(name))
|
|
4384
4771
|
return name.text;
|
|
4772
|
+
if (ts.isStringLiteralLike(name))
|
|
4773
|
+
return name.text;
|
|
4774
|
+
if (ts.isNumericLiteral(name))
|
|
4775
|
+
return String(Number(name.text));
|
|
4385
4776
|
// #private methods key by their spelled name ('#m') — '#' is
|
|
4386
4777
|
// unspellable in public identifiers, the accessor-colon precedent.
|
|
4387
4778
|
if (ts.isPrivateIdentifier(name))
|
|
@@ -5341,6 +5732,16 @@ function assignedThisFieldType(lowerer, expr) {
|
|
|
5341
5732
|
}
|
|
5342
5733
|
export function lowerNew(lowerer, expr) {
|
|
5343
5734
|
const loc = locOf(expr);
|
|
5735
|
+
let intlConstructor = expr.expression;
|
|
5736
|
+
if (ts.isIdentifier(intlConstructor)) {
|
|
5737
|
+
const symbol = lowerer.resolveValueSymbol(intlConstructor);
|
|
5738
|
+
const declaration = symbol ? lowerer.checker.valueDeclarationOf(symbol) : undefined;
|
|
5739
|
+
if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer)
|
|
5740
|
+
intlConstructor = declaration.initializer;
|
|
5741
|
+
}
|
|
5742
|
+
const intl = ts.isPropertyAccessExpression(intlConstructor) ? lowerer.stdlibGlobalMember(intlConstructor, "Intl") : null;
|
|
5743
|
+
if (intl && intl !== "Segmenter")
|
|
5744
|
+
lowerer.noLowering(`new Intl.${intl}`, expr, undefined, lowerer.checker.getSymbolAtLocation(expr.expression));
|
|
5344
5745
|
const instanceConstructor = lowerInstanceConstructorNew(lowerer, expr);
|
|
5345
5746
|
if (instanceConstructor)
|
|
5346
5747
|
return instanceConstructor;
|
|
@@ -6164,7 +6565,7 @@ export function lowerNew(lowerer, expr) {
|
|
|
6164
6565
|
}
|
|
6165
6566
|
const callee = lowerer.lowerExpr(expr.expression);
|
|
6166
6567
|
if (callee.type.kind === "dyn")
|
|
6167
|
-
return
|
|
6568
|
+
return lowerCheckedConstructor(lowerer, expr, callee);
|
|
6168
6569
|
if (callee.type.kind !== "classval")
|
|
6169
6570
|
lowerer.badType(expr.expression, lowerer.typeOf(expr.expression));
|
|
6170
6571
|
lowerer.noteEdge(`%${info.def.name}.constructor`);
|
|
@@ -6189,6 +6590,10 @@ export function lowerNew(lowerer, expr) {
|
|
|
6189
6590
|
if (calleeT?.kind === "union") {
|
|
6190
6591
|
const def = lowerer.unions.get(calleeT.unionId);
|
|
6191
6592
|
if (def?.arms.some((a) => a.kind === "classval")) {
|
|
6593
|
+
if (def.arms.every((arm) => arm.kind === "classval" || isJsSourceFile(expr.getSourceFile()) && isUnitType(arm)) && lowerer.dynConvertible(calleeT)) {
|
|
6594
|
+
const callee = lowerer.lowerExprExpecting(expr.expression, DYN);
|
|
6595
|
+
return lowerCheckedConstructor(lowerer, expr, callee);
|
|
6596
|
+
}
|
|
6192
6597
|
lowerer.unsupported("SC1090", expr, "constructing through a union of class values (annotate the slot with the common constructor type — e.g. `new () => Base` — or narrow first)");
|
|
6193
6598
|
}
|
|
6194
6599
|
}
|
|
@@ -6216,11 +6621,20 @@ export function lowerNew(lowerer, expr) {
|
|
|
6216
6621
|
}
|
|
6217
6622
|
// Checked construction supports emitted classes and builtin typed arrays.
|
|
6218
6623
|
const callee = tryLowerExpression(lowerer, expr.expression);
|
|
6219
|
-
if (callee && (callee.type.kind === "dyn" || callee.type.kind === "func"
|
|
6220
|
-
return
|
|
6624
|
+
if (callee && (callee.type.kind === "dyn" || (callee.type.kind === "func" || callee.type.kind === "classval") && lowerer.dynConvertible(callee.type))) {
|
|
6625
|
+
return lowerCheckedConstructor(lowerer, expr, lowerer.coerceToExpected(callee, DYN));
|
|
6221
6626
|
}
|
|
6222
6627
|
lowerer.unsupported("SC1090", expr, "constructing values other than classes declared in the program");
|
|
6223
6628
|
}
|
|
6629
|
+
function lowerCheckedConstructor(lowerer, expr, callee) {
|
|
6630
|
+
const loc = locOf(expr);
|
|
6631
|
+
const args = expr.arguments ?? [];
|
|
6632
|
+
if (args.some(ts.isSpreadElement)) {
|
|
6633
|
+
const packed = lowerer.completeArgs(args, [{ type: DYN, mode: "dynRest" }], loc, expr)[0];
|
|
6634
|
+
return checkedClassConstructionPacked(lowerer, callee, packed, loc);
|
|
6635
|
+
}
|
|
6636
|
+
return checkedClassConstruction(lowerer, callee, args.map((arg) => lowerer.lowerExprExpecting(arg, DYN)), loc);
|
|
6637
|
+
}
|
|
6224
6638
|
/** A getter/setter invocation over an accessor target's receiver — the
|
|
6225
6639
|
* same whole-program devirtualization as method calls: a virtualCall
|
|
6226
6640
|
* when some strict subclass of the receiver's static class overrides
|