diff --git a/ROADMAP.md b/ROADMAP.md index 370b6683f93..67e603fde19 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -107,8 +107,8 @@ type Bit = True | False // |[Bit]| = 2 type Byte = Tuple // |[Byte]| = 2^8 // Level 2: Named compositions (opaque namespaces) -// Int is List> — a namespace, not a compiler-known concept -// String is List — another namespace +// Int is Interpret — a namespace, not a compiler-known concept +// String is List — another namespace // Level 3: Algebraic structures // Optionality: T? is cardinality 0..1 on the binding site @@ -167,6 +167,207 @@ definitions; the compiler reads structure from those declarations; names are opaque. This is sufficient for the thesis without requiring the full algebraic foundation up front. +### Compositional Basis + +The thesis says the compiler processes graph structure, not names. But +what IS the irreducible structure? This section answers: what is +primitive, what are the structural constructors, and what is derived. + +**1. Compiler-model primitives (what the compiler operates on):** + +- `Node` as the universal carrier +- Product composition (Conj) — record/struct/tuple +- Coproduct composition (Disj) — enum/variant/sum +- Cardinality on bindings (Required, CardOptional) +- Generic slot composition (`` — Phase 3) +- Recursion / self-reference (SCC-detected cycle metadata) +- Collection constructors (List, Set, Map — type-level, not name-level) + +**2. Value/type-algebra primitives (what the kernel knows):** + +Currently honest state: `Int`, `String`, `Bool`, `Float`, `Unit` are +kernel primitives. The compiler treats them as special leaves via +`is_kernel_type`. The algebraic vision says they should be declared +compositions (`Int` = named namespace over `Interpret`), +but the compiler does not yet derive their properties from structure. +This is an acknowledged intermediate state, not a +claimed-but-unimplemented derivation. + +The kernel will shrink as the algebraic model matures: +- Phase 3+: kernel types get real `.dag` declarations +- Phase 5+: `is_kernel_type` dissolves into structural property reads +- Beyond: bit-graph model; `Int` genuinely derived from `Bit` + +**3. Structural constructors (how compositions are built):** + +| Constructor | Builds | Compiler representation | +|-------------|--------|------------------------| +| Product | Records, tuples, structs | `connective: Conj`, named children | +| Coproduct | Enums, variants, sums | `connective: Disj`, named children | +| Cardinality | Presence/absence on bindings | `return_cardinality: CardOptional` | +| Parameterization | Generic types (`List`) | Slot substitution before inference | +| Recursion | Self-referential types | SCC cycle metadata on Node | +| Collection | Indexed structures | `List`, `Set`, `Map` type constructors | + +**4. What is derived (named namespaces over the above):** + +| Derived concept | Composition | +|----------------|-------------| +| Tuple | Unnamed product (Conj, positional children) | +| Record / struct | Named product (Conj, named children) | +| Enum | Named coproduct (Disj, variant children) | +| Optional binding | Cardinality 0..1 on the binding site | +| Type alias | Namespace over an existing composition | +| Function | `Callable { params: List, return: Node }` | +| Service | Record of operations + transport/runtime metadata | + +Names are opaque namespaces over structure. The compiler's semantic +basis is not the set of named user types; it is the set of structural +constructors and binding/cardinality facts from which named declarations +are composed. + +### Composition Stack + +The algebraic type vision (above) covers Layers 0-3: logic, machine +primitives, named numeric/text types, and collection algebras. These +are already modeled in `dsl/std/` (`logic.dag`, `bit.dag`, +`integer.dag`, `float.dag`, `types.dag`). + +Between collections and any domain application — including the compiler +itself — there is a layer of **structural compositions** that are +general-purpose but currently only exist implicitly inside the +compiler's ad-hoc type definitions. The compiler's `Node` is a tree; +its `ModuleGraph` is a DAG; its `SourceSpan` is a span over a +sequence. Any program that processes trees, dependency graphs, or +positional data needs the same structures. + +The thesis says the compiler is a generic graph processor. If the +compiler needs **structural carrier types** that aren't in std, that's +a gap in std — not a special case in the compiler. (Domain-specific +records like `TypeFacts`, `EmitGraphInfo`, `Diagnostic` are still +compiler semantics; the point is that the **shapes** those records +inhabit should come from shared vocabulary.) + +``` +Layer 0: Logic + Classical = True | False (std/logic.dag) + +Layer 1: Machine + Bit = Classical where width(1) (std/bit.dag) + Vector (fixed-width bitvector) + Byte = Vector<8, Bit> + Word32 = Vector<32, Bit> + Word64 = Vector<64, Bit> + Int32 = Interpret + + Note: fixed-width machine data uses Vector/bitvector, not List. + List is a variable-length collection (Layer 3). This preserves + the machine/container distinction the roadmap warns about. + +Layer 2: Named compositions + Int = Int64 = Interpret (std/integer.dag) + Nat = UInt64 = Interpret + Char = Int where range(0, 1114111) (std/types.dag) + String = List (std/string_type.dag) + +Layer 3: Collection algebras + List = Σ n. Fin(n) → A (std/types.dag) + Set = A → Bool + Map = K → (1 + V) + +Layer 4: Structural compositions *** MISSING FROM STD *** + Span = { start: I, end: I } where start <= end + LabeledTree = Tree<{ name: String, value: A }> + Tree = { value: A, children: List> } + DAG = { nodes: Map, + edges: Set> } + where acyclic(edges) + Annotated = { value: A, facts: F } + + Derived conveniences (not new primitives): + Pair = ergonomic alias for Conj with two fields + Either = ergonomic alias for Disj with two variants + SourceSpan = Span + ByteRange = Span + TimeRange = Span` | Recursive hierarchical structure | Within-module syntax nodes | File systems, org charts, UI components | +| `DAG` | Directed acyclic graph (nodes + edges + acyclicity) | Module graph, dependency order | Build systems, task scheduling, workflows | +| `Annotated` | Value enriched with computed facts | TypedModule = Annotated | Any pipeline that accumulates metadata | +| `LabeledTree` | Named tree (Tree combinator) | AST node structure | Any labeled hierarchy | + +**The compiler as an instance of std vocabulary:** + +Within-module syntax nodes are trees; the module graph is a DAG. +`Node` is the universal carrier at the compiler level, but its +within-module shape is `LabeledTree`. The pipeline +enriches this with computed facts via `Annotated`: + +``` +ASTNode = Annotated, NodeFacts> +Module = { name: String, tree: ASTNode, imports: List } +ModuleGraph = Annotated, TopologyFacts> +TypedModule = Annotated +ResolvedGraph = DAG +``` + +The split is: `LabeledTree` owns the recursive tree carrier (`name`, +`children`), `NodePayload` owns the non-structural per-node fields +(`span`, `params`, `uses`, `transport`, `expr_data`, etc.), and +`NodeFacts` owns resolved/inferred/computed metadata. That lines up +with the pipeline story: resolve adds resolved facts, infer adds type +facts, emit reads facts but does not re-resolve them. `connective` and +`return_cardinality` remain explicit structural annotations in the +compiler's current `Node` model; Phase 5 work decides whether they +collapse into the carrier layer or separate binding/edge annotations. + +**The fact composition contract as `Annotated`:** + +Each pipeline stage produces `Annotated`: + +``` +resolve: Module → Annotated +infer: Annotated → Annotated +emit: Annotated → List +``` + +No stage re-derives a fact from a lower level. The type system makes +the contract visible: if emit needs a `TypeFact`, the signature +guarantees it was composed during infer. + +**P5.11 is the convergence prerequisite.** Once `ExprData` children +dissolve into `node.children`, `Node` cleanly maps to +`LabeledTree`. The ~5000 lines of custom `ExprData` +walkers reduce to `Tree.map(fn)`. P5.11 moves the compiler's AST +from ad-hoc internals into std vocabulary. + +**Phase placement:** With P3.8 (recursive generics) landed, `Tree` +and `Annotated` are expressible in `.dag` today. These types +should be declared in `dsl/std/` as soon as generics are stable +(Phase 3), not deferred to post-Phase 5. The full compiler refactor +to use them as instances is later work, but the types themselves +should exist early so intermediate steps move toward the design. + +**Type Layer Downgrade metric.** The composition stack provides an +objective architectural-debt metric. When a pipeline stage re-derives +a fact by looking up a name, it is performing a **layer downgrade** — +dropping from a higher layer (annotated structure) back to a lower +layer (name string). Facts should only compose upward. Layer +downgrades are the precise measure of the compositional violations +identified in the C1-C5 work. + ### Collection Denotational Model Collections are not one algebra — they are a **family of related @@ -2228,6 +2429,264 @@ match succeeds. - Each convergence step survives re-bootstrap and fixed-point verification - The compiler is in a clean place to start real L2 work +### Phase 5 Parallel: Compositional Pipeline (Performance through Fact Composition) + +**Motivation:** The compiler works, but its pipeline has compensating +re-derivation where later stages re-derive facts that earlier stages +already computed. The staged pipeline (resolve → infer → emit) is +correct; the problem is that stages fail to compose facts at their +definition site, forcing downstream stages to re-resolve through +environments that should be opaque to them. Self-compile takes +~20 minutes. The root cause is not missing optimization — it is broken +fact composition. + +**Root-cause analysis (2026-03-26):** Inference fabricates name-only +references at 5 sites where the structural fact is already available in +the TypeEnv. Emit then calls `resolve_scrutinee_type_node` 12+ times to +follow those name references back to the structural definitions. Emit +rebuilds `InferScope` to perform these lookups. Service expansion uses a +5-pass fixpoint where topo order enables single-pass computation. All of +these are **layer downgrades** (see Composition Stack) — facts dropping +from annotated structure back to name strings. + +**Principle (from the thesis):** "If the graph is suboptimal, a fact is +missing. Fix: improve the domain model." The fix is not a post-hoc +resolution pass — that would add a compensating re-derivation stage. +The fix is: compose the structural fact at the site where its inputs +are first available. `.inferred` is the structurally authoritative +resolved type because inference uses `binding.resolved`, not the name. +("Structurally authoritative" means the type is resolved through the +environment — not that it must be eagerly inlined into a deep copy. +Graph sharing and canonical resolved nodes are fine; what matters is +that the authority is structural, not name-based.) + +#### The Fact Composition Contract + +The pipeline has three fact-producing boundaries. Each boundary produces +a contract — a guarantee about what facts are available downstream. + +**Resolve contract:** Every type reference in the graph is resolved to +its structural definition. `TypeBinding.resolved` carries the full +structural node (connective, children, cardinality). Names flow through +for diagnostics, but the structural composition is the authority. + +**Infer contract:** Every expression node's `.inferred` carries the +structurally authoritative resolved type of that expression — not a +bare name, not an unresolved alias. This falls out naturally: when +inference sets `.inferred`, it uses `binding.resolved` (the structural +fact from the TypeEnv), not `leaf_node(name: binding.name)`. Graph +sharing is fine; canonical resolved nodes are fine; what must not +happen is storing a name that forces downstream re-resolution. + +**Emit contract:** Pure translation. Emit reads `.inferred` to get +structural types, reads `EmitGraphInfo` for rendering metadata, and +translates to target-language text. Emit never calls inference functions, +never resolves through TypeEnv, never rebuilds scope for type lookups. + +#### Why the infer contract is currently broken (5 sites) + +The TypeEnv already carries resolved structural types. But inference +fabricates name-only references at these sites instead of using the +structural fact: + +| Site | File:Line | What happens | Fix | Status | +|------|-----------|-------------|-----|--------| +| Variant constructor locals | `04_infer.dag:2217`, `04_items.dag:130` | `resolved: leaf_node(name: binding.name)` — bare parent enum name | Use `binding.resolved` / `item` — the structural enum definition | **Done** (C2.1) | +| Optional Some/None | `04_infer.dag:2244` | `resolved: leaf_node(name: "Optional")` — hardcoded name | Delete explicit overrides — general variant mechanism (C2.1) already handles Optional via `kernel_optional` coproduct in env | **Done** (C2.2) | +| Type alias resolution | `04_resolve.dag:353` | `resolve_node_bounded` returns alias_node wrapper without following `.inferred` | Follow ONE level of `.inferred` for alias nodes (leaf + .inferred, no structure). O(1), safe with topo ordering. | **Done** (C2.3) | +| Func call return type | `04_infer.dag:1065` | `s.inferred` from sig used directly | **Already fixed by C2.3** — the return type flows through `resolve_item_types` → `resolve_node_bounded` (now with alias following) before reaching `DeclaredFuncSig` | **Done** (via C2.3) | +| ResolvedFuncSig | `04_sigs.dag:112` | `dsig.inferred.value` unwrapped without resolution | **Already fixed by C2.3** — same resolution chain | **Done** (via C2.3) | + +**Implementation findings (2026-03-26):** + +- C2.3 was the key fix — not in `build_type_env` (TypeEnv incomplete + there) but in `resolve_node_bounded` (TypeEnv complete, topo order + guarantees target already resolved). The alias_node was a one-level + indirection that forced emit to re-resolve. +- C2.4 (func sig) turned out to be already fixed by C2.3: the return + type flows through `resolve_item_types` → `resolve_optional_node` → + `resolve_node_bounded` (now with alias following). +- C2.2 (Optional) turned out to be redundant with C2.1: the explicit + `leaf_node(name: "Optional")` overrides were undoing C2.1's fix. + Deleting them lets the general coproduct mechanism handle Optional. + +**Key observation:** None of these required a new pass. Each was a site +where the structural fact existed but was discarded in favor of a name. + +#### C1: Node vocabulary into core + +Node constructors and structural predicates currently live in +`04_types.dag` but are imported by `02_parse.dag`, `03_resolve.dag`, +and all emit files. They read/write Node structural fields (connective, +cardinality) and need no environment — they are Node vocabulary, not +stage-4 concepts. The parser importing from `04_types` is a dependency +inversion. + +| ID | Item | Scope | Notes | +|----|------|-------|-------| +| C1.1 | Move timeless node constructors to `00_core.dag` | **Done** | `leaf_node`, `with_optional_cardinality`, `with_required_cardinality`, `no_span` | +| C1.2 | Move structural predicates to `00_core.dag` | **Done** | `node_is_product`, `node_is_coproduct`, `node_has_structure` | +| C1.3 | Update import statements | **Done** | 14 .dag files updated | + +**What stays in `04_types.dag`:** Three categories: + +1. *Name-checking predicates* (`node_is_container`, `node_is_map`, + `node_is_optional` — reads `n.name` or encodes cardinality policy). + L1 domain knowledge; stays until dissolution. +2. *Bridge-era policy helpers* (`rt_type`, `error_type_node`, + `child_inferred_or_name`, `node_is_leaf`). These encode + stage-boundary semantics (what to return on error, how to fall + back). Moving them into core would freeze transitional policy into + the deepest layer. +3. *Domain constructors* (`map_node`, `tuple_node`, `container_node`, + `callable_node`). Hardcode names like "Map", "Tuple". L1 domain + knowledge. +4. *Type comparison and normalization* (`node_type_compatible`, + `normalize_access_type_node`, `normalize_type_name`). Inference- + specific logic. + +**Gate:** `02_parse.dag` has zero imports from `04_*` modules. + +#### C2: Fix fact composition at definition sites + +Strengthen the infer contract so `.inferred` is always structural. No +new Node fields. No post-hoc resolution pass. Fix the 5 sites where +inference fabricates name-only references instead of using the +structural fact from the environment. + +| ID | Item | Status | Notes | +|----|------|--------|-------| +| C2.1 | Variant constructor locals | **Done** | `binding.resolved` / `item` instead of `leaf_node(name)`. Two sites: `04_infer.dag:2217`, `04_items.dag:130` | +| C2.2 | Optional Some/None locals | **Done** | Deleted explicit overrides; general coproduct mechanism handles it | +| C2.3 | Alias resolution in resolve_node_bounded | **Done** | Follow ONE level of `.inferred` for alias nodes. `04_resolve.dag:353` | +| C2.4 | ResolvedFuncSig construction | **Done** (via C2.3) | Return type already flows through resolve_node_bounded | +| C2.5 | Delete `resolve_scrutinee_type_node` from emit | **Remaining** | 12 call sites in `05_emit_rust.dag`. After C2.1-C2.4, `.inferred` is structurally authoritative — emit should read it directly. Per-site migration needed; each `resolve_scrutinee_type_node(env: scope.type_env, n: rt)` becomes just `rt`. See implementation notes below. | +| C2.6 | Delete `infer_record_lit` from emit | **Remaining** | 1 call site in `emit_record_lit_full`. Record literal types should be fully resolved during infer. | +| C2.7 | Simplify `type_needs_rc` | **Remaining** | `05_emit_rust.dag:1584`. Remove TypeEnv parameter; works on resolved nodes directly. Depends on C2.5. | + +**C2.5 implementation notes (for next worker):** + +The 12 `resolve_scrutinee_type_node` call sites in `05_emit_rust.dag` +follow this pattern: read `.inferred.node` (a type that might be an +alias), then call `resolve_scrutinee_type_node(env: scope.type_env, n: rt)` +to follow the alias to the structural target. + +After C2.1-C2.4, `.inferred.node` should already be the structural +target (aliases followed by resolve_node_bounded, variant locals +carry structural parent). Each call site becomes: + +``` +// Before: +let resolved = resolve_scrutinee_type_node(env: scope.type_env, n: rt) +// After: +let resolved = rt +``` + +Call sites (05_emit_rust.dag): L1604 (`type_needs_rc_seen`), L1615 +(`rust_map_value_type`), L1838/L1859 (`emit_typed_call_expr`), L2188 +(`MethodMap` optional check), L2332 (`MethodSortBy`), L2568/L2571 +(`is_already_optional` ExprVar), L2593/L2599/L2605/L2612 +(`is_already_optional` fallbacks). + +**Risk:** Each replacement must be semantically equivalent. Test after +each site or in small batches. The `is_already_optional` function +(lines 2560-2615) has the most complex logic and should be simplified +as a whole rather than site-by-site. + +After all 12 sites are migrated: remove `import v2.compiler.infer_lookup +{ resolve_scrutinee_type_node }` from `05_emit_rust.dag`. + +**Invariant alignment:** +- No duplicate representations: `.inferred` is the single authority for + the structurally authoritative resolved type. No separate + `resolved_type` field. +- No compensating re-derivation: facts composed at definition site, not + in a post-hoc walk or downstream re-resolution. +- Correctness by construction: impossible for emit to receive an + unresolved alias — the composition guarantees structural authority. +- EmitGraphInfo carries rendering metadata (presentation facts), not + re-derived type authority. The distinction: "how to render this type + in Rust" is emit's job; "what type is this expression" is not. + +**Gate:** `05_emit*.dag` have zero imports from `04_infer.dag`, +`04_lookup.dag`, `04_env.dag` (except type definitions). + +#### C3: Scope elimination (falls out from C2) + +`InferScope` is reconstructed in emit to access `TypeEnv` for type +resolution and `scope.locals` for variable lookups. After C2, TypeEnv +is no longer needed. The remaining `scope.locals` usage +(`is_already_optional` at `05_emit_rust.dag:2570`) checks variable +cardinality, which is available on the variable node's `.inferred`. + +| ID | Item | Scope | Notes | +|----|------|-------|-------| +| C3.1 | Audit remaining scope usage | Analysis | After C2, classify every `scope.type_env` read — expected to be dead | +| C3.2 | Move `recursive_type_set` to `EmitGraphInfo` | 1 usage at `05_emit_rust.dag:671` | Only remaining `scope.type_env` read in emit | +| C3.3 | Stop populating `type_env` in emit scope | `05_emit.dag:266` | `module_emit_scope` uses empty TypeEnv | + +**Gate:** No emit file reads `scope.type_env`. + +#### C4: Single-pass transitive computations + +`expand_transitive_services` runs 5 fixed iterations after inference. +This is a compensating mechanism for not using the topological order +that resolve already computed. A single topo-order fold computes the +transitive closure by construction — when you process a function, all +its callees are already finalized. + +| ID | Item | Scope | Notes | +|----|------|-------|-------| +| C4.1 | Early convergence for service expansion | **Done** | `04_service.dag`: exits early when total service count is stable between passes. For the current `ItemInfo` shape this is equivalent to registry stability because service sets only grow by deduped union. Most codebases converge in 1-2 passes instead of always running 5. Full topo-order fold with SCC condensation deferred to when Layer 4 `DAG` types exist. | +| C4.2 | Audit for other fixpoint iterations | Pipeline-wide | Are there other compensating mechanisms for missing topo-order composition? | + +**Invariant alignment:** Correctness by construction — topo order over +the condensed DAG guarantees completeness. Mutual dependencies are +handled by SCC condensation, not by iterating to fixpoint. + +**Current gate:** service expansion no longer relies on a hardcoded +pass count; it halts when the registry stabilizes. + +**Future gate:** No fixpoint iteration in the pipeline. + +#### C5: Timing instrumentation + +| ID | Item | Scope | Notes | +|----|------|-------|-------| +| C5.1 | Add `Instant`-based timing to stage0 `compile.rs` | **Done** | Per-phase timing on all paths including early-return errors | +| C5.2 | Measure after each fix lands | Ongoing | Requires stage0 regeneration to see .dag change impact | + +**Sequencing:** C5.1 first (baseline). C1 is mechanical, independent. +C2.1-C2.4 fix fact composition (can be done incrementally, one site at +a time). C2.5-C2.7 delete the re-derivation from emit (depends on +C2.1-C2.4). C3 falls out from C2. C4 is independent. + +Recommended order: C5.1 → C1 → C2.1-C2.4 → C4.1 → C2.5-C2.7 → C3 + +This work runs as a parallel lane alongside L1/L2 dissolution. The +diagnostic ratchet (0 diagnostics) and fixed-point test are the quality +gates — no change may regress them. + +#### Relationship to other design tracks + +**L1 dissolution:** C2 accelerates L1. When `.inferred` is structural, +inference no longer branches on type names for type resolution. The +name-checking predicates (`node_is_container`, `node_is_map`) remain +as L1 bridges but lose their primary consumer (emit's type resolution). + +**Language extdeps:** C2 clears the path for shared emit. When emit +reads structural facts directly instead of re-resolving through TypeEnv, +the emit spine becomes language-agnostic — target-language rendering +decisions (Rust `Box`, Python `Optional[T]`) are the only remaining +language-specific logic. This is the prerequisite for +`dsl/extdeps/languages/` (Phase 4/M6). + +**Algebraic type vision:** C2 enforces that `.inferred` is a structural +composition — the same principle that the algebraic vision applies to +types themselves (Bit → compositions → types). When types compose from +the fundamental unit and `.inferred` carries that composition, the +compiler processes structure end-to-end. + ### Beyond Phase 5: Bit-Graph Model The full algebraic vision — machine primitives grounded in diff --git a/src/v2/00_core.dag b/src/v2/00_core.dag index d10bb6af394..8b904534e49 100644 --- a/src/v2/00_core.dag +++ b/src/v2/00_core.dag @@ -989,3 +989,39 @@ fn is_module_node(n: Node) -> Bool { fn is_token_node(n: Node) -> Bool { n.properties |> any(p => p.name == "is_token") } + +// ========================================================================= +// Node vocabulary (structural constructors and predicates) +// +// These operate on Node structural fields (connective, cardinality) and +// need no environment. They are the shared vocabulary for all pipeline +// stages — not stage-specific concepts. Moved from 04_types.dag (C1). +// ========================================================================= + +fn leaf_node(name: String) -> Node { + Node { name: name, span: SourceSpan { start: 0, end: 0 }, children: [], connective: none, params: [], inferred: none, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } +} + +fn no_span() -> SourceSpan { + SourceSpan { start: 0, end: 0 } +} + +fn with_optional_cardinality(n: Node) -> Node { + Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: CardOptional, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } +} + +fn with_required_cardinality(n: Node) -> Node { + Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: Required, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } +} + +fn node_is_product(n: Node) -> Bool { + n.connective != none && n.connective == Some { value: Conj } +} + +fn node_is_coproduct(n: Node) -> Bool { + n.connective != none && n.connective == Some { value: Disj } +} + +fn node_has_structure(n: Node) -> Bool { + node_is_product(n: n) || node_is_coproduct(n: n) +} diff --git a/src/v2/02_parse.dag b/src/v2/02_parse.dag index edf94215d3b..acece3049e2 100644 --- a/src/v2/02_parse.dag +++ b/src/v2/02_parse.dag @@ -55,11 +55,10 @@ import v2.std.core { Ident, StrBegin, StrMid, StrEnd, Newline, Eof, Unknown, Diagnostic, Error, - SourceSpan + SourceSpan, + node_is_product, node_is_coproduct, with_required_cardinality } -import v2.compiler.infer_types { node_is_optional, node_is_product, node_is_coproduct, with_required_cardinality, callable_node } - // ========================================================================= // Parser state types // ========================================================================= @@ -1675,10 +1674,8 @@ fn parse_callable_type_expr(tokens: List, state: ParserState, start_span: let ret = parse_type_expr(tokens: tokens, state: r3.state) if has_err(err: ret.err) { return TypeResult { type_expr: dummy_te, state: ret.state, err: ret.err } } - let te = callable_node(func_params: params_result.params, ret: ret.type_expr) - // Set span from source location - let te_span = Node { name: te.name, span: start_span, children: te.children, connective: te.connective, params: te.params, inferred: te.inferred, return_cardinality: te.return_cardinality, uses: te.uses, body: te.body, transport: te.transport, properties: te.properties, type_annotation: te.type_annotation, config: te.config, is_self_recursive: te.is_self_recursive, has_non_tail_self_call: te.has_non_tail_self_call, expr_data: te.expr_data } - maybe_optional(tokens: tokens, state: ret.state, te: te_span, start_span: start_span) + let te = Node { name: "Callable", span: start_span, children: [], connective: none, params: params_result.params, inferred: Some { value: Resolved { node: ret.type_expr } }, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } + maybe_optional(tokens: tokens, state: ret.state, te: te, start_span: start_span) } // Parse comma-separated type expressions for callable parameter types. @@ -2575,7 +2572,8 @@ fn is_container_name(name: String) -> Bool { // Extract a string name from a Node for use in property values. fn node_to_name_str(n: Node) -> String { let has_children = n.children |> count > 0 - if node_is_optional(n: n) { + let is_optional = n.return_cardinality == CardOptional + if is_optional { concat("Optional_", node_to_name_str(n: with_required_cardinality(n: n))) } else if n.name == "Map" { if n.children |> count > 1 { concat("Map_", node_to_name_str(n: last_child_or_self(n: n))) } diff --git a/src/v2/04_access.dag b/src/v2/04_access.dag index a04bd85e947..346579ca810 100644 --- a/src/v2/04_access.dag +++ b/src/v2/04_access.dag @@ -11,15 +11,14 @@ import std.types { SourceSpan } import v2.std.core { Node, InferredNode, Resolved, CompilerError, - Diagnostic, Severity, Error + Diagnostic, Severity, Error, + leaf_node, with_optional_cardinality, node_has_structure } import v2.compiler.infer_types { - leaf_node, with_optional_cardinality, node_is_map, normalize_access_type_node, node_type_equals, - is_int_type_node, is_string_type_node, - node_has_structure + is_int_type_node, is_string_type_node } // ========================================================================= diff --git a/src/v2/04_emit_info.dag b/src/v2/04_emit_info.dag index 90b868433ca..e9915bf6d79 100644 --- a/src/v2/04_emit_info.dag +++ b/src/v2/04_emit_info.dag @@ -15,11 +15,11 @@ import v2.std.core { InferredNode, Resolved, FieldAccessStyle, StoredField, EnumAccessor, TupleFirst, TupleSecond, FieldValueShape, PlainValue, OptionalValue, - FieldSummary + FieldSummary, + node_has_structure, node_is_product, with_required_cardinality } import v2.compiler.infer_types { - node_has_structure, node_is_product, node_is_optional, - with_required_cardinality, + node_is_optional, normalize_type_name, normalize_access_type_node, rt_type, child_inferred_or_name, node_type_equals } @@ -41,6 +41,7 @@ type EmitGraphInfo { variant_to_enum: Map enum_variant_membership: Map field_type_names: Map + recursive_type_set: Map } type EmitInfoBuildState { @@ -59,7 +60,8 @@ fn empty_emit_graph_info() -> EmitGraphInfo { type_summaries: empty_map(), variant_to_enum: empty_map(), enum_variant_membership: empty_map(), - field_type_names: empty_map() + field_type_names: empty_map(), + recursive_type_set: empty_map() } } diff --git a/src/v2/04_infer.dag b/src/v2/04_infer.dag index 0bedccb1ba5..b5eb4e2b424 100644 --- a/src/v2/04_infer.dag +++ b/src/v2/04_infer.dag @@ -72,19 +72,19 @@ import v2.std.core { UnaryOpKind, Not, Neg, MatchArm, MatchPattern, FieldBinding, FieldInit, NamedArg, StringPart, make_transport_node, local_transport_node, - Text, Interpolation + Text, Interpolation, + leaf_node, with_optional_cardinality, with_required_cardinality, + node_is_product, node_is_coproduct, node_has_structure, no_span } import v2.compiler.resolve { ModuleGraph, ResolvedModule, ResolvedImport } import v2.compiler.infer_types { child_inferred_or_name, - leaf_node, with_optional_cardinality, with_required_cardinality, container_node, map_node, bare_map_node, tuple_node, callable_node, callable_inferred, error_type_node, node_is_container, node_is_optional, node_is_map, node_is_leaf, node_is_named_ref, - node_is_product, node_is_coproduct, node_has_structure, normalize_access_type_node, normalize_type_name, node_type_shape, node_type_compatible, node_type_equals, prefer_specific_type, node_type_deps, @@ -92,7 +92,7 @@ import v2.compiler.infer_types { method_receiver_element_node, infer_literal_node, infer_binop_type_node, extract_optional_inner_node, for_each_element_type_node, - rt_type, no_span, + rt_type, emit_map_has } import v2.compiler.infer_method { @@ -2214,12 +2214,17 @@ fn build_module_context(contributions: List, item_registry: empty_map(), diag_chunks: [] ) + // C2.1: variant constructor locals carry the structural parent enum node, + // not leaf_node(name). This means .inferred on variant expressions will be + // structurally authoritative — emit can read it directly without re-resolving + // through TypeEnv. binding.resolved.name still returns the enum's name for + // lookup_variant_parent_enum and record lit inference. let imported_variant_locals = fold(map_values(env.bindings), init: empty_map(), f: (acc, binding) => if node_is_coproduct(n: binding.resolved) { fold(binding.resolved.children, init: acc, f: (vacc, child) => map_insert(vacc, child.name, TypeBinding { name: child.name, - resolved: leaf_node(name: binding.name) + resolved: binding.resolved }) ) } else { acc } @@ -2239,15 +2244,11 @@ fn build_module_context(contributions: List, items: local.resolved_items, module_name: module_name ) - // Variant locals come from env_variant_locals (computed once from env.bindings, - // which already includes all local + imported disjunction types). - let optional_locals = map_insert(env_variant_locals, "Some", TypeBinding { - name: "Some", resolved: leaf_node(name: "Optional") - }) - let optional_locals = map_insert(optional_locals, "None", TypeBinding { - name: "None", resolved: leaf_node(name: "Optional") - }) - let all_locals = fold(map_values(merged_scope.svc_locals), init: optional_locals, f: (acc, binding) => + // C2.2: Optional Some/None now handled by the general variant constructor + // mechanism (C2.1). kernel_optional is a Disj node in env.bindings, so + // imported_variant_locals already registers Some and None with the structural + // parent node. No need for explicit leaf_node(name: "Optional") override. + let all_locals = fold(map_values(merged_scope.svc_locals), init: env_variant_locals, f: (acc, binding) => map_insert(acc, binding.name, binding) ) ModuleContext { @@ -2503,11 +2504,15 @@ fn build_emit_graph_info(modules: List) -> EmitGraphInfo { add_emit_item_summary(state: inner_state, item: item) ) ) + let all_recursive = modules |> fold(init: empty_map(), f: (acc, m) => + map_merge(acc, m.type_env.recursive_type_set) + ) EmitGraphInfo { type_summaries: built.type_summaries, variant_to_enum: built.variant_to_enum, enum_variant_membership: built.enum_variant_membership, - field_type_names: built.field_type_names + field_type_names: built.field_type_names, + recursive_type_set: all_recursive } } diff --git a/src/v2/04_items.dag b/src/v2/04_items.dag index c1e8562849f..21115a68ce6 100644 --- a/src/v2/04_items.dag +++ b/src/v2/04_items.dag @@ -13,11 +13,11 @@ import v2.std.core { InferredNode, Resolved, CompilerError, Cardinality, Required, Diagnostic, - expr_has_self_call, expr_has_non_tail_self_call + expr_has_self_call, expr_has_non_tail_self_call, + leaf_node, node_has_structure, node_is_product, node_is_coproduct } import v2.compiler.infer_types { - leaf_node, rt_type, - node_has_structure, node_is_product, node_is_coproduct + rt_type } import v2.compiler.infer_env { TypeEnv, TypeBinding @@ -121,13 +121,14 @@ fn item_kind(item: Node) -> ItemKind { kind } +// C2.1: variant locals carry the structural parent node, not leaf_node(name). fn variant_locals_from_items(items: List, init: Map) -> Map { fold(items, init: init, f: (acc, item) => if node_is_coproduct(n: item) { fold(item.children, init: acc, f: (vacc, child) => map_insert(vacc, child.name, TypeBinding { name: child.name, - resolved: leaf_node(name: item.name) + resolved: item }) ) } else { acc } diff --git a/src/v2/04_lookup.dag b/src/v2/04_lookup.dag index 47710b813a9..ddf08f71992 100644 --- a/src/v2/04_lookup.dag +++ b/src/v2/04_lookup.dag @@ -19,14 +19,14 @@ import v2.std.core { RuntimeBridgeMethod, FieldAccessStyle, OptionalUnwrap, FieldValueShape, PlainValue, OptionalValue, - FieldSummary + FieldSummary, + leaf_node, with_optional_cardinality, with_required_cardinality, + node_is_product, node_is_coproduct, node_has_structure } import v2.compiler.infer_types { child_inferred_or_name, - leaf_node, with_optional_cardinality, with_required_cardinality, error_type_node, node_is_optional, node_is_map, - node_is_product, node_is_coproduct, node_has_structure, normalize_access_type_node, normalize_type_name, rt_type, rt_node, emit_map_has diff --git a/src/v2/04_method.dag b/src/v2/04_method.dag index 4eb22546ff7..8826f9c519c 100644 --- a/src/v2/04_method.dag +++ b/src/v2/04_method.dag @@ -22,10 +22,11 @@ import v2.std.core { BridgeMapContainsKey, BridgeCharAt, BridgeStringAt, BridgeStringLength, BridgeLength, BridgeStartsWith, BridgeEndsWith, BridgeToString, BridgeTrim, BridgeToLower, BridgeToUpper, BridgeReplace, BridgeSubstring, BridgeToInt, BridgeEmptyMap, - BridgeContains, BridgeReverse, BridgeLookup + BridgeContains, BridgeReverse, BridgeLookup, + leaf_node, with_optional_cardinality } import v2.compiler.infer_types { - leaf_node, with_optional_cardinality, container_node, tuple_node, error_type_node, + container_node, tuple_node, error_type_node, bare_map_node, method_receiver_element_node } diff --git a/src/v2/04_patterns.dag b/src/v2/04_patterns.dag index e69a2d6c479..99fbb292fe9 100644 --- a/src/v2/04_patterns.dag +++ b/src/v2/04_patterns.dag @@ -17,14 +17,14 @@ import v2.std.core { Cardinality, Required, Diagnostic, Severity, Error, ErrorCategory, FieldNotFound, VariantNotFound, InvalidOperation, - MatchArm, MatchPattern + MatchArm, MatchPattern, + leaf_node, node_is_coproduct, node_has_structure, with_optional_cardinality } import v2.compiler.infer_types { child_inferred_or_name, - leaf_node, error_type_node, node_is_bridge_error_name, node_is_bridge_dynamic_name, - node_is_optional, node_is_coproduct, node_has_structure, with_optional_cardinality, + node_is_optional, extract_optional_inner_node, emit_map_has } diff --git a/src/v2/04_resolve.dag b/src/v2/04_resolve.dag index a95192f032b..a8ee24e7bce 100644 --- a/src/v2/04_resolve.dag +++ b/src/v2/04_resolve.dag @@ -29,12 +29,13 @@ import v2.std.core { make_transport_node, local_transport_node, is_kernel_type, is_transport_kind, ErrorCategory, UnresolvedName, TypeMismatch, InvalidOperation, CascadeError, - TransportKind, LocalTransport + TransportKind, LocalTransport, + leaf_node, with_optional_cardinality, with_required_cardinality, + node_has_structure, node_is_product, no_span } import v2.compiler.infer_types { - leaf_node, with_optional_cardinality, with_required_cardinality, - node_has_structure, node_is_product, node_is_optional, node_is_map, node_is_container, - rt_type, no_span + node_is_optional, node_is_map, node_is_container, + rt_type } import v2.compiler.infer_env { TypeEnv, lookup_type, is_recursive_type @@ -355,11 +356,26 @@ fn resolve_node_bounded(n: Node, env: TypeEnv, module_name: String, depth: Int) // any dependent type is processed. Re-resolving the looked-up node // would redundantly walk the entire expanded subtree, causing // exponential blowup on diamond-shaped dependency graphs (OOM). + // + // C2.3: For alias nodes (leaf with .inferred but no structure), + // follow ONE level of .inferred to return the structural target. + // This is O(1), not recursive — the target is already resolved + // by topo ordering. Without this, downstream consumers see an + // alias_node wrapper and must re-resolve through TypeEnv (layer + // downgrade from structural to name-based). + // // Preserve CardOptional from the reference node: the env binding // always stores the canonical (Required) definition, but the // reference site may carry CardOptional (e.g. String? → String // in env). Without this, optionality is silently discarded. - let final_resolved = if node_is_optional(n: n) { with_optional_cardinality(n: resolved) } else { resolved } + let structurally_resolved = if node_has_structure(n: resolved) == false && resolved.children |> count == 0 && resolved.inferred != none { + match rt_node(n: resolved) { + Typed { node: target } => target + InferError { message: _, span: _ } => resolved + Untyped => resolved + } + } else { resolved } + let final_resolved = if node_is_optional(n: n) { with_optional_cardinality(n: structurally_resolved) } else { structurally_resolved } NodeResolveResult { resolved: final_resolved, diagnostics: [] } None => if is_kernel_type(name: n.name) || n.name == "Dynamic" || n.name == "Error" || n.name == "Callable" { diff --git a/src/v2/04_service.dag b/src/v2/04_service.dag index d742b98517d..4742aef4918 100644 --- a/src/v2/04_service.dag +++ b/src/v2/04_service.dag @@ -16,10 +16,10 @@ import v2.std.core { ExprFieldAccess, ExprMethodCall, ExprCall, ExprVar, Cardinality, Required, InferredNode, Resolved, - expr_children + expr_children, + leaf_node, no_span, node_has_structure } import v2.compiler.infer_types { - leaf_node, no_span, node_has_structure, emit_map_has } import v2.compiler.infer_items { @@ -166,11 +166,25 @@ fn expand_transitive_services_once(modules: List, registry: Map) -> Int { + map_values(registry) |> fold(init: 0, f: (acc, info) => acc + info.service_names |> count) +} + +// C4.1: converge to fixpoint instead of hardcoded 5 passes. Each pass +// propagates service dependencies from callees to callers. The loop +// terminates when no new service names are discovered (total count stable). +// For the current ItemInfo shape this is equivalent to full-registry +// stability: service_names only changes by monotone deduped union, so the +// total count can increase but never decrease or redistribute. +// Bounded by remaining_passes as safety net against pathological cases. fn expand_transitive_services(modules: List, registry: Map, remaining_passes: Int) -> Map { if remaining_passes <= 0 { registry } else { + let before = total_service_count(registry: registry) let next = expand_transitive_services_once(modules: modules, registry: registry) - expand_transitive_services(modules: modules, registry: next, remaining_passes: remaining_passes - 1) + let after = total_service_count(registry: next) + if before == after { registry } + else { expand_transitive_services(modules: modules, registry: next, remaining_passes: remaining_passes - 1) } } } diff --git a/src/v2/04_types.dag b/src/v2/04_types.dag index ba0a80a3221..2f92e686847 100644 --- a/src/v2/04_types.dag +++ b/src/v2/04_types.dag @@ -20,7 +20,9 @@ import v2.std.core { is_kernel_type, is_kernel_numeric, is_kernel_textual, BinOpKind, BinEq, BinNe, BinLt, BinGt, BinLe, BinGe, BinAnd, BinOr, NullCoalesce, InferredNode, Resolved, CompilerError, rt_node, has_inferred, - NodeType, Typed, InferError, Untyped + NodeType, Typed, InferError, Untyped, + leaf_node, no_span, with_optional_cardinality, with_required_cardinality, + node_is_product, node_is_coproduct, node_has_structure } // ========================================================================= @@ -32,13 +34,13 @@ fn child_inferred_or_name(ch: Node) -> Node { else { rt_type(n: ch) } } -fn leaf_node(name: String) -> Node { - Node { name: name, span: SourceSpan { start: 0, end: 0 }, children: [], connective: none, params: [], inferred: none, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } -} +// leaf_node, with_optional_cardinality, with_required_cardinality +// moved to 00_core.dag (C1 — timeless node vocabulary). // Convenience: extract the return type Node, falling back to Unit. // Use for emission callers and statement contexts where Unit is correct. // For inference callers that need error-awareness, match rt_node directly. +// Stays here: encodes bridge-era stage-boundary policy (Unit fallback). fn rt_type(n: Node) -> Node { match rt_node(n: n) { Typed { node: rt } => rt @@ -47,17 +49,6 @@ fn rt_type(n: Node) -> Node { } } -// P5.7a: product_property() and coproduct_property() deleted. -// connective (Conj/Disj) is the sole authority for product/coproduct. - -fn with_optional_cardinality(n: Node) -> Node { - Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: CardOptional, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } -} - -fn with_required_cardinality(n: Node) -> Node { - Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: Required, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } -} - fn container_node(kind_name: String, element: Node) -> Node { Node { name: kind_name, span: SourceSpan { start: 0, end: 0 }, children: [element], connective: none, params: [], inferred: none, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } } @@ -111,26 +102,13 @@ fn node_is_bridge_dynamic_name(n: Node) -> Bool { n.name == "Dynamic" } -fn no_span() -> SourceSpan { - SourceSpan { start: 0, end: 0 } -} +// no_span, node_is_product, node_is_coproduct, node_has_structure +// moved to 00_core.dag (C1 — timeless node vocabulary). // ========================================================================= -// Type predicates +// Type predicates (name-checking and bridge-era — stay here until L1=0) // ========================================================================= -fn node_is_product(n: Node) -> Bool { - n.connective != none && n.connective == Some { value: Conj } -} - -fn node_is_coproduct(n: Node) -> Bool { - n.connective != none && n.connective == Some { value: Disj } -} - -fn node_has_structure(n: Node) -> Bool { - node_is_product(n: n) || node_is_coproduct(n: n) -} - fn node_is_container(n: Node) -> Bool { n.name == "List" || n.name == "Set" || n.name == "NonEmptyList" || n.name == "NonEmptySet" } diff --git a/src/v2/05_emit.dag b/src/v2/05_emit.dag index 8096cc33f97..101ed761758 100644 --- a/src/v2/05_emit.dag +++ b/src/v2/05_emit.dag @@ -49,11 +49,12 @@ import v2.std.core { TransportKind, RestTransport, ShellTransport, FileTransport, is_transport_kind, transport_has_auth, - field_init_operation_modifier, operation_modifier_name + field_init_operation_modifier, operation_modifier_name, + leaf_node, node_is_product, node_is_coproduct, node_has_structure, with_required_cardinality } import v2.compiler.infer_env { TypeEnv, TypeBinding } -import v2.compiler.infer_types { leaf_node, node_is_optional, node_is_map, node_is_product, node_is_coproduct, node_has_structure, with_required_cardinality, rt_type, emit_map_has } +import v2.compiler.infer_types { node_is_optional, node_is_map, rt_type, emit_map_has } import v2.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } import v2.compiler.infer_items { TypedModule, ResolvedGraph, ItemInfo @@ -265,7 +266,7 @@ fn empty_emit_scope() -> InferScope { fn module_emit_scope(typed_module: TypedModule) -> InferScope { InferScope { - type_env: typed_module.type_env, + type_env: TypeEnv { bindings: empty_map(), recursive_types: [], recursive_type_set: empty_map() }, func_env: typed_module.func_env, locals: empty_map(), module_name: typed_module.module.name, diff --git a/src/v2/05_emit_go.dag b/src/v2/05_emit_go.dag index 9f91ea19c82..369499c2fc7 100644 --- a/src/v2/05_emit_go.dag +++ b/src/v2/05_emit_go.dag @@ -60,7 +60,8 @@ import v2.std.core { BridgeEmptyMap, BridgeContains, BridgeReverse, BridgeLookup, Text, Interpolation, FieldSummary, FieldAccessStyle, - StoredField, EnumAccessor, OptionalUnwrap, TupleFirst, TupleSecond + StoredField, EnumAccessor, OptionalUnwrap, TupleFirst, TupleSecond, + leaf_node, with_required_cardinality } import v2.compiler.artifact { RenderTarget, Go } @@ -69,7 +70,7 @@ import v2.compiler.languages { scaffold_for_target, test_conventions_for_target } import v2.compiler.infer_env { TypeEnv, TypeBinding } -import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, leaf_node, with_required_cardinality, rt_type } +import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, rt_type } import v2.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } import v2.compiler.infer_items { ResolvedGraph, TypedModule, diff --git a/src/v2/05_emit_python.dag b/src/v2/05_emit_python.dag index 8b5a867ba75..f3c5c8fc5db 100644 --- a/src/v2/05_emit_python.dag +++ b/src/v2/05_emit_python.dag @@ -50,7 +50,8 @@ import v2.std.core { BridgeToLower, BridgeToUpper, BridgeReplace, BridgeSubstring, BridgeToInt, BridgeEmptyMap, BridgeContains, BridgeReverse, BridgeLookup, FieldSummary, TupleFirst, TupleSecond, - Text, Interpolation + Text, Interpolation, + leaf_node, with_required_cardinality } import v2.compiler.artifact { RenderTarget, Python } @@ -58,7 +59,7 @@ import v2.compiler.languages { scaffold_for_target, serialization_for_target, test_conventions_for_target } import v2.compiler.infer_env { TypeEnv, TypeBinding } -import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, leaf_node, with_required_cardinality, rt_type } +import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, rt_type } import v2.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } import v2.compiler.infer_items { ResolvedGraph, TypedModule, diff --git a/src/v2/05_emit_rust.dag b/src/v2/05_emit_rust.dag index c730877b451..c7a45fe9e93 100644 --- a/src/v2/05_emit_rust.dag +++ b/src/v2/05_emit_rust.dag @@ -57,7 +57,8 @@ import v2.std.core { transport_base_url, transport_has_auth, transport_auth_token, transport_auth_header_name, transport_headers, transport_env, - expr_has_self_call, expr_has_non_tail_self_call + expr_has_self_call, expr_has_non_tail_self_call, + leaf_node, with_required_cardinality, node_has_structure } import v2.compiler.artifact { RenderTarget, Rust } @@ -73,9 +74,8 @@ import v2.compiler.runtime_rust { rust_runtime_source } import v2.compiler.infer_env { TypeEnv, TypeBinding } import v2.compiler.infer_types { normalize_access_type_node, for_each_element_type_node, - node_is_optional, node_is_map, node_is_container, node_has_structure, + node_is_optional, node_is_map, node_is_container, is_int_type_node, is_string_type_node, is_bool_type_node, is_float_type_node, - leaf_node, with_required_cardinality, rt_type, emit_map_has } import v2.compiler.infer_sigs { ResolvedFuncEnv } @@ -86,13 +86,9 @@ import v2.compiler.infer_items { import v2.compiler.infer_service { is_typed_service_call_receiver, extract_typed_service_name } -import v2.compiler.infer_lookup { - resolve_scrutinee_type_node -} import v2.compiler.infer { InferScope, build_params_scope, extend_scope, - infer_record_lit, build_emit_graph_info, expr_span } @@ -668,7 +664,7 @@ fn emit_non_empty_wrappers() -> String { fn emit_typed_item(item: Node, registry: Map, scope: InferScope, vtoe: Map, rc_types: Map, emit_info: EmitGraphInfo) -> String { let kind = classify_typed_item(item: item) if (kind == TypedItemTypeDef) { - emit_type_def_from_connective(item: item, recursive_types: scope.type_env.recursive_type_set, rc_types: rc_types) + emit_type_def_from_connective(item: item, recursive_types: emit_info.recursive_type_set, rc_types: rc_types) } else if (kind == TypedItemTypeAlias) { concat(rust_visibility_prefix(), "type ", item.name, " = ", emit_node_type_rc(n: rt_type(n: item), target: Rust, rc_types: rc_types), ";") } else if (kind == TypedItemTypeDecl) { @@ -1581,11 +1577,11 @@ fn emit_typed_field_access(base: Node, field: String, summary: FieldSummary?, re } } -fn type_needs_rc(env: TypeEnv, type_node: Node) -> Bool { - type_needs_rc_seen(env: env, type_node: type_node, seen: empty_map()) +fn type_needs_rc(type_node: Node) -> Bool { + type_needs_rc_seen(type_node: type_node, seen: empty_map()) } -fn type_needs_rc_seen(env: TypeEnv, type_node: Node, seen: Map) -> Bool { +fn type_needs_rc_seen(type_node: Node, seen: Map) -> Bool { let normed = normalize_access_type_node(n: type_node) let normed_kind = classify_type_structure(n: normed) if (normed_kind == TypeConj) { @@ -1596,23 +1592,23 @@ fn type_needs_rc_seen(env: TypeEnv, type_node: Node, seen: Map) -> let canonical = normed.name if normed.inferred != none { let next_seen = if canonical == "" { seen } else { map_insert(seen, canonical, true) } - type_needs_rc_seen(env: env, type_node: rt_type(n: normed), seen: next_seen) + type_needs_rc_seen(type_node: rt_type(n: normed), seen: next_seen) } else if canonical != "" && emit_map_has(m: seen, key: canonical) { false } else { let next_seen = if canonical == "" { seen } else { map_insert(seen, canonical, true) } - let resolved = resolve_scrutinee_type_node(env: env, n: normed) + let resolved = normed if node_has_structure(n: resolved) == false && resolved.inferred == none && resolved.name == normed.name && resolved.children |> count == 0 { false } else { - type_needs_rc_seen(env: env, type_node: resolved, seen: next_seen) + type_needs_rc_seen(type_node: resolved, seen: next_seen) } } } } fn rust_map_value_type(receiver_type: Node, scope: InferScope) -> Node? { - let resolved = resolve_scrutinee_type_node(env: scope.type_env, n: normalize_access_type_node(n: receiver_type)) + let resolved = normalize_access_type_node(n: receiver_type) let map_type = normalize_access_type_node(n: resolved) if node_is_map(n: map_type) { match map_type.children |> skip(1) |> first { @@ -1639,7 +1635,7 @@ fn rust_lookup_receiver_needs_rc_wrap(receiver: Node, scope: InferScope) -> Bool match receiver.inferred { Some { value: Resolved { node: receiver_type } } => match rust_map_value_type(receiver_type: receiver_type, scope: scope) { - Some { value: value_type } => type_needs_rc(env: scope.type_env, type_node: value_type) + Some { value: value_type } => type_needs_rc(type_node: value_type) None => false } _ => false @@ -1835,7 +1831,7 @@ fn emit_typed_call_expr(func: String, args: List, inferred: InferredNo let call_str = if func == "empty_map" { match inferred { Some { value: Resolved { node: ret_type } } => - let resolved_ret = resolve_scrutinee_type_node(env: scope.type_env, n: ret_type) + let resolved_ret = ret_type let type_str = emit_node_type_rc(n: resolved_ret, target: Rust, rc_types: rc_types) if type_str != "" && type_str != "Dynamic" { concat("<", type_str, ">::new()") @@ -1856,9 +1852,9 @@ fn emit_typed_call_expr(func: String, args: List, inferred: InferredNo else { match inferred { Some { value: Resolved { node: ret_type } } => - let resolved_ret = resolve_scrutinee_type_node(env: scope.type_env, n: ret_type) + let resolved_ret = ret_type if node_is_optional(n: resolved_ret) { - type_needs_rc(env: scope.type_env, type_node: with_required_cardinality(n: resolved_ret)) + type_needs_rc(type_node: with_required_cardinality(n: resolved_ret)) } else { false } _ => false } @@ -2185,7 +2181,7 @@ fn emit_intrinsic_typed_method_call(intrinsic: IntrinsicMethod, fold_accumulator // RC4: If the receiver is Optional, emit .map(|p| body) to preserve Option type. // Otherwise use the standard for-loop collect pattern for lists. let recv_is_optional = match receiver.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } if recv_is_optional { @@ -2329,7 +2325,7 @@ fn emit_intrinsic_typed_method_call(intrinsic: IntrinsicMethod, fold_accumulator // Annotate sort_by closure params with the element type to avoid E0282. let elem_type_str = match receiver.inferred { Some { value: Resolved { node: rt } } => - let resolved = resolve_scrutinee_type_node(env: scope.type_env, n: rt) + let resolved = rt let elem = for_each_element_type_node(n: resolved) // Dynamic audit: correct guard — if sort_by element type is Dynamic, // fall back to "_" so Rust infers the closure param type. @@ -2529,15 +2525,6 @@ fn emit_typed_let(name: String, value: Node, body: Node?, registry: Map, span: SourceSpan, registry: Map, scope: InferScope, rc_types: Map, emit_info: EmitGraphInfo) -> String { - let inferred = infer_record_lit(type_name: type_name, field_inits: fields, span: span, scope: scope) - match inferred.typed.expr_data { - ExprRecordLit { type_name: tn, fields: fs, parent_enum: parent_enum } => - emit_typed_record_lit(type_name: tn, fields: fs, parent_enum: parent_enum, resolved_type: rt_type(n: inferred.typed), registry: registry, scope: scope, depth: 0, vtoe: empty_map(), rc_types: rc_types, emit_info: emit_info) - _ => - "compile_error!(\"internal error: infer_record_lit did not produce ExprRecordLit\")" - } -} fn is_optional_struct_field(emit_info: EmitGraphInfo, struct_name: String, field_name: String) -> Bool { match lookup_emit_type_summary(emit_info: emit_info, type_name: struct_name) { @@ -2565,10 +2552,10 @@ fn is_already_optional(texpr: Node, emit_info: EmitGraphInfo, scope: InferScope) // then resolve to the definition. resolve_scrutinee_type_node can // lose use-site cardinality when resolving to the type definition. match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: rt) || node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => match map_get(scope.locals, n) { - Some { value: binding } => node_is_optional(n: binding.resolved) || node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: binding.resolved)) + Some { value: binding } => node_is_optional(n: binding.resolved) None => false } } @@ -2590,26 +2577,26 @@ fn is_already_optional(texpr: Node, emit_info: EmitGraphInfo, scope: InferScope) else { match b.inferred { Some { value: Resolved { node: base_type } } => - let resolved_base = resolve_scrutinee_type_node(env: scope.type_env, n: base_type) + let resolved_base = base_type if is_optional_struct_field(emit_info: emit_info, struct_name: resolved_base.name, field_name: f) { true } else { // Fallback: struct may not be in type_summaries; check expression return type match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } } _ => match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } } } _ => match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: rt) || node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } } diff --git a/src/v2/stage0/src/emit.rs b/src/v2/stage0/src/emit.rs index ad4c5277a52..98711add4c5 100644 --- a/src/v2/stage0/src/emit.rs +++ b/src/v2/stage0/src/emit.rs @@ -453,7 +453,7 @@ pub fn empty_emit_scope() -> Rc { } pub fn module_emit_scope(typed_module: Rc) -> Rc { - Rc::new(InferScope { type_env: typed_module.type_env.clone(), func_env: typed_module.func_env.clone(), locals: Rc::new(std::collections::HashMap::new()), module_name: typed_module.module.name.clone(), service_registry: Rc::new(std::collections::HashMap::new()), item_registry: typed_module.item_registry.clone() }) + Rc::new(InferScope { type_env: Rc::new(TypeEnv { bindings: Rc::new(std::collections::HashMap::new()), recursive_types: Rc::new(Vec::new()), recursive_type_set: Rc::new(std::collections::HashMap::new()) }), func_env: typed_module.func_env.clone(), locals: Rc::new(std::collections::HashMap::new()), module_name: typed_module.module.name.clone(), service_registry: Rc::new(std::collections::HashMap::new()), item_registry: typed_module.item_registry.clone() }) } pub fn scope_after_expr(texpr: Rc, scope: Rc) -> Rc { diff --git a/src/v2/stage0/src/emit_rust.rs b/src/v2/stage0/src/emit_rust.rs index 6d017d8b2ae..6b500b87684 100644 --- a/src/v2/stage0/src/emit_rust.rs +++ b/src/v2/stage0/src/emit_rust.rs @@ -8,7 +8,6 @@ use crate::infer_types::*; use crate::infer_sigs::*; use crate::infer_items::*; use crate::infer_service::*; -use crate::infer_lookup::*; use crate::infer::*; use crate::infer_emit_info::*; use crate::emit::*; @@ -1037,7 +1036,7 @@ pub fn emit_non_empty_wrappers() -> String { pub fn emit_typed_item(item: Rc, registry: Rc>>, scope: Rc, vtoe: Rc>, rc_types: Rc>, emit_info: Rc) -> String { let kind = classify_typed_item(item.clone()); if kind.clone() == TypedItemKind::TypedItemTypeDef { - emit_type_def_from_connective(item.clone(), scope.type_env.recursive_type_set.clone(), rc_types.clone()) + emit_type_def_from_connective(item.clone(), emit_info.recursive_type_set.clone(), rc_types.clone()) } else { if kind.clone() == TypedItemKind::TypedItemTypeAlias { v2_rt::concat(v2_rt::concat(v2_rt::concat(v2_rt::concat(v2_rt::concat(rust_visibility_prefix(), "type ".to_string()), item.name.clone()), " = ".to_string()), emit_node_type_rc(rt_type(item.clone()), RenderTarget::Rust, rc_types.clone())), ";".to_string()) @@ -2679,17 +2678,15 @@ pub fn emit_typed_field_access(base: Rc, field: &str, summary: Option, type_node: Rc) -> bool { - type_needs_rc_seen(env.clone(), type_node.clone(), Rc::new(std::collections::HashMap::new())) +pub fn type_needs_rc(type_node: Rc) -> bool { + type_needs_rc_seen(type_node.clone(), Rc::new(std::collections::HashMap::new())) } -pub fn type_needs_rc_seen(env: Rc, type_node: Rc, seen: Rc>) -> bool { +pub fn type_needs_rc_seen(type_node: Rc, seen: Rc>) -> bool { stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { - let mut __tco_p_env = env; let mut __tco_p_type_node = type_node; let mut __tco_p_seen = seen; loop { - let env = __tco_p_env; let type_node = __tco_p_type_node; let seen = __tco_p_seen; let normed = normalize_access_type_node(type_node); @@ -2725,12 +2722,10 @@ pub fn type_needs_rc_seen(env: Rc, type_node: Rc, seen: Rc, type_node: Rc, seen: Rc, type_node: Rc, seen: Rc, type_node: Rc, seen: Rc, scope: Rc) -> Option> { - let resolved = resolve_scrutinee_type_node(scope.type_env.clone(), normalize_access_type_node(receiver_type.clone())); + let resolved = normalize_access_type_node(receiver_type.clone()); let map_type = normalize_access_type_node(resolved.clone()); if node_is_map(map_type.clone()) { match map_type.children.clone().get((1_i64) as usize).cloned() { @@ -2814,7 +2807,7 @@ pub fn rust_lookup_receiver_needs_rc_wrap(receiver: Rc, scope: Rc { match rust_map_value_type(receiver_type.clone(), scope.clone()) { Some(value_type) => { - type_needs_rc(scope.type_env.clone(), value_type.clone()) + type_needs_rc(value_type.clone()) } None => { false @@ -3098,7 +3091,7 @@ pub fn emit_typed_call_expr(func: &str, args: Rc>>, inferred: O match inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: ret_type, .. }) => { { - let resolved_ret = resolve_scrutinee_type_node(scope.type_env.clone(), ret_type.clone()); + let resolved_ret = ret_type.clone(); let type_str = emit_node_type_rc(resolved_ret.clone(), RenderTarget::Rust, rc_types.clone()); if (type_str.clone() != "") && (type_str.clone() != "Dynamic") { v2_rt::concat(v2_rt::concat("<".to_string(), type_str.clone()), ">::new()".to_string()) @@ -3129,9 +3122,9 @@ pub fn emit_typed_call_expr(func: &str, args: Rc>>, inferred: O match inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: ret_type, .. }) => { { - let resolved_ret = resolve_scrutinee_type_node(scope.type_env.clone(), ret_type.clone()); + let resolved_ret = ret_type.clone(); if node_is_optional(resolved_ret.clone()) { - type_needs_rc(scope.type_env.clone(), with_required_cardinality(resolved_ret.clone())) + type_needs_rc(with_required_cardinality(resolved_ret.clone())) } else { false } @@ -3813,7 +3806,7 @@ pub fn emit_intrinsic_typed_method_call(intrinsic: IntrinsicMethod, fold_accumul { let recv_is_optional = match receiver.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: rt, .. }) => { - node_is_optional(resolve_scrutinee_type_node(scope.type_env.clone(), rt.clone())) + node_is_optional(rt.clone()) } _ => { false @@ -4093,7 +4086,7 @@ pub fn emit_intrinsic_typed_method_call(intrinsic: IntrinsicMethod, fold_accumul let elem_type_str = match receiver.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: rt, .. }) => { { - let resolved = resolve_scrutinee_type_node(scope.type_env.clone(), rt.clone()); + let resolved = rt.clone(); let elem = for_each_element_type_node(resolved.clone()); if (elem.name.clone() != "") && (elem.name.clone() != "Dynamic") { emit_node_type_rc(elem.clone(), RenderTarget::Rust, rc_types.clone()) @@ -4475,18 +4468,6 @@ pub fn emit_typed_let(name: &str, value: Rc, body: Option>, regis }) } -pub fn emit_record_lit_full(type_name: Option, fields: Rc>>, span: SourceSpan, registry: Rc>>, scope: Rc, rc_types: Rc>, emit_info: Rc) -> String { - let inferred = infer_record_lit(type_name, fields.clone(), span, scope.clone()); - match inferred.typed.expr_data.as_ref() { - ExprData::ExprRecordLit { type_name: tn, fields: fs, parent_enum, .. } => { - emit_typed_record_lit(tn.clone(), fs.clone(), parent_enum.clone(), rt_type(inferred.typed.clone()), registry.clone(), scope.clone(), 0_i64, Rc::new(std::collections::HashMap::new()), rc_types.clone(), emit_info.clone()) - } - _ => { - "compile_error!(\"internal error: infer_record_lit did not produce ExprRecordLit\")".to_string() - } -} -} - pub fn is_optional_struct_field(emit_info: Rc, struct_name: &str, field_name: &str) -> bool { match lookup_emit_type_summary(emit_info.clone(), &struct_name) { Some(summary) => { @@ -4530,12 +4511,12 @@ pub fn is_already_optional(texpr: Rc, emit_info: Rc, scope: } else { match texpr.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: rt, .. }) => { - node_is_optional(rt.clone()) || node_is_optional(resolve_scrutinee_type_node(scope.type_env.clone(), rt.clone())) + node_is_optional(rt.clone()) } _ => { match scope.locals.clone().get(&n.clone()).cloned() { Some(binding) => { - node_is_optional(binding.resolved.clone()) || node_is_optional(resolve_scrutinee_type_node(scope.type_env.clone(), binding.resolved.clone())) + node_is_optional(binding.resolved.clone()) } None => { false @@ -4579,13 +4560,13 @@ pub fn is_already_optional(texpr: Rc, emit_info: Rc, scope: match b.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: base_type, .. }) => { { - let resolved_base = resolve_scrutinee_type_node(scope.type_env.clone(), base_type.clone()); + let resolved_base = base_type.clone(); if is_optional_struct_field(emit_info.clone(), &resolved_base.name, &f) { true } else { match texpr.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: rt, .. }) => { - node_is_optional(resolve_scrutinee_type_node(scope.type_env.clone(), rt.clone())) + node_is_optional(rt.clone()) } _ => { false @@ -4597,7 +4578,7 @@ pub fn is_already_optional(texpr: Rc, emit_info: Rc, scope: _ => { match texpr.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: rt, .. }) => { - node_is_optional(resolve_scrutinee_type_node(scope.type_env.clone(), rt.clone())) + node_is_optional(rt.clone()) } _ => { false @@ -4611,7 +4592,7 @@ pub fn is_already_optional(texpr: Rc, emit_info: Rc, scope: _ => { match texpr.inferred.as_ref().map(|__rc| __rc.as_ref()) { Some(InferredNode::Resolved { node: rt, .. }) => { - node_is_optional(rt.clone()) || node_is_optional(resolve_scrutinee_type_node(scope.type_env.clone(), rt.clone())) + node_is_optional(rt.clone()) } _ => { false diff --git a/src/v2/stage0/src/generated_tests.rs b/src/v2/stage0/src/generated_tests.rs index a54872fcf77..fb627f1cea5 100644 --- a/src/v2/stage0/src/generated_tests.rs +++ b/src/v2/stage0/src/generated_tests.rs @@ -2795,6 +2795,42 @@ fn is_module_node(n: Node) -> Bool { fn is_token_node(n: Node) -> Bool { n.properties |> any(p => p.name == "is_token") } + +// ========================================================================= +// Node vocabulary (structural constructors and predicates) +// +// These operate on Node structural fields (connective, cardinality) and +// need no environment. They are the shared vocabulary for all pipeline +// stages — not stage-specific concepts. Moved from 04_types.dag (C1). +// ========================================================================= + +fn leaf_node(name: String) -> Node { + Node { name: name, span: SourceSpan { start: 0, end: 0 }, children: [], connective: none, params: [], inferred: none, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } +} + +fn no_span() -> SourceSpan { + SourceSpan { start: 0, end: 0 } +} + +fn with_optional_cardinality(n: Node) -> Node { + Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: CardOptional, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } +} + +fn with_required_cardinality(n: Node) -> Node { + Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: Required, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } +} + +fn node_is_product(n: Node) -> Bool { + n.connective != none && n.connective == Some { value: Conj } +} + +fn node_is_coproduct(n: Node) -> Bool { + n.connective != none && n.connective == Some { value: Disj } +} + +fn node_has_structure(n: Node) -> Bool { + node_is_product(n: n) || node_is_coproduct(n: n) +} "##; const SRC_V2_01_TOKENIZE_DAG_SOURCE: &str = r##"// v2 tokenizer -- pure function from source string to token list. // @@ -3361,11 +3397,10 @@ import v2.std.core { Ident, StrBegin, StrMid, StrEnd, Newline, Eof, Unknown, Diagnostic, Error, - SourceSpan + SourceSpan, + node_is_product, node_is_coproduct, with_required_cardinality } -import v2.compiler.infer_types { node_is_optional, node_is_product, node_is_coproduct, with_required_cardinality, callable_node } - // ========================================================================= // Parser state types // ========================================================================= @@ -4981,10 +5016,8 @@ fn parse_callable_type_expr(tokens: List, state: ParserState, start_span: let ret = parse_type_expr(tokens: tokens, state: r3.state) if has_err(err: ret.err) { return TypeResult { type_expr: dummy_te, state: ret.state, err: ret.err } } - let te = callable_node(func_params: params_result.params, ret: ret.type_expr) - // Set span from source location - let te_span = Node { name: te.name, span: start_span, children: te.children, connective: te.connective, params: te.params, inferred: te.inferred, return_cardinality: te.return_cardinality, uses: te.uses, body: te.body, transport: te.transport, properties: te.properties, type_annotation: te.type_annotation, config: te.config, is_self_recursive: te.is_self_recursive, has_non_tail_self_call: te.has_non_tail_self_call, expr_data: te.expr_data } - maybe_optional(tokens: tokens, state: ret.state, te: te_span, start_span: start_span) + let te = Node { name: "Callable", span: start_span, children: [], connective: none, params: params_result.params, inferred: Some { value: Resolved { node: ret.type_expr } }, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } + maybe_optional(tokens: tokens, state: ret.state, te: te, start_span: start_span) } // Parse comma-separated type expressions for callable parameter types. @@ -5881,7 +5914,8 @@ fn is_container_name(name: String) -> Bool { // Extract a string name from a Node for use in property values. fn node_to_name_str(n: Node) -> String { let has_children = n.children |> count > 0 - if node_is_optional(n: n) { + let is_optional = n.return_cardinality == CardOptional + if is_optional { concat("Optional_", node_to_name_str(n: with_required_cardinality(n: n))) } else if n.name == "Map" { if n.children |> count > 1 { concat("Map_", node_to_name_str(n: last_child_or_self(n: n))) } @@ -8422,15 +8456,14 @@ import std.types { SourceSpan } import v2.std.core { Node, InferredNode, Resolved, CompilerError, - Diagnostic, Severity, Error + Diagnostic, Severity, Error, + leaf_node, with_optional_cardinality, node_has_structure } import v2.compiler.infer_types { - leaf_node, with_optional_cardinality, node_is_map, normalize_access_type_node, node_type_equals, - is_int_type_node, is_string_type_node, - node_has_structure + is_int_type_node, is_string_type_node } // ========================================================================= @@ -8636,11 +8669,11 @@ import v2.std.core { InferredNode, Resolved, FieldAccessStyle, StoredField, EnumAccessor, TupleFirst, TupleSecond, FieldValueShape, PlainValue, OptionalValue, - FieldSummary + FieldSummary, + node_has_structure, node_is_product, with_required_cardinality } import v2.compiler.infer_types { - node_has_structure, node_is_product, node_is_optional, - with_required_cardinality, + node_is_optional, normalize_type_name, normalize_access_type_node, rt_type, child_inferred_or_name, node_type_equals } @@ -8662,6 +8695,7 @@ type EmitGraphInfo { variant_to_enum: Map enum_variant_membership: Map field_type_names: Map + recursive_type_set: Map } type EmitInfoBuildState { @@ -8680,7 +8714,8 @@ fn empty_emit_graph_info() -> EmitGraphInfo { type_summaries: empty_map(), variant_to_enum: empty_map(), enum_variant_membership: empty_map(), - field_type_names: empty_map() + field_type_names: empty_map(), + recursive_type_set: empty_map() } } @@ -9028,19 +9063,19 @@ import v2.std.core { UnaryOpKind, Not, Neg, MatchArm, MatchPattern, FieldBinding, FieldInit, NamedArg, StringPart, make_transport_node, local_transport_node, - Text, Interpolation + Text, Interpolation, + leaf_node, with_optional_cardinality, with_required_cardinality, + node_is_product, node_is_coproduct, node_has_structure, no_span } import v2.compiler.resolve { ModuleGraph, ResolvedModule, ResolvedImport } import v2.compiler.infer_types { child_inferred_or_name, - leaf_node, with_optional_cardinality, with_required_cardinality, container_node, map_node, bare_map_node, tuple_node, callable_node, callable_inferred, error_type_node, node_is_container, node_is_optional, node_is_map, node_is_leaf, node_is_named_ref, - node_is_product, node_is_coproduct, node_has_structure, normalize_access_type_node, normalize_type_name, node_type_shape, node_type_compatible, node_type_equals, prefer_specific_type, node_type_deps, @@ -9048,7 +9083,7 @@ import v2.compiler.infer_types { method_receiver_element_node, infer_literal_node, infer_binop_type_node, extract_optional_inner_node, for_each_element_type_node, - rt_type, no_span, + rt_type, emit_map_has } import v2.compiler.infer_method { @@ -11170,12 +11205,17 @@ fn build_module_context(contributions: List, item_registry: empty_map(), diag_chunks: [] ) + // C2.1: variant constructor locals carry the structural parent enum node, + // not leaf_node(name). This means .inferred on variant expressions will be + // structurally authoritative — emit can read it directly without re-resolving + // through TypeEnv. binding.resolved.name still returns the enum's name for + // lookup_variant_parent_enum and record lit inference. let imported_variant_locals = fold(map_values(env.bindings), init: empty_map(), f: (acc, binding) => if node_is_coproduct(n: binding.resolved) { fold(binding.resolved.children, init: acc, f: (vacc, child) => map_insert(vacc, child.name, TypeBinding { name: child.name, - resolved: leaf_node(name: binding.name) + resolved: binding.resolved }) ) } else { acc } @@ -11195,15 +11235,11 @@ fn build_module_context(contributions: List, items: local.resolved_items, module_name: module_name ) - // Variant locals come from env_variant_locals (computed once from env.bindings, - // which already includes all local + imported disjunction types). - let optional_locals = map_insert(env_variant_locals, "Some", TypeBinding { - name: "Some", resolved: leaf_node(name: "Optional") - }) - let optional_locals = map_insert(optional_locals, "None", TypeBinding { - name: "None", resolved: leaf_node(name: "Optional") - }) - let all_locals = fold(map_values(merged_scope.svc_locals), init: optional_locals, f: (acc, binding) => + // C2.2: Optional Some/None now handled by the general variant constructor + // mechanism (C2.1). kernel_optional is a Disj node in env.bindings, so + // imported_variant_locals already registers Some and None with the structural + // parent node. No need for explicit leaf_node(name: "Optional") override. + let all_locals = fold(map_values(merged_scope.svc_locals), init: env_variant_locals, f: (acc, binding) => map_insert(acc, binding.name, binding) ) ModuleContext { @@ -11459,11 +11495,15 @@ fn build_emit_graph_info(modules: List) -> EmitGraphInfo { add_emit_item_summary(state: inner_state, item: item) ) ) + let all_recursive = modules |> fold(init: empty_map(), f: (acc, m) => + map_merge(acc, m.type_env.recursive_type_set) + ) EmitGraphInfo { type_summaries: built.type_summaries, variant_to_enum: built.variant_to_enum, enum_variant_membership: built.enum_variant_membership, - field_type_names: built.field_type_names + field_type_names: built.field_type_names, + recursive_type_set: all_recursive } } @@ -11551,11 +11591,11 @@ import v2.std.core { InferredNode, Resolved, CompilerError, Cardinality, Required, Diagnostic, - expr_has_self_call, expr_has_non_tail_self_call + expr_has_self_call, expr_has_non_tail_self_call, + leaf_node, node_has_structure, node_is_product, node_is_coproduct } import v2.compiler.infer_types { - leaf_node, rt_type, - node_has_structure, node_is_product, node_is_coproduct + rt_type } import v2.compiler.infer_env { TypeEnv, TypeBinding @@ -11659,13 +11699,14 @@ fn item_kind(item: Node) -> ItemKind { kind } +// C2.1: variant locals carry the structural parent node, not leaf_node(name). fn variant_locals_from_items(items: List, init: Map) -> Map { fold(items, init: init, f: (acc, item) => if node_is_coproduct(n: item) { fold(item.children, init: acc, f: (vacc, child) => map_insert(vacc, child.name, TypeBinding { name: child.name, - resolved: leaf_node(name: item.name) + resolved: item }) ) } else { acc } @@ -11693,14 +11734,14 @@ import v2.std.core { RuntimeBridgeMethod, FieldAccessStyle, OptionalUnwrap, FieldValueShape, PlainValue, OptionalValue, - FieldSummary + FieldSummary, + leaf_node, with_optional_cardinality, with_required_cardinality, + node_is_product, node_is_coproduct, node_has_structure } import v2.compiler.infer_types { child_inferred_or_name, - leaf_node, with_optional_cardinality, with_required_cardinality, error_type_node, node_is_optional, node_is_map, - node_is_product, node_is_coproduct, node_has_structure, normalize_access_type_node, normalize_type_name, rt_type, rt_node, emit_map_has @@ -11949,10 +11990,11 @@ import v2.std.core { BridgeMapContainsKey, BridgeCharAt, BridgeStringAt, BridgeStringLength, BridgeLength, BridgeStartsWith, BridgeEndsWith, BridgeToString, BridgeTrim, BridgeToLower, BridgeToUpper, BridgeReplace, BridgeSubstring, BridgeToInt, BridgeEmptyMap, - BridgeContains, BridgeReverse, BridgeLookup + BridgeContains, BridgeReverse, BridgeLookup, + leaf_node, with_optional_cardinality } import v2.compiler.infer_types { - leaf_node, with_optional_cardinality, container_node, tuple_node, error_type_node, + container_node, tuple_node, error_type_node, bare_map_node, method_receiver_element_node } @@ -12172,14 +12214,14 @@ import v2.std.core { Cardinality, Required, Diagnostic, Severity, Error, ErrorCategory, FieldNotFound, VariantNotFound, InvalidOperation, - MatchArm, MatchPattern + MatchArm, MatchPattern, + leaf_node, node_is_coproduct, node_has_structure, with_optional_cardinality } import v2.compiler.infer_types { child_inferred_or_name, - leaf_node, error_type_node, node_is_bridge_error_name, node_is_bridge_dynamic_name, - node_is_optional, node_is_coproduct, node_has_structure, with_optional_cardinality, + node_is_optional, extract_optional_inner_node, emit_map_has } @@ -12441,12 +12483,13 @@ import v2.std.core { make_transport_node, local_transport_node, is_kernel_type, is_transport_kind, ErrorCategory, UnresolvedName, TypeMismatch, InvalidOperation, CascadeError, - TransportKind, LocalTransport + TransportKind, LocalTransport, + leaf_node, with_optional_cardinality, with_required_cardinality, + node_has_structure, node_is_product, no_span } import v2.compiler.infer_types { - leaf_node, with_optional_cardinality, with_required_cardinality, - node_has_structure, node_is_product, node_is_optional, node_is_map, node_is_container, - rt_type, no_span + node_is_optional, node_is_map, node_is_container, + rt_type } import v2.compiler.infer_env { TypeEnv, lookup_type, is_recursive_type @@ -12767,11 +12810,26 @@ fn resolve_node_bounded(n: Node, env: TypeEnv, module_name: String, depth: Int) // any dependent type is processed. Re-resolving the looked-up node // would redundantly walk the entire expanded subtree, causing // exponential blowup on diamond-shaped dependency graphs (OOM). + // + // C2.3: For alias nodes (leaf with .inferred but no structure), + // follow ONE level of .inferred to return the structural target. + // This is O(1), not recursive — the target is already resolved + // by topo ordering. Without this, downstream consumers see an + // alias_node wrapper and must re-resolve through TypeEnv (layer + // downgrade from structural to name-based). + // // Preserve CardOptional from the reference node: the env binding // always stores the canonical (Required) definition, but the // reference site may carry CardOptional (e.g. String? → String // in env). Without this, optionality is silently discarded. - let final_resolved = if node_is_optional(n: n) { with_optional_cardinality(n: resolved) } else { resolved } + let structurally_resolved = if node_has_structure(n: resolved) == false && resolved.children |> count == 0 && resolved.inferred != none { + match rt_node(n: resolved) { + Typed { node: target } => target + InferError { message: _, span: _ } => resolved + Untyped => resolved + } + } else { resolved } + let final_resolved = if node_is_optional(n: n) { with_optional_cardinality(n: structurally_resolved) } else { structurally_resolved } NodeResolveResult { resolved: final_resolved, diagnostics: [] } None => if is_kernel_type(name: n.name) || n.name == "Dynamic" || n.name == "Error" || n.name == "Callable" { @@ -13212,10 +13270,10 @@ import v2.std.core { ExprFieldAccess, ExprMethodCall, ExprCall, ExprVar, Cardinality, Required, InferredNode, Resolved, - expr_children + expr_children, + leaf_node, no_span, node_has_structure } import v2.compiler.infer_types { - leaf_node, no_span, node_has_structure, emit_map_has } import v2.compiler.infer_items { @@ -13362,11 +13420,25 @@ fn expand_transitive_services_once(modules: List, registry: Map) -> Int { + map_values(registry) |> fold(init: 0, f: (acc, info) => acc + info.service_names |> count) +} + +// C4.1: converge to fixpoint instead of hardcoded 5 passes. Each pass +// propagates service dependencies from callees to callers. The loop +// terminates when no new service names are discovered (total count stable). +// For the current ItemInfo shape this is equivalent to full-registry +// stability: service_names only changes by monotone deduped union, so the +// total count can increase but never decrease or redistribute. +// Bounded by remaining_passes as safety net against pathological cases. fn expand_transitive_services(modules: List, registry: Map, remaining_passes: Int) -> Map { if remaining_passes <= 0 { registry } else { + let before = total_service_count(registry: registry) let next = expand_transitive_services_once(modules: modules, registry: registry) - expand_transitive_services(modules: modules, registry: next, remaining_passes: remaining_passes - 1) + let after = total_service_count(registry: next) + if before == after { registry } + else { expand_transitive_services(modules: modules, registry: next, remaining_passes: remaining_passes - 1) } } } @@ -13701,7 +13773,9 @@ import v2.std.core { is_kernel_type, is_kernel_numeric, is_kernel_textual, BinOpKind, BinEq, BinNe, BinLt, BinGt, BinLe, BinGe, BinAnd, BinOr, NullCoalesce, InferredNode, Resolved, CompilerError, rt_node, has_inferred, - NodeType, Typed, InferError, Untyped + NodeType, Typed, InferError, Untyped, + leaf_node, no_span, with_optional_cardinality, with_required_cardinality, + node_is_product, node_is_coproduct, node_has_structure } // ========================================================================= @@ -13713,13 +13787,13 @@ fn child_inferred_or_name(ch: Node) -> Node { else { rt_type(n: ch) } } -fn leaf_node(name: String) -> Node { - Node { name: name, span: SourceSpan { start: 0, end: 0 }, children: [], connective: none, params: [], inferred: none, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } -} +// leaf_node, with_optional_cardinality, with_required_cardinality +// moved to 00_core.dag (C1 — timeless node vocabulary). // Convenience: extract the return type Node, falling back to Unit. // Use for emission callers and statement contexts where Unit is correct. // For inference callers that need error-awareness, match rt_node directly. +// Stays here: encodes bridge-era stage-boundary policy (Unit fallback). fn rt_type(n: Node) -> Node { match rt_node(n: n) { Typed { node: rt } => rt @@ -13728,17 +13802,6 @@ fn rt_type(n: Node) -> Node { } } -// P5.7a: product_property() and coproduct_property() deleted. -// connective (Conj/Disj) is the sole authority for product/coproduct. - -fn with_optional_cardinality(n: Node) -> Node { - Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: CardOptional, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } -} - -fn with_required_cardinality(n: Node) -> Node { - Node { name: n.name, span: n.span, children: n.children, connective: n.connective, params: n.params, inferred: n.inferred, return_cardinality: Required, uses: n.uses, body: n.body, transport: n.transport, properties: n.properties, type_annotation: n.type_annotation, config: n.config, is_self_recursive: n.is_self_recursive, has_non_tail_self_call: n.has_non_tail_self_call, expr_data: n.expr_data } -} - fn container_node(kind_name: String, element: Node) -> Node { Node { name: kind_name, span: SourceSpan { start: 0, end: 0 }, children: [element], connective: none, params: [], inferred: none, return_cardinality: Required, uses: [], body: none, transport: none, properties: [], type_annotation: none, config: none, is_self_recursive: false, has_non_tail_self_call: false, expr_data: NoExprData } } @@ -13792,26 +13855,13 @@ fn node_is_bridge_dynamic_name(n: Node) -> Bool { n.name == "Dynamic" } -fn no_span() -> SourceSpan { - SourceSpan { start: 0, end: 0 } -} +// no_span, node_is_product, node_is_coproduct, node_has_structure +// moved to 00_core.dag (C1 — timeless node vocabulary). // ========================================================================= -// Type predicates +// Type predicates (name-checking and bridge-era — stay here until L1=0) // ========================================================================= -fn node_is_product(n: Node) -> Bool { - n.connective != none && n.connective == Some { value: Conj } -} - -fn node_is_coproduct(n: Node) -> Bool { - n.connective != none && n.connective == Some { value: Disj } -} - -fn node_has_structure(n: Node) -> Bool { - node_is_product(n: n) || node_is_coproduct(n: n) -} - fn node_is_container(n: Node) -> Bool { n.name == "List" || n.name == "Set" || n.name == "NonEmptyList" || n.name == "NonEmptySet" } @@ -14241,11 +14291,12 @@ import v2.std.core { TransportKind, RestTransport, ShellTransport, FileTransport, is_transport_kind, transport_has_auth, - field_init_operation_modifier, operation_modifier_name + field_init_operation_modifier, operation_modifier_name, + leaf_node, node_is_product, node_is_coproduct, node_has_structure, with_required_cardinality } import v2.compiler.infer_env { TypeEnv, TypeBinding } -import v2.compiler.infer_types { leaf_node, node_is_optional, node_is_map, node_is_product, node_is_coproduct, node_has_structure, with_required_cardinality, rt_type, emit_map_has } +import v2.compiler.infer_types { node_is_optional, node_is_map, rt_type, emit_map_has } import v2.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } import v2.compiler.infer_items { TypedModule, ResolvedGraph, ItemInfo @@ -14457,7 +14508,7 @@ fn empty_emit_scope() -> InferScope { fn module_emit_scope(typed_module: TypedModule) -> InferScope { InferScope { - type_env: typed_module.type_env, + type_env: TypeEnv { bindings: empty_map(), recursive_types: [], recursive_type_set: empty_map() }, func_env: typed_module.func_env, locals: empty_map(), module_name: typed_module.module.name, @@ -15754,7 +15805,8 @@ import v2.std.core { BridgeEmptyMap, BridgeContains, BridgeReverse, BridgeLookup, Text, Interpolation, FieldSummary, FieldAccessStyle, - StoredField, EnumAccessor, OptionalUnwrap, TupleFirst, TupleSecond + StoredField, EnumAccessor, OptionalUnwrap, TupleFirst, TupleSecond, + leaf_node, with_required_cardinality } import v2.compiler.artifact { RenderTarget, Go } @@ -15763,7 +15815,7 @@ import v2.compiler.languages { scaffold_for_target, test_conventions_for_target } import v2.compiler.infer_env { TypeEnv, TypeBinding } -import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, leaf_node, with_required_cardinality, rt_type } +import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, rt_type } import v2.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } import v2.compiler.infer_items { ResolvedGraph, TypedModule, @@ -17091,7 +17143,8 @@ import v2.std.core { BridgeToLower, BridgeToUpper, BridgeReplace, BridgeSubstring, BridgeToInt, BridgeEmptyMap, BridgeContains, BridgeReverse, BridgeLookup, FieldSummary, TupleFirst, TupleSecond, - Text, Interpolation + Text, Interpolation, + leaf_node, with_required_cardinality } import v2.compiler.artifact { RenderTarget, Python } @@ -17099,7 +17152,7 @@ import v2.compiler.languages { scaffold_for_target, serialization_for_target, test_conventions_for_target } import v2.compiler.infer_env { TypeEnv, TypeBinding } -import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, leaf_node, with_required_cardinality, rt_type } +import v2.compiler.infer_types { for_each_element_type_node, node_is_optional, node_is_map, rt_type } import v2.compiler.infer_sigs { ResolvedFuncSig, ResolvedFuncEnv } import v2.compiler.infer_items { ResolvedGraph, TypedModule, @@ -18408,7 +18461,8 @@ import v2.std.core { transport_base_url, transport_has_auth, transport_auth_token, transport_auth_header_name, transport_headers, transport_env, - expr_has_self_call, expr_has_non_tail_self_call + expr_has_self_call, expr_has_non_tail_self_call, + leaf_node, with_required_cardinality, node_has_structure } import v2.compiler.artifact { RenderTarget, Rust } @@ -18424,9 +18478,8 @@ import v2.compiler.runtime_rust { rust_runtime_source } import v2.compiler.infer_env { TypeEnv, TypeBinding } import v2.compiler.infer_types { normalize_access_type_node, for_each_element_type_node, - node_is_optional, node_is_map, node_is_container, node_has_structure, + node_is_optional, node_is_map, node_is_container, is_int_type_node, is_string_type_node, is_bool_type_node, is_float_type_node, - leaf_node, with_required_cardinality, rt_type, emit_map_has } import v2.compiler.infer_sigs { ResolvedFuncEnv } @@ -18437,13 +18490,9 @@ import v2.compiler.infer_items { import v2.compiler.infer_service { is_typed_service_call_receiver, extract_typed_service_name } -import v2.compiler.infer_lookup { - resolve_scrutinee_type_node -} import v2.compiler.infer { InferScope, build_params_scope, extend_scope, - infer_record_lit, build_emit_graph_info, expr_span } @@ -19019,7 +19068,7 @@ fn emit_non_empty_wrappers() -> String { fn emit_typed_item(item: Node, registry: Map, scope: InferScope, vtoe: Map, rc_types: Map, emit_info: EmitGraphInfo) -> String { let kind = classify_typed_item(item: item) if (kind == TypedItemTypeDef) { - emit_type_def_from_connective(item: item, recursive_types: scope.type_env.recursive_type_set, rc_types: rc_types) + emit_type_def_from_connective(item: item, recursive_types: emit_info.recursive_type_set, rc_types: rc_types) } else if (kind == TypedItemTypeAlias) { concat(rust_visibility_prefix(), "type ", item.name, " = ", emit_node_type_rc(n: rt_type(n: item), target: Rust, rc_types: rc_types), ";") } else if (kind == TypedItemTypeDecl) { @@ -19932,11 +19981,11 @@ fn emit_typed_field_access(base: Node, field: String, summary: FieldSummary?, re } } -fn type_needs_rc(env: TypeEnv, type_node: Node) -> Bool { - type_needs_rc_seen(env: env, type_node: type_node, seen: empty_map()) +fn type_needs_rc(type_node: Node) -> Bool { + type_needs_rc_seen(type_node: type_node, seen: empty_map()) } -fn type_needs_rc_seen(env: TypeEnv, type_node: Node, seen: Map) -> Bool { +fn type_needs_rc_seen(type_node: Node, seen: Map) -> Bool { let normed = normalize_access_type_node(n: type_node) let normed_kind = classify_type_structure(n: normed) if (normed_kind == TypeConj) { @@ -19947,23 +19996,23 @@ fn type_needs_rc_seen(env: TypeEnv, type_node: Node, seen: Map) -> let canonical = normed.name if normed.inferred != none { let next_seen = if canonical == "" { seen } else { map_insert(seen, canonical, true) } - type_needs_rc_seen(env: env, type_node: rt_type(n: normed), seen: next_seen) + type_needs_rc_seen(type_node: rt_type(n: normed), seen: next_seen) } else if canonical != "" && emit_map_has(m: seen, key: canonical) { false } else { let next_seen = if canonical == "" { seen } else { map_insert(seen, canonical, true) } - let resolved = resolve_scrutinee_type_node(env: env, n: normed) + let resolved = normed if node_has_structure(n: resolved) == false && resolved.inferred == none && resolved.name == normed.name && resolved.children |> count == 0 { false } else { - type_needs_rc_seen(env: env, type_node: resolved, seen: next_seen) + type_needs_rc_seen(type_node: resolved, seen: next_seen) } } } } fn rust_map_value_type(receiver_type: Node, scope: InferScope) -> Node? { - let resolved = resolve_scrutinee_type_node(env: scope.type_env, n: normalize_access_type_node(n: receiver_type)) + let resolved = normalize_access_type_node(n: receiver_type) let map_type = normalize_access_type_node(n: resolved) if node_is_map(n: map_type) { match map_type.children |> skip(1) |> first { @@ -19990,7 +20039,7 @@ fn rust_lookup_receiver_needs_rc_wrap(receiver: Node, scope: InferScope) -> Bool match receiver.inferred { Some { value: Resolved { node: receiver_type } } => match rust_map_value_type(receiver_type: receiver_type, scope: scope) { - Some { value: value_type } => type_needs_rc(env: scope.type_env, type_node: value_type) + Some { value: value_type } => type_needs_rc(type_node: value_type) None => false } _ => false @@ -20186,7 +20235,7 @@ fn emit_typed_call_expr(func: String, args: List, inferred: InferredNo let call_str = if func == "empty_map" { match inferred { Some { value: Resolved { node: ret_type } } => - let resolved_ret = resolve_scrutinee_type_node(env: scope.type_env, n: ret_type) + let resolved_ret = ret_type let type_str = emit_node_type_rc(n: resolved_ret, target: Rust, rc_types: rc_types) if type_str != "" && type_str != "Dynamic" { concat("<", type_str, ">::new()") @@ -20207,9 +20256,9 @@ fn emit_typed_call_expr(func: String, args: List, inferred: InferredNo else { match inferred { Some { value: Resolved { node: ret_type } } => - let resolved_ret = resolve_scrutinee_type_node(env: scope.type_env, n: ret_type) + let resolved_ret = ret_type if node_is_optional(n: resolved_ret) { - type_needs_rc(env: scope.type_env, type_node: with_required_cardinality(n: resolved_ret)) + type_needs_rc(type_node: with_required_cardinality(n: resolved_ret)) } else { false } _ => false } @@ -20536,7 +20585,7 @@ fn emit_intrinsic_typed_method_call(intrinsic: IntrinsicMethod, fold_accumulator // RC4: If the receiver is Optional, emit .map(|p| body) to preserve Option type. // Otherwise use the standard for-loop collect pattern for lists. let recv_is_optional = match receiver.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } if recv_is_optional { @@ -20680,7 +20729,7 @@ fn emit_intrinsic_typed_method_call(intrinsic: IntrinsicMethod, fold_accumulator // Annotate sort_by closure params with the element type to avoid E0282. let elem_type_str = match receiver.inferred { Some { value: Resolved { node: rt } } => - let resolved = resolve_scrutinee_type_node(env: scope.type_env, n: rt) + let resolved = rt let elem = for_each_element_type_node(n: resolved) // Dynamic audit: correct guard — if sort_by element type is Dynamic, // fall back to "_" so Rust infers the closure param type. @@ -20880,15 +20929,6 @@ fn emit_typed_let(name: String, value: Node, body: Node?, registry: Map, span: SourceSpan, registry: Map, scope: InferScope, rc_types: Map, emit_info: EmitGraphInfo) -> String { - let inferred = infer_record_lit(type_name: type_name, field_inits: fields, span: span, scope: scope) - match inferred.typed.expr_data { - ExprRecordLit { type_name: tn, fields: fs, parent_enum: parent_enum } => - emit_typed_record_lit(type_name: tn, fields: fs, parent_enum: parent_enum, resolved_type: rt_type(n: inferred.typed), registry: registry, scope: scope, depth: 0, vtoe: empty_map(), rc_types: rc_types, emit_info: emit_info) - _ => - "compile_error!(\"internal error: infer_record_lit did not produce ExprRecordLit\")" - } -} fn is_optional_struct_field(emit_info: EmitGraphInfo, struct_name: String, field_name: String) -> Bool { match lookup_emit_type_summary(emit_info: emit_info, type_name: struct_name) { @@ -20916,10 +20956,10 @@ fn is_already_optional(texpr: Node, emit_info: EmitGraphInfo, scope: InferScope) // then resolve to the definition. resolve_scrutinee_type_node can // lose use-site cardinality when resolving to the type definition. match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: rt) || node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => match map_get(scope.locals, n) { - Some { value: binding } => node_is_optional(n: binding.resolved) || node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: binding.resolved)) + Some { value: binding } => node_is_optional(n: binding.resolved) None => false } } @@ -20941,26 +20981,26 @@ fn is_already_optional(texpr: Node, emit_info: EmitGraphInfo, scope: InferScope) else { match b.inferred { Some { value: Resolved { node: base_type } } => - let resolved_base = resolve_scrutinee_type_node(env: scope.type_env, n: base_type) + let resolved_base = base_type if is_optional_struct_field(emit_info: emit_info, struct_name: resolved_base.name, field_name: f) { true } else { // Fallback: struct may not be in type_summaries; check expression return type match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } } _ => match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } } } _ => match texpr.inferred { - Some { value: Resolved { node: rt } } => node_is_optional(n: rt) || node_is_optional(n: resolve_scrutinee_type_node(env: scope.type_env, n: rt)) + Some { value: Resolved { node: rt } } => node_is_optional(n: rt) _ => false } } diff --git a/src/v2/stage0/src/infer.rs b/src/v2/stage0/src/infer.rs index 6759f617251..4ec9964ecce 100644 --- a/src/v2/stage0/src/infer.rs +++ b/src/v2/stage0/src/infer.rs @@ -2939,7 +2939,7 @@ pub fn build_module_context(contributions: Rc>>, parent __acc_6 = { let __rc_9 = __acc_6; let mut __map_ins_8 = Rc::try_unwrap(__rc_9).unwrap_or_else(|rc| (*rc).clone()); - __map_ins_8.insert(__elem_7.name.clone(), Rc::new(TypeBinding { name: __elem_7.name.clone(), resolved: leaf_node(&__elem_5.name) })); + __map_ins_8.insert(__elem_7.name.clone(), Rc::new(TypeBinding { name: __elem_7.name.clone(), resolved: __elem_5.resolved.clone() })); Rc::new(__map_ins_8) }; } @@ -2960,43 +2960,31 @@ pub fn build_module_context(contributions: Rc>>, parent Rc::new(__map_merged_10) }; let resolve_result = resolve_func_sigs(all_declared_sigs.clone(), local.resolved_items.clone(), &module_name); - let optional_locals = { - let __rc_13 = env_variant_locals; - let mut __map_ins_12 = Rc::try_unwrap(__rc_13).unwrap_or_else(|rc| (*rc).clone()); - __map_ins_12.insert("Some".to_string(), Rc::new(TypeBinding { name: "Some".to_string(), resolved: leaf_node("Optional") })); - Rc::new(__map_ins_12) -}; - let optional_locals = { - let __rc_15 = optional_locals; - let mut __map_ins_14 = Rc::try_unwrap(__rc_15).unwrap_or_else(|rc| (*rc).clone()); - __map_ins_14.insert("None".to_string(), Rc::new(TypeBinding { name: "None".to_string(), resolved: leaf_node("Optional") })); - Rc::new(__map_ins_14) -}; let all_locals = { - let mut __acc_20 = optional_locals.clone(); - for __elem_21 in ({ - let __rc_16 = merged_scope.svc_locals.clone(); - let __map_unwrapped_17 = Rc::try_unwrap(__rc_16).unwrap_or_else(|rc| (*rc).clone()); - let mut __entries_18 = __map_unwrapped_17.into_iter().collect::>(); - __entries_18.sort_by(|a, b| a.0.cmp(&b.0)); - let __values_19 = __entries_18.into_iter().map(|(_, value)| value).collect::>(); - Rc::new(__values_19) + let mut __acc_16 = env_variant_locals.clone(); + for __elem_17 in ({ + let __rc_12 = merged_scope.svc_locals.clone(); + let __map_unwrapped_13 = Rc::try_unwrap(__rc_12).unwrap_or_else(|rc| (*rc).clone()); + let mut __entries_14 = __map_unwrapped_13.into_iter().collect::>(); + __entries_14.sort_by(|a, b| a.0.cmp(&b.0)); + let __values_15 = __entries_14.into_iter().map(|(_, value)| value).collect::>(); + Rc::new(__values_15) }).iter().cloned() { - __acc_20 = { - let __rc_23 = __acc_20; - let mut __map_ins_22 = Rc::try_unwrap(__rc_23).unwrap_or_else(|rc| (*rc).clone()); - __map_ins_22.insert(__elem_21.name.clone(), __elem_21.clone()); - Rc::new(__map_ins_22) + __acc_16 = { + let __rc_19 = __acc_16; + let mut __map_ins_18 = Rc::try_unwrap(__rc_19).unwrap_or_else(|rc| (*rc).clone()); + __map_ins_18.insert(__elem_17.name.clone(), __elem_17.clone()); + Rc::new(__map_ins_18) }; } - __acc_20 + __acc_16 }; Rc::new(ModuleContext { resolved_items: local.resolved_items.clone(), func_env: resolve_result.func_env.clone(), svc_registry: merged_scope.svc_registry.clone(), locals: all_locals.clone(), item_registry: local.item_registry.clone(), diagnostics: v2_rt::concat({ - let mut __flat_mapped_26 = Vec::new(); - for __elem_27 in local.diag_chunks.iter().cloned() { - __flat_mapped_26.extend(__elem_27.clone().iter().cloned()); + let mut __flat_mapped_22 = Vec::new(); + for __elem_23 in local.diag_chunks.iter().cloned() { + __flat_mapped_22.extend(__elem_23.clone().iter().cloned()); } - Rc::new(__flat_mapped_26) + Rc::new(__flat_mapped_22) }, resolve_result.diagnostics.clone()) }) } @@ -3312,7 +3300,19 @@ pub fn build_emit_graph_info(modules: Rc>>) -> Rc) -> Rc { diff --git a/src/v2/stage0/src/infer_emit_info.rs b/src/v2/stage0/src/infer_emit_info.rs index f3670e5a1ad..aa4284d2691 100644 --- a/src/v2/stage0/src/infer_emit_info.rs +++ b/src/v2/stage0/src/infer_emit_info.rs @@ -24,6 +24,7 @@ pub struct EmitGraphInfo { pub variant_to_enum: Rc>, pub enum_variant_membership: Rc>, pub field_type_names: Rc>, + pub recursive_type_set: Rc>, } #[derive(Debug, Clone, PartialEq, Eq, Default)] @@ -35,7 +36,7 @@ pub struct EmitInfoBuildState { } pub fn empty_emit_graph_info() -> Rc { - Rc::new(EmitGraphInfo { type_summaries: Rc::new(std::collections::HashMap::new()), variant_to_enum: Rc::new(std::collections::HashMap::new()), enum_variant_membership: Rc::new(std::collections::HashMap::new()), field_type_names: Rc::new(std::collections::HashMap::new()) }) + Rc::new(EmitGraphInfo { type_summaries: Rc::new(std::collections::HashMap::new()), variant_to_enum: Rc::new(std::collections::HashMap::new()), enum_variant_membership: Rc::new(std::collections::HashMap::new()), field_type_names: Rc::new(std::collections::HashMap::new()), recursive_type_set: Rc::new(std::collections::HashMap::new()) }) } pub fn normalize_emit_type_name(type_name: &str) -> String { diff --git a/src/v2/stage0/src/infer_items.rs b/src/v2/stage0/src/infer_items.rs index bdb0df685d3..28dd573325f 100644 --- a/src/v2/stage0/src/infer_items.rs +++ b/src/v2/stage0/src/infer_items.rs @@ -144,7 +144,7 @@ pub fn variant_locals_from_items(items: Rc>>, init: Rc, env: Rc, module_name: &str, de } } else { if (({ - let __len_45 = n.children.clone().len(); - __len_45 as i64 + let __len_46 = n.children.clone().len(); + __len_46 as i64 }) > 0_i64) && is_user_generic_use_site(n.clone(), env.clone()) { let decl = match lookup_type(env.clone(), &n.name) { Some(d) => { @@ -409,8 +409,8 @@ pub fn resolve_node_bounded(n: Rc, env: Rc, module_name: &str, de Rc::new(NodeResolveResult { resolved: Rc::new(Node { name: n.name.clone(), span: n.span.clone(), children: Rc::new(vec!(key_resolved.clone(), val_resolved.clone())), connective: n.connective.clone(), params: n.params.clone(), inferred: n.inferred.clone(), return_cardinality: n.return_cardinality.clone(), uses: n.uses.clone(), body: n.body.clone(), transport: n.transport.clone(), properties: n.properties.clone(), type_annotation: n.type_annotation.clone(), config: n.config.clone(), is_self_recursive: false, has_non_tail_self_call: false, expr_data: Rc::new(ExprData::NoExprData) }), diagnostics: v2_rt::concat(key_diags.clone(), val_diags.clone()) }) } else { if ({ - let __len_44 = n.children.clone().len(); - __len_44 as i64 + let __len_45 = n.children.clone().len(); + __len_45 as i64 }) == 1_i64 { match n.children.clone().first().cloned() { Some(el) => { @@ -427,8 +427,8 @@ pub fn resolve_node_bounded(n: Rc, env: Rc, module_name: &str, de } } else { if ({ - let __len_43 = n.children.clone().len(); - __len_43 as i64 + let __len_44 = n.children.clone().len(); + __len_44 as i64 }) == 0_i64 { if is_recursive_type(env.clone(), &n.name) { Rc::new(NodeResolveResult { resolved: n.clone(), diagnostics: Rc::new(Vec::new()) }) @@ -436,10 +436,28 @@ pub fn resolve_node_bounded(n: Rc, env: Rc, module_name: &str, de match lookup_type(env.clone(), &n.name) { Some(resolved) => { { - let final_resolved = if node_is_optional(n.clone()) { - with_optional_cardinality(resolved.clone()) + let structurally_resolved = if ((node_has_structure(resolved.clone()) == false) && (({ + let __len_43 = resolved.children.clone().len(); + __len_43 as i64 +}) == 0_i64)) && (resolved.inferred.clone().is_some()) { + match rt_node(resolved.clone()).as_ref() { + NodeType::Typed { node: target, .. } => { + target.clone() + } + NodeType::InferError { message: _, span: _, .. } => { + resolved.clone() + } + NodeType::Untyped => { + resolved.clone() + } +} } else { resolved.clone() +}; + let final_resolved = if node_is_optional(n.clone()) { + with_optional_cardinality(structurally_resolved.clone()) +} else { + structurally_resolved.clone() }; Rc::new(NodeResolveResult { resolved: final_resolved.clone(), diagnostics: Rc::new(Vec::new()) }) } diff --git a/src/v2/stage0/src/infer_service.rs b/src/v2/stage0/src/infer_service.rs index 3e0c6500587..9335c9d3359 100644 --- a/src/v2/stage0/src/infer_service.rs +++ b/src/v2/stage0/src/infer_service.rs @@ -259,6 +259,26 @@ if __cond { } } +pub fn total_service_count(registry: Rc>>) -> i64 { + { + let mut __acc_4 = 0_i64; + for __elem_5 in ({ + let __rc_0 = registry.clone(); + let __map_unwrapped_1 = Rc::try_unwrap(__rc_0).unwrap_or_else(|rc| (*rc).clone()); + let mut __entries_2 = __map_unwrapped_1.into_iter().collect::>(); + __entries_2.sort_by(|a, b| a.0.cmp(&b.0)); + let __values_3 = __entries_2.into_iter().map(|(_, value)| value).collect::>(); + Rc::new(__values_3) +}).iter().cloned() { + __acc_4 = __acc_4.clone() + ({ + let __len_6 = __elem_5.service_names.clone().len(); + __len_6 as i64 +}); + } + __acc_4 +} +} + pub fn expand_transitive_services(modules: Rc>>, registry: Rc>>, remaining_passes: i64) -> Rc>> { stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { let mut __tco_p_modules = modules; @@ -271,7 +291,12 @@ pub fn expand_transitive_services(modules: Rc>>, registry: R if remaining_passes.clone() <= 0_i64 { break registry.clone(); } else { + let before = total_service_count(registry.clone()); let next = expand_transitive_services_once(modules.clone(), registry.clone()); + let after = total_service_count(next.clone()); + if before == after { + break registry.clone(); +} else { { let __tco_0 = modules.clone(); let __tco_1 = next.clone(); @@ -282,6 +307,7 @@ pub fn expand_transitive_services(modules: Rc>>, registry: R continue; } +}; }; } }) diff --git a/src/v2/stage0/src/infer_types.rs b/src/v2/stage0/src/infer_types.rs index acf5a71eb1c..16c2248cefb 100644 --- a/src/v2/stage0/src/infer_types.rs +++ b/src/v2/stage0/src/infer_types.rs @@ -11,10 +11,6 @@ pub fn child_inferred_or_name(ch: Rc) -> Rc { } } -pub fn leaf_node(name: &str) -> Rc { - Rc::new(Node { name: name.to_string(), span: SourceSpan { start: 0_i64, end: 0_i64 }, children: Rc::new(Vec::new()), connective: None, params: Rc::new(Vec::new()), inferred: None, return_cardinality: Cardinality::Required, uses: Rc::new(Vec::new()), body: None, transport: None, properties: Rc::new(Vec::new()), type_annotation: None, config: None, is_self_recursive: false, has_non_tail_self_call: false, expr_data: Rc::new(ExprData::NoExprData) }) -} - pub fn rt_type(n: Rc) -> Rc { match rt_node(n.clone()).as_ref() { NodeType::Typed { node: rt, .. } => { @@ -29,14 +25,6 @@ pub fn rt_type(n: Rc) -> Rc { } } -pub fn with_optional_cardinality(n: Rc) -> Rc { - Rc::new(Node { name: n.name.clone(), span: n.span.clone(), children: n.children.clone(), connective: n.connective.clone(), params: n.params.clone(), inferred: n.inferred.clone(), return_cardinality: Cardinality::CardOptional, uses: n.uses.clone(), body: n.body.clone(), transport: n.transport.clone(), properties: n.properties.clone(), type_annotation: n.type_annotation.clone(), config: n.config.clone(), is_self_recursive: n.is_self_recursive.clone(), has_non_tail_self_call: n.has_non_tail_self_call.clone(), expr_data: n.expr_data.clone() }) -} - -pub fn with_required_cardinality(n: Rc) -> Rc { - Rc::new(Node { name: n.name.clone(), span: n.span.clone(), children: n.children.clone(), connective: n.connective.clone(), params: n.params.clone(), inferred: n.inferred.clone(), return_cardinality: Cardinality::Required, uses: n.uses.clone(), body: n.body.clone(), transport: n.transport.clone(), properties: n.properties.clone(), type_annotation: n.type_annotation.clone(), config: n.config.clone(), is_self_recursive: n.is_self_recursive.clone(), has_non_tail_self_call: n.has_non_tail_self_call.clone(), expr_data: n.expr_data.clone() }) -} - pub fn container_node(kind_name: &str, element: Rc) -> Rc { Rc::new(Node { name: kind_name.to_string(), span: SourceSpan { start: 0_i64, end: 0_i64 }, children: Rc::new(vec!(element.clone())), connective: None, params: Rc::new(Vec::new()), inferred: None, return_cardinality: Cardinality::Required, uses: Rc::new(Vec::new()), body: None, transport: None, properties: Rc::new(Vec::new()), type_annotation: None, config: None, is_self_recursive: false, has_non_tail_self_call: false, expr_data: Rc::new(ExprData::NoExprData) }) } @@ -87,22 +75,6 @@ pub fn node_is_bridge_dynamic_name(n: Rc) -> bool { n.name.clone() == "Dynamic" } -pub fn no_span() -> SourceSpan { - SourceSpan { start: 0_i64, end: 0_i64 } -} - -pub fn node_is_product(n: Rc) -> bool { - (n.connective.clone().is_some()) && (n.connective.clone() == Some(Connective::Conj)) -} - -pub fn node_is_coproduct(n: Rc) -> bool { - (n.connective.clone().is_some()) && (n.connective.clone() == Some(Connective::Disj)) -} - -pub fn node_has_structure(n: Rc) -> bool { - node_is_product(n.clone()) || node_is_coproduct(n.clone()) -} - pub fn node_is_container(n: Rc) -> bool { (((n.name.clone() == "List") || (n.name.clone() == "Set")) || (n.name.clone() == "NonEmptyList")) || (n.name.clone() == "NonEmptySet") } diff --git a/src/v2/stage0/src/parse.rs b/src/v2/stage0/src/parse.rs index 14d99a8a52b..b5fd626eb96 100644 --- a/src/v2/stage0/src/parse.rs +++ b/src/v2/stage0/src/parse.rs @@ -1,5 +1,4 @@ use crate::v2_core::*; -use crate::infer_types::*; use crate::v2_rt; use std::collections::HashMap; use std::rc::Rc; @@ -3597,9 +3596,8 @@ pub fn parse_callable_type_expr(tokens: Rc>>, state: Rc) -> String { let __len_0 = n.children.clone().len(); __len_0 as i64 }) > 0_i64; - if node_is_optional(n.clone()) { + let is_optional = n.return_cardinality.clone() == Cardinality::CardOptional; + if is_optional.clone() { v2_rt::concat("Optional_".to_string(), node_to_name_str(with_required_cardinality(n.clone()))) } else { if n.name.clone() == "Map" { diff --git a/src/v2/stage0/src/tokenize.rs b/src/v2/stage0/src/tokenize.rs index 2b6ae64ed3b..636d5fff0e5 100644 --- a/src/v2/stage0/src/tokenize.rs +++ b/src/v2/stage0/src/tokenize.rs @@ -307,14 +307,6 @@ impl Default for StringScanResult { } impl StringScanResult { - pub fn end_pos(&self) -> i64 { - match self { - StringScanResult::ClosedString { end_pos, .. } => end_pos.clone(), - StringScanResult::InterpolationStart { end_pos, .. } => end_pos.clone(), - StringScanResult::UnterminatedString { end_pos, .. } => end_pos.clone() - } - } - pub fn content(&self) -> String { match self { StringScanResult::ClosedString { content, .. } => content.clone(), @@ -322,6 +314,14 @@ impl StringScanResult { StringScanResult::UnterminatedString { content, .. } => content.clone() } } + + pub fn end_pos(&self) -> i64 { + match self { + StringScanResult::ClosedString { end_pos, .. } => end_pos.clone(), + StringScanResult::InterpolationStart { end_pos, .. } => end_pos.clone(), + StringScanResult::UnterminatedString { end_pos, .. } => end_pos.clone() + } + } } pub fn scan_string(source: Rc, pos: Rc) -> Rc { diff --git a/src/v2/stage0/src/v2_core.rs b/src/v2/stage0/src/v2_core.rs index 45d0454c0c3..996630a60fa 100644 --- a/src/v2/stage0/src/v2_core.rs +++ b/src/v2/stage0/src/v2_core.rs @@ -1441,3 +1441,31 @@ pub fn is_token_node(n: Rc) -> bool { } } +pub fn leaf_node(name: &str) -> Rc { + Rc::new(Node { name: name.to_string(), span: SourceSpan { start: 0_i64, end: 0_i64 }, children: Rc::new(Vec::new()), connective: None, params: Rc::new(Vec::new()), inferred: None, return_cardinality: Cardinality::Required, uses: Rc::new(Vec::new()), body: None, transport: None, properties: Rc::new(Vec::new()), type_annotation: None, config: None, is_self_recursive: false, has_non_tail_self_call: false, expr_data: Rc::new(ExprData::NoExprData) }) +} + +pub fn no_span() -> SourceSpan { + SourceSpan { start: 0_i64, end: 0_i64 } +} + +pub fn with_optional_cardinality(n: Rc) -> Rc { + Rc::new(Node { name: n.name.clone(), span: n.span.clone(), children: n.children.clone(), connective: n.connective.clone(), params: n.params.clone(), inferred: n.inferred.clone(), return_cardinality: Cardinality::CardOptional, uses: n.uses.clone(), body: n.body.clone(), transport: n.transport.clone(), properties: n.properties.clone(), type_annotation: n.type_annotation.clone(), config: n.config.clone(), is_self_recursive: n.is_self_recursive.clone(), has_non_tail_self_call: n.has_non_tail_self_call.clone(), expr_data: n.expr_data.clone() }) +} + +pub fn with_required_cardinality(n: Rc) -> Rc { + Rc::new(Node { name: n.name.clone(), span: n.span.clone(), children: n.children.clone(), connective: n.connective.clone(), params: n.params.clone(), inferred: n.inferred.clone(), return_cardinality: Cardinality::Required, uses: n.uses.clone(), body: n.body.clone(), transport: n.transport.clone(), properties: n.properties.clone(), type_annotation: n.type_annotation.clone(), config: n.config.clone(), is_self_recursive: n.is_self_recursive.clone(), has_non_tail_self_call: n.has_non_tail_self_call.clone(), expr_data: n.expr_data.clone() }) +} + +pub fn node_is_product(n: Rc) -> bool { + (n.connective.clone().is_some()) && (n.connective.clone() == Some(Connective::Conj)) +} + +pub fn node_is_coproduct(n: Rc) -> bool { + (n.connective.clone().is_some()) && (n.connective.clone() == Some(Connective::Disj)) +} + +pub fn node_has_structure(n: Rc) -> bool { + node_is_product(n.clone()) || node_is_coproduct(n.clone()) +} + diff --git a/src/v2/tests/src/infer_semantics.rs b/src/v2/tests/src/infer_semantics.rs index 0d710db8608..24557f6bd42 100644 --- a/src/v2/tests/src/infer_semantics.rs +++ b/src/v2/tests/src/infer_semantics.rs @@ -4,9 +4,9 @@ use v2_compiler::infer_access; use v2_compiler::infer_env::TypeEnv; use v2_compiler::infer_patterns::{self, NodeLookupStatus}; use v2_compiler::infer_types::{ - bare_map_node, container_node, leaf_node, map_node, with_optional_cardinality, + bare_map_node, container_node, map_node, }; -use v2_compiler::v2_core::{Cardinality, InferredNode, MatchArm, MatchPattern, Node, NodeType, SourceSpan}; +use v2_compiler::v2_core::{Cardinality, InferredNode, MatchArm, MatchPattern, Node, NodeType, SourceSpan, leaf_node, with_optional_cardinality}; fn zero_span() -> SourceSpan { SourceSpan { start: 0, end: 0 } diff --git a/src/v2/tests/src/pipeline.rs b/src/v2/tests/src/pipeline.rs index cc781c30b2e..ab64cfc2076 100644 --- a/src/v2/tests/src/pipeline.rs +++ b/src/v2/tests/src/pipeline.rs @@ -224,6 +224,13 @@ fn indirect_type_alias_cycles_do_not_recurse_forever() { assert_no_diagnostics(&result); } +#[test] +fn chained_type_alias_field_access() { + let source = "module test\ntype A { val: Int }\ntype B = A\ntype C = B\nfn get(x: C) -> Int {\n x.val\n}\n"; + let result = compile_dag(source); + assert_no_diagnostics(&result); +} + #[test] fn string_index_and_slice_emit_runtime_calls() { let source = "module test\nfn char_at(s: String) -> String {\n s[0]\n}\nfn substr(s: String) -> String {\n s[0..1]\n}\n";