Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
463 changes: 461 additions & 2 deletions ROADMAP.md

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions src/v2/00_core.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
14 changes: 6 additions & 8 deletions src/v2/02_parse.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
// =========================================================================
Expand Down Expand Up @@ -1675,10 +1674,8 @@ fn parse_callable_type_expr(tokens: List<Token>, 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.
Expand Down Expand Up @@ -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))) }
Expand Down
7 changes: 3 additions & 4 deletions src/v2/04_access.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

// =========================================================================
Expand Down
10 changes: 6 additions & 4 deletions src/v2/04_emit_info.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -41,6 +41,7 @@ type EmitGraphInfo {
variant_to_enum: Map<String, String>
enum_variant_membership: Map<String, Bool>
field_type_names: Map<String, String>
recursive_type_set: Map<String, Bool>
}

type EmitInfoBuildState {
Expand All @@ -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()
}
}

Expand Down
35 changes: 20 additions & 15 deletions src/v2/04_infer.dag
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,27 @@ 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,
is_int_type_node, is_string_type_node, is_bool_type_node, is_float_type_node,
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 {
Expand Down Expand Up @@ -2214,12 +2214,17 @@ fn build_module_context(contributions: List<ItemContribution>,
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 }
Expand All @@ -2239,15 +2244,11 @@ fn build_module_context(contributions: List<ItemContribution>,
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 {
Expand Down Expand Up @@ -2503,11 +2504,15 @@ fn build_emit_graph_info(modules: List<TypedModule>) -> 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
}
}

Expand Down
9 changes: 5 additions & 4 deletions src/v2/04_items.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Node>, init: Map<String, TypeBinding>) -> Map<String, TypeBinding> {
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
Comment thread
cursor[bot] marked this conversation as resolved.
Resolved
})
)
} else { acc }
Expand Down
6 changes: 3 additions & 3 deletions src/v2/04_lookup.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions src/v2/04_method.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 3 additions & 3 deletions src/v2/04_patterns.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
26 changes: 21 additions & 5 deletions src/v2/04_resolve.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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" {
Expand Down
20 changes: 17 additions & 3 deletions src/v2/04_service.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -166,11 +166,25 @@ fn expand_transitive_services_once(modules: List<TypedModule>, registry: Map<Str
)
}

fn total_service_count(registry: Map<String, ItemInfo>) -> 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<TypedModule>, registry: Map<String, ItemInfo>, remaining_passes: Int) -> Map<String, ItemInfo> {
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) }
}
}

Expand Down
Loading
Loading