Feat/kern core runtime#397
Merged
Merged
Conversation
* Add portable expression logic foundation * Format portable expression foundation changes
Bumps the minor-and-patch group with 5 updates: | Package | From | To | | --- | --- | --- | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.9.1` | `25.9.2` | | [next](https://github.com/vercel/next.js) | `16.2.6` | `16.2.7` | | [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.7` | | [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react) | `19.2.15` | `19.2.17` | | [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.6` | `19.2.7` | Updates `@types/node` from 25.9.1 to 25.9.2 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `next` from 16.2.6 to 16.2.7 - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](vercel/next.js@v16.2.6...v16.2.7) Updates `react` from 19.2.6 to 19.2.7 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react) Updates `@types/react` from 19.2.15 to 19.2.17 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) Updates `react-dom` from 19.2.6 to 19.2.7 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom) Updates `@types/react` from 19.2.15 to 19.2.17 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.9.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: next dependency-version: 16.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: react dependency-version: 19.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: "@types/react" dependency-version: 19.2.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: react-dom dependency-version: 19.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor-and-patch - dependency-name: "@types/react" dependency-version: 19.2.17 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: minor-and-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Add portable expression logic foundation * Format portable expression foundation changes
# Conflicts: # packages/core/src/core-runtime/index.ts # packages/core/src/semantic-validator.ts # packages/core/tests/class-semantics.test.ts
A static setter whose param snake-cased to `cls` emitted invalid Python (`def label(cls, cls):`). The body collision guard reserved only `self`, never the injected metaclass receiver. Reserve the actual receiver (`cls` for static accessors, `self` for instance members) and fail codegen early with a clear message instead of producing a SyntaxError. Closes the codex (0.96) finding from the PR 2.1 static-accessor agon review. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Class-method bodies emitted `this.items.push(x)` verbatim — invalid Python, since lists have no `.push` — while the route emitter already lowered it. The two Python emitters had drifted: each open-coded its own array dispatch. Extract the portable-array lowering into one shared module (core/expr/list-ops.ts) that BOTH the route emitter and the class-method body emitter delegate to, so `arr.push(x)` lowers identically (TS native; Python `(recv.append(x) or len(recv))`, matching JS push's new-length return) wherever it appears — parity by construction, no per-path drift. The class-body hook lowers only a single-arg call on a pure, guard-free receiver; the shim names the receiver twice, so an impure receiver such as `makeBag().items.push(x)` would run its effects twice on Python and break JS parity — those fall through unchanged. Agon-planned (6-engine brainstorm, unanimous Approach C) and agon-reviewed (codex 0.97 receiver double-evaluation closed via the isReceiverChainPure gate; the method-name set is kept private behind a predicate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- class-conformance.mjs: differential fixture proving per-instance list isolation AND push return-parity — two Bags, append twice to one, the other's first push returns 1 (not 3). Kills shared-mutable-default, unlowered push (Python crash, ts != py), and push without JS-return parity (None != 1). Also refresh the stale "list mutation excluded" scope comment. - class-python.test.ts: pure-receiver push lowers to the shared append+len shim; impure-receiver push does NOT lower (locks the codex double-eval fix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…il-fast stubs KERN classes can be `abstract` and `implements` an interface, but Python codegen dropped both and TS only carried the keywords. Define KERN's semantics and emit them with TS<->Python parity by construction (Approach B+, agon-chosen): - `abstract` is ERASED at runtime on both targets (a plain, instantiable class) — matching how TS `abstract` is erased in compiled JS. A handler-less member under an abstract class lowers to a fail-fast body: `raise NotImplementedError` (Python) / `throw new Error` (TS), so an un-overridden abstract member fails identically on both. TS keeps the class-level `abstract` keyword (tsc still rejects `new X()`); Python emits a plain class (no ABC/metaclass). Covers instance methods, getters, setters, AND static accessors (the metaclass path). - `implements` is erased on Python (the semantic validator owns conformance) and left as a `# implements: Y` marker comment; the single-inheritance base is unchanged. TS keeps its existing `implements` emission. Abstract enforcement (reject `new Abstract()`, require concrete overrides) lives in KERN's validator, not the host type-checker — a deferred follow-up. Agon-planned (6-engine brainstorm, Approach B+) and agon-reviewed (codex 0.99: abstract static accessors must raise too, now fixed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- class-conformance.mjs (now 12/12): abstract polymorphic dispatch (Shape/Square), abstract template method reading an inherited field default (Formatter), and an abstract static accessor override dispatched through a chained metaclass. - class-python.test.ts: abstract instance method + abstract static accessor each emit the fail-fast `raise` (not a silent body); `implements` is erased to a marker comment with no runtime base. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or semantics)
A derived class whose explicit constructor touches `this`/`self` without calling
super() crashed on TS ("must call super constructor before accessing this") but
worked on Python — the JS "super-before-this" rule leaking into KERN as a parity
break. KERN now OWNS the rule: a derived constructor that omits super() gets an
implicit no-arg super() / super().__init__() injected as the FIRST statement on
BOTH targets (Java/Python ergonomics), threaded so field defaults and the body
still run after it. The author writes explicit super(args) only to pass args up.
Agon-planned (6-engine brainstorm, unanimous Option C). This is C's codegen arm
(implicit injection). C's safety arm — a validator diagnostic when the base
constructor REQUIRES args and a derived ctor omits super(args) — is a follow-up in
the consolidated validator-enforcement slice (with the deferred abstract
concrete-must-override check); until then that edge injects a no-arg super() that
fails identically on both targets (parity preserved, just without a friendly
compile-time error).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…guards - class-conformance.mjs (now 13/13): a derived class whose ctor omits super() and reads an inherited field default -> get() == 7 + 1 identically on both targets. Kills no-super-injected (TS crash), super-after-this (TS crash), base-init skipped (this.tag undefined -> NaN/AttributeError), field-defaults-before-super. - class-python.test.ts: derived ctor injects super().__init__() before field defaults and body; a non-derived ctor gets none. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… runtime, codegen PR4 Part 1 made codegen inject an implicit super() for derived constructors that omit it, but the semantic validator and the in-process KERN runtime still enforced the old "explicit super is mandatory" rule — so a program legal in generated TS/Python was rejected by the validator and threw in the interpreter (a target-vs-runtime split, flagged blocking by review). This lands Option C as one coherent semantic across all four layers, behind a single source of truth — a new canonical predicate `hasDirectSuperCtorCall(ctor)` (packages/core/src/constructor-super.ts). It answers structurally, from the IR, the one question every layer must answer identically: does the constructor make a direct super(...) call (counting if-branches, not lambdas, not super.member)? This replaces three divergent textual heuristics — the validator's own walk plus the codegens' emitted-text scans (`/\bsuper\s*\(/` and `"super().__init__"`) that 5/6 reviewers flagged as false-matching `super(` inside strings/comments. - Validator: a derived ctor with no direct super is now LEGAL (implicit base init); the only error is class-constructor-implicit-super-needs-args, raised when the base ctor requires arguments an arg-less implicit super can't supply. A direct super keeps the full discipline (double / conditional / this-before-super). class-constructor-missing-super is retired from the user path. The descriptive superStatus substrate fact is left untouched (still reports 'missing' etc.). - Runtime: initializeClassLayer initializes the base FIRST, then derived field defaults, then the body for implicit-mode ctors (frame pre-marked superCalled so this/super access is unguarded; a stray late super still trips double-init). - TS + Python codegen: the inject/splice decision now consumes the shared predicate instead of scanning emitted text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- constructor-super.test.ts: pins hasDirectSuperCtorCall on the canonical cases (omit, straight-line super, super-in-if, lambda-only, super.member, double) so all four layers can't drift from the single source of truth. - class-semantics: flips the omitted-super and lambda-only cases to legal; the super.kind() (member, no ctor super) case drops to implicit-mode legal (this-before-super 2 -> 1); adds coverage for the new class-constructor-implicit-super-needs-args arity diagnostic and that an explicit super(args) satisfies an arg-requiring base. - core-runtime: a lambda-only super now attempts implicit base init (fails an arg-requiring base with "missing required argument" — proving the lambda super isn't counted); adds the positive implicit-base-init case mirroring the class-conformance Box/Base fixture inside the interpreter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er arity check Review (codex, confidence 1.0, verified repro) found baseConstructorRequiresArgs only inspected the immediate base's own constructor. For `C extends B extends A` where B has no constructor and A requires arguments, the validator accepted C's omitted super() while the runtime threw `A.constructor missing required argument` — re-opening the exact validator/runtime split this reconciliation set out to close (initializeClassLayer forwards [] through constructor-less bases to A). Walk up the inheritance chain through constructor-less bases to the first ancestor that actually declares a constructor — the one implicit init reaches with no args — and test ITS required params (with a cycle guard). This matches the runtime's forwarding precisely. Adds regression coverage for the transitive arg-requiring base (flagged) and the transitive no-arg base (accepted). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nostic Re-review (codex, confidence 1.0) noted the transitive arity check rejected the right constructor but the message still named the immediate base. For `C extends B extends A` (B constructor-less, A requires args) it read "base class 'B' has a constructor that requires arguments" — but B has no constructor; A does. argRequiringEffectiveBaseName now returns the name of the class whose constructor is actually reached and requires args (undefined when implicit init succeeds), and the diagnostic names that class. Test asserts the transitive message names 'A', not 'B'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
origin/main carried #396 — a squash-merge of an EARLIER snapshot of this branch's Python class-codegen work (#396 touched only package.json, packages/python/*, and scripts/class-conformance.mjs). Because the squash hid the shared lineage, git saw the branch's continued history as divergent and flagged conflicts in the four Python-side files. Resolution: take the branch side for all four. Verified each is the strict evolution of #396's snapshot — origin/main contributed zero lines the branch lacks except code the branch deliberately superseded: - codegen-body-python.ts / class-python.test.ts: 0 main-only lines (superset) - class-conformance.mjs: only a stale "needs list-append lowering" scope note - generators/data.ts: main's unique lines are the OLD superIdx-existence super decision + pre-hasDirectSuperCtorCall import that PR4 replaced with the canonical hasDirectSuperCtorCall(ctor) predicate + Option-C implicit-super injection + abstract-member raise stubs. The full class-conformance + python + core gates re-run post-merge to confirm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
KERN now owns its abstract contract at the VALIDATOR layer (codegen/runtime
stay the loud backstop), so TS and Python reject the same programs by
construction — the validator runs before codegen, making enforcement
parity-free. Closes the soundness/asymmetry gap where `new Abstract()` and
unimplemented abstract members only failed at runtime (and only tsc, not
Python, caught the direct instantiation).
Two rules:
- class-abstract-instantiation: reject `new <AbstractClass>(...)` anywhere,
including the abstract class's own static factory (matches TS). Module-wide
pass over BODY_EXPRESSION_PROPS; resolves the constructor target by
descending the postfix chain to its head ident (so `new Shape().area()`
is attributed to Shape per JS `new` precedence) and skips qualified
(`pkg.Shape`), non-ident, and unresolved callees conservatively.
- class-abstract-member-unimplemented: a CONCRETE class must implement every
inherited abstract member. Driven by a dedicated collectAbstractObligations
lineage walker keyed by (static, name, kind) — NOT effectiveClassMemberFacts,
which collapses members by name+static and would let a getter-only override
silently erase the sibling abstract setter (or a same-name different-kind
member erase an abstract method). Getter/setter are independent obligations;
a same-name different-kind collision stays owned by class-member-conflict.
Multi-level abstract chains require the override only at the concrete leaf;
abstract subclasses may carry/inherit abstract members. Runtime `new Abstract()`
guard deferred (validator is the gate; the codegen raise/throw stub remains the
backstop) — panel-unanimous.
Design via 6-engine agon brainstorm; the effectiveClassMemberFacts soundness
hole was flagged independently by codex/kimi/minimax and verified in code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Validator unit tests for both abstract rules:
- instantiation: direct `new Abstract()` rejected; concrete subclass accepted;
rejected inside the abstract class's own static factory; concrete/unresolved
`new` not flagged.
- concrete-must-override: missing override rejected (message names class +
kind + member + declaring ancestor); full override accepted; abstract
subclass may leave it unimplemented; multi-level chain requires it only at
the concrete leaf (names the original abstract declarer).
- getter/setter pair: overriding only the getter still flags the abstract
setter (the soundness case); overriding both is accepted.
- same-name different-kind (abstract method vs field) does not satisfy the
method obligation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review (codex 0.98, kimi, agy 0.85 — 3-engine convergence) found the
class-abstract-instantiation pass scanned only BODY_EXPRESSION_PROPS, which
excludes `default`. So `new Abstract()` in a field `default=` or `param
default=` initializer was silently un-checked — a real soundness gap, since
field initializers treat `value` and `default` equivalently and both lower to
runtime code. Verified empirically: `field s default="new Shape()"` was MISSED
while `value={{ new Shape() }}` was caught.
Scan `default` too, local to this pass (INSTANTIATION_EXPRESSION_PROPS) so the
shared super-detection / shape-usage walks are unaffected. Regression test added.
Also documented the multi-root visibility-union behavior (codex 0.86): the pass
resolves classes across roots via the same union extends/implements already use;
all production callers validate a single root, so it is not a false-positive
surface in practice. The cycle-short-circuit (minimax 0.75) only affects programs
that already carry a class-inheritance-cycle diagnostic; deferred as the cycle is
the primary error. Remaining review items were nits or self-disproven.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The conformance-bad-cases suite asserted a `class-constructor-missing-super`
("constructor does not call super") violation, but Option-C implicit-super
(commits f0d9d1c/62855ebf) intentionally retired that rule: omitting
`super(...)` is now legal because KERN injects base init. `class-semantics`
unit tests already assert the rule is absent, so the conformance fixture was
stale and CI's `pnpm test:kern` failed on it.
Give `ProtocolBase` a required-arg constructor so `MissingSuper` (which omits
super) now trips the *replacement* detector
`class-constructor-implicit-super-needs-args`, and reword the assertion to
match. Preserves detector coverage; the derived classes that call `super('u1')`
explicitly are unaffected. Full test:kern green (233/233, coverage 100%).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frozen oracle for the value→string coercion-parity slice. 14 differential fixtures (compile to TS + Python, run both, assert ts==python==expected): 10 RED-at-base (Python diverges: bool→"True", null→"None", undefined→"None", 1.0→"1.0", arrays→"[1, 2, 3]", str+x→TypeError) force the implementation; 4 GREEN guards (2+3==5, 5/null/undefined ?? 9) must stay green so an over-eager `+` coercion or a non-nullish `undefined` sentinel is caught. Expected values are JS/TS truth → correct by construction. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
KERN is one language emitted to BOTH TS and Python; coercion must be parity by construction. JS coerces values to strings (true→"true", null→"null", undefined→"undefined", 1.0→"1", [1,2,3]→"1,2,3", "a"+true→"atrue", Infinity→"Infinity") in ways Python's str()/+ do not. This lowers those semantics on the Python target (TS already IS JS). Scope: native KERN handler bodies only, gated by `coerceJsValues` (default true; helpers inject function-locally there). The helper-less Ground/React declarative layer opts out (GROUND_EMIT) and keeps the pre-slice forms, so its output is byte-identical — no regression, no NameError from undefined helper references. - helpers.ts: _kern_fmt extended (str/bool/null/int-float/array/dict/ Infinity/NaN); _KERN_UNDEFINED sentinel with __bool__→False so JS undefined stays falsy (!undefined, ternary, ||, if); __kern_add for the JS `+` overload (string concat if string-ish incl tuple, else ToNumber add null→0/undefined→NaN/bool→0,1, try/except fallback for exotic hosts). - codegen-body-python.ts: undefLit→sentinel; tmplLit interpolation wrapped in _kern_fmt; binary `+` → __kern_add / _kern_fmt; `??` excludes the sentinel; dynamic typeof reports "undefined" for a stored sentinel. All gated on coerceJsValues. - ground.ts: 6 emitPyExpression calls opt out via GROUND_EMIT. - coercion-conformance.mjs: 20 discriminating fixtures (ts==python== expected), incl Infinity/-Infinity via float overflow (KERN has no `e` notation). Verified: oracle 20/20, class 13/13, expr 273/273, full monorepo test green (# fail 0), lint clean. Follow-ups (documented, out of slice scope): extend the fmt IR-semantics contract to the coercion superset; sentinel JSON-serialization at the route boundary; cross-function sentinel identity (function-local helper injection). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Why
How
Checklist
tsc -bpassespnpm testpassespnpm test:kernpassespnpm lintpasseskern review packages/ --recursivechecked