/** * Lowering pass — TypeNode graph → Declaration IR. * * This module converts the emitter's type graph (TypeNode/PropertyNode) * into the language-agnostic Declaration IR (FileDecl/TypeDecl). * * The lowering is shared across all 5 target languages. Per-language * emitter functions consume the FileDecl tree and emit code. * * Key responsibilities: * - Classify every property into a PropertyCategory * - Build load/save method specifications * - Resolve polymorphic dispatch * - Resolve collection helpers * - Resolve factory methods via the Expression IR * - Compute file-level imports */ import { TypeNode, PropertyNode } from "./ast.js"; import { TypeRegistry } from "./expansion.js"; import { PropertyCategory, FileDecl, TypeDecl, PolymorphicDispatchDecl } from "./declarations.js"; /** * Lower a base TypeNode (and all its children) into a FileDecl. * * This is the main entry point for the lowering pass. It produces a complete * FileDecl containing one or more TypeDecls (parent + children for polymorphic types). * * The result is fully language-agnostic — per-language emitters handle rendering. * * @param node - The base TypeNode (must not have a parent — i.e., `node.base === null`) * @param registry - TypeRegistry for resolving type references * @param polymorphicTypeNames - Set of type names that are polymorphic bases * @param serializationClosure - Set of simple names in the serialization closure * of some `@serializable` root; types outside it emit no load/save. When * omitted, all types serialize (legacy behavior for direct callers/tests). */ export declare function lowerFile(node: TypeNode, registry: TypeRegistry, polymorphicTypeNames?: Set, serializationClosure?: Set): FileDecl; /** * Collect all polymorphic type names from a set of nodes. */ export declare function collectPolymorphicTypeNames(rootNode: TypeNode, registry: TypeRegistry): Set; /** * Compute the serialization closure across a set of root nodes. * * Serialization is opt-in: the roots are the models marked `@serializable`, * plus every model that appears as a seam operation parameter or return type * (those are serialization boundaries the emitter's own vector/conformance * harness loads and saves). From each root the closure grows by: * (a) transitive property reachability — every referenced model type is * pulled in so nested shapes can load/save (including dictionary/`Record` * value models, whose element type is carried out-of-band in * `dictValueType`), * (b) discriminated variant expansion — every child of a genuine * `@discriminator` base is pulled in so polymorphic load/save stays total, * and * (c) base-chain inheritance — a serialized derived model reaches its base(s), * whose load/save its own generated methods delegate to. * * Plain `extends` subclasses of a NON-discriminated base are NOT auto-pulled: * `childTypes` holds every derived model, so expansion is gated on the node * actually being a discriminated base (`discriminator && childTypes.length`), * mirroring {@link TypeNode.retrievePolymorphicTypes} and * {@link collectPolymorphicTypeNames}. * * A field withheld from BOTH directions (`@sensitive` with no arguments, i.e. * least-privilege) carries no reachability: the type it references is only * pulled in if some other, non-fully-withheld path reaches it. Field-level * per-direction withholding does not remove a type from the closure — it is a * property-level omission handled during emission, not a closure hole, so * polymorphic load stays total. The closure is deliberately direction-agnostic: * a participating type receives the full load+save capability (never half), so * the union of load- and save-reachability is the correct membership set. * * The returned set contains the simple names of every type that participates in * serialization, matching the emitter-wide simple-name gating convention * (`collectPolymorphicTypeNames`). Types absent from it emit no load/save. The * walk is cycle-safe (the closure set doubles as the visited set) and resolves * referenced types via `prop.type` first, falling back to the registry by name * so reachability survives the build-time `.type` cycle-prevention gap (a * repeated element type carries `.type` only on its first occurrence). */ export declare function computeSerializationClosure(nodes: TypeNode[], registry: TypeRegistry): Set; /** * Lower a single TypeNode into a TypeDecl. */ export declare function lowerType(node: TypeNode, registry: TypeRegistry, polymorphicTypeNames: Set, serializationClosure?: Set): TypeDecl; /** * Classify a property into one of 5 categories. * This is the fundamental decision that drives ALL code generation. * * Decision tree: * isDict → "dict" * isCollection && isScalar → "collection_scalar" * isCollection && !isScalar → "collection_complex" * isScalar → "scalar" * !isScalar → "complex" */ export declare function classifyProperty(prop: PropertyNode, polymorphicTypeNames: Set): PropertyCategory; /** * Lower polymorphic dispatch specification from TypeNode. * Returns null if the type is not polymorphic (no discriminator or no children). * * Exported so the behavioral `@dispatch` seam rail (src/ir/callable.ts) can reuse * the SAME discriminator lowering that drives the shape `Load` switch — one decl, * two twins (shape construction + behavior resolution). */ export declare function lowerPolymorphicDispatch(node: TypeNode): PolymorphicDispatchDecl | null; /** * A provider whose wire name is claimed by more than one canonical field. * * Because `fromWire(provider)` inverts the wire map (wire name → canonical * field), two canonical fields mapping to the same provider wire name cannot be * disambiguated on the way back in. That is an author error, surfaced as the * `typra-emitter-wire-collision` diagnostic (see resolveModel in ast.ts, which * has access to the compiler `Program` needed to report it — lowering runs once * per target language and would otherwise report the same collision N times). */ export interface WireCollision { /** Provider identifier the collision occurs for (e.g. "openai"). */ provider: string; /** The shared provider-native wire name. */ wireName: string; /** Canonical field names that collide on `wireName`, in declaration order. */ fields: string[]; } /** * Detect wire-name collisions in a set of field → provider-name mappings. * * Iterates `mappings` (and each field's providers) in declaration order so the * reported collisions are deterministic. Returns one {@link WireCollision} per * `(provider, wireName)` pair claimed by two or more canonical fields. */ export declare function detectWireCollisions(mappings: { fieldName: string; wireNames: Record; }[]): WireCollision[];