Commits
The Test job failed at 'cargo binstall cargo-nextest' — cargo-binstall is
not preinstalled on the runner. Use taiki-e/install-action to fetch a
prebuilt nextest binary instead. Also set up Node (the codegen tests execute
emitted JS under node) and add Swatinem/rust-cache to avoid a cold build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The push reconciliation reset the local repo to a fresh clone of origin
(which sat at the pre-session base), abandoning the entire session's
commit chain. That chain was never pushed, so its 168 commits are not
recoverable at the git layer — but the fully-integrated working tree
survived in the `nymph_lang-ws-corestd` workspace directory (verified
1034/1034 green). This commit re-lands that complete tree onto main.
Recovered work (previously each its own slice; history could not be
preserved, so it lands here as one recovery commit):
- LSP: rich hover (declaration structure, named signatures, generic
bounds, keyword docs, match-arm/for-loop pattern hover, markdown
rendering), variant-construction hover, this-receiver go-to-def, and
resolution-aware semantic tokens with consistent use-site classes.
- External linkage L0-L3: in-process oxc TS strip, the Option/Result
ABI seam (global enum discriminants), and linked list/map/set
surfaces that materialise and run under Node.
- print/println wired to real JS via free-function linkage.
- Mutable types (`mut T`): view mutability, method-mutability gating,
and the mut/namespace func model.
- Prelude infrastructure (`check_module_with_prelude`), the core/std
groundwork, ambient math, and the docs reference + guided tour.
Also drops the obsolete `stdlib/dist/**` (superseded by in-process
strip) and gitignores the internal agent progress trackers.
int<->uint operators (the base's `9cdd417`) are intentionally not
carried here: that work needs a clean rework (method-call ambiguity
fix + real tests) now that its prerequisites are present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add the missing arithmetic/comparison/equality operator impls between
`int` and `uint` in stdlib/src/ops (both directions), so mixed-numeric
expressions type-check instead of failing "operator not implemented".
- Fix exhaustiveness checking for `uint` scrutinees (range/literal pattern
coverage over the unsigned domain was mishandled).
- Improve the operator-resolution-failure diagnostic: instead of a bare
"not implemented", say which operator, which operand types, and which
interface implements it — e.g. "the `**` operator is not implemented for
`A` and `B`; implement `Power` to support it".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
fn_type_of now scans parameter-position types for synthetic impl-Trait
params (index >= 1<<28) and extends the call-site subst with a fresh
inference var per synthetic, so func f(x: Iface) is callable with
concrete arguments exactly like func f<T: Iface>(x: T) - including
function-as-value references, which share the same path. Return-
position synthetics stay rigid (still loudly rejected). Closes the
last golden-corpus finding; the corpus now runs with zero ignored
tests. NOTE: call-site bound enforcement does not exist for either
spelling (pre-existing soundness hole, now documented in the ledger).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Kahn topological sort + worklist fixpoint (module-let reordering),
Hindley-Milner-style unification and its occurs check, and the
hash-consing type interner. Maranget usefulness was already named.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Closes golden-corpus findings 1-3 (their ignored tests are now active).
HirStmt::Return lowers return in statement position and emits a JS
return; statement-position control flow now flattens to plain blocks
instead of IIFEs, and an emit-time guard panics loudly on a return
inside an expression-position block/if/match (it would return from
the emitted IIFE). Same-JS-scope let redeclarations (including lets
shadowing params) rename to name$N during lowering - $ is illegal in
Nymph identifiers - while nested-block shadowing keeps source names.
Top-level lets lower into the HIR module and emit as module consts,
topologically ordered by their dependencies with function let-refs
computed as a worklist fixpoint over the call graph (mutual recursion
converges to the cycle union); unbreakable let cycles panic loudly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Enums with methods emit a proto object inside the enum IIFE and every
variant value is created with Object.create(proto) - nullary
singletons stay frozen and field-variant factories keep their own
[TAG] so tag-identity matching is unchanged; method-less enums emit
byte-identical JS (pinned by test). HirEnum gains methods, collected
through the same path as structs (collect_adt_methods: top-level
impl/impl-for, enum-body inherent funcs and nested interface impls
with default materialization, duplicate-name panic). Closes two gaps:
top-level enum impls no longer panic in lowering, and enum-BODY
members were silently dropped (type-checked then vanished from the
emitted JS).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
29 compile-clean programs plus 12 Node-executed programs covering the
implemented surface (slices 0-4C-c), pinned so future slices cannot
silently regress known-good Nymph. Four should-work-but-does-not
findings are checked in as ignored tests: return ICEs in lowering,
let shadowing emits invalid JS, top-level let is silently dropped,
and impl-Trait param sugar rejects concrete call-site arguments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Comparison operators reach parity with the arithmetic arm: generic
params route through dispatch_operator (bound -> loud lowering
deferral, none -> NotImplemented) and unresolved operands join the
per-body pending queue, so a comparison whose operand is pinned to a
user ADT later in the body now dispatches to the impl method instead
of silently emitting native JS < on objects. The fallback resolver is
op-class aware (comparison_method vs binary_method; comparison nodes
keep their boolean type on finalization). Never-pinned comparison
operands now diagnose CannotInferOperandType (previously compiled
clean with a wrong eager resolution). Equality stays always-native
(===, reference semantics on ADTs) and logical ops on generics were
already loud type errors - both now pinned by tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Un-overridden default-bodied interface methods are now checked once
generically (this bound to a rigid bounded param; same-interface calls
on this resolve against the interface signature, bypassing blanket
impls) and lowered onto every implementing struct class. Concrete
InterfaceDefault resolutions dispatch as plain UserImpl; GenericBound
keeps its loud lowering panic. Duplicate class method names now panic
(two defaults, or override vs sibling default). Also closes two
adjacent silent miscompiles: explicit calls to a default-only method
used to emit a missing-method JS call that crashed at runtime, and
impl-for on non-Reference types (e.g. lists) was silently dropped.
Removes the unsound min/max defaults from stdlib Comparable (mixed
Self with an unconstrained Other in their return type; unusable
before materialization existed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Closes the known silent gap from Slice 4B: -v, !v, and ~v on user types
now record a Resolution on the PrefixOp node and lower to .negate() /
.not() / .bit_not() calls instead of native JS unary operators.
Primitive operands stay native; BoolNot keeps its default-to-boolean
semantics for unresolved operands; unresolved negate/bit_not operands
join the per-body pending queue (declaration-order independent);
interface-default and missing resolutions panic loudly. Also adds the
missing UnOp::BitNot end-to-end (HIR variant + oxc BitwiseNot emit) —
a zero-diagnostic ~int program used to panic in lowering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Closes both confirmed Slice 4B review findings plus the review-loop
follow-ups. Compound assignment now records and dispatches the operator
Resolution like a bare binary op (v1 += v2 on a user type emits
v1 = v1.plus(v2) instead of native JS +). The infer_binary fallback no
longer silently accepts non-primitive operands: generic params route
through dispatch_operator (bound -> loud lowering deferral, no bound ->
NotImplemented), and unresolved inference vars are queued and finalized
per function body while that bodys param_bounds are live, so checking
is declaration-order independent; still-unbound operands get a
CannotInferOperandType diagnostic. Trial inference rolls back its
pending operators the way it rolls back diagnostics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Nested-impl and top-level impl-for plus both dispatch to .plus() in the
emitted JS; an operator inside the method body stays a native float +
while the outer one dispatches; int literal + float stays native.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
lower_binary reads the checker annotation: builtin kinds keep the
native HirExpr::Binary path, UserImpl lowers to a lhs.method(rhs) call,
and interface-default or missing resolutions panic loudly instead of
silently emitting a native JS operator for user types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Checker now records a Resolution (method name + DispatchKind) on each
BinaryOp node per the slice-4b D3 table: same/mixed primitives and
equality stay BuiltinEager, boolean and/or is BuiltinShortCircuit,
user-ADT impls are UserImpl, and interface default bodies are tagged
UserImplDefaultMethod. resolve_method now returns a MethodResolution
carrying where the matched body lives.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Nested impl blocks inside struct bodies and top-level impl-for declarations targeting a struct now contribute their func members to HirClass.methods, so operator dispatch (slice 4B task 3) has a real method to call. Impl-for on non-struct types panics loudly via the extended leftover assert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fills the three test gaps flagged by the slice 4A review. All passed immediately - the paths were already correct, just untested.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review of slice 4A found that impl blocks on enums type-check but were silently dropped in lowering, emitting JS that crashes at runtime. Lowering now panics on: non-func impl members, non-func struct inner members, and impls targeting non-struct types (the leftover methods_by_type assert). Adds a should_panic test pinning the enum-impl case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HirExpr::This emits the JS this keyword. emit_method builds each method as a class element (mirroring emit_func param/body handling) and pushes them into the class body after the constructor. Node: an inherent method reading this.x + this.y returns 7; a method taking an arg returns 15.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ExprKind::This lowers to HirExpr::This. lower_module gains a first pass collecting inherent instance methods from top-level inherent impl blocks (keyed by struct name), attached to each HirClass along with the struct own inner func members. lower_method mirrors lower_func. Non-Reference impl targets and interface impls are skipped for later slices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HirClass gains methods: Vec<HirMethod>; HirMethod carries a method's name/params/body; HirExpr::This models the receiver. emit.rs has a temporary unreachable! arm for This until Task 3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Struct instance methods → JS class methods; this → JS this; method calls already lower structurally. Key finding: checker already checks inherent method bodies (members.rs), so no checker change needed for 4A. Operators, interfaces, enum methods deferred to later sub-slices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A thin top-level facade over parse (nymph-syntax) → check (nymph-sema) → lower → emit (nymph-codegen). compile(source, path) runs the full pipeline and returns the JS module string or the error diagnostics that blocked it; check(source, path) runs parse+check only and returns every diagnostic (errors and warnings) for tooling/LSP use. Re-exports Diagnostic/Severity so consumers depend only on this crate. Uncomments the workspace member. 5 integration tests, build/test/clippy/fmt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 3B review follow-ups (no Critical findings). Adds Node tests for #[a, ...mid, b] (from-end index + interior slice) and #{key: v} map patterns — both passed, confirming those untested paths were already correct. Moves the binding-union rejection from a codegen assert to lowering (a descriptive panic consistent with sibling deferrals) via pat_binds, with a should_panic test pinning it; codegen keeps a defensive debug_assert. Skips the redundant length>=0 test for a rest-only list pattern (#[...rest] is irrefutable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
List patterns test length (exact or >=), bind prefix by index and suffix from the end (Subject::IndexFromEnd), and bind rest to a .slice (Subject::Slice). Map patterns test .has(key) and match against .get(key) (Subject::MapGet). Range patterns emit bound comparisons; unions OR their sub-tests (binding unions rejected). Node: list head/spread, range banding, nullary-variant unions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrites match from an if/else-if chain to a labeled block + break, so a matched-but-guard-failed arm falls through to the next arm. match_arm emits 'if (<test>) { const <binds>; if (<guard>) { <r> = <body>; break <m>; } }' with each of test/guard/label optional (the unguarded last arm is the testless tail). Struct/tuple patterns compile to field/index bindings with sub-tests AND-ed in via and_test. Node: tuple destructuring + guard fall-through, struct field binding. All 3A match tests still pass (rewrite is behavior-preserving).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guards lower to HirArm.guard. lower_pattern now handles the full surface: struct patterns (Pattern::Struct with no recorded variant → HirPat::Struct), tuples, lists (prefix/rest/suffix split), maps (literal keys), ranges, text-only strings, and unions. Deferred edges panic loudly: map-rest, non-literal map keys, escaped string patterns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HirArm gains an optional guard; HirLit gains Str; HirPat gains Struct/Tuple/List{prefix,rest,suffix}/Map/Range/Or; HirRange models the five range forms. emit.rs adds Subject::Index and temporary unreachable! arms until Tasks 3-4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
struct/tuple/list/map/range/string/union patterns + guards; labeled-block emission rewrite for guard fall-through; no checker change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 3A review follow-ups (no Critical findings). Adds a should_panic test pinning the guard-in-lowering panic (guards type-check but defer to 3B), plus Node tests for nested variant patterns (Wrap(i = A(n))) and match in value position (1 + match ...) — the trickiest previously-untested paths. Fixes the stale exhaustiveness doc comment in infer_pattern.rs and the _s/_r naming drift in emit.rs. Binding-with-subpattern (name = pat) does not parse in match-arm position, so that HirPat::Binding sub branch is unreachable from match (harmless). Known pre-existing: gensym _tN temps are not reserved against user identifiers (affects if/while too).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
match lowers to 'const _s = <scrutinee>; let _r; if(<test0>){<binds0>; _r=<body0>} else if … else {…}' → _r. Patterns compile to a boolean test plus (name, Subject) bindings; a re-emittable Subject sidesteps oxc's non-cloneable arena expressions. Variant tests use the 2C ABI (_s?.[TAG] === E.V[TAG]); exhaustiveness lets the last arm skip its test. Node: unwrap_or(Some 42)->42, None->0; literal+wildcard classify 100/200/300.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scalar/binding/placeholder/variant patterns (nested); struct-path patterns resolve to variants via the span-keyed side-table. Guards and aggregate/range/string/union patterns panic (deferred to 3B).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Patterns carry no NodeId, so variant-pattern (enum, variant) resolution is recorded by the pattern's unique span for lowering to consume. Recorded at both variant-pattern sites: nullary-binding (None) and struct-path (Some(value)). variant_resolution widened to pub(crate) for cross-module reuse.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HirExpr::Match carries a scrutinee and typed arms; HirPat is Wildcard/Binding/Lit/Variant; HirLit is the scalar literal a pattern compares by ===. emit.rs has a temporary unreachable! arm until Task 4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
match over literals/bindings/placeholders/variants; span-keyed variant-pattern resolution; guards + aggregate patterns deferred to 3B.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Critical review finding: a field-carrying variant used as a bare value (let g = Some) type-checked as a positional constructor function but lowered to VariantRef → the object-arg factory, silently miscompiling a later call (args dropped). variant_value now rejects it with a clear FieldVariantAsValue error (nullary-as-value stays valid), mirroring how structs already reject type-as-value. Adds check tests (reject field variant / accept nullary), a qualified-nullary lowering test, and dedups the tag-object construction in emit_enum.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Test job failed at 'cargo binstall cargo-nextest' — cargo-binstall is
not preinstalled on the runner. Use taiki-e/install-action to fetch a
prebuilt nextest binary instead. Also set up Node (the codegen tests execute
emitted JS under node) and add Swatinem/rust-cache to avoid a cold build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The push reconciliation reset the local repo to a fresh clone of origin
(which sat at the pre-session base), abandoning the entire session's
commit chain. That chain was never pushed, so its 168 commits are not
recoverable at the git layer — but the fully-integrated working tree
survived in the `nymph_lang-ws-corestd` workspace directory (verified
1034/1034 green). This commit re-lands that complete tree onto main.
Recovered work (previously each its own slice; history could not be
preserved, so it lands here as one recovery commit):
- LSP: rich hover (declaration structure, named signatures, generic
bounds, keyword docs, match-arm/for-loop pattern hover, markdown
rendering), variant-construction hover, this-receiver go-to-def, and
resolution-aware semantic tokens with consistent use-site classes.
- External linkage L0-L3: in-process oxc TS strip, the Option/Result
ABI seam (global enum discriminants), and linked list/map/set
surfaces that materialise and run under Node.
- print/println wired to real JS via free-function linkage.
- Mutable types (`mut T`): view mutability, method-mutability gating,
and the mut/namespace func model.
- Prelude infrastructure (`check_module_with_prelude`), the core/std
groundwork, ambient math, and the docs reference + guided tour.
Also drops the obsolete `stdlib/dist/**` (superseded by in-process
strip) and gitignores the internal agent progress trackers.
int<->uint operators (the base's `9cdd417`) are intentionally not
carried here: that work needs a clean rework (method-call ambiguity
fix + real tests) now that its prerequisites are present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add the missing arithmetic/comparison/equality operator impls between
`int` and `uint` in stdlib/src/ops (both directions), so mixed-numeric
expressions type-check instead of failing "operator not implemented".
- Fix exhaustiveness checking for `uint` scrutinees (range/literal pattern
coverage over the unsigned domain was mishandled).
- Improve the operator-resolution-failure diagnostic: instead of a bare
"not implemented", say which operator, which operand types, and which
interface implements it — e.g. "the `**` operator is not implemented for
`A` and `B`; implement `Power` to support it".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fn_type_of now scans parameter-position types for synthetic impl-Trait
params (index >= 1<<28) and extends the call-site subst with a fresh
inference var per synthetic, so func f(x: Iface) is callable with
concrete arguments exactly like func f<T: Iface>(x: T) - including
function-as-value references, which share the same path. Return-
position synthetics stay rigid (still loudly rejected). Closes the
last golden-corpus finding; the corpus now runs with zero ignored
tests. NOTE: call-site bound enforcement does not exist for either
spelling (pre-existing soundness hole, now documented in the ledger).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Kahn topological sort + worklist fixpoint (module-let reordering),
Hindley-Milner-style unification and its occurs check, and the
hash-consing type interner. Maranget usefulness was already named.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Closes golden-corpus findings 1-3 (their ignored tests are now active).
HirStmt::Return lowers return in statement position and emits a JS
return; statement-position control flow now flattens to plain blocks
instead of IIFEs, and an emit-time guard panics loudly on a return
inside an expression-position block/if/match (it would return from
the emitted IIFE). Same-JS-scope let redeclarations (including lets
shadowing params) rename to name$N during lowering - $ is illegal in
Nymph identifiers - while nested-block shadowing keeps source names.
Top-level lets lower into the HIR module and emit as module consts,
topologically ordered by their dependencies with function let-refs
computed as a worklist fixpoint over the call graph (mutual recursion
converges to the cycle union); unbreakable let cycles panic loudly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Enums with methods emit a proto object inside the enum IIFE and every
variant value is created with Object.create(proto) - nullary
singletons stay frozen and field-variant factories keep their own
[TAG] so tag-identity matching is unchanged; method-less enums emit
byte-identical JS (pinned by test). HirEnum gains methods, collected
through the same path as structs (collect_adt_methods: top-level
impl/impl-for, enum-body inherent funcs and nested interface impls
with default materialization, duplicate-name panic). Closes two gaps:
top-level enum impls no longer panic in lowering, and enum-BODY
members were silently dropped (type-checked then vanished from the
emitted JS).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
29 compile-clean programs plus 12 Node-executed programs covering the
implemented surface (slices 0-4C-c), pinned so future slices cannot
silently regress known-good Nymph. Four should-work-but-does-not
findings are checked in as ignored tests: return ICEs in lowering,
let shadowing emits invalid JS, top-level let is silently dropped,
and impl-Trait param sugar rejects concrete call-site arguments.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Comparison operators reach parity with the arithmetic arm: generic
params route through dispatch_operator (bound -> loud lowering
deferral, none -> NotImplemented) and unresolved operands join the
per-body pending queue, so a comparison whose operand is pinned to a
user ADT later in the body now dispatches to the impl method instead
of silently emitting native JS < on objects. The fallback resolver is
op-class aware (comparison_method vs binary_method; comparison nodes
keep their boolean type on finalization). Never-pinned comparison
operands now diagnose CannotInferOperandType (previously compiled
clean with a wrong eager resolution). Equality stays always-native
(===, reference semantics on ADTs) and logical ops on generics were
already loud type errors - both now pinned by tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Un-overridden default-bodied interface methods are now checked once
generically (this bound to a rigid bounded param; same-interface calls
on this resolve against the interface signature, bypassing blanket
impls) and lowered onto every implementing struct class. Concrete
InterfaceDefault resolutions dispatch as plain UserImpl; GenericBound
keeps its loud lowering panic. Duplicate class method names now panic
(two defaults, or override vs sibling default). Also closes two
adjacent silent miscompiles: explicit calls to a default-only method
used to emit a missing-method JS call that crashed at runtime, and
impl-for on non-Reference types (e.g. lists) was silently dropped.
Removes the unsound min/max defaults from stdlib Comparable (mixed
Self with an unconstrained Other in their return type; unusable
before materialization existed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Closes the known silent gap from Slice 4B: -v, !v, and ~v on user types
now record a Resolution on the PrefixOp node and lower to .negate() /
.not() / .bit_not() calls instead of native JS unary operators.
Primitive operands stay native; BoolNot keeps its default-to-boolean
semantics for unresolved operands; unresolved negate/bit_not operands
join the per-body pending queue (declaration-order independent);
interface-default and missing resolutions panic loudly. Also adds the
missing UnOp::BitNot end-to-end (HIR variant + oxc BitwiseNot emit) —
a zero-diagnostic ~int program used to panic in lowering.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Closes both confirmed Slice 4B review findings plus the review-loop
follow-ups. Compound assignment now records and dispatches the operator
Resolution like a bare binary op (v1 += v2 on a user type emits
v1 = v1.plus(v2) instead of native JS +). The infer_binary fallback no
longer silently accepts non-primitive operands: generic params route
through dispatch_operator (bound -> loud lowering deferral, no bound ->
NotImplemented), and unresolved inference vars are queued and finalized
per function body while that bodys param_bounds are live, so checking
is declaration-order independent; still-unbound operands get a
CannotInferOperandType diagnostic. Trial inference rolls back its
pending operators the way it rolls back diagnostics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Nested-impl and top-level impl-for plus both dispatch to .plus() in the
emitted JS; an operator inside the method body stays a native float +
while the outer one dispatches; int literal + float stays native.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
lower_binary reads the checker annotation: builtin kinds keep the
native HirExpr::Binary path, UserImpl lowers to a lhs.method(rhs) call,
and interface-default or missing resolutions panic loudly instead of
silently emitting a native JS operator for user types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Checker now records a Resolution (method name + DispatchKind) on each
BinaryOp node per the slice-4b D3 table: same/mixed primitives and
equality stay BuiltinEager, boolean and/or is BuiltinShortCircuit,
user-ADT impls are UserImpl, and interface default bodies are tagged
UserImplDefaultMethod. resolve_method now returns a MethodResolution
carrying where the matched body lives.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kRDspADaPqFtHzPM4sY1N
Nested impl blocks inside struct bodies and top-level impl-for declarations targeting a struct now contribute their func members to HirClass.methods, so operator dispatch (slice 4B task 3) has a real method to call. Impl-for on non-struct types panics loudly via the extended leftover assert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review of slice 4A found that impl blocks on enums type-check but were silently dropped in lowering, emitting JS that crashes at runtime. Lowering now panics on: non-func impl members, non-func struct inner members, and impls targeting non-struct types (the leftover methods_by_type assert). Adds a should_panic test pinning the enum-impl case.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HirExpr::This emits the JS this keyword. emit_method builds each method as a class element (mirroring emit_func param/body handling) and pushes them into the class body after the constructor. Node: an inherent method reading this.x + this.y returns 7; a method taking an arg returns 15.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ExprKind::This lowers to HirExpr::This. lower_module gains a first pass collecting inherent instance methods from top-level inherent impl blocks (keyed by struct name), attached to each HirClass along with the struct own inner func members. lower_method mirrors lower_func. Non-Reference impl targets and interface impls are skipped for later slices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Struct instance methods → JS class methods; this → JS this; method calls already lower structurally. Key finding: checker already checks inherent method bodies (members.rs), so no checker change needed for 4A. Operators, interfaces, enum methods deferred to later sub-slices.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A thin top-level facade over parse (nymph-syntax) → check (nymph-sema) → lower → emit (nymph-codegen). compile(source, path) runs the full pipeline and returns the JS module string or the error diagnostics that blocked it; check(source, path) runs parse+check only and returns every diagnostic (errors and warnings) for tooling/LSP use. Re-exports Diagnostic/Severity so consumers depend only on this crate. Uncomments the workspace member. 5 integration tests, build/test/clippy/fmt clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 3B review follow-ups (no Critical findings). Adds Node tests for #[a, ...mid, b] (from-end index + interior slice) and #{key: v} map patterns — both passed, confirming those untested paths were already correct. Moves the binding-union rejection from a codegen assert to lowering (a descriptive panic consistent with sibling deferrals) via pat_binds, with a should_panic test pinning it; codegen keeps a defensive debug_assert. Skips the redundant length>=0 test for a rest-only list pattern (#[...rest] is irrefutable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
List patterns test length (exact or >=), bind prefix by index and suffix from the end (Subject::IndexFromEnd), and bind rest to a .slice (Subject::Slice). Map patterns test .has(key) and match against .get(key) (Subject::MapGet). Range patterns emit bound comparisons; unions OR their sub-tests (binding unions rejected). Node: list head/spread, range banding, nullary-variant unions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rewrites match from an if/else-if chain to a labeled block + break, so a matched-but-guard-failed arm falls through to the next arm. match_arm emits 'if (<test>) { const <binds>; if (<guard>) { <r> = <body>; break <m>; } }' with each of test/guard/label optional (the unguarded last arm is the testless tail). Struct/tuple patterns compile to field/index bindings with sub-tests AND-ed in via and_test. Node: tuple destructuring + guard fall-through, struct field binding. All 3A match tests still pass (rewrite is behavior-preserving).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guards lower to HirArm.guard. lower_pattern now handles the full surface: struct patterns (Pattern::Struct with no recorded variant → HirPat::Struct), tuples, lists (prefix/rest/suffix split), maps (literal keys), ranges, text-only strings, and unions. Deferred edges panic loudly: map-rest, non-literal map keys, escaped string patterns.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 3A review follow-ups (no Critical findings). Adds a should_panic test pinning the guard-in-lowering panic (guards type-check but defer to 3B), plus Node tests for nested variant patterns (Wrap(i = A(n))) and match in value position (1 + match ...) — the trickiest previously-untested paths. Fixes the stale exhaustiveness doc comment in infer_pattern.rs and the _s/_r naming drift in emit.rs. Binding-with-subpattern (name = pat) does not parse in match-arm position, so that HirPat::Binding sub branch is unreachable from match (harmless). Known pre-existing: gensym _tN temps are not reserved against user identifiers (affects if/while too).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
match lowers to 'const _s = <scrutinee>; let _r; if(<test0>){<binds0>; _r=<body0>} else if … else {…}' → _r. Patterns compile to a boolean test plus (name, Subject) bindings; a re-emittable Subject sidesteps oxc's non-cloneable arena expressions. Variant tests use the 2C ABI (_s?.[TAG] === E.V[TAG]); exhaustiveness lets the last arm skip its test. Node: unwrap_or(Some 42)->42, None->0; literal+wildcard classify 100/200/300.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Patterns carry no NodeId, so variant-pattern (enum, variant) resolution is recorded by the pattern's unique span for lowering to consume. Recorded at both variant-pattern sites: nullary-binding (None) and struct-path (Some(value)). variant_resolution widened to pub(crate) for cross-module reuse.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Critical review finding: a field-carrying variant used as a bare value (let g = Some) type-checked as a positional constructor function but lowered to VariantRef → the object-arg factory, silently miscompiling a later call (args dropped). variant_value now rejects it with a clear FieldVariantAsValue error (nullary-as-value stays valid), mirroring how structs already reject type-as-value. Adds check tests (reject field variant / accept nullary), a qualified-nullary lowering test, and dedups the tag-object construction in emit_enum.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>