@kernlang/core 3.5.9-canary.220.1.c398cd95 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capability-matrix.js +8 -6
- package/dist/capability-matrix.js.map +1 -1
- package/dist/closure-eligibility.d.ts +104 -0
- package/dist/closure-eligibility.js +443 -0
- package/dist/closure-eligibility.js.map +1 -0
- package/dist/closure-python-lowering.d.ts +26 -0
- package/dist/closure-python-lowering.js +132 -22
- package/dist/closure-python-lowering.js.map +1 -1
- package/dist/codegen/body-ts.js +13 -0
- package/dist/codegen/body-ts.js.map +1 -1
- package/dist/codegen/type-system.js +50 -3
- package/dist/codegen/type-system.js.map +1 -1
- package/dist/codegen-core.js +29 -0
- package/dist/codegen-core.js.map +1 -1
- package/dist/codegen-expression.js +7 -0
- package/dist/codegen-expression.js.map +1 -1
- package/dist/constructor-super.d.ts +34 -0
- package/dist/constructor-super.js +122 -0
- package/dist/constructor-super.js.map +1 -0
- package/dist/core-contracts/boolean.d.ts +147 -0
- package/dist/core-contracts/boolean.js +110 -0
- package/dist/core-contracts/boolean.js.map +1 -0
- package/dist/core-contracts/function.d.ts +6 -0
- package/dist/core-contracts/function.js +7 -0
- package/dist/core-contracts/function.js.map +1 -0
- package/dist/core-contracts/index.d.ts +1032 -0
- package/dist/core-contracts/index.js +30 -0
- package/dist/core-contracts/index.js.map +1 -0
- package/dist/core-contracts/list.d.ts +87 -0
- package/dist/core-contracts/list.js +54 -0
- package/dist/core-contracts/list.js.map +1 -0
- package/dist/core-contracts/nullish.d.ts +12 -0
- package/dist/core-contracts/nullish.js +13 -0
- package/dist/core-contracts/nullish.js.map +1 -0
- package/dist/core-contracts/number.d.ts +291 -0
- package/dist/core-contracts/number.js +214 -0
- package/dist/core-contracts/number.js.map +1 -0
- package/dist/core-contracts/record.d.ts +60 -0
- package/dist/core-contracts/record.js +32 -0
- package/dist/core-contracts/record.js.map +1 -0
- package/dist/core-contracts/schema.d.ts +71 -0
- package/dist/core-contracts/schema.js +98 -0
- package/dist/core-contracts/schema.js.map +1 -0
- package/dist/core-contracts/semantics.d.ts +7 -0
- package/dist/core-contracts/semantics.js +221 -0
- package/dist/core-contracts/semantics.js.map +1 -0
- package/dist/core-contracts/string.d.ts +414 -0
- package/dist/core-contracts/string.js +335 -0
- package/dist/core-contracts/string.js.map +1 -0
- package/dist/core-runtime/contract-adapter.d.ts +8 -0
- package/dist/core-runtime/contract-adapter.js +73 -0
- package/dist/core-runtime/contract-adapter.js.map +1 -0
- package/dist/core-runtime/index.d.ts +109 -0
- package/dist/core-runtime/index.js +1946 -0
- package/dist/core-runtime/index.js.map +1 -0
- package/dist/core-runtime/shape-validator.d.ts +61 -0
- package/dist/core-runtime/shape-validator.js +564 -0
- package/dist/core-runtime/shape-validator.js.map +1 -0
- package/dist/core-runtime/value-brand.d.ts +2 -0
- package/dist/core-runtime/value-brand.js +6 -0
- package/dist/core-runtime/value-brand.js.map +1 -0
- package/dist/decompiler.js +46 -0
- package/dist/decompiler.js.map +1 -1
- package/dist/eligibility-taxonomy.d.ts +58 -0
- package/dist/eligibility-taxonomy.generated.d.ts +472 -0
- package/dist/eligibility-taxonomy.generated.js +566 -0
- package/dist/eligibility-taxonomy.generated.js.map +1 -0
- package/dist/eligibility-taxonomy.js +55 -0
- package/dist/eligibility-taxonomy.js.map +1 -0
- package/dist/index.d.ts +19 -2
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/instanceof-rhs.d.ts +53 -0
- package/dist/instanceof-rhs.js +74 -0
- package/dist/instanceof-rhs.js.map +1 -0
- package/dist/ir/semantics/lambda.js +9 -1
- package/dist/ir/semantics/lambda.js.map +1 -1
- package/dist/native-eligibility-ast.d.ts +5 -0
- package/dist/native-eligibility-ast.js +242 -63
- package/dist/native-eligibility-ast.js.map +1 -1
- package/dist/parser-expression.d.ts +1 -1
- package/dist/parser-expression.js +90 -1
- package/dist/parser-expression.js.map +1 -1
- package/dist/rag-assertions.d.ts +3 -0
- package/dist/rag-assertions.js +15 -0
- package/dist/rag-assertions.js.map +1 -0
- package/dist/rag-runtime.d.ts +169 -0
- package/dist/rag-runtime.js +823 -0
- package/dist/rag-runtime.js.map +1 -0
- package/dist/schema.d.ts +1 -0
- package/dist/schema.js +197 -2
- package/dist/schema.js.map +1 -1
- package/dist/semantic-substrate.d.ts +108 -0
- package/dist/semantic-substrate.js +263 -0
- package/dist/semantic-substrate.js.map +1 -0
- package/dist/semantic-validator.d.ts +332 -0
- package/dist/semantic-validator.js +3387 -0
- package/dist/semantic-validator.js.map +1 -1
- package/dist/spec.d.ts +2 -2
- package/dist/spec.js +15 -1
- package/dist/spec.js.map +1 -1
- package/dist/value-ir.d.ts +11 -1
- package/dist/value-ir.js.map +1 -1
- package/package.json +5 -1
|
@@ -21,8 +21,67 @@
|
|
|
21
21
|
* instead of a generic "ineligible". */
|
|
22
22
|
import ts from 'typescript';
|
|
23
23
|
import { supportedCompoundAssignmentOperator } from './assignment-operators.js';
|
|
24
|
+
import { classifyClosureBlock } from './closure-eligibility.js';
|
|
24
25
|
import { emitTypeAnnotation } from './codegen/emitters.js';
|
|
26
|
+
import { deterministicRows, loadEligibilityTaxonomy } from './eligibility-taxonomy.js';
|
|
27
|
+
import { instanceofRhsRejectReasonForName } from './instanceof-rhs.js';
|
|
25
28
|
import { parseExpression } from './parser-expression.js';
|
|
29
|
+
let taxonomyIndexCache = null;
|
|
30
|
+
function taxonomyIndex() {
|
|
31
|
+
if (taxonomyIndexCache !== null)
|
|
32
|
+
return taxonomyIndexCache;
|
|
33
|
+
const exact = new Set();
|
|
34
|
+
const prefixes = [];
|
|
35
|
+
for (const row of deterministicRows(loadEligibilityTaxonomy())) {
|
|
36
|
+
if (row.verdict !== 'ineligible' || row.reason === undefined)
|
|
37
|
+
continue;
|
|
38
|
+
if (row.construct.endsWith('-')) {
|
|
39
|
+
prefixes.push(row.reason);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
exact.add(row.reason);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
taxonomyIndexCache = { exactReasonCodes: exact, reasonFamilyPrefixes: prefixes };
|
|
46
|
+
return taxonomyIndexCache;
|
|
47
|
+
}
|
|
48
|
+
/** Coherence-violation collector. `reject()` records (fail-safe, never throws)
|
|
49
|
+
* any reason string with no deterministic taxonomy row — exact or family — so
|
|
50
|
+
* the coherence-gate tests can assert the live classifier never emits an
|
|
51
|
+
* un-taxonomied reason. Production never reads this; tests do, via the getter
|
|
52
|
+
* and the reset below. A Set, not an array: violations are reason CODES, so
|
|
53
|
+
* duplicates add nothing and growth is bounded by the (finite) code space
|
|
54
|
+
* even if an un-taxonomied reason ships and fires on a hot path (agon
|
|
55
|
+
* review, codex 0.96). */
|
|
56
|
+
const coherenceViolationsInternal = new Set();
|
|
57
|
+
/** A snapshot of the recorded coherence violations (reasons emitted with no
|
|
58
|
+
* taxonomy row). Empty in a coherent build. */
|
|
59
|
+
export function coherenceViolations() {
|
|
60
|
+
return [...coherenceViolationsInternal];
|
|
61
|
+
}
|
|
62
|
+
/** Test-only: clear the recorded coherence violations between runs. */
|
|
63
|
+
export function resetCoherenceViolations() {
|
|
64
|
+
coherenceViolationsInternal.clear();
|
|
65
|
+
}
|
|
66
|
+
/** Fail-safe taxonomy AUTHORITY gate for an emitted ineligible reason.
|
|
67
|
+
*
|
|
68
|
+
* Pure indirection: ALWAYS returns `reason` UNCHANGED, never throws, never
|
|
69
|
+
* alters classifier behavior. Its ONLY side effect is recording a coherence
|
|
70
|
+
* violation when `reason` has no deterministic taxonomy row (exact match or a
|
|
71
|
+
* matching dynamic-family prefix). The classifier wraps every ineligible
|
|
72
|
+
* reason in `reject(...)`; the recorded violations are surfaced only to the
|
|
73
|
+
* coherence-gate tests. */
|
|
74
|
+
function reject(reason) {
|
|
75
|
+
const index = taxonomyIndex();
|
|
76
|
+
if (index.exactReasonCodes.has(reason))
|
|
77
|
+
return reason;
|
|
78
|
+
for (const prefix of index.reasonFamilyPrefixes) {
|
|
79
|
+
if (reason.startsWith(prefix))
|
|
80
|
+
return reason;
|
|
81
|
+
}
|
|
82
|
+
coherenceViolationsInternal.add(reason);
|
|
83
|
+
return reason;
|
|
84
|
+
}
|
|
26
85
|
/** True when `exprText` parses cleanly under KERN's parser-expression.
|
|
27
86
|
*
|
|
28
87
|
* Multi-line input is accepted: the parser itself is whitespace-insensitive,
|
|
@@ -174,9 +233,9 @@ export function isValidKernTypeAnnotation(typeText) {
|
|
|
174
233
|
*/
|
|
175
234
|
function classifyParseFailureBoundary(bodyText) {
|
|
176
235
|
if (/\{\{\s*[A-Za-z_$][\w$.-]*\s*\}\}/.test(bodyText))
|
|
177
|
-
return 'template-placeholder';
|
|
236
|
+
return reject('template-placeholder');
|
|
178
237
|
if (isObjectFragmentBody(bodyText))
|
|
179
|
-
return 'foreign-by-design';
|
|
238
|
+
return reject('foreign-by-design');
|
|
180
239
|
return null;
|
|
181
240
|
}
|
|
182
241
|
function isObjectFragmentBody(bodyText) {
|
|
@@ -512,21 +571,132 @@ function isSimpleTrailingStmt(node) {
|
|
|
512
571
|
}
|
|
513
572
|
return false;
|
|
514
573
|
}
|
|
574
|
+
/** Collect the raw text (braces included) of every block-bodied arrow
|
|
575
|
+
* (`x => { … }`) inside a statement subtree. TS-AST walk, never string
|
|
576
|
+
* scanning — `arrow.body.getText(sf)` yields the exact `{ … }` source. */
|
|
577
|
+
function collectBlockArrowRaws(node, sf) {
|
|
578
|
+
const raws = [];
|
|
579
|
+
const visit = (n) => {
|
|
580
|
+
if (ts.isArrowFunction(n) && ts.isBlock(n.body)) {
|
|
581
|
+
raws.push(n.body.getText(sf));
|
|
582
|
+
// Don't descend into the block body: the v1 closure gate already rejects
|
|
583
|
+
// nested arrows, so a nested block arrow can't be independently eligible.
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
ts.forEachChild(n, visit);
|
|
587
|
+
};
|
|
588
|
+
visit(node);
|
|
589
|
+
return raws;
|
|
590
|
+
}
|
|
591
|
+
/** Eligibility verdict for any block-bodied arrows in a statement (slices
|
|
592
|
+
* 0+1+2). Returns a reject reason or `null` if the statement's block arrows
|
|
593
|
+
* are all gate-passing:
|
|
594
|
+
* - any arrow whose `classifyClosureBlock` is non-null → that gate reason
|
|
595
|
+
* (the statement is ineligible for the same reason the closure is).
|
|
596
|
+
* - otherwise `null` (eligible — funnels through isValidKernExpression for
|
|
597
|
+
* the statement's own expression validity, which now parses the arrow).
|
|
598
|
+
*
|
|
599
|
+
* Slice 2 lifted the former `closure-in-loop` reject: a gate-passing block
|
|
600
|
+
* arrow inside a loop is now eligible. The Python lowerer pins per-iteration
|
|
601
|
+
* captures via default args (`def __kern_closure_N(p, x=x):`) so JS
|
|
602
|
+
* by-reference / per-iteration capture semantics are preserved across both
|
|
603
|
+
* targets. The classifier therefore no longer consults `ctx.loopDepth`. */
|
|
604
|
+
function classifyBlockArrows(stmt, sf) {
|
|
605
|
+
const raws = collectBlockArrowRaws(stmt, sf);
|
|
606
|
+
if (raws.length === 0)
|
|
607
|
+
return null;
|
|
608
|
+
for (const raw of raws) {
|
|
609
|
+
const gateReason = classifyClosureBlock(raw);
|
|
610
|
+
if (gateReason !== null)
|
|
611
|
+
return reject(gateReason);
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
/** Scan a statement subtree for an `instanceof` whose RHS KERN cannot lower
|
|
616
|
+
* (eligible ≡ lowerable, spec §3). Returns the first reject reason or `null`:
|
|
617
|
+
* - RHS is a bare ident in the reject set →
|
|
618
|
+
* `instanceof-rhs-wrapper-rejected` (String/Number/Boolean) or
|
|
619
|
+
* `instanceof-rhs-unsupported-builtin` (Object/Function/Date/RegExp/
|
|
620
|
+
* Promise/Map/Set/Symbol/BigInt).
|
|
621
|
+
* - RHS is NOT an identifier (call/literal/binary/etc.) →
|
|
622
|
+
* `instanceof-rhs-not-a-type-name`. A member-access RHS (`a.b.C`) is a
|
|
623
|
+
* qualified type name and stays accepted (emits as-is, like a user class).
|
|
624
|
+
* Accepted host idents (`Array`/`Error`/`TypeError`) and user-class /
|
|
625
|
+
* member RHS pass (return `null`); they emit as-is or via the host map.
|
|
626
|
+
*
|
|
627
|
+
* Detection is on the TS AST (`ts.isBinaryExpression` + `InstanceOfKeyword`),
|
|
628
|
+
* mirroring the Python emitter's fail-closed reject so the gate and the
|
|
629
|
+
* lowerer share one source of truth (`instanceof-rhs.ts`). The whole subtree
|
|
630
|
+
* is scanned so a rejected `instanceof` nested in any expression position
|
|
631
|
+
* (ternary arm, call arg, arrow body, …) is caught. */
|
|
632
|
+
// TODO(taxonomy-fold): the `instanceof` RHS reject set in instanceof-rhs.ts
|
|
633
|
+
// (INSTANCEOF_RHS_WRAPPER_REJECT / INSTANCEOF_RHS_BUILTIN_REJECT) is the target
|
|
634
|
+
// declarative shape for a future deterministic-row extraction slice — fold those
|
|
635
|
+
// RHS-name → reason mappings into the taxonomy so this dynamic reason source is
|
|
636
|
+
// itself authority-backed, not just family-validated at runtime. Deferred (phase 2
|
|
637
|
+
// scope is the AUTHORITY inversion; instanceof-rhs.ts fold-in is a separate slice).
|
|
638
|
+
function classifyInstanceofRhs(node) {
|
|
639
|
+
let reason = null;
|
|
640
|
+
const visit = (n) => {
|
|
641
|
+
if (reason !== null)
|
|
642
|
+
return;
|
|
643
|
+
if (ts.isBinaryExpression(n) && n.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
|
644
|
+
// Unwrap parens (`x instanceof (Error)`) before classification — the TS
|
|
645
|
+
// AST preserves them and a parenthesized bare ident is still a type
|
|
646
|
+
// name (agon review, 3-engine convergence). KERN's expression parser
|
|
647
|
+
// unwraps parens at parse time, so the emitter side never sees them.
|
|
648
|
+
let rhs = n.right;
|
|
649
|
+
while (ts.isParenthesizedExpression(rhs))
|
|
650
|
+
rhs = rhs.expression;
|
|
651
|
+
if (ts.isIdentifier(rhs)) {
|
|
652
|
+
const rhsReason = instanceofRhsRejectReasonForName(rhs.text);
|
|
653
|
+
reason = rhsReason === null ? null : reject(rhsReason);
|
|
654
|
+
}
|
|
655
|
+
else if (!ts.isPropertyAccessExpression(rhs)) {
|
|
656
|
+
// Member RHS (`a.b.C`) is a qualified type name → accepted (emit
|
|
657
|
+
// as-is). Anything else (call/literal/parenthesized/binary) is not a
|
|
658
|
+
// type name and cannot be lowered.
|
|
659
|
+
reason = reject('instanceof-rhs-not-a-type-name');
|
|
660
|
+
}
|
|
661
|
+
if (reason !== null)
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
ts.forEachChild(n, visit);
|
|
665
|
+
};
|
|
666
|
+
visit(node);
|
|
667
|
+
return reason;
|
|
668
|
+
}
|
|
515
669
|
/** Classify a single statement. Returns null if the migrator can emit it,
|
|
516
670
|
* otherwise a kebab-case reason. Recurses through if/try branches. */
|
|
517
671
|
function classifyStmt(stmt, sf, ctx) {
|
|
672
|
+
// Eligible ≡ lowerable (spec §3): a statement carrying an `instanceof` with a
|
|
673
|
+
// RHS the Python emitter fail-closes on is ineligible, with a reason that
|
|
674
|
+
// names the exact RHS problem. Run before the per-shape checks so the
|
|
675
|
+
// instanceof reason wins over a generic `*-bad-expr`.
|
|
676
|
+
const instanceofReason = classifyInstanceofRhs(stmt);
|
|
677
|
+
if (instanceofReason !== null)
|
|
678
|
+
return instanceofReason;
|
|
679
|
+
// Slices 0+1+2 — a statement containing a block-bodied arrow is eligible IFF
|
|
680
|
+
// every such arrow passes the v1 closure gate. Slice 2 lifted the former
|
|
681
|
+
// in-loop reject (the Python lowerer now pins per-iteration captures), so the
|
|
682
|
+
// loop context no longer matters here. The statement's own expression
|
|
683
|
+
// validity is still checked below via isValidKernExpression — the block arrow
|
|
684
|
+
// inside it parses now.
|
|
685
|
+
const closureReason = classifyBlockArrows(stmt, sf);
|
|
686
|
+
if (closureReason !== null)
|
|
687
|
+
return closureReason;
|
|
518
688
|
if (ts.isVariableStatement(stmt)) {
|
|
519
689
|
const flags = stmt.declarationList.flags;
|
|
520
690
|
const isConst = (flags & ts.NodeFlags.Const) !== 0;
|
|
521
691
|
const isLet = (flags & ts.NodeFlags.Let) !== 0;
|
|
522
692
|
if (!isConst && !isLet)
|
|
523
|
-
return 'var-non-const';
|
|
693
|
+
return reject('var-non-const');
|
|
524
694
|
const decls = stmt.declarationList.declarations;
|
|
525
695
|
if (decls.length !== 1)
|
|
526
|
-
return 'var-multi-decl';
|
|
696
|
+
return reject('var-multi-decl');
|
|
527
697
|
const decl = decls[0];
|
|
528
698
|
if (decl.type && !isValidKernTypeAnnotation(decl.type.getText(sf)))
|
|
529
|
-
return 'var-bad-type';
|
|
699
|
+
return reject('var-bad-type');
|
|
530
700
|
if (!decl.initializer) {
|
|
531
701
|
// `let x;` migrates to `let name=x kind=let` (the body emitter handles
|
|
532
702
|
// missing `value=` by emitting `let x = undefined;`, matching TS
|
|
@@ -534,7 +704,7 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
534
704
|
// TS parse error in practice, but defensively reject them anyway since
|
|
535
705
|
// the migrator can only emit identifier-named lets in this branch.
|
|
536
706
|
if (!ts.isIdentifier(decl.name))
|
|
537
|
-
return 'var-destructure';
|
|
707
|
+
return reject('var-destructure');
|
|
538
708
|
return null;
|
|
539
709
|
}
|
|
540
710
|
if (!ts.isIdentifier(decl.name))
|
|
@@ -552,13 +722,13 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
552
722
|
const raw = decl.initializer.getText(sf);
|
|
553
723
|
const body = raw.slice(1, -1);
|
|
554
724
|
if (body.includes('\n'))
|
|
555
|
-
return 'var-template-multiline';
|
|
725
|
+
return reject('var-template-multiline');
|
|
556
726
|
if (hasTsOnlyTemplateEscape(body))
|
|
557
|
-
return 'var-template-escapes';
|
|
727
|
+
return reject('var-template-escapes');
|
|
558
728
|
return null;
|
|
559
729
|
}
|
|
560
730
|
if (!isValidKernExpression(decl.initializer.getText(sf)))
|
|
561
|
-
return 'var-bad-expr';
|
|
731
|
+
return reject('var-bad-expr');
|
|
562
732
|
return null;
|
|
563
733
|
}
|
|
564
734
|
if (ts.isReturnStatement(stmt)) {
|
|
@@ -570,41 +740,43 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
570
740
|
const raw = stmt.expression.getText(sf);
|
|
571
741
|
const body = raw.slice(1, -1);
|
|
572
742
|
if (body.includes('\n'))
|
|
573
|
-
return 'return-template-multiline';
|
|
743
|
+
return reject('return-template-multiline');
|
|
574
744
|
if (hasTsOnlyTemplateEscape(body))
|
|
575
|
-
return 'return-template-escapes';
|
|
745
|
+
return reject('return-template-escapes');
|
|
576
746
|
return null;
|
|
577
747
|
}
|
|
578
748
|
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
579
|
-
return 'return-bad-expr';
|
|
749
|
+
return reject('return-bad-expr');
|
|
580
750
|
return null;
|
|
581
751
|
}
|
|
582
752
|
if (ts.isThrowStatement(stmt)) {
|
|
753
|
+
// `throw;` is structurally unreachable (always a TS parse error) and has no
|
|
754
|
+
// taxonomy row — NOT routed through reject() (it would never validate).
|
|
583
755
|
if (!stmt.expression)
|
|
584
756
|
return 'throw-no-expr';
|
|
585
757
|
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
586
|
-
return 'throw-bad-expr';
|
|
758
|
+
return reject('throw-bad-expr');
|
|
587
759
|
return null;
|
|
588
760
|
}
|
|
589
761
|
if (ts.isBreakStatement(stmt)) {
|
|
590
762
|
if (stmt.label)
|
|
591
|
-
return 'break-labeled';
|
|
592
|
-
return ctx.loopDepth > 0 ? null : 'break-outside-loop';
|
|
763
|
+
return reject('break-labeled');
|
|
764
|
+
return ctx.loopDepth > 0 ? null : reject('break-outside-loop');
|
|
593
765
|
}
|
|
594
766
|
if (ts.isContinueStatement(stmt)) {
|
|
595
767
|
if (stmt.label)
|
|
596
|
-
return 'continue-labeled';
|
|
597
|
-
return ctx.loopDepth > 0 ? null : 'continue-outside-loop';
|
|
768
|
+
return reject('continue-labeled');
|
|
769
|
+
return ctx.loopDepth > 0 ? null : reject('continue-outside-loop');
|
|
598
770
|
}
|
|
599
771
|
if (ts.isIfStatement(stmt)) {
|
|
600
772
|
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
601
|
-
return 'if-bad-cond';
|
|
773
|
+
return reject('if-bad-cond');
|
|
602
774
|
// Body emitters (`emitNativeKernBodyTS` / `emitNativeKernBodyPython`) always
|
|
603
775
|
// wrap `if` bodies in braces / indented blocks. A raw `if (cond) stmt;`
|
|
604
776
|
// would migrate to `if cond=… → { stmt; }` and lose byte-equivalence under
|
|
605
777
|
// `--verify`. Mirror the `for-of-non-block` / `while-non-block` guards.
|
|
606
778
|
if (!ctx.allowNonBlock && !ts.isBlock(stmt.thenStatement))
|
|
607
|
-
return 'if-non-block-then';
|
|
779
|
+
return reject('if-non-block-then');
|
|
608
780
|
const thenReason = classifyBranch(stmt.thenStatement, sf, ctx);
|
|
609
781
|
if (thenReason !== null)
|
|
610
782
|
return thenReason;
|
|
@@ -615,7 +787,7 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
615
787
|
// 88c06dcc on dev). classifyBranch handles the nested IfStatement
|
|
616
788
|
// by re-entering classifyStmt, so the recursion is automatic.
|
|
617
789
|
if (!ctx.allowNonBlock && !ts.isIfStatement(stmt.elseStatement) && !ts.isBlock(stmt.elseStatement)) {
|
|
618
|
-
return 'if-non-block-else';
|
|
790
|
+
return reject('if-non-block-else');
|
|
619
791
|
}
|
|
620
792
|
const elseReason = classifyBranch(stmt.elseStatement, sf, ctx);
|
|
621
793
|
if (elseReason !== null)
|
|
@@ -629,12 +801,14 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
629
801
|
// (body-ts.ts:286-292 / codegen-body-python.ts:316-323), and the schema
|
|
630
802
|
// permits `finally` as a `try` child. The only remaining requirement is
|
|
631
803
|
// the TS-level shape — at least one of `catch`/`finally` must be present.
|
|
804
|
+
// `try {}` without catch|finally is structurally unreachable (always a TS
|
|
805
|
+
// parse error) and has no taxonomy row — NOT routed through reject().
|
|
632
806
|
if (!stmt.catchClause && !stmt.finallyBlock)
|
|
633
807
|
return 'try-no-catch';
|
|
634
808
|
if (stmt.catchClause) {
|
|
635
809
|
const cc = stmt.catchClause;
|
|
636
810
|
if (cc.variableDeclaration && !ts.isIdentifier(cc.variableDeclaration.name))
|
|
637
|
-
return 'try-destruct-catch';
|
|
811
|
+
return reject('try-destruct-catch');
|
|
638
812
|
const catchReason = classifyBranch(cc.block, sf, ctx);
|
|
639
813
|
if (catchReason !== null)
|
|
640
814
|
return catchReason;
|
|
@@ -659,12 +833,12 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
659
833
|
const op = stmt.expression.operatorToken.kind;
|
|
660
834
|
if (op >= ts.SyntaxKind.FirstAssignment && op <= ts.SyntaxKind.LastAssignment) {
|
|
661
835
|
if (op !== ts.SyntaxKind.EqualsToken && !supportedCompoundAssignmentOperator(op)) {
|
|
662
|
-
return 'expr-stmt-assignment';
|
|
836
|
+
return reject('expr-stmt-assignment');
|
|
663
837
|
}
|
|
664
838
|
if (!isValidKernAssignmentTarget(stmt.expression.left.getText(sf)))
|
|
665
|
-
return 'expr-stmt-bad-assign-target';
|
|
839
|
+
return reject('expr-stmt-bad-assign-target');
|
|
666
840
|
if (!isValidKernAssignmentValue(stmt.expression.right.getText(sf)))
|
|
667
|
-
return 'expr-stmt-bad-assign-value';
|
|
841
|
+
return reject('expr-stmt-bad-assign-value');
|
|
668
842
|
return null;
|
|
669
843
|
}
|
|
670
844
|
}
|
|
@@ -680,7 +854,7 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
680
854
|
// raw-handler authors get a clear reason and the migrator never rewrites
|
|
681
855
|
// bytes it cannot reproduce.
|
|
682
856
|
if (!isValidKernAssignmentTarget(stmt.expression.operand.getText(sf))) {
|
|
683
|
-
return 'expr-stmt-bad-assign-target';
|
|
857
|
+
return reject('expr-stmt-bad-assign-target');
|
|
684
858
|
}
|
|
685
859
|
return null;
|
|
686
860
|
}
|
|
@@ -688,32 +862,32 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
688
862
|
if (ts.isPrefixUnaryExpression(stmt.expression)) {
|
|
689
863
|
const op = stmt.expression.operator;
|
|
690
864
|
if (op === ts.SyntaxKind.PlusPlusToken || op === ts.SyntaxKind.MinusMinusToken)
|
|
691
|
-
return 'expr-stmt-mutation';
|
|
865
|
+
return reject('expr-stmt-mutation');
|
|
692
866
|
}
|
|
693
867
|
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
694
|
-
return 'expr-stmt-bad-expr';
|
|
868
|
+
return reject('expr-stmt-bad-expr');
|
|
695
869
|
return null;
|
|
696
870
|
}
|
|
697
871
|
if (ts.isForOfStatement(stmt)) {
|
|
698
872
|
if (!ts.isVariableDeclarationList(stmt.initializer))
|
|
699
|
-
return 'for-of-non-decl';
|
|
873
|
+
return reject('for-of-non-decl');
|
|
700
874
|
if (!(stmt.initializer.flags & ts.NodeFlags.Const))
|
|
701
|
-
return 'for-of-non-const';
|
|
875
|
+
return reject('for-of-non-const');
|
|
702
876
|
const decls = stmt.initializer.declarations;
|
|
703
877
|
if (decls.length !== 1)
|
|
704
|
-
return 'for-of-multi-decl';
|
|
878
|
+
return reject('for-of-multi-decl');
|
|
705
879
|
const decl = decls[0];
|
|
706
880
|
if (decl.initializer)
|
|
707
|
-
return 'for-of-init';
|
|
881
|
+
return reject('for-of-init');
|
|
708
882
|
if (!ts.isIdentifier(decl.name)) {
|
|
709
883
|
const entryBinding = parseForOfEntryBinding(decl.name);
|
|
710
884
|
if (entryBinding === null)
|
|
711
|
-
return 'for-of-destructure';
|
|
885
|
+
return reject('for-of-destructure');
|
|
712
886
|
if (stmt.awaitModifier && canonicalObjectEntriesSource(stmt.expression, sf) !== null) {
|
|
713
|
-
return 'for-of-async-object-entries';
|
|
887
|
+
return reject('for-of-async-object-entries');
|
|
714
888
|
}
|
|
715
889
|
if (stmt.awaitModifier && entryBinding.kind !== 'pair')
|
|
716
|
-
return 'for-of-async-entry';
|
|
890
|
+
return reject('for-of-async-entry');
|
|
717
891
|
// KERN-GAPS: sync pair iteration (`for (const [k, v] of expr)`) lifts to
|
|
718
892
|
// `each pairKey=k pairValue=v in=expr` regardless of whether `expr` is
|
|
719
893
|
// `Object.entries(...)` — Map.entries(), arrays-of-pairs, and generators
|
|
@@ -723,64 +897,68 @@ function classifyStmt(stmt, sf, ctx) {
|
|
|
723
897
|
if (!stmt.awaitModifier &&
|
|
724
898
|
entryBinding.kind !== 'pair' &&
|
|
725
899
|
canonicalObjectEntriesSource(stmt.expression, sf) === null) {
|
|
726
|
-
return 'for-of-sync-pair';
|
|
900
|
+
return reject('for-of-sync-pair');
|
|
727
901
|
}
|
|
728
902
|
if (decl.type)
|
|
729
|
-
return 'for-of-destructure-type';
|
|
903
|
+
return reject('for-of-destructure-type');
|
|
730
904
|
}
|
|
731
905
|
else if (decl.type && !isValidKernTypeAnnotation(decl.type.getText(sf))) {
|
|
732
|
-
return 'for-of-bad-type';
|
|
906
|
+
return reject('for-of-bad-type');
|
|
733
907
|
}
|
|
734
908
|
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
735
|
-
return 'for-of-bad-expr';
|
|
909
|
+
return reject('for-of-bad-expr');
|
|
736
910
|
// Only block-shaped loops are currently migratable. `each` always emits
|
|
737
911
|
// braces, so migrating `for (const x of xs) do(x);` would drift under
|
|
738
912
|
// --verify even though it is semantically close.
|
|
739
913
|
if (!ctx.allowNonBlock && !ts.isBlock(stmt.statement))
|
|
740
|
-
return 'for-of-non-block';
|
|
914
|
+
return reject('for-of-non-block');
|
|
741
915
|
if (ts.isBlock(stmt.statement) && stmt.statement.statements.length === 0)
|
|
742
|
-
return 'for-of-empty-body';
|
|
916
|
+
return reject('for-of-empty-body');
|
|
743
917
|
return classifyBranch(stmt.statement, sf, { ...ctx, loopDepth: ctx.loopDepth + 1 });
|
|
744
918
|
}
|
|
745
919
|
if (ts.isWhileStatement(stmt)) {
|
|
746
920
|
if (!isValidKernExpression(stmt.expression.getText(sf)))
|
|
747
|
-
return 'while-bad-cond';
|
|
921
|
+
return reject('while-bad-cond');
|
|
748
922
|
if (!ctx.allowNonBlock && !ts.isBlock(stmt.statement))
|
|
749
|
-
return 'while-non-block';
|
|
923
|
+
return reject('while-non-block');
|
|
750
924
|
if (ts.isBlock(stmt.statement) && stmt.statement.statements.length === 0)
|
|
751
|
-
return 'while-empty-body';
|
|
925
|
+
return reject('while-empty-body');
|
|
752
926
|
return classifyBranch(stmt.statement, sf, { ...ctx, loopDepth: ctx.loopDepth + 1 });
|
|
753
927
|
}
|
|
754
928
|
if (ts.isForStatement(stmt) || ts.isForInStatement(stmt))
|
|
755
|
-
return 'for-stmt';
|
|
929
|
+
return reject('for-stmt');
|
|
756
930
|
if (ts.isDoStatement(stmt))
|
|
757
|
-
return 'do-while-stmt';
|
|
931
|
+
return reject('do-while-stmt');
|
|
758
932
|
if (ts.isSwitchStatement(stmt))
|
|
759
|
-
return 'switch-stmt';
|
|
933
|
+
return reject('switch-stmt');
|
|
760
934
|
if (ts.isBlock(stmt))
|
|
761
|
-
return 'bare-block';
|
|
935
|
+
return reject('bare-block');
|
|
762
936
|
// Fallback — the TS SyntaxKind name surfaces in diagnostics so users have
|
|
763
937
|
// a starting point when they hit something exotic (label, with, debugger).
|
|
764
|
-
|
|
938
|
+
// Validated against the `unsupported-stmt-` family prefix row.
|
|
939
|
+
return reject(`unsupported-stmt-${ts.SyntaxKind[stmt.kind]}`);
|
|
765
940
|
}
|
|
766
941
|
function classifyDestructureDecl(decl, sf) {
|
|
942
|
+
// `var-no-init` is structurally unreachable (an uninitialised destructure
|
|
943
|
+
// short-circuits to `var-destructure` before this branch is reached) and has
|
|
944
|
+
// no taxonomy row — NOT routed through reject().
|
|
767
945
|
if (!decl.initializer)
|
|
768
946
|
return 'var-no-init';
|
|
769
947
|
if (!isValidKernExpression(decl.initializer.getText(sf)))
|
|
770
|
-
return 'var-destructure-bad-expr';
|
|
948
|
+
return reject('var-destructure-bad-expr');
|
|
771
949
|
const name = decl.name;
|
|
772
950
|
if (ts.isObjectBindingPattern(name)) {
|
|
773
951
|
if (name.elements.length === 0)
|
|
774
|
-
return 'var-destructure-empty';
|
|
952
|
+
return reject('var-destructure-empty');
|
|
775
953
|
for (const element of name.elements) {
|
|
776
954
|
if (element.dotDotDotToken)
|
|
777
|
-
return 'var-destructure-rest';
|
|
955
|
+
return reject('var-destructure-rest');
|
|
778
956
|
if (element.initializer)
|
|
779
|
-
return 'var-destructure-default';
|
|
957
|
+
return reject('var-destructure-default');
|
|
780
958
|
if (!ts.isIdentifier(element.name))
|
|
781
|
-
return 'var-destructure-nested';
|
|
959
|
+
return reject('var-destructure-nested');
|
|
782
960
|
if (element.propertyName && !ts.isIdentifier(element.propertyName))
|
|
783
|
-
return 'var-destructure-computed';
|
|
961
|
+
return reject('var-destructure-computed');
|
|
784
962
|
}
|
|
785
963
|
return null;
|
|
786
964
|
}
|
|
@@ -791,17 +969,17 @@ function classifyDestructureDecl(decl, sf) {
|
|
|
791
969
|
continue;
|
|
792
970
|
concreteElements++;
|
|
793
971
|
if (element.dotDotDotToken)
|
|
794
|
-
return 'var-destructure-rest';
|
|
972
|
+
return reject('var-destructure-rest');
|
|
795
973
|
if (element.initializer)
|
|
796
|
-
return 'var-destructure-default';
|
|
974
|
+
return reject('var-destructure-default');
|
|
797
975
|
if (!ts.isIdentifier(element.name))
|
|
798
|
-
return 'var-destructure-nested';
|
|
976
|
+
return reject('var-destructure-nested');
|
|
799
977
|
}
|
|
800
978
|
if (concreteElements === 0)
|
|
801
|
-
return 'var-destructure-empty';
|
|
979
|
+
return reject('var-destructure-empty');
|
|
802
980
|
return null;
|
|
803
981
|
}
|
|
804
|
-
return 'var-destructure';
|
|
982
|
+
return reject('var-destructure');
|
|
805
983
|
}
|
|
806
984
|
function parseForOfEntryBinding(name) {
|
|
807
985
|
if (!ts.isArrayBindingPattern(name))
|
|
@@ -855,18 +1033,19 @@ export function classifyHandlerBodyAst(rawBody, opts) {
|
|
|
855
1033
|
// public type — the migrator reads it the same way.
|
|
856
1034
|
const diags = sf.parseDiagnostics;
|
|
857
1035
|
if (diags && diags.length > 0) {
|
|
1036
|
+
// `classifyParseFailureBoundary` already routes its reasons through reject().
|
|
858
1037
|
const parseBoundary = classifyParseFailureBoundary(rawBody);
|
|
859
1038
|
if (parseBoundary !== null)
|
|
860
1039
|
return { eligible: false, reason: parseBoundary };
|
|
861
1040
|
if (hasComments(rawBody) && !hasOnlyMigratableComments(rawBody)) {
|
|
862
|
-
return { eligible: false, reason: 'comments-present' };
|
|
1041
|
+
return { eligible: false, reason: reject('comments-present') };
|
|
863
1042
|
}
|
|
864
|
-
return { eligible: false, reason: 'ts-parse-error' };
|
|
1043
|
+
return { eligible: false, reason: reject('ts-parse-error') };
|
|
865
1044
|
}
|
|
866
1045
|
if (isHostInteropBody(sf))
|
|
867
|
-
return { eligible: false, reason: 'foreign-by-design' };
|
|
1046
|
+
return { eligible: false, reason: reject('foreign-by-design') };
|
|
868
1047
|
if (hasComments(rawBody) && !hasOnlyMigratableComments(rawBody)) {
|
|
869
|
-
return { eligible: false, reason: 'comments-present' };
|
|
1048
|
+
return { eligible: false, reason: reject('comments-present') };
|
|
870
1049
|
}
|
|
871
1050
|
for (const stmt of sf.statements) {
|
|
872
1051
|
const r = classifyStmt(stmt, sf, { loopDepth: 0, allowNonBlock: opts?.allowNonBlock });
|