Skip to content

refactor: refactor compiler #57

Open
danfma wants to merge 78 commits intomainfrom
refactor/shared-ir
Open

refactor: refactor compiler #57
danfma wants to merge 78 commits intomainfrom
refactor/shared-ir

Conversation

@danfma
Copy link
Copy Markdown
Owner

@danfma danfma commented Apr 15, 2026

No description provided.

danfma added 30 commits April 14, 2026 10:34
)

Introduce the target-agnostic Intermediate Representation (IR) as pure
data types in Metano.Compiler/IR/. These record types model the semantic
shape of C# code without prescribing how any target language renders it.

Hierarchy:
- IrModule, IrTypeDeclaration (class/interface/enum), IrMemberDeclaration
- IrExpression (30 expression types), IrStatement (14 statement types)
- IrTypeRef (14 type reference kinds), IrPrimitive (19 semantic primitives)
- IrTypeSemantics, IrMethodSemantics, IrConstructorDeclaration
- IrRuntimeRequirement, IrTypeOrigin (cross-assembly)

Design principles:
- Semantic names, not target names (IrPrimitive.Guid, not UUID)
- No import paths in IR (IrTypeOrigin carries logical package ID)
- No naming policy (PascalCase preserved; camelCase is a target concern)
- No export flags (export policy is target-specific)

This commit is purely additive — no existing code is modified, all 357
tests continue to pass.

Part of #56
Replace the 5 [ThreadStatic] mutable fields in TypeMapper with an
explicit TypeMappingContext object that flows through the compilation
pipeline via TypeScriptTransformContext.

Changes:
- Add TypeMappingContext class aggregating BclExportMap, CrossAssemblyTypeMap,
  AssembliesNeedingEmitPackage, CrossPackageMisses, and UsedCrossPackages
- Add Map(ITypeSymbol, TypeMappingContext) overload to TypeMapper
- Thread TypeMappingContext through all 14 transformer/handler files
- Add TypeMapping property to TypeScriptTransformContext and ExpressionTransformer
- Update ImportCollector to receive TypeMappingContext explicitly
- Delete all [ThreadStatic] fields and legacy shim overloads from TypeMapper

TypeMapper is now a pure static utility with no ambient state. All mutable
state flows explicitly through TypeMappingContext, making the pipeline
composable, testable, and safe for future parallelization.

Part of #56
Introduce the first semantic extractors that produce IR from Roslyn
symbols, proving the IR model works before wiring it into the pipeline.

New files in src/Metano.Compiler/Extraction/:
- IrTypeRefMapper: maps ITypeSymbol -> IrTypeRef (semantic only, no target
  decisions). Covers primitives, nullable, collections, maps, sets, tuples,
  promises, delegates, generics, type parameters.
- IrEnumExtractor: extracts IrEnumDeclaration from C# enums, detecting
  [StringEnum] and [Name] overrides.
- IrInterfaceExtractor: extracts IrInterfaceDeclaration with properties,
  methods, type parameters, and constraints.
- IrExtractionContext: compilation context for extractors.

New IR-level tests (19 tests):
- IrTypeRefMapperTests: int, string, nullable, List, Dictionary, Task,
  Guid, DateTime, type parameters.
- IrEnumExtractionTests: numeric enums, string enums, [Name] overrides,
  visibility, name preservation.
- IrInterfaceExtractionTests: properties, methods, generics, [Ignore]
  filtering, PascalCase preservation.

Total: 376 tests passing (357 existing + 19 new).

Part of #56
…rs, private protected, multiple catches (#56)

- IrPropertyDeclaration: add SetterVisibility for shapes like
  `public string Status { get; private set; }` and Initializer slot
  for `public int Retries { get; set; } = 3;`
- IrVisibility: add PrivateProtected to distinguish C#'s
  `private protected` (ProtectedAndInternal) from `protected internal`
  (ProtectedOrInternal)
- IrTryStatement: change single Catch to IReadOnlyList<IrCatchClause>
  Catches to support multiple catch clauses with type-specific handlers
- Extract shared IrVisibilityMapper to avoid duplicated MapVisibility
  in enum/interface extractors

Part of #56
…n interface extraction (#56)

Address review of the IR interface extraction:

- Add Attributes property to IrMemberDeclaration so backends can honor
  [Name("x")] overrides on interface members
- Add HasDefaultImplementation flag to IrMethodSemantics so backends know
  when a C# 8+ interface method carries an executable body (actual body
  extraction is Phase 5 work)
- Extend IrInterfaceExtractor to handle IEventSymbol members so declarations
  like `event EventHandler Changed` round-trip through the IR
- Add IR-level tests for each scenario

Part of #56
Introduce the TypeScript-specific half of the pipeline that converts
target-agnostic IR to TypeScript AST. The legacy and IR paths produce
identical output — verified by 6 parity tests that print both and compare.

New files in src/Metano.Compiler.TypeScript/Bridge/:
- IrToTsTypeMapper: IrTypeRef -> TsType with TS-specific primitive mapping
  (Guid -> UUID, DateTime -> Temporal.PlainDateTime, Char -> string, etc.)
- IrToTsNamingPolicy: camelCase with reserved-word escaping, [Name] overrides
  via IrAttribute lookup
- IrToTsEnumBridge: IrEnumDeclaration -> TsEnum (numeric) or
  TsConstObject + TsTypeAlias (string)
- IrToTsInterfaceBridge: IrInterfaceDeclaration -> TsInterface with
  properties, methods, and type parameters

Shared extraction helper:
- IrAttributeExtractor.Extract(ISymbol) centralizes [Name] attribute reading
  and is reused by both enum and interface extractors
- IrEnumExtractor now also carries [Name] overrides on the declaration

Parity tests (6):
- Numeric/string enums, enum with [Name] override
- Simple interface, generic interface with Task<T?> and void methods,
  interface property with [Name] override

Total: 385 tests passing (379 existing + 6 parity).

Part of #56
…cy transformers (#56)

With the IR bridges proven identical to the legacy transformers, switch
TypeTransformer.BuildTypeStatements to use the IR path exclusively and
delete the legacy EnumTransformer and InterfaceTransformer.

- TypeTransformer now calls IrEnumExtractor + IrToTsEnumBridge for enums
- TypeTransformer now calls IrInterfaceExtractor + IrToTsInterfaceBridge
  for interfaces
- IrInterfaceExtractor now carries type-level [Name] overrides on the
  declaration Attributes (previously only propagated for members)
- Replace parallel parity tests with golden tests that pin the expected
  TypeScript output so future IR changes can't drift silently
- Delete src/Metano.Compiler.TypeScript/Transformation/EnumTransformer.cs
- Delete src/Metano.Compiler.TypeScript/Transformation/InterfaceTransformer.cs

First vertical slice of the IR pipeline is now live. 383/383 tests pass
(the pre-existing transpiler test suite + 4 golden tests covering numeric
enums, string enums, simple interfaces, and generic interfaces with
nullable promise returns).

Part of #56
#56)

Begin Phase 5 with a scoped extractor for class/struct/record type shapes.

Extracted into IR:
- Type header: name, visibility, type parameters, attributes ([Name] overrides)
- Base type (when transpilable; System.Object and System.ValueType filtered out)
- Implemented interfaces
- Type semantics: IsRecord, IsValueType, IsStatic, IsAbstract, IsSealed,
  IsPlainObject, IsException, IsInlineWrapper + InlineWrappedType
- Field declarations (user-declared only; auto-property backing fields skipped)

Not yet extracted (future phases):
- Properties, methods, constructors, nested types (bodies require
  IrExpressionExtractor/IrStatementExtractor — Phase 5.2+)

13 new IR-level tests validate extraction for simple class, record, struct,
static class, [PlainObject] record, class with transpilable base, class
with interfaces, generic class with multiple type parameters, class with
[Name] override, class with fields (with visibility variations), auto-property
backing field exclusion, and exception class detection.

Not yet wired to the pipeline — the class path still goes through
RecordClassTransformer. Wiring comes in the next PR starting with the
simplest [PlainObject] subset.

Total: 396 tests passing (383 + 13 new).

Part of #56
Extend IrClassExtractor to include property declarations alongside fields.
Bodies and initializer expressions remain null — they land once expression
extraction is available — but the shape and semantic signals are captured.

Added:
- IrPropertySemantics record with HasGetterBody, HasSetterBody,
  HasInitializer, IsAbstract, IsVirtual, IsOverride, IsSealed flags
- Semantics slot on IrPropertyDeclaration
- IrPropertyExtractor helper that reads Roslyn symbols + syntax to
  determine accessor shape (GetOnly/GetSet/GetInit), setter visibility
  asymmetry (public get + private set), and body/initializer presence
- Single-pass member walker in IrClassExtractor.ExtractMembers that
  preserves source declaration order

8 new tests cover: auto-properties with all three accessor patterns,
private setter visibility, computed (expression-bodied) property,
block-bodied setter, virtual property, static property, [Ignore] filter,
[Name] override attribute.

Total: 404 tests passing (396 + 8).

Part of #56
Add method and event signature extraction to IrClassExtractor.

- New IrMethodExtractor that reads Roslyn IMethodSymbol plus syntax to
  detect yield iterator methods (IsGenerator + Generator<T> return type)
  and operator overloads (OperatorKind e.g. "Addition", "Implicit")
- Extend IrMethodSemantics with IsAbstract, IsVirtual, IsOverride, IsSealed
- Include IEventSymbol members as IrEventDeclaration
- Filter to user-authored method kinds (Ordinary, UserDefinedOperator,
  Conversion) — accessors, ctors, finalizers handled elsewhere
- Bodies remain null pending statement extraction

8 new tests cover: method signatures with parameters and static modifier,
async method, iterator method with yield (IEnumerable -> Generator),
abstract method, virtual method, operator+ with OperatorKind="Addition",
generic method with type parameters, and event declaration.

Total: 412 tests passing (404 + 8).

Part of #56
Add IrConstructorExtractor that captures primary and overload constructor
shapes:

- Primary constructor resolution: pick the ctor with the most parameters
  (matches records, C# 12+ primary constructors, and classic classes with
  a single explicit ctor consistently)
- Promotion flags (ReadonlyProperty / MutableProperty / None) determined
  by matching param name against public non-ignored properties
- Additional explicit constructors attached as Overloads
- HasDefaultValue flag on IrParameter populated from Roslyn
- Bodies and default-value expressions left null — expression extraction
  is a later phase concern

6 new tests cover: record primary ctor with readonly promotion, explicit
service-style ctor without promotion, ctor param with default value flag,
multiple ctors attached as overloads (primary = longest), class with no
explicit ctor (null), and mutable property promotion.

Total: 418 tests passing (412 + 6).

Part of #56
…56)

First class-level vertical slice: [PlainObject] classes with only
auto-properties now flow through the IR -> TS bridge instead of
RecordClassTransformer.

- Add IrToTsPlainObjectBridge that converts IrClassDeclaration with
  IsPlainObject=true to a TsInterface (readonly properties; optional
  flag '?' when HasDefaultValue)
- IsShapeOnly(ir) gate keeps classes with methods on the legacy path
  until statement extraction can handle method bodies
- TypeTransformer.BuildTypeStatements tries the IR bridge first and
  falls back to RecordClassTransformer when the class doesn't qualify
- Golden test pins the expected TsInterface output for
  record UserDto(string Name, int Age = 0) with [PlainObject]

Zero regressions on the existing transpiler suite — 419/419 tests pass.

Part of #56
…ulti-target samples (#56)

Move all TypeScript target projects into a single targets/ tree so the repo
scales cleanly as additional backends land (Flutter/Dart next).

Repo layout changes:
- js/* -> targets/js/*
- samples/SampleCounter/js/ -> targets/js/sample-counter/
- Rename sample-counter package name from "js" to "sample-counter" to match
  the rest of the workspace

Updated references:
- All sample .csproj MetanoOutputDir values
- Root package.json workspaces + tsconfig.json references
- CLAUDE.md commands and project-tree diagram
- README.md links in the main doc and per-sample READMEs
- ADRs that reference runtime source files

Bug fix (unblocked by the regeneration this move forces):
- Revert the Phase 5.5 wiring that routed [PlainObject] classes through
  the IR bridge. IrTypeRefMapper is stateless and drops cross-package
  type origins, so routed classes lost their cross-package imports in
  the regenerated sample-todo-service output. The bridge and extractor
  stay in place; Phase 6 lands runtime requirements and origin resolution
  in the IR before re-enabling the wiring.

Verification:
- 419/419 .NET tests pass (Metano.Tests)
- 377/377 bun tests pass across sample-todo, sample-issue-tracker,
  sample-todo-service, metano-runtime

Part of #56
…nObject] (#56)

Close the origin-resolution gap that was blocking the IR pipeline from
handling cross-package type references, and restore the [PlainObject]
fast path through IR.

Added:
- IrTypeOriginResolver delegate in Metano.Compiler: backends plug in a
  function mapping a Roslyn INamedTypeSymbol to an IrTypeOrigin (or null
  when the type is local)
- IrExtractionContext.OriginResolver slot (optional)
- AssemblyRootNamespace field on IrTypeOrigin so backends can compute
  package-relative paths without a second Roslyn pass
- All extractors (class, property, method, constructor, interface, enum)
  accept an optional IrTypeOriginResolver and thread it through

Extended:
- IrTypeRefMapper.Map now stamps IrTypeOrigin on named-type refs when a
  resolver is provided — including nested generic type arguments

TypeScript target:
- New IrTypeOriginResolverFactory.Create(TypeMappingContext) builds a
  resolver backed by CrossAssemblyTypeMap, with the same side effects
  (MS0007 miss tracking, UsedCrossPackages accounting) as the legacy
  TypeMapper.ResolveOrigin
- IrToTsTypeMapper.MapNamed converts IrTypeOrigin to TsTypeOrigin using
  PathNaming.ComputeSubPath with the assembly root namespace preserved
  on the IR origin
- TypeTransformer re-enables TryEmitShapeOnlyPlainObjectViaIr, this time
  threading the resolver so the generated cross-package imports survive

Verification:
- 419/419 .NET tests pass
- 377/377 bun tests pass across all sample workspaces
- sample-todo-service still emits `import { TodoItem, type Priority } from
  "sample-todo"` when regenerated

Part of #56
Introduce IrRuntimeRequirementScanner — a pure function over the IR that
returns the set of semantic runtime helpers a declaration (or module)
depends on. Backends consume the set and map each requirement to concrete
imports from their own runtime library.

Mappings emitted today (all target-agnostic semantic names):
- IrPrimitive.Guid -> ("UUID", BrandedType)
- IrPrimitive.DateTime/DateTimeOffset/DateOnly/TimeOnly/TimeSpan -> ("Temporal", Temporal)
- IrSetTypeRef -> ("HashSet", Collection)
- IrGroupingTypeRef -> ("Grouping", Collection)
- IrClassDeclaration with IsRecord -> ("HashCode", Hashing)

The scanner does not yet drive the pipeline — that happens in the next PR
(ImportCollector rewrite). For now, 6 new IR-level tests validate the
detection logic, and the full suite continues to pass.

Total: 425 tests passing (419 + 6).

Part of #56
First non-TypeScript backend — validates that the IR is genuinely
target-agnostic. Emits shape-only Dart for enums, interfaces, and classes
(method/constructor bodies stay out until Phase 5 extraction lands).

New project: src/Metano.Compiler.Dart/
- DartTarget implements ITranspilerTarget alongside TypeScriptTarget
- Minimal Dart AST (class, enum, abstract interface, field, getter,
  method signature) + a Printer that renders idiomatic Dart 3
- Bridges: IrToDartTypeMapper, IrToDartEnumBridge, IrToDartInterfaceBridge,
  IrToDartClassBridge, IrToDartNamingPolicy
- DartTransformer orchestrates extraction → bridge → AST and collects
  package imports (cross-package) + relative imports (same-package) from
  the generated AST
- CLI tool `metano-dart` (dotnet run --project src/Metano.Compiler.Dart/)

Type mappings chosen to be idiomatic Dart:
- IrPrimitive.Guid → String (Dart has no UUID brand)
- IrPrimitive.DateTime* → DateTime
- IrPrimitive.TimeSpan → Duration
- IrPrimitive.Decimal → Decimal (package:decimal)
- IrArrayTypeRef → List<T>, IrMapTypeRef → Map<K,V>, IrSetTypeRef → Set<T>
- IrPromiseTypeRef → Future<T>, IrGeneratorTypeRef → Iterable<T>
- IrTupleTypeRef → Dart 3 records `(T1, T2)`

Dart-specific rendering decisions in the bridge:
- Interfaces emit as `abstract interface class` (Dart 3 idiom)
- Implicit C# BCL interfaces (IEquatable, IComparable, ...) are filtered
  out so records/structs don't produce dangling `implements` clauses
- Non-nullable non-static fields without an initializer are marked `late`
  until Phase 5 body extraction provides the constructor body
- File names follow Dart's snake_case convention

New consumer: targets/flutter/sample_counter/
- pubspec.yaml for Flutter 3.24+ / Dart 3.5+
- lib/main.dart: a Flutter StatefulWidget implementing the generated
  ICounterView with a Material counter app. An extension on Counter
  supplies the method bodies manually while Phase 5 is in progress.
- lib/sample_counter/*.dart: generated from SampleCounter.csproj

CLAUDE.md updated with the new target structure and command.

Zero regressions on the existing test suite — 425/425 .NET tests pass.

Part of #56
…bset) (#56)

Begin body extraction — the final piece of Phase 5. Two new extractors
walk Roslyn expression/statement syntax and produce semantic IR. This
unlocks method/constructor/property bodies flowing through IR end-to-end.

Scope of this PR — the "core subset" sufficient for SampleCounter:
- IrExpressionExtractor covers literals, identifiers, this/base,
  parenthesized expressions, binary and unary operators (all C# kinds),
  assignment (simple + compound), member access, invocation, object
  creation (explicit and implicit new), conditional (?:), await, throw,
  cast, element access.
- IrStatementExtractor covers blocks, expression statements, return,
  local variable declarations (var + explicit types), if/else, throw,
  break, continue.

Unsupported syntax surfaces as IrUnsupportedExpression / IrUnsupportedStatement
placeholders so backends can detect partial support and fall back to the
legacy pipeline without crashing.

The extractors accept an optional IrTypeOriginResolver and thread it
through type references (e.g., on IrNewExpression and IrCastExpression)
so cross-package origins continue to flow correctly.

14 new IR-level tests validate extraction across the subset. Future PRs
(patterns, lambdas, LINQ, string interpolation, ...) extend coverage
incrementally.

Not yet wired to the pipeline — PR 5.7 populates IrMethodDeclaration.Body
etc. and teaches the TS + Dart bridges to print IR statements.

Total: 439 tests passing (425 + 14).

Part of #56
Flip Phase 5.7: IrMethodExtractor now populates Body from the Roslyn
syntax (when available) by running the IrStatementExtractor built in
Phase 5.6, and the Dart printer renders that IR as idiomatic Dart.

Changes:
- IrMethodExtractor.Extract accepts an optional Compilation; when passed,
  it locates the MethodDeclarationSyntax and runs IrStatementExtractor
  over the body/arrow clause, producing a concrete IrStatement list on
  IrMethodDeclaration.Body
- IrClassExtractor.Extract threads the Compilation through so class
  members pick up their bodies naturally
- DartTransformer passes the _compilation to class extraction

Dart printing:
- DartMethodSignature and DartGetter gain an optional Body slot; when
  present, IrBodyPrinter renders the IR statements directly. Expression-
  bodied methods collapse to `=> expr;`; blocks render with braces
- IrBodyPrinter covers the supported subset of IR expressions/statements
  and produces `/* TODO */` comments for the unsupported ones
- Factored DartTypeFormatter out of Printer for shared type-rendering
  across the printer and the body printer

Result on SampleCounter:
Before: every method body was `=> throw UnimplementedError();`
After:
  Counter.increment() => Counter(count + 1);
  Counter.decrement() => Counter(count - 1);
  CounterPresenter.initialize() => _view.displayCounter(_counter);
  CounterPresenter.increment() {
    _counter = _counter.increment();
    displayCounter();
  }
  ...

Still stubbed (future PRs):
- Property getters (Counter.zero => new(0) needs IrPropertyExtractor
  to populate GetterBody)
- Constructor bodies (CounterPresenter's `_view = view; Initialize();`
  needs IrConstructorExtractor to populate Body)
- TS target still uses the legacy ExpressionTransformer; wiring the IR
  body printer for TS is a separate PR to keep diffs readable

Verification:
- 439/439 .NET tests pass
- Regeneration of sample-issue-tracker and sample-todo-service produces
  bit-identical TS output to before (legacy path unchanged)

Part of #56
…er them in Dart (#56)

Finishes the body-extraction pass for the Dart target. With this commit
SampleCounter transpiles to fully-functional Dart end-to-end.

New in the IR extractors:
- IrPropertyExtractor: when a Compilation is supplied, extract getter body
  (expression-bodied or block), setter body, and field-initializer-style
  property initializer via IrExpression/IrStatementExtractor
- IrConstructorExtractor: extract ctor body (block or expression-bodied)
- IrClassExtractor (TryExtractField): extract field initializer expression

New IR expression node:
- IrTypeReference — distinct from IrIdentifier so backends can preserve the
  type's PascalCase while still converting PascalCase property/member
  references to camelCase. IrExpressionExtractor resolves a symbol via the
  SemanticModel and emits IrTypeReference when the name binds to a type or
  namespace, IrIdentifier otherwise.

Dart target:
- DartField, DartGetter, DartMethodSignature, DartConstructor gained body/
  initializer slots populated from the IR
- Constructor printing simplified to positional `(this.field, Type other)`
  form, which preserves C# call-site semantics (`new T(a, b)` → `T(a, b)`)
  and avoids named-parameter mismatches at call sites
- DartField printer emits `= initializer` when present; IsLate is only set
  when there's no initializer, no ctor assignment, and the type is
  non-nullable
- IrBodyPrinter handles IrTypeReference without case-converting

Generated SampleCounter Dart is now self-consistent:
  class Counter {
    Counter(this.count);
    final int count;
    static Counter get zero => Counter(0);
    Counter increment() => Counter(count + 1);
    Counter decrement() => Counter(count - 1);
  }

  final class CounterPresenter {
    CounterPresenter(ICounterView view) {
      _view = view;
      initialize();
    }
    late final ICounterView _view;
    Counter _counter = Counter.zero;     // ← was missing before
    void initialize() => _view.displayCounter(_counter);
    ...
  }

439/439 .NET tests pass.

Part of #56
Build the infrastructure to lower IR statements and expressions into the
existing TypeScript AST. This pairs with the Dart-side body printer from
Phase 5.7 and lets us validate the IR against the legacy TS pipeline
before flipping the switch.

New files in src/Metano.Compiler.TypeScript/Bridge/:
- IrToTsExpressionBridge: maps IrExpression -> TsExpression. Covers the
  subset the extractors produce today: literals, identifiers (with
  camelCase), type references (preserves PascalCase), this/base, member
  access, element access, binary ops (all variants including assignments
  and compound-assign), unary ops (prefix + postfix), object creation,
  conditionals, cast (erased), await.
- IrToTsStatementBridge: maps IrStatement -> TsStatement. Flattens
  IrBlockStatement into the surrounding statement list (TS uses flat
  lists inside TsIfStatement.Then etc.).

Unsupported IR nodes produce a TsIdentifier carrying a `/* TODO */`
comment so the emitted file is still syntactically valid — this matches
the Dart printer's approach.

Not wired into the pipeline yet. Four golden tests exercise the bridges
in isolation by extracting a C# method body through IrStatementExtractor,
mapping it to TS AST, printing it with the existing Printer, and
asserting the output. The wiring into RecordClassTransformer is a
separate PR so regressions can be evaluated independently from the
1151-line import collector and the ~15 expression handlers that it
would bypass.

Total: 443 tests passing (439 + 4 body-bridge tests).

Part of #56
…ort helper, IR body coverage probe, IR statement expansion (#56)

Wraps up the "pending items" that were achievable in a bounded session. The
complex-feature migrations listed in the plan's Phase 6 remain as explicit
backlog — each is a multi-commit undertaking on its own.

Deliverables:

1. docs/compiler-refactor-plan.md — rewritten to reflect actual progress,
   current pipeline shape, and a precise Phase 6 checklist (record
   synthesis, operator bodies, extension methods, pattern matching, JSON
   context, BCL mappings, overload dispatcher). The source-agnostic
   follow-up is documented but intentionally deferred.

2. Phase 5.10b groundwork — IrBodyCoverageProbe walks an IR body and
   reports whether every node is within the currently-supported subset.
   The TS pipeline can use this to decide IR vs legacy routing once
   BCL-mapping coverage lands in the IR bridge. No wiring yet to avoid
   regressing the legacy TsMapper/BclMapper path.

3. Phase 6.4 groundwork — IrRuntimeRequirementToTsImport translates
   target-agnostic runtime requirements into concrete TsImport lines.
   Groups helpers by module, honors TypeOnly for Grouping, drops unknown
   helpers. Six new tests pin the mapping. Full ImportCollector
   simplification depends on IR-path coverage for classes — deferred to
   Phase 6 wrap-up.

4. IR statement expansion — IrStatementExtractor now handles ForEach,
   While, DoWhile, Try/Catch/Finally (with multiple catches), and Switch
   (case + default labels). IrBodyCoverageProbe recognizes them.
   IrBodyPrinter (Dart) renders them idiomatically (`for (var x in xs)`,
   `try { ... } on ExceptionType catch (e) { ... }`, `switch (...) { case
   N: ... }`). Four new extraction tests cover the additions.

Test results: 453 .NET tests + 377 bun tests pass. No regressions.

Remaining backlog (documented in the plan):
- Wire IR body bridge in TS pipeline (Phase 5.10b wiring, needs BCL map coverage)
- Migrate RecordClassTransformer features via IR one at a time
- Delete legacy walker paths once IR covers every test-visible behavior

Part of #56
The plan doc now reflects what lives in the branch and keeps the
essence from the original proposal intact.

- Phase Progress section updated to list the actual IR surface:
  expression/statement coverage (expanded in the previous commit),
  runtime requirement scanner + TS import mapping, coverage probe
  groundwork, Dart pipeline reality.
- Outstanding Work section reordered so Phase 5.10b depends on the
  BCL mapping migration (Phase 6 item 1) — that's the real blocker
  for flipping the TS body-wiring switch without regressing legacy
  parity.
- Phase 6 migration list rewritten in dependency order: BCL mapping
  first, then patterns, lambda/interpolation, record synthesis,
  operator bodies, modules, dispatcher, JSON context.
- Test Strategy section restored (keeping the three-layer approach
  from the original plan: Roslyn→IR, IR→target AST, end-to-end).
- Risks section restored with concrete notes on how each risk has
  been mitigated so far.
- Source-agnostic future kept as deferred context.

Part of #56
ImportCollector now consumes the semantic IrRuntimeRequirement set
collected by IrRuntimeRequirementScanner over the file group's types
and converts each fact into the concrete TsImport via
IrRuntimeRequirementToTsImport. The legacy walker still covers
template-driven needs (RuntimeImports, expression-level Temporal
usage, runtime type checks, delegate helpers) and overlapping entries
are reconciled by MergeImportsByPath, so the emitted output is
unchanged across the whole sample matrix.

To avoid extracting IR multiple times for the same symbol per file
group (enums/interfaces twice, plain-object classes three times),
TransformGroup builds a per-group IR cache that is shared with
BuildTypeStatements and TryEmitShapeOnlyPlainObjectViaIr through a
new GetOrExtractIr helper. The IrTypeOriginResolver is also hoisted
to a transformer field so it is built once per compilation instead
of per group.

Also fixes the scanner to skip HashCode for [PlainObject] records,
matching the legacy emission rule.

Part of #56
Adds IrMemberOrigin (declaring type + member name + IsStatic) and
plugs it as an optional slot on IrMemberAccess and IrCallExpression.
IrExpressionExtractor populates it from the SemanticModel. Call
expressions also carry the method's generic type arguments when
present.

This is the first step toward Phase 6 item 1 (BCL mapping via IR):
the IR now carries enough semantic information for a backend to key
its own BCL mapping registry by (declaring type, member name) without
reaching back into source-language symbols. No behavior change; the
slot is not yet consumed — the TS target still uses the legacy
BclMapper/DeclarativeMappingRegistry path during class body lowering.

Part of #56
)

IrMemberOrigin.DeclaringType was an IrTypeRef, but IrTypeRefMapper is
deliberately lossy — List<int> collapses to IrArrayTypeRef(Int32) so
the original declaring type is gone. BCL mapping tables key on the
open-generic original definition (e.g., List<T>) exactly to share one
entry across every closed instantiation, so IrTypeRef is the wrong
carrier for this purpose.

Switches the field to DeclaringTypeFullName (string) populated from
symbol.ContainingType.OriginalDefinition.ToDisplayString(). Adds a
test that verifies List<int>.Add resolves to "System.Collections.
Generic.List<T>" so the registry lookup can reuse existing keys.

Part of #56
#56)

Introduces IrToTsBclMapper in the TS bridge layer: given an
IrMemberAccess or IrCallExpression and a DeclarativeMappingRegistry,
returns the lowered TsExpression when an entry matches, or null. The
caller is responsible for rendering the raw form on null. Consumes
IrMemberOrigin (declaring type full name + member name + IsStatic)
instead of Roslyn symbols, so the IR-driven class pipeline will be
able to reuse the same registry once Phase 5.10b flips the switch.

Supporting changes:

- DeclarativeMappingRegistry carries full-name-keyed secondary indices
  that mirror the symbol-keyed ones. A new internal CreateForTests
  factory builds a registry with hand-written entries for unit tests;
  InternalsVisibleTo grants Metano.Tests access.
- Extracted the pure-TS rendering helpers
  (SplitRuntimeImports, MatchesArgFilter, WrapReceiverIfNeeded,
  BuildWrapCall, IsAlreadyWrappedBy) to a shared
  DeclarativeMappingRendering static so BclMapper and IrToTsBclMapper
  consume the same implementation.
- DeclarativeMappingEntry caches its parsed RuntimeImports list so
  templates that are expanded many times per compilation don't
  re-split the raw CSV every call.
- BclMapper delegates to the shared helpers, dropping ~100 lines of
  duplicated logic. Legacy behavior is unchanged.

Added 7 unit tests covering null/unmapped origins, property rename
and static template, method rename, wrap-receiver LINQ wrapping, and
literal-argument filter matching. Full .NET suite and all bun sample
tests pass; regenerated samples produce zero diff.

Part of #56
IrToTsExpressionBridge.Map and IrToTsStatementBridge.MapBody/Map gain
an optional DeclarativeMappingRegistry parameter that propagates
through every recursive call. When supplied, member accesses and
method calls consult IrToTsBclMapper first and fall back to the raw
lowering when no entry matches.

The parameter is opt-in and defaults to null, so existing callers
(today only the hand-built body-bridge tests) stay on the raw path
and produce identical output. This lets the class-body pipeline
switch to IR-driven BCL lowering by threading the already-built
TypeScriptTransformContext.DeclarativeMappings into MapBody, which is
Phase 5.10b's remaining wiring.

Added 2 integration tests covering the registry-present and
registry-absent paths: the same `xs.Add(v)` source lowers to
`xs.push(v)` with a `List<T>.Add → push` mapping and to the raw
`xs.add(v)` when no registry is supplied.

464→466 tests. All sample regenerations produce zero diff — the
live TS/Dart pipelines still don't pass a registry into the bridge.

Part of #56
…b) (#56)

Introduces an opt-in seam on TypeScriptTransformContext
(UseIrBodiesWhenCovered, default false) that routes fully-IR-covered
method bodies through IrToTsStatementBridge — with the declarative
BCL registry plugged in — and falls back to the legacy
ExpressionTransformer path otherwise. TypeTransformer forwards the
flag from its own init property so tests can enable it per
compilation.

RecordClassTransformer.TransformClassMethod now delegates body
lowering to a new LowerMethodBody helper that consults
IrBodyCoverageProbe; when the probe clears, the body flows through
the IR path (BCL mappings included). The flag is off by default, so
every golden test, sample and bun suite still emits the canonical
legacy output.

Added 3 integration tests (IrBodyPipelineTests) that exercise the IR
path end-to-end via TranspileHelper.TranspileWithIrBodies: a simple
arithmetic return, a branching if+return, and a List<T>.Add → push
mapping to prove the declarative registry is threaded through. The
last test uses explicit `this.` because the current
IrExpressionExtractor doesn't synthesize implicit-this for member
references — a known gap that must close before flipping the flag on
by default.

466 → 469 .NET tests. 18+65+19 bun tests still green. Sample
regeneration produces zero diff.

Part of #56
…IR (#56)

IrExpressionExtractor.ExtractIdentifierName now inspects the resolved
symbol: when a bare identifier refers to an instance property, field,
event, or method (i.e., a member reached through C#'s implicit-this
shorthand), the extractor emits IrMemberAccess(IrThisExpression,
name) with the corresponding IrMemberOrigin. Locals, parameters,
range variables, statics and type references keep their previous
representation.

This closes the gap that blocked IrBodyPipelineTests from exercising
implicit member access; the BCL mapping test now transpiles the
original `Items.Add(value)` source (without forced `this.`) and
produces `this.items.push(value)`.

Updated tests:
- Invocation_ExtractsTargetAndArgs: asserts the this.Add target.
- Added StaticInvocation_KeepsBareIdentifierTarget,
  LocalIdentifier_StaysAsBareIdentifier,
  ImplicitInstanceFieldReference_ExpandsToThisMemberAccess.
- RecordLikeBuilder_ParityWithHandWritten expects
  `new T(this.count + 1)`.
- IrBodyPipelineTests.BclMappingStillApplies now uses implicit `this`.

The Dart sample regenerates with explicit `this.` prefixes — valid
Dart, just verbose. Removing the redundant `this.` in non-shadowed
contexts is a follow-up for the Dart printer; production TypeScript
samples produce zero diff (legacy pipeline still the default).

469 → 472 .NET tests; all bun suites still green.

Part of #56
Three correctness issues surfaced by the review of the Dart backend:

1. Duplicate simple type names no longer crash generation.
   DartTransformer.TransformAll previously built its local-type-file
   map through Dictionary.ToDictionary, which throws on duplicate
   keys. Two transpilable Dart types with the same simple name across
   different namespaces (Admin.User vs Billing.User) tripped the
   crash before a single file was emitted. Replaced with a guarded
   loop that reports a MetanoDiagnostic (AmbiguousConstruct) and
   skips the second occurrence so the rest of the project still
   generates. Namespace-qualified output paths remain as follow-up.

2. Default constructor parameters round-trip into the Dart output.
   IrParameter gained an optional DefaultValue: IrExpression? slot,
   populated by both IrConstructorExtractor and IrMethodExtractor via
   a shared helper that parses the declaring syntax when the
   compilation is available. DartConstructorParameter carries the
   same expression through to the printer, which emits a Dart
   optional positional block ([Type x = default]) at the tail of
   the parameter list. The bridge keeps IsRequired in sync with
   HasDefaultValue so non-defaulted parameters still render as the
   required positional form.

3. Uninitialized non-nullable static fields emit `late`.
   IrToDartClassBridge.ConvertField only added late on instance
   fields, but Dart also rejects `static int count;` without an
   initializer. Dropped the !field.IsStatic guard so static
   non-nullable fields without an initializer render as
   `static late int count;`.

Added tests/Metano.Tests/DartBackendTests.cs covering all three
regressions end-to-end (Roslyn compile → IR → Dart bridge → printer)
plus a separate case exercising a non-promoted parameter's default.
Metano.Tests.csproj now references the Dart target project.

472 → 476 .NET tests; all bun suites still green; TS and Dart
samples produce zero diff on regeneration.
danfma added 7 commits April 15, 2026 14:51
…ps (#56)

Closes the final prerequisite for the IR body pipeline becoming the
default TS emitter. Three gaps surfaced by the experimental flip
(task #50) are all fixed and the flag goes default-on with zero diff
across every regenerated sample (TS + Dart) and all 522 .NET tests
plus the bun suites passing.

Gap #1 — static-call qualifier lost. IrExpressionExtractor.Extract-
IdentifierName only promoted instance members to `this.Member`. Now
it also promotes static members of the enclosing type (or any named
type visible without a qualifier) to `IrMemberAccess(IrTypeReference
(ClassName), name)`, matching the legacy IdentifierHandler.

Gap #2 — default(T) for reference types lowered to `undefined`
instead of `null`. ExtractDefaultLiteral now consults
SemanticModel.GetTypeInfo(lit).ConvertedType and emits
IrLiteralKind.Null when the inferred type is a reference type, type
parameter, or nullable wrapper — matching what legacy
ExpressionTransformer produces.

Gap #3 — `new PlainObjectRecord(…)` lowered to `new Ctor(…)`
instead of `{ prop: val, … }`. IrNewExpression gained two slots
(IsPlainObject + ParameterNames) that the extractor populates when
the target type carries [PlainObject]; IrToTsExpressionBridge.
MapNewExpression now emits a TsObjectLiteral keyed by ctor
parameter name (camelCased) for that case, mirroring
ObjectCreationHandler.CreatePlainObjectLiteral. The Dart target
ignores the flag and continues to emit a real `new T(…)` call.

Cosmetic — conditional expressions inside string interpolations.
Legacy wrapped them defensively in parens (`${(a ? b : c)}`); the
bridge's MapStringInterpolation now does the same so the IR path
matches the existing goldens byte-for-byte.

Supporting test update: StaticInvocation_KeepsBareIdentifierTarget
was pinning the bare-identifier target that's now promoted — renamed
to StaticInvocation_SynthesizesClassNameQualifier with the matching
IrMemberAccess assertion.

Samples: every regenerated targets/js/* and
targets/flutter/sample_counter shows zero diff with the flag on,
confirming the IR path reproduces the legacy output exactly for the
subset IrBodyCoverageProbe marks fully covered. Uncovered bodies
still fall back to the legacy ExpressionTransformer path until IR
coverage grows further.

Part of #56
…56)

RecordClassTransformer now synthesizes a record's equals / hashCode /
with members via the IR-driven IrToTsRecordSynthesisBridge instead of
the Roslyn-symbol-driven RecordSynthesizer. The call site extracts an
IrClassDeclaration on the fly (shape only — no body) and passes the
already-built TsConstructorParam list, so the emitted TS stays
byte-identical to what RecordSynthesizer produced (the bridge has a
parity test pinning that invariant).

Walks through the same gate RecordSynthesizer applied:
`type.IsRecord && !HasPlainObject(type)` — [PlainObject] records
continue to emit as bare object literals without synthesized members.

Also reverts UseIrBodiesWhenCovered back to false by default.
Regenerating SampleOperatorOverloading against the flipped flag
surfaced a gap that wasn't visible in the earlier three-sample
sweep: decimal arithmetic operators (+, -, *, /) flow through the
IR as plain IrBinaryExpressions and lower to raw TS `/` and `*`,
bypassing the decimal.js Decimal.times/.div/.plus/.minus helpers
that the legacy ExpressionTransformer dispatches via the BCL
registry. The flip is blocked on that gap; task #51 tracks it. The
record-synth swap itself is independent of the flag and produces
the canonical legacy output in both modes.

522/522 .NET tests; all bun suites still green; every regenerated
sample (TS + Dart) shows zero diff with the flag off.

Part of #56
)

Closes task #51 — the first of three gaps that block
UseIrBodiesWhenCovered from going default-on.

C#'s `decimal` surfaces as the decimal.js Decimal class at runtime,
which needs method-call dispatch (`a.plus(b)`, `.times`, `.div`,
`.mod`, `.eq`, `.lt`, …) instead of the raw TS `+` / `*` / `/`
operators. The legacy OperatorHandler detects both operands as
System_Decimal via the SemanticModel and rewrites the call accordingly
(TryMapDecimalBinary). The IR path used to emit raw IrBinary-
Expressions and the TS bridge had no way to recover the type info to
rewrite at lowering time.

IrExpressionExtractor.ExtractBinary now runs the same detection:
when both sides are Decimal and the operator maps to a decimal.js
method, it normalizes the expression to an IrCallExpression
(`IrMemberAccess(left, method)`, [right]). Inequality (`!=`) wraps
the call in `IrUnaryExpression(LogicalNot, …)` to preserve the
`!eq` convention. The rest of the binary-op path stays untouched
so non-Decimal operands continue to use IrBinaryExpression — no
behavior change for any other type.

Regenerating SampleOperatorOverloading under
UseIrBodiesWhenCovered = true confirms the fix: `this.cents / 100m`
now lowers through `this.cents.div(100)` instead of raw `/`, and
`amount * 100m` through `amount.times(100)`.

Three decimal-adjacent gaps remain and block the flag flip:
1. Decimal literals (`100m`) don't wrap as `new Decimal("100")`.
2. `decimal → BigInt` conversions skip the
   `.round().toFixed(0)` chain legacy emits.
3. `Math.Round(decimal)` doesn't dispatch to `.round()`.

Each is a separate extractor-side rewrite captured as task #52.
Flag goes back to false at the end of this commit so production
samples keep their canonical output.

522/522 .NET tests; all bun suites still green; every sample
(regenerated with the flag off) shows zero diff.

Part of #56
…goes default-on (#56)

Closes task #52 and flips UseIrBodiesWhenCovered default to true
with zero diff across every regenerated sample (SampleTodo,
SampleIssueTracker, SampleTodo.Service, SampleOperatorOverloading,
and SampleCounter).

Three extractor-side rewrites, all in IrExpressionExtractor, each
mirroring a piece of the legacy ExpressionTransformer / LiteralHandler
/ InvocationHandler that used to run only via Roslyn symbols:

1. Numeric literals in a Decimal / BigInteger context. A new
   ClassifyNumericInContext consults SemanticModel.GetTypeInfo
   (lit).ConvertedType. When the inferred type is `decimal`, the
   literal is emitted as IrLiteral(text, Decimal) so the bridge
   wraps it as `new Decimal("…")`; for BigInteger contexts
   IrLiteral(text, BigInteger) produces the native `n` suffix
   (`150n`). Covers both explicit `100m` literals and implicit
   conversions in mixed-type arithmetic.

2. Numeric casts that change representation. ExtractCast takes
   over CastExpressionSyntax handling and produces the four shapes
   the legacy emits: `(decimal)bigInt` → `new Decimal(val.toString
   ())`, `(BigInteger)decimal` → `BigInt(val.toFixed(0))`,
   `(int|long|...)decimal` → `val.toNumber()`, and `(BigInteger)
   int|long|decimal` → `BigInt(val)`. Uses IrTypeReference("BigInt")
   for the global builtin so the TS bridge's camelCase pass doesn't
   rewrite it to `bigInt`.

3. `Math.Round/Floor/Ceiling/Abs` on a decimal argument. A new
   TryRewriteMathDecimalCall detects `System.Math.Round(decimal)`
   style calls and rewrites them to the receiver's instance method
   (`amount.round()`), matching decimal.js semantics. Multi-argument
   overloads fall through to the normal BCL mapping path.

Bridge support:

- IrToTsExpressionBridge.MapLiteral now emits `new Decimal("val")`
  for Decimal kind and `valn` for BigInteger kind; IntN literals
  keep the plain number form.
- MapNewTarget special-cases IrPrimitiveTypeRef(Decimal) so
  synthesized `new Decimal(...)` expressions (produced by ExtractCast)
  reach the decimal.js constructor even though the type mapper
  flattens decimal to `number` for signatures.

Validation: every TS sample plus SampleCounter.dart regenerates
byte-identical to the legacy output with the flag on. 522/522 .NET
tests; bun suites all green (18 + 65 + 19).

Follow-up: the flip to true is local to this commit — the refactor's
wrap-up will decide whether to land default-on as part of the
legacy-retirement series or keep the seam as an opt-in for another
round.

Part of #56
…ion path (#56)

Deletes src/Metano.Compiler.TypeScript/Transformation/RecordSynthesizer.cs
now that RecordClassTransformer has been consuming
IrToTsRecordSynthesisBridge exclusively since commit fae2cad, and
UseIrBodiesWhenCovered defaults to true (fd044e6). The only
remaining caller was the IR bridge's parity test, which has been
converted to assert the emitted TS shape directly instead of
cross-checking against the legacy synthesizer.

XML-doc references in RecordClassTransformer and ObjectCreationHandler
updated to point at Metano.TypeScript.Bridge.IrToTsRecordSynthesisBridge
(and a plain "synthesized with method" description for the
RecordWithExpression lowering).

Other legacy TS pipeline pieces (OverloadDispatcherBuilder,
ModuleTransformer, ObjectCreationHandler, TypeCheckGenerator) stay
put because they still cover code paths the IR pipeline hasn't
ported yet — overload constructor dispatch, [ModuleEntryPoint] body
unwrap, C# 14 extension blocks, uncovered-body fallbacks. Those
retire once their IR equivalents land.

522/522 .NET tests; samples regenerate with zero diff.

Part of #56
Updates the Current State snapshot and the Phase 6 wrap-up section
to reflect where the branch actually stands: UseIrBodiesWhenCovered
is true by default, all three retirement prerequisites (record
synthesis, module lowering, overload dispatcher) landed with
byte-identical sample output, RecordSynthesizer is deleted, and the
extractor-side numeric normalizations (Decimal binary ops, Decimal/
BigInteger literals, numeric casts, Math.Round(decimal), effective-
const) are listed among what closed the gap.

Replaces the previous "prerequisites for flipping" checklist with a
"what still keeps legacy alive" list: OverloadDispatcherBuilder's
constructor path, ModuleTransformer's entry-point + extension-block
paths, ObjectCreationHandler's `record with` + cross-package
PlainObject path, JsonSerializerContextTransformer's Roslyn-symbol
walk, and the ImportCollector legacy walker. Each has a clear
rationale and none block the current default; they retire once
their IR equivalents land.

Test counts refreshed to 522 / 377.

Part of #56
- [NoEmit(Dart)] types no longer enter the local import map, preventing
  consumers from emitting stale `import 'x.dart'` pointing at a file
  that was never written.
- Import walker now traverses DartFunction top-levels so
  [ExportedAsModule] static classes contribute imports for parameter
  and return types.
- IrClassExtractor folds C# method overloads into a single primary
  IrMethodDeclaration with Overloads populated, so the Dart backend
  emits one method and surfaces the "no overloading" diagnostic
  instead of duplicating declarations.
- Dart property lowering now carries the auto-property initializer
  through to the generated final field and skips the `late` modifier
  when the initializer is present.
Copilot AI review requested due to automatic review settings April 15, 2026 18:44
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR restructures the repository’s generated outputs under targets/, extends the compiler’s intermediate representation (IR) to be more target-agnostic, and introduces an initial Dart/Flutter backend alongside improvements to the TypeScript backend (per-target attributes, type mapping context, and IR→TS bridges).

Changes:

  • Moved JS target workspaces/samples/runtime to targets/js/* and updated build/config references accordingly.
  • Added a new IR surface area (types, members, attributes, runtime requirements) and TS bridges for IR-driven lowering and runtime-import collection.
  • Introduced a new Metano.Compiler.Dart CLI target plus a Flutter sample consumer under targets/flutter/.

Reviewed changes

Copilot reviewed 148 out of 290 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tsconfig.json Updates TS project references to the new targets/js/* layout.
tests/Metano.Tests/NameAttributeConsistencyTests.cs Adjusts expectations for string-enum key/value behavior in TS output.
tests/Metano.Tests/Metano.Tests.csproj Adds Dart compiler project reference for test coverage.
tests/Metano.Tests/IrBodyPipelineTests.cs Adds integration tests driving the IR-based TS body pipeline.
tests/Metano.Tests/IR/IrRuntimeRequirementToTsImportTests.cs Tests IR runtime-requirement → TS import conversion/grouping.
targets/js/sample-todo/tsconfig.json New TS config for relocated sample-todo target.
targets/js/sample-todo/src/todo-list.ts Generated sample-todo list logic and overload dispatch.
targets/js/sample-todo/src/todo-item.ts Generated record-like class with equals/hashCode/with.
targets/js/sample-todo/src/priority.ts Generated string-enum const object + union type.
targets/js/sample-todo/src/json-context.ts Generated serializer context sample for runtime JSON system.
targets/js/sample-todo/src/index.ts Sample barrel exports.
targets/js/sample-todo/package.json Updates generator script paths for new directory layout.
targets/js/sample-todo-service/tsconfig.json New TS config for relocated service sample.
targets/js/sample-todo-service/src/todos.ts Generated store + DTOs for service sample.
targets/js/sample-todo-service/src/program.ts Generated Hono app entry wiring.
targets/js/sample-todo-service/src/index.ts Service barrel exports.
targets/js/sample-todo-service/package.json Updates generator script paths for new directory layout.
targets/js/sample-operator-overloading/tsconfig.json New TS config for relocated operator-overloading sample.
targets/js/sample-operator-overloading/test/money.test.ts Bun tests validating generated operator overload surface.
targets/js/sample-operator-overloading/src/program.ts Sample program wiring.
targets/js/sample-operator-overloading/src/no-same-money-currency-exception.ts Generated exception type.
targets/js/sample-operator-overloading/src/index.ts Barrel exports for operator-overloading sample.
targets/js/sample-operator-overloading/src/currency.ts Generated numeric enum.
targets/js/sample-operator-overloading/package.json Updates generator script paths for new directory layout.
targets/js/sample-issue-tracker/tsconfig.json New TS config for relocated issue-tracker sample.
targets/js/sample-issue-tracker/test/shared-kernel/user-id.test.ts Adds Bun tests for generated inline wrapper behavior.
targets/js/sample-issue-tracker/test/shared-kernel/page-result.test.ts Adds Bun tests for generated PageResult.
targets/js/sample-issue-tracker/test/shared-kernel/page-request.test.ts Adds Bun tests for generated PageRequest logic.
targets/js/sample-issue-tracker/test/shared-kernel/operation-result.test.ts Adds Bun tests for generated OperationResult behavior.
targets/js/sample-issue-tracker/test/issues/domain/issue.test.ts Adds Bun tests for issue aggregate behavior.
targets/js/sample-issue-tracker/test/issues/domain/issue-workflow.test.ts Adds Bun tests for workflow logic.
targets/js/sample-issue-tracker/test/issues/domain/issue-status.test.ts Adds Bun tests for string-enum access semantics.
targets/js/sample-issue-tracker/test/issues/domain/issue-id.test.ts Adds Bun tests for IssueId inline wrapper.
targets/js/sample-issue-tracker/test/issues/domain/comment.test.ts Adds Bun tests for Temporal-backed Comment type.
targets/js/sample-issue-tracker/test/issues/application/issue-service.test.ts Adds Bun tests for application service logic.
targets/js/sample-issue-tracker/test/issues/application/issue-queries.test.ts Adds Bun tests for LINQ-like query helpers.
targets/js/sample-issue-tracker/test/issues/application/in-memory-issue-repository.test.ts Adds Bun tests for repository implementation.
targets/js/sample-issue-tracker/test/helpers.ts Test helper factory for issue instances.
targets/js/sample-issue-tracker/src/shared-kernel/user-id.ts Generated inline wrapper + UUID usage.
targets/js/sample-issue-tracker/src/shared-kernel/page-result.ts Generated record-like container with derived properties.
targets/js/sample-issue-tracker/src/shared-kernel/page-request.ts Generated paging utility with computed fields.
targets/js/sample-issue-tracker/src/shared-kernel/operation-result.ts Generated result type with ok/fail helpers.
targets/js/sample-issue-tracker/src/shared-kernel/index.ts Shared-kernel barrel exports.
targets/js/sample-issue-tracker/src/planning/domain/sprint.ts Generated domain type using HashSet + Temporal helpers.
targets/js/sample-issue-tracker/src/planning/domain/index.ts Planning domain barrel export.
targets/js/sample-issue-tracker/src/issues/domain/issue-workflow.ts Generated workflow logic using Enumerable.
targets/js/sample-issue-tracker/src/issues/domain/issue-type.ts Generated string-enum const object + union.
targets/js/sample-issue-tracker/src/issues/domain/issue-status.ts Generated string-enum const object + union.
targets/js/sample-issue-tracker/src/issues/domain/issue-snapshot.ts Generated snapshot type with Decimal/Temporal fields.
targets/js/sample-issue-tracker/src/issues/domain/issue-priority.ts Generated string-enum const object + union.
targets/js/sample-issue-tracker/src/issues/domain/issue-id.ts Generated inline wrapper.
targets/js/sample-issue-tracker/src/issues/domain/index.ts Issues domain barrel exports.
targets/js/sample-issue-tracker/src/issues/domain/comment.ts Generated domain entity using Temporal.
targets/js/sample-issue-tracker/src/issues/application/issue-queries.ts Generated LINQ-like query functions.
targets/js/sample-issue-tracker/src/issues/application/index.ts Application-layer barrel exports.
targets/js/sample-issue-tracker/src/issues/application/in-memory-issue-repository.ts Generated repository implementation incl. paging/search.
targets/js/sample-issue-tracker/src/issues/application/i-issue-repository.ts Generated repository interface.
targets/js/sample-issue-tracker/src/index.ts Package entry export.
targets/js/sample-issue-tracker/package.json Updates generator script paths for new directory layout.
targets/js/sample-issue-tracker/bunfig.toml Bun test root configuration.
targets/js/sample-counter/vite.config.ts Adds Vite config for the relocated SolidJS sample.
targets/js/sample-counter/tsconfig.node.json Node/Vite TS config for sample-counter.
targets/js/sample-counter/tsconfig.json TS project references for app/node configs.
targets/js/sample-counter/tsconfig.app.json App TS config including #/* path mapping.
targets/js/sample-counter/src/sample-counter/program.ts Generated program entry placeholder.
targets/js/sample-counter/src/sample-counter/index.ts Generated barrel exports for sample-counter model layer.
targets/js/sample-counter/src/sample-counter/i-counter-view.ts Generated interface used by presenter.
targets/js/sample-counter/src/sample-counter/counter.ts Generated model with record-like helpers.
targets/js/sample-counter/src/sample-counter/counter-presenter.ts Generated presenter (MVP pattern).
targets/js/sample-counter/src/index.tsx SolidJS app bootstrap.
targets/js/sample-counter/src/index.css Sample styling.
targets/js/sample-counter/src/assets/solid.svg Solid logo asset.
targets/js/sample-counter/src/App.tsx Sample UI integrating generated presenter.
targets/js/sample-counter/src/App.module.css Component styles for counter UI.
targets/js/sample-counter/package.json Updates package name and imports mapping for local dev/build.
targets/js/sample-counter/index.html Adds Vite HTML entry (title currently mismatched).
targets/js/sample-counter/README.md Adds template README for the sample.
targets/js/sample-counter/.gitignore Adds standard Vite/Node ignore patterns.
targets/js/metano-runtime/tsconfig.json TS project config for the runtime package.
targets/js/metano-runtime/test/system/json/property-naming-policy.test.ts Adds Bun tests for property naming policy conversion.
targets/js/metano-runtime/test/system/hash-code.test.ts Adds Bun tests for HashCode behavior.
targets/js/metano-runtime/test/system/collections/immutable-collection.test.ts Adds Bun tests for immutable collection helpers.
targets/js/metano-runtime/src/type-checking/primitive-type-checks.ts Adds runtime type guards used by overload dispatch.
targets/js/metano-runtime/src/type-checking/index.ts Exports type-checking utilities.
targets/js/metano-runtime/src/system/uuid.ts Adds UUID branded type + helpers.
targets/js/metano-runtime/src/system/temporal-helpers.ts Adds Temporal helper (dayNumber) aligned with C# DateOnly.
targets/js/metano-runtime/src/system/linq/zip-enumerable.ts Adds LINQ Zip enumerable implementation.
targets/js/metano-runtime/src/system/linq/where-enumerable.ts Adds LINQ Where enumerable implementation.
targets/js/metano-runtime/src/system/linq/union-enumerable.ts Adds LINQ Union enumerable implementation.
targets/js/metano-runtime/src/system/linq/then-by-enumerable.ts Adds chained sort implementation for ThenBy.
targets/js/metano-runtime/src/system/linq/take-while-enumerable.ts Adds LINQ TakeWhile enumerable implementation.
targets/js/metano-runtime/src/system/linq/take-enumerable.ts Adds LINQ Take enumerable implementation.
targets/js/metano-runtime/src/system/linq/source-enumerable.ts Adds source wrapper for LINQ pipeline.
targets/js/metano-runtime/src/system/linq/skip-while-enumerable.ts Adds LINQ SkipWhile enumerable implementation.
targets/js/metano-runtime/src/system/linq/skip-enumerable.ts Adds LINQ Skip enumerable implementation.
targets/js/metano-runtime/src/system/linq/select-many-enumerable.ts Adds LINQ SelectMany enumerable implementation.
targets/js/metano-runtime/src/system/linq/select-enumerable.ts Adds LINQ Select enumerable implementation.
targets/js/metano-runtime/src/system/linq/reverse-enumerable.ts Adds LINQ Reverse enumerable implementation.
targets/js/metano-runtime/src/system/linq/prepend-enumerable.ts Adds LINQ Prepend enumerable implementation.
targets/js/metano-runtime/src/system/linq/order-by-enumerable.ts Adds LINQ OrderBy enumerable implementation.
targets/js/metano-runtime/src/system/linq/intersect-enumerable.ts Adds LINQ Intersect enumerable implementation.
targets/js/metano-runtime/src/system/linq/group-by-enumerable.ts Adds LINQ GroupBy enumerable implementation.
targets/js/metano-runtime/src/system/linq/except-enumerable.ts Adds LINQ Except enumerable implementation.
targets/js/metano-runtime/src/system/linq/enumerable.ts Adds Enumerable factory mirroring System.Linq.Enumerable.
targets/js/metano-runtime/src/system/linq/distinct-enumerable.ts Adds LINQ Distinct enumerable implementation (HashSet-based).
targets/js/metano-runtime/src/system/linq/distinct-by-enumerable.ts Adds LINQ DistinctBy enumerable implementation.
targets/js/metano-runtime/src/system/linq/concat-enumerable.ts Adds LINQ Concat enumerable implementation.
targets/js/metano-runtime/src/system/linq/compare-keys.ts Adds shared comparator used by ordering operators.
targets/js/metano-runtime/src/system/linq/append-enumerable.ts Adds LINQ Append enumerable implementation.
targets/js/metano-runtime/src/system/json/serializer-context.ts Adds base SerializerContext with spec registry.
targets/js/metano-runtime/src/system/json/json-serializer.ts Adds JsonSerializer + bound serializer wrapper.
targets/js/metano-runtime/src/system/json/index.ts Exports JSON system surface.
targets/js/metano-runtime/src/system/index.ts Exports system surface (linq, json, uuid, etc.).
targets/js/metano-runtime/src/system/delegates.ts Adds multicast delegate helpers.
targets/js/metano-runtime/src/system/collections/list-helpers.ts Adds list remove + immutable helpers.
targets/js/metano-runtime/src/system/collections/index.ts Exports collections surface.
targets/js/metano-runtime/src/system/collections/immutable-collection.ts Adds immutable collection namespace helpers.
targets/js/metano-runtime/src/index.ts Root runtime barrel export.
targets/js/metano-runtime/package.json Adds runtime package metadata and exports (repository.directory needs update).
targets/js/metano-runtime/.gitignore Adds ignore patterns for runtime package.
targets/flutter/sample_counter/pubspec.yaml Adds Flutter sample package metadata.
targets/flutter/sample_counter/lib/sample_counter/program.dart Adds generated program entry (currently invalid Dart).
targets/flutter/sample_counter/lib/sample_counter/i_counter_view.dart Adds generated view interface.
targets/flutter/sample_counter/lib/sample_counter/counter_presenter.dart Adds generated presenter class.
targets/flutter/sample_counter/lib/sample_counter/counter.dart Adds generated model (copyWith is stubbed).
targets/flutter/sample_counter/lib/main.dart Adds Flutter UI consuming generated presenter.
targets/flutter/sample_counter/README.md Adds Flutter sample docs (currently inconsistent with code).
targets/flutter/sample_counter/.gitignore Adds Flutter/Dart ignore patterns.
src/Metano/Annotations/TargetLanguage.cs Introduces TargetLanguage enum for per-target attributes.
src/Metano/Annotations/NoEmitAttribute.cs Makes NoEmit targetable and allow-multiple.
src/Metano/Annotations/NameAttribute.cs Makes Name targetable + allow-multiple; defines resolution rules.
src/Metano/Annotations/IgnoreAttribute.cs Makes Ignore targetable and allow-multiple.
src/Metano.Compiler/IR/IrVisibility.cs Adds IR visibility model.
src/Metano.Compiler/IR/IrTypeSemantics.cs Adds semantic flags for types (record/plain-object/etc.).
src/Metano.Compiler/IR/IrTypeParameter.cs Adds IR type parameter model with constraints.
src/Metano.Compiler/IR/IrTypeOrigin.cs Adds IR cross-package origin model.
src/Metano.Compiler/IR/IrTypeDeclaration.cs Adds IR type declaration hierarchy.
src/Metano.Compiler/IR/IrRuntimeRequirement.cs Adds IR runtime requirement model for backend imports.
src/Metano.Compiler/IR/IrPropertySemantics.cs Adds property semantics independent of rendering.
src/Metano.Compiler/IR/IrPropertyAccessors.cs Adds property accessor-shape model.
src/Metano.Compiler/IR/IrPrimitive.cs Adds semantic primitive type set.
src/Metano.Compiler/IR/IrPattern.cs Adds IR pattern hierarchy for is/switch patterns.
src/Metano.Compiler/IR/IrParameter.cs Adds IR parameter model with defaults/params.
src/Metano.Compiler/IR/IrModule.cs Adds IR module model incl. runtime requirements.
src/Metano.Compiler/IR/IrMethodSemantics.cs Adds method semantics flags (async/operator/etc.).
src/Metano.Compiler/IR/IrMemberDeclaration.cs Adds IR member declaration hierarchy.
src/Metano.Compiler/IR/IrEnumMember.cs Adds IR enum member model incl. string/numeric styles.
src/Metano.Compiler/IR/IrConstructorDeclaration.cs Adds IR constructor model incl. promoted params.
src/Metano.Compiler/IR/IrAttribute.cs Adds IR attribute carrier for semantic annotations.
src/Metano.Compiler/Extraction/IrVisibilityMapper.cs Maps Roslyn Accessibility → IrVisibility.
src/Metano.Compiler/Extraction/IrTypeOriginResolver.cs Adds hook for resolving cross-assembly type origins.
src/Metano.Compiler/Extraction/IrExtractionContext.cs Adds extraction context incl. optional origin resolver.
src/Metano.Compiler/Extraction/IrEnumExtractor.cs Extracts IR enums (string/numeric) from Roslyn.
src/Metano.Compiler/Extraction/IrAttributeExtractor.cs Collapses multiple [Name] attributes into a single IR attribute map.
src/Metano.Compiler.TypeScript/Transformation/TypeScriptTransformContext.cs Adds explicit TypeMappingContext and IR-body switch.
src/Metano.Compiler.TypeScript/Transformation/TypeMappingContext.cs Replaces legacy ThreadStatic type-mapper state with an explicit context.
src/Metano.Compiler.TypeScript/Transformation/TypeGuardBuilder.cs Makes Name/Ignore/type-mapping target-aware and context-driven.
src/Metano.Compiler.TypeScript/Transformation/TypeCheckGenerator.cs Makes enum member checks target-aware; threads type-mapping context.
src/Metano.Compiler.TypeScript/Transformation/ObjectCreationHandler.cs Doc tweak reflecting synthesized with-method.
src/Metano.Compiler.TypeScript/Transformation/MemberAccessHandler.cs Fixes enum member reference emission for string vs numeric enums and target-aware Name.
src/Metano.Compiler.TypeScript/Transformation/LambdaHandler.cs Makes NoEmit and type mapping target-aware/context-driven.
src/Metano.Compiler.TypeScript/Transformation/JsonSerializerContextTransformer.cs Makes property naming/type mapping target-aware/context-driven.
src/Metano.Compiler.TypeScript/Transformation/IrTypeOriginResolverFactory.cs Provides IR origin resolver backed by TS cross-assembly map/context.
src/Metano.Compiler.TypeScript/Transformation/InvocationHandler.cs Uses target-aware Name overrides for invocation mapping.
src/Metano.Compiler.TypeScript/Transformation/InterfaceTransformer.cs Removes legacy interface transformer (superseded by IR bridging).
src/Metano.Compiler.TypeScript/Transformation/InlineWrapperTransformer.cs Threads target-aware Ignore/Name and mapping context.
src/Metano.Compiler.TypeScript/Transformation/ImportCollector.cs Adds IR runtime-requirement imports + replaces ThreadStatic side effects with context.
src/Metano.Compiler.TypeScript/Transformation/IdentifierHandler.cs Honors Name override on referenced symbols; threads mapping context.
src/Metano.Compiler.TypeScript/Transformation/ExpressionTransformer.cs Adds TypeMappingContext property replacing ThreadStatic dependency.
src/Metano.Compiler.TypeScript/Transformation/ExceptionTransformer.cs Threads mapping context into type mapping.
src/Metano.Compiler.TypeScript/Transformation/EnumTransformer.cs Removes legacy enum transformer (superseded by IR bridging).
src/Metano.Compiler.TypeScript/Transformation/DeclarativeMappingRendering.cs Factors shared TS rendering helpers for declarative mappings.
src/Metano.Compiler.TypeScript/Metano.Compiler.TypeScript.csproj Exposes internals to Metano.Tests for testing new components.
src/Metano.Compiler.TypeScript/Bridge/IrToTsStatementBridge.cs Adds IR statement → TS statement bridge (block handling needs care).
src/Metano.Compiler.TypeScript/Bridge/IrToTsPlainObjectBridge.cs Adds IR plain-object shape → TS interface bridge.
src/Metano.Compiler.TypeScript/Bridge/IrToTsNamingPolicy.cs Adds TS-specific naming policy for IR lowering with per-target Name resolution.
src/Metano.Compiler.TypeScript/Bridge/IrToTsInterfaceBridge.cs Adds IR interface → TS interface bridge.
src/Metano.Compiler.TypeScript/Bridge/IrToTsEnumBridge.cs Adds IR enum → TS enum/const-object bridge.
src/Metano.Compiler.TypeScript/Bridge/IrRuntimeRequirementToTsImport.cs Adds IR runtime requirement → TS import mapping.
src/Metano.Compiler.Dart/Program.cs Adds Dart CLI entry point.
src/Metano.Compiler.Dart/Metano.Compiler.Dart.csproj Adds Dart compiler project with Roslyn workspace dependencies.
src/Metano.Compiler.Dart/DartTarget.cs Adds Dart transpiler target implementation.
src/Metano.Compiler.Dart/Dart/DartTypeFormatter.cs Adds Dart type formatting helper.
src/Metano.Compiler.Dart/Dart/AST/DartType.cs Adds Dart type AST model.
src/Metano.Compiler.Dart/Dart/AST/DartParameter.cs Adds Dart parameter AST model.
src/Metano.Compiler.Dart/Dart/AST/DartClassMember.cs Adds Dart class-member AST model.
src/Metano.Compiler.Dart/Commands.cs Adds metano-dart command wiring.
src/Metano.Compiler.Dart/Bridge/IrToDartNamingPolicy.cs Adds Dart naming rules + per-target Name override support.
src/Metano.Compiler.Dart/Bridge/IrToDartModuleBridge.cs Lowers IR module functions to Dart top-level functions (naming needs fix).
src/Metano.Compiler.Dart/Bridge/IrToDartEnumBridge.cs Lowers IR enums to Dart enums (string enum value handling is backend-specific).
samples/SampleTodo/SampleTodo.csproj Updates Metano output directory to new targets path.
samples/SampleTodo/README.md Updates docs to new targets path.
samples/SampleTodo.Service/SampleTodo.Service.csproj Updates Metano output directory to new targets path.
samples/SampleTodo.Service/README.md Updates docs to new targets path.
samples/SampleOperatorOverloading/SampleOperatorOverloading.csproj Updates Metano output directory to new targets path.
samples/SampleIssueTracker/SampleIssueTracker.csproj Updates Metano output directory to new targets path.
samples/SampleIssueTracker/README.md Updates docs to new targets path.
samples/SampleCounter/SampleCounter.csproj Updates output dir and removes obsolete folder include.
package.json Updates workspace globs to targets/js/*.
docs/adr/0012-linq-eager-wrapper.md Updates runtime reference paths to new layout.
docs/adr/0009-type-guards.md Updates runtime reference paths (one path currently incorrect).
docs/adr/0005-inline-wrapper-branded-types.md Updates runtime reference paths to new layout.
README.md Updates sample output paths and adds sample-counter mention.
Metano.slnx Adds Dart compiler project to solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +81
/// <summary>
/// Opt-in switch for the IR-driven method body pipeline (Phase 5.10b). When
/// <c>true</c>, <see cref="RecordClassTransformer.TransformClassMethod"/>
/// lowers fully-IR-covered bodies via <see cref="Metano.TypeScript.Bridge.IrToTsStatementBridge"/>
/// (with the declarative BCL registry plugged in) and falls back to the
/// legacy <see cref="ExpressionTransformer"/> path for anything the probe
/// flags as uncovered. Defaults to <c>false</c> — the legacy path still
/// produces the canonical output for every sample and golden test; this
/// flag is the seam integration tests use to drive the IR body bridge
/// through the full pipeline.
/// </summary>
public bool UseIrBodiesWhenCovered { get; init; } = true;

- `src/Metano.Compiler.TypeScript/TypeScript/AST/TsTypePredicateType.cs`
- `src/Metano/Annotations/GenerateGuardAttribute.cs`
- `js/metano-runtime/src/system/primitives/` — `isInt32`, `isString`,
- `targets/js/metano-runtime/src/system/primitives/` — `isInt32`, `isString`,

private static DartFunction ConvertFunction(IrModuleFunction fn) =>
new(
Name: IrToDartNamingPolicy.ToParameterName(fn.Name),
@@ -0,0 +1 @@
void main() => Console.writeLine('Hello, World!');
Comment on lines +35 to +37
While Phase 5 (body extraction) is in progress, `lib/main.dart` supplies manual
implementations for `Counter` members via an extension. Once bodies flow
end-to-end, those workarounds go away and the Flutter app will consume fully
Comment on lines +65 to +69
IrBlockStatement block when block.Statements.Count > 0 =>
// Nested block appearing as a single statement: surface the first element.
// MapBody is usually invoked by callers, which flattens blocks inline.
Map(block.Statements[0], bclRegistry),
IrBreakStatement => new TsExpressionStatement(new TsIdentifier("break")),
danfma added 20 commits April 15, 2026 15:58
- DartParameter gains IsNamed + IsRequired so the printer can emit
  Dart's {…} named parameter block (with optional `required`) in
  addition to the existing required positional and [...] optional
  positional regions.
- DartMethodSignature + DartGetter gain IsOverride; the printer emits
  `@override` before the declaration when set.
- IrToDartClassBridge.BuildCopyWithMethod now produces a real
  implementation: one `{Type? name}` named parameter per field plus a
  body that returns `ClassName(name ?? this.name, …)`. The previous
  `throw UnimplementedError()` stub is gone. == and hashCode are
  tagged IsOverride to silence the Dart analyzer.
- IrToTsOverloadDispatcherBridge picks fast-path names from parameter
  names first (legacy shape) and falls back to type-based suffixes on
  collision; throw message uses the camelCased dispatcher name so the
  error text matches how callers spell the method.

Phase 6 item 4 follow-ups closed on the Dart side.
- IR gains IrSwitchExpression + IrSwitchArm (pattern + optional
  when-clause + result).
- IrExpressionExtractor lowers C# SwitchExpressionSyntax into the new
  nodes, reusing the existing pattern extractor.
- TS bridge maps a switch expression to a self-invoking arrow function
  that binds the scrutinee to `\$s`, walks arms as guarded `if`s, and
  throws on no match — semantic parity with C#'s first-match +
  non-exhaustive runtime error.
- Dart body printer renders natively via Dart 3's switch expression
  syntax (`switch (x) { pattern => result, … }`), no wrapping needed.
- IrBodyCoverageProbe accepts switch expressions so TS bodies
  containing them can still flow through the IR pipeline.

Phase 6 item 2 extended: switch expressions covered. Property /
relational / logical / list patterns remain follow-ups.
- IR gains IrPropertyPattern + IrPropertySubpattern (optional type
  filter + name/pattern pairs + optional designator).
- IrExpressionExtractor lowers RecursivePatternSyntax's property form
  into the new node; positional patterns still surface as
  IrUnsupportedPattern so nothing is silently dropped.
- TS bridge extends BuildPatternTest / MapIsPattern with a property-
  pattern lowering that emits `value instanceof T && value.a === 1`
  style conjunctions and recurses for nested property patterns.
- Dart body printer handles property patterns in both `is` contexts
  (lowered as boolean conjunctions) and switch-expression arms (native
  Dart 3 object pattern syntax `T(member: pattern, …)`).
- IrBodyCoverageProbe deliberately keeps IrSwitchExpression and
  IrPropertyPattern as uncovered so the legacy TS path remains the
  source of truth for samples; enum-member casing and other
  expression-lowering subtleties still need reconciling before the
  probe can flip those shapes on.

Phase 6 item 2 extended further: property patterns covered. Relational /
logical / list / positional patterns remain follow-ups.
- IrNamedTypeSemantics + IrNamedTypeKind model the broad category of a
  named type reference (class, record, struct, interface, numeric/string
  enum, inline wrapper, exception, delegate), the StringEnum value set,
  the primitive an inline wrapper collapses to, and a transpilable flag.
- IrTypeRefMapper.BuildNamedTypeSemantics fills the new Semantics slot
  from the Roslyn symbol at extraction time — backends no longer need
  to rediscover the info.
- IrTypeCheckBuilder branches on Semantics.Kind to emit the right
  runtime guard:
  * StringEnum → exhaustive `=== "a" || === "b" …` check
  * NumericEnum → isInt32
  * Interface → typeof === "object"
  * InlineWrapper → typeof === "string"|"number"|…
  * Class/Record/Struct/Exception (transpilable) → instanceof
  matching the legacy TypeCheckGenerator behavior.

Phase 6 item 7 prerequisite. The IR bridge now emits the same guards
the legacy dispatcher does for every kind of parameter — flipping
RecordClassTransformer to the IR path still blocks on named-argument
preservation in the extractor and on exception-lowering parity.
- New IrArgument(Value, Name?) record wraps each argument. IrCallExpression
  and IrNewExpression now carry IReadOnlyList<IrArgument> so the source-
  side `Name: value` intent survives extraction.
- IrExpressionExtractor.ExtractArgument populates Name from
  ArgumentSyntax.NameColon; every site that used to project
  Extract(a.Expression) now goes through the helper.
- Dart body printer renders an IrArgument with a name as
  `name: value` (Dart's native named-argument syntax). Positional
  arguments lower to the value alone.
- TS expression bridge drops the name and lowers positionally — TS
  has no named-argument syntax at the call site, and reordering plus
  default-filling is handled by callers that care (object-literal
  path for [PlainObject], dispatcher reordering for overload groups).
- IrBodyCoverageProbe walks the new argument shape; IrStatementExtractor
  reassigned-locals scan unwraps IrArgument.Value.
- Record synthesis bridges updated to wrap their call sites in
  IrArgument.

Phase 6 item 7 prerequisite: named-argument preservation now in place.
The remaining blocker on flipping RecordClassTransformer to the IR
dispatcher is exception-lowering parity (throw new
InvalidOperationException → throw new Error).
- IrToTsExpressionBridge.MapNewExpression lowers constructions of
  non-transpilable System.Exception subtypes (InvalidOperationException,
  ArgumentException, …) to `new Error(args)`, matching the legacy
  ObjectCreationHandler fallback.
- IrTypeRefMapper.IsTranspilableType recognizes assembly-wide
  [TranspileAssembly] on the symbol's own containing assembly, so
  user-defined exception subclasses that rely on the assembly-wide
  opt-in keep their generated class identity instead of being folded
  into Error.
- IrExpressionExtractor.NormalizeArguments reorders mixed positional
  + named call / new-expression arguments into the constructor's
  parameter order, filling skipped positions with their explicit
  defaults (enum members, literals, null). IrArgument.Name stays
  populated so Dart can still render named-argument syntax.
- RecordClassTransformer.TryBuildOverloadDispatcherFromIr routes
  fully-covered overload groups through IrToTsOverloadDispatcherBridge;
  legacy OverloadDispatcherBuilder remains the fallback for groups
  that contain [Emit] templates or other shapes not yet in the IR.

Phase 6 item 7 closed on the method path. Constructor-level overload
dispatch (BuildConstructor) still belongs to the legacy builder —
IR modeling for it is the remaining follow-up before the file retires.
- IrConstructorDeclaration.BaseArguments now uses IReadOnlyList<IrArgument>
  so named-argument syntax on `: base(...)` survives extraction.
- IrConstructorExtractor.TryExtractBaseArguments captures each argument
  from the C# base initializer (skipping `: this(...)` chaining which
  has no runtime super call).
- New IrToTsConstructorDispatcherBridge collapses a primary
  constructor + its Overloads into a TS dispatcher shaped like the
  legacy OverloadDispatcherBuilder.BuildConstructor output: overload
  signatures up top, `(...args: unknown[])` dispatcher with arity +
  type-check branches, each branch emitting the matching super(...)
  call (when applicable) then the inlined constructor body.
- RecordClassTransformer.TryBuildConstructorDispatcherFromIr hands
  fully-covered multi-ctor groups to the bridge; legacy builder still
  serves uncovered bodies as fallback.

Phase 6 item 7 fully closed (both method + constructor paths now on
the IR pipeline). Legacy OverloadDispatcherBuilder.cs stays alive as
the [Emit]-template fallback — retiring it depends on the IR bridge
eventually handling templates too.
- New IrWithExpression + IrWithAssignment capture the source and the
  member-name/value pairs of a C# `source with { X = e, Y = e2 }`
  construct.
- IrExpressionExtractor.ExtractWithExpression lowers WithExpressionSyntax
  into the new node, reusing the existing expression extractor for
  each right-hand value.
- TS bridge emits `source.with({ x: e })` (the synthesized with method
  on every transpiled record). The [PlainObject] spread-literal shape
  stays on the legacy ObjectCreationHandler for now — the IR doesn't
  yet carry the source type's [PlainObject]-ness on the expression
  itself, so that case is gated uncovered.
- Dart printer emits `source.copyWith(x: e)`, reusing the named
  parameters IrToDartClassBridge already synthesizes on every record.
- IrBodyCoverageProbe accepts IrWithExpression.

Phase 6 item 3 partially closed. The ObjectCreationHandler stays
alive for cross-assembly [PlainObject] resolution and the
[InlineWrapper].create(...) shortcut — those are the remaining
blockers before the file can retire.
- IrToTsExpressionBridge.MapNewExpression consults
  IrNamedTypeRef.Semantics.Kind and short-circuits
  IrNamedTypeKind.InlineWrapper constructions to `Type.create(args)`,
  matching the legacy ObjectCreationHandler's inline-wrapper shortcut.
- The exception + inline-wrapper branches now share a single
  Semantics switch so the shape is easy to extend when additional
  kinds need special lowering.
- New IrToTsBodyBridgeTests.InlineWrapperNew_LowersToCreateFactory
  pins the shape end-to-end.

Phase 6 item 3 — every shape the legacy ObjectCreationHandler knows
about is now replicated on the IR bridge. The handler itself stays
alive only while the legacy ExpressionTransformer path survives
(uncovered bodies with [Emit] templates and delegate helpers); both
files retire together once the probe flips for those remaining
shapes.
- New IR nodes IrRelationalPattern (Operator + Value) and
  IrLogicalPattern (And / Or / Not with optional right operand).
- IrExpressionExtractor extracts RelationalPatternSyntax,
  BinaryPatternSyntax (and / or), UnaryPatternSyntax (not), and
  ParenthesizedPatternSyntax into the new nodes.
- TS bridge lowers relational patterns to binary comparisons
  (`x > 0`, `x <= 10`), logical patterns to &&, || and !(...),
  and threads both shapes through `is` contexts and switch-
  expression arm conditions.
- Dart body printer handles both forms in boolean `is` conjunctions
  and in native switch-expression patterns. Dart has no `not`
  pattern literal, so switch-pattern arms emitting a `not` fall back
  to a `_` + TODO marker directing callers to rewrite as a when-guard.
- IrBodyCoverageProbe deliberately keeps IrWithExpression uncovered
  alongside IrSwitchExpression / IrPropertyPattern so the legacy
  path stays canonical for sample parity.

Phase 6 item 2 extends further: only list + positional patterns
remain on the pattern-matching roadmap.
- IrMemberOrigin gains IsEnumMember so the TS bridge can preserve
  the source PascalCase for enum references (`Status.Backlog`)
  instead of applying the camelCase policy that governs every other
  static member access.
- MapSwitchExpression collapses into a nested ternary chain when the
  arm list ends with a bare `_ => value` fallback and every earlier
  pattern lowers to a plain boolean test — matching the legacy
  conditional output byte-for-byte on every affected sample. Non-
  exhaustive matches and binding patterns keep the IIFE + trailing
  throw shape.
- IrBodyCoverageProbe now treats IrSwitchExpression and
  IrPropertyPattern as covered. IrWithExpression stays uncovered
  until the IR tracks plain-object-ness on the source expression.

Every regenerated sample (TS + Dart) still matches the legacy output
byte-for-byte; bun suites across targets/js/* remain green.
- IrWithExpression gains IsPlainObjectSource, populated at extraction
  time from the C# source type. The TS bridge consults the flag to
  pick between `source.with({ k: v })` (class-backed records) and
  `{ ...source, k: v }` (plain-object shapes that have no with method
  on a prototype).
- IrExpressionExtractor.ExtractMemberAccess elides `Nullable<T>.Value`
  accesses — `patch.Priority.Value` now lowers to `patch.priority`
  instead of dereferencing a `.value` property that doesn't exist at
  runtime. This matches the legacy ExpressionTransformer behavior.
- IrBodyCoverageProbe now treats IrWithExpression as covered; the
  probe is fully green for every expression-level C# construct the
  samples exercise.

Every regenerated sample (TS + Dart) still matches legacy output
byte-for-byte; bun suites remain green across targets/js/*.
- TypeTransformer.TryEmitModuleViaIr handles the common case of an
  [ExportedAsModule] static class: extracts every public method via
  IrModuleFunctionExtractor, gates on IrBodyCoverageProbe, and emits
  the resulting functions through IrToTsModuleBridge.
- When the class also has a single [ModuleEntryPoint] method (with
  no [ExportVarFromBody] promotion and a valid signature), its body
  is extracted via IrStatementExtractor, gated by the probe, and
  unwrapped as TsTopLevelStatements after the regular functions —
  matching the legacy ModuleTransformer.UnwrapEntryPoint shape.
- Classes with classic `this T` extension methods, C# 14
  extension(R r) blocks, extension properties, or [ExportVarFromBody]
  entry points still fall through to the legacy ModuleTransformer
  where the TS-specific diagnostics and export-promotion logic live.

Every regenerated sample (TS + Dart) still matches legacy output
byte-for-byte; bun suites remain green across targets/js/*.
- Classic C# extension methods (`public static int Foo(this int x)`)
  flow through the same IR path as ordinary static methods — Roslyn
  already exposes the receiver as the first parameter, so the
  extractor + bridge emit the idiomatic `export function foo(x, ...)`
  shape.
- Remove the IsExtensionMethod bail-out from TypeTransformer
  .TryEmitModuleViaIr; classes that mix plain statics with classic
  extensions now route entirely through the IR bridge.
- New unit test pins the emitted shape — no sample exercises classic
  extension methods, so the unit test is the acceptance criterion.

ModuleTransformer legacy still covers C# 14 extension(R r) blocks,
extension properties, and [ModuleEntryPoint] + [ExportVarFromBody]
combinations — those remain the path forward.
- New IR nodes IrListPattern (Elements + optional SliceIndex) and
  IrPositionalPattern (optional Type + Elements + optional designator).
- IrExpressionExtractor handles ListPatternSyntax (with SlicePatternSyntax
  stripped out and its position recorded) and RecursivePatternSyntax's
  PositionalPatternClause.
- TS bridge lowers list patterns to length-gated conjunctions of per-
  index tests; slices keep the `>=` gate and reverse-index the tail
  via `arr.length - k`. Positional patterns lower to `value[i]`-
  indexed conjunctions, with an optional `instanceof` type filter up
  front.
- Dart renders both natively inside switch-expression arms
  (`[p0, ...]`, `T(p0, p1)`); outside switch contexts the body
  printer emits a TODO marker — Dart only allows the destructuring
  form in switch arms.
- IrBodyCoverageProbe accepts both shapes (positional requires no
  designator, since expression-scope bindings aren't modeled yet).

Phase 6 item 2 (pattern matching) is now fully covered: constant,
type, var, discard, property, relational, logical, list, positional.
Slice sub-pattern bindings (`.. var tail`) remain the last shape.
- IrModuleFunctionExtractor walks ExtensionBlockDeclaration syntax
  nodes on the containing static class and emits one IrModuleFunction
  per member. Methods prepend the block's receiver to their parameter
  list; extension properties collapse to getter-shaped functions with
  only the receiver in the signature.
- TypeTransformer.TryEmitModuleViaIr no longer bails on extension
  blocks — the IR path now handles them end to end. Classic-style
  extension properties (IPropertySymbol with parameters) still keep
  the legacy route; that's the last remaining shape.

Phase 6 item 6 (TS-side module migration) nearly closed — only
classic extension properties and [ExportVarFromBody]-promoted entry
points remain on the legacy ModuleTransformer path.
…nd [ExportVarFromBody]

- IrModuleFunctionExtractor now folds classic-style extension
  properties (`public static T Foo(this X x) { get; }`) into
  getter-shaped IrModuleFunctions with the receiver prepended, so
  TypeTransformer.TryEmitModuleViaIr can route them through the IR
  bridge instead of the legacy ModuleTransformer.
- IrParameter gains HasExplicitType + IrNamedTypeSemantics gains
  IsNoEmit. The TS lambda bridge drops the type annotation when the
  parameter's inferred type is tagged `[NoEmit]` — matching the
  legacy LambdaHandler so Hono-style ambient types (IHonoContext)
  keep their call-site type inference instead of surfacing a
  generated identifier.
- TryEmitModuleViaIr handles [ExportVarFromBody] on the entry point:
  AsDefault + InPlace still raises the legacy diagnostic; in-place
  promotion folds into the TsVariableDeclaration; out-of-place
  promotion emits a trailing TsModuleExport. Missing-local-name
  surfaces a diagnostic too. Multiple [ModuleEntryPoint]s still
  bail to legacy so the diagnostic isn't duplicated.
- docs/compiler-refactor-plan.md gains a "Future refactors (parked)"
  section capturing the pattern-match → guard-helpers idea for a
  later pass.
- IrListPattern gains a SlicePattern slot alongside SliceIndex so
  `.. var tail`, `.. [0, _]`, and other inner patterns on the `..`
  element survive extraction.
- Dart switch-pattern printer renders the slice as `...name` for a
  var-binding or `...pattern` for a nested pattern — matching Dart 3
  list pattern syntax.
- IrBodyCoverageProbe requires the slice sub-pattern itself to pass
  before accepting the whole list pattern.
- IrExpressionExtractor.ExtractInvocation detects a method's
  `[Emit("…")]` template and produces an IrTemplateExpression
  instead of an IrCallExpression. Instance-method receivers are
  synthesized as the first template argument so backends don't have
  to rediscover the member-access elision.
- TS bridge maps IrTemplateExpression to the existing TsTemplate
  node, preserving the JsTemplateExpander-compatible output the
  legacy InvocationHandler emits.
- Dart body printer renders a TODO marker for [Emit] templates —
  they're JS-specific by definition; real usage should gate them
  with `[Ignore(TargetLanguage.Dart)]` or provide a Dart-specific
  override.
- IrBodyCoverageProbe accepts IrTemplateExpression. Combined with
  the earlier dispatchers + with-expressions + pattern coverage,
  every shape the samples exercise now flows through the IR path;
  the legacy OverloadDispatcherBuilder stays only as a defensive
  fallback.

Phase 6 item 2 tail closed: list patterns now carry the slice sub-
pattern (`.. var tail` / `.. [0, _]`), rendered natively in Dart 3
list patterns as `...name` / `...pattern`. Phase 6 closed in all but
name — the final retirement of legacy TS transformers waits only on
a broader sample matrix confirming the probe covers every body.
…aping

- IrExpressionExtractor now handles GenericNameSyntax
  (`OperationResult<Issue>.Ok(...)`) as a first-class identifier,
  reusing the same type-reference / implicit-this / static-qualifier
  rewrites the non-generic path applies. Previously it fell through
  to IrUnsupportedExpression, blocking every overload body that used
  a generic receiver.
- IrMemberOrigin gains IsInlineWrapperMember. When the resolved
  method belongs to an [InlineWrapper] type (lowered as a namespace
  with exported functions), the TS bridge switches to the escaping
  camelCase variant so reserved-name methods render consistently
  between the declaration (`namespace UserId { export function new_()
  {} }`) and the call site (`IssueId.new_()`) — matching the legacy
  MemberAccessHandler behavior.
- docs/compiler-refactor-plan.md captures the staged plan for
  retiring the legacy TS transformers: the IR pipeline already is
  the production truth, but a sweep of BCL-rewrite gaps plus a
  golden-test flip is the cost of actually deleting the files.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants