Open
Conversation
) 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.
…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.
There was a problem hiding this comment.
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.DartCLI target plus a Flutter sample consumer undertargets/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")), |
- 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.