diff --git a/src/v2/compiler/complexity_gate/comprep_pipeline_bridge.dag b/src/v2/compiler/complexity_gate/comprep_pipeline_bridge.dag new file mode 100644 index 00000000000..47c5f10ed9c --- /dev/null +++ b/src/v2/compiler/complexity_gate/comprep_pipeline_bridge.dag @@ -0,0 +1,64 @@ +// Scope: shared tokenize→parse→normalize→resolve bridge for COMPREP test-subject producers. +// Single authority for the pipeline chain; per-fn producers pass their GrammarRoot + +// production_name and call comprep_source_resolved_root to obtain the resolved Arrow Node. +// Consumers: wave-1 subject producers (can migrate), D2 rewrite-rule test-subject producers. +// Design: DESIGN.md §2 horizontal — one pipeline path, derived per subject (not forked per fn). +// Hardwired: dag_language_model_wave1 (MVP1 .dag surface). New media → new bridge or param. + +module v2.test.complexity_gate.comprep_pipeline_bridge + +import v2.compiler.normalize { normalize } +import v2.compiler.parse { ValidatedGrammarRoot, grammar_validate_for_parse, parse_production_tree } +import v2.compiler.resolve { resolve } +import v2.compiler.tokenize { tokenize } +import v2.extdeps.languages.dag { dag_language_model_wave1 } +import v2.std.collection { List } +import v2.std.compilers.lexing { Token } +import v2.std.diagnostic { Accepted, Outcome, Rejected, bind_outcome, port_locus } +import v2.std.grammar { GrammarRoot, ParseTree } +import v2.std.node { Node, Symbol } +import v2.std.text { String } + + +// Run the four-stage comprep pipeline on a source snippet: +// tokenize → validate+parse (parse_root, production_name) → normalize → resolve +// Returns the resolved Arrow Node, ready for body extraction by the caller. +// +// file: diagnostic locus symbol — callers pass their own (e.g. ^d2_nested_slow_form_file). +// parse_root / production_name: the grammar this snippet is parsed under — callers construct. +// Grammar validation is fail-closed before any tokenize work. +fn comprep_source_resolved_root( + source_text: String, + parse_root: GrammarRoot, + production_name: Symbol, + file: Symbol +) -> Outcome { + let lm = dag_language_model_wave1() + match grammar_validate_for_parse( + root: parse_root, + at: port_locus(port: ^comprep_pipeline_bridge_grammar_locus) + ) { + Rejected { diagnostics: r } => Rejected { diagnostics: r } + Accepted { value: validated, diagnostics: _ } => + bind_outcome( + o: tokenize(text: source_text, file: file, rules: lm.lex), + f: fn(token_stream) { + bind_outcome( + o: parse_production_tree( + tokens: token_stream.tokens, + validated: validated, + production_name: production_name + ), + f: fn(parsed) { + bind_outcome( + o: normalize(parse_tree: parsed), + f: fn(normalized) { + resolve(tree: normalized, lm: lm) + } + ) + } + ) + } + ) + } +} diff --git a/src/v2/lens/cost.dag b/src/v2/lens/cost.dag index e9ddbd8ce5a..11b3e2e3750 100644 --- a/src/v2/lens/cost.dag +++ b/src/v2/lens/cost.dag @@ -189,6 +189,11 @@ fn base_cost_for_connective(c: Connective, n: Node) -> SymbolicCost { Disj => zero_cost() Arrow => unit_cost() Cardinality => linear_in_node(n: n) + // 🟡 cost-model gap: Instantiation/recursion-depth → ExponentialCost is unmodeled. + // Self-calling folds to ~unit_cost (constant), so complexity_lens cannot detect + // ClassExponential for naive recursive functions. Blocks naive-recursion→memoize + // rewrite detection. dissolve-on: Instantiation base cost models recursion depth + // (model-before-implement; new modeling, not a patch in this site). Instantiation => unit_cost() } } @@ -391,7 +396,11 @@ fn compose_child_cost( ) -> SymbolicCost { match mode { SequentialCost => symbolic_sequential(a: acc, b: child) - AlternativeCost => symbolic_max(a: acc, b: child) + // Per-alternative factor = max(acc, max(branch, one decision step)). symbolic_max + // floors ONLY zero: max(acc, max(0,1))=max(acc,1) — a zero-cost branch still costs + // one decision; max(acc, max(1,1))=max(acc,1) and max(acc, max(linear,1))=max(acc,linear) + // leave unit/linear branches unchanged. symbolic_max itself absorbing zero untouched. + AlternativeCost => symbolic_max(a: acc, b: symbolic_max(a: child, b: unit_cost())) // Per-iteration factor = max(body, one control step). symbolic_max floors ONLY // zero: max(0,1)=1 (empty body -> O(n)); max(1,1)=1 and max(linear,1)=linear leave // unit/linear bodies unchanged vs plain symbolic_product. Absorbing zero untouched. diff --git a/src/v2/lens/cost/disj_alternative_floor_test.dag b/src/v2/lens/cost/disj_alternative_floor_test.dag new file mode 100644 index 00000000000..0f148f0ab4c --- /dev/null +++ b/src/v2/lens/cost/disj_alternative_floor_test.dag @@ -0,0 +1,71 @@ +// Status: EXECUTED Bool witness — all-zero Disj arms still cost at least O(1). +// +// Pins the cost-lens AlternativeCost floor (cost.dag `compose_child_cost` AlternativeCost arm). +// Two checks together pin the CORRECT fix uniquely: +// (1) bug-fix: cost_lens(Disj([Atom, Atom])) projects unit_cost(), NOT zero_cost() — +// selecting between alternatives has a non-trivial cost even when every branch body is free. +// (2) control: symbolic_max's zero-absorption is PRESERVED — symbolic_max(0, 0) == 0. +// (2) is what distinguishes the right fix (symbolic_max(child, unit) floor at the +// AlternativeCost layer) from the wrong one (removing zero-absorption inside symbolic_max): +// BOTH make (1) green, but the wrong fix makes (2) RED. + +module v2.test.lens_cost.disj_alternative_floor + +import v2.lens.cost { + cost_lens, + symbolic_max, + unit_cost, + zero_cost +} +import v2.std.logic { Bool } +import v2.std.node { + Atom, + Disj, + Edge, + Node, + Positional, + SyntheticOccurrence, + TypeNode +} +import v2.std.witness { Holds } + +// Two zero-cost Disj arms (Atom base cost = zero_cost). +data disj_floor_arm_a: Node = Node { + kind: TypeNode { connective: Atom { identity: ^disj_floor_arm_a_atom } }, + children: [], + occurrence_id: SyntheticOccurrence +} + +data disj_floor_arm_b: Node = Node { + kind: TypeNode { connective: Atom { identity: ^disj_floor_arm_b_atom } }, + children: [], + occurrence_id: SyntheticOccurrence +} + +// Disj with two zero-cost arms — the structure cost_lens folds. +data disj_floor_input: Node = Node { + kind: TypeNode { connective: Disj }, + children: [ + Edge { label: Positional, target: disj_floor_arm_a }, + Edge { label: Positional, target: disj_floor_arm_b } + ], + occurrence_id: SyntheticOccurrence +} + +// (1) Bug-fix: a Disj over zero-cost branches projects unit_cost(), not zero_cost(). +fn disj_floor_projects_unit() -> Bool { + match cost_lens(n: disj_floor_input) { + Holds { value: actual } => actual == unit_cost() + _ => false + } +} + +// (2) Control: symbolic_max keeps absorbing zero — max(0, 0) == 0. The wrong fix +// (removing zero-absorption inside symbolic_max) makes this return unit_cost(), going RED here. +fn symbolic_max_absorbs_zero() -> Bool { + symbolic_max(a: zero_cost(), b: zero_cost()) == zero_cost() +} + +test fn disj_alternative_floor_holds() -> Bool { + disj_floor_projects_unit() && symbolic_max_absorbs_zero() +} diff --git a/src/v2/lens/rewrite/nested_membership.dag b/src/v2/lens/rewrite/nested_membership.dag new file mode 100644 index 00000000000..35f962b3548 --- /dev/null +++ b/src/v2/lens/rewrite/nested_membership.dag @@ -0,0 +1,51 @@ +module v2.lens.rewrite.nested_membership + +import v2.std.algebra { is_empty } +import v2.std.collection { Absent, Optional, Present, list_at_optional, optional_absent, optional_present } +import v2.std.node { Cardinality, Node, SyntheticOccurrence, TypeNode } +import v2.std.node_query { node_labeled_child_edges, node_positional_child_targets } + +// Rewrite: nested Cardinality O(n·m) → single Cardinality O(n). +// Models: precompute membership set(inner body); replace inner O(n) scan with O(1) lookup. +// Precondition: outer is Cardinality with exactly one positional child (= inner Cardinality) +// and no named children (outer body has no mutation coordinate — safe to hoist inner.children). +// Returns: Present { value: node } with inner.children in the outer Cardinality slot. +// +// 🟡 grammar-gap: MVP1 dag grammar cannot produce nested-loop source text; +// end-to-end witness uses hand-constructed Node (single-level loop through bridge confirms liveness). +// dissolve-on: dag grammar production for nested-cardinality source (writer-semantics round-trip). + +// Helper: takes the candidate inner Node (typed) and checks if it is Cardinality, returning +// the hoisted rewrite if so. Extracted from try_nested_membership_rewrite so that the +// `inner` binding from list_at_optional — otherwise inferred as generic T — is concretized +// as Node by the typed parameter here (same pattern as node_is_callee_reference in node_query). +fn try_hoist_inner_cardinality(inner: Node) -> Optional { + match inner.kind { + TypeNode { connective: Cardinality } => + optional_present(value: Node { + kind: TypeNode { connective: Cardinality }, + children: inner.children, + occurrence_id: SyntheticOccurrence + }) + _ => optional_absent() + } +} + +fn try_nested_membership_rewrite(n: Node) -> Optional { + match n.kind { + TypeNode { connective: Cardinality } => + if is_empty(xs: node_labeled_child_edges(node: n)) { + match list_at_optional(xs: node_positional_child_targets(node: n), index: 0) { + Absent => optional_absent() + Present { value: inner } => + match list_at_optional(xs: node_positional_child_targets(node: n), index: 1) { + Present { value: _ } => optional_absent() + Absent => try_hoist_inner_cardinality(inner: inner) + } + } + } else { + optional_absent() + } + _ => optional_absent() + } +} diff --git a/src/v2/lens/rewrite/nested_membership_test.dag b/src/v2/lens/rewrite/nested_membership_test.dag new file mode 100644 index 00000000000..6897e96c796 --- /dev/null +++ b/src/v2/lens/rewrite/nested_membership_test.dag @@ -0,0 +1,172 @@ +// Status: EXECUTED Bool witnesses — 4-witness matrix (a/b/c/d) + end-to-end pipeline guard. +// +// Pins the nested-membership rewrite rule (try_nested_membership_rewrite): +// (a) fires: Present on a nested Cardinality → Cardinality input. +// (b) class-drop: slow form = ClassPolynomial(2), fast form = ClassLinear. +// (c) correct: rewritten children equal inner.children (the hoist is exact). +// (d) control: outer Cardinality with a Named edge → Absent (mutation coordinate guards fire). +// (e) pipeline guard: comprep bridge resolves MVP1 loop source (bridge proved live). +// Bridge witness limited to single-level loop (grammar gap below); rewrite correctly declines. +// +// 🟡 grammar-gap: nested-cardinality source text (e.g. "fn outer() -> Int { loop loop 1 }") +// not confirmed parseable in MVP1 grammar; hand-constructed Nodes drive witnesses a-d. +// dissolve-on: dag grammar nested-loop production + resolve round-trip verification. + +module v2.test.lens_rewrite.nested_membership + +import v2.lens.complexity { ComplexityBound, complexity_bound_class, complexity_lens } +import v2.lens.cost { AsymptoticClass, ClassLinear, ClassPolynomial, PolynomialDegree, polynomial_degree_two } +import v2.lens.rewrite.nested_membership { try_nested_membership_rewrite } +import v2.std.collection { Absent, Optional, Present, Set, list_at_optional } +import v2.std.diagnostic { Accepted, Rejected } +import v2.std.grammar { GrammarProduction, GrammarRoot } +import v2.std.logic { Bool } +import v2.std.node { + Atom, + Cardinality, + ComputationNode, + Edge, + Named, + Node, + Positional, + SyntheticOccurrence, + Transform, + TypeNode +} +import v2.std.witness { Holds, Witness } +import v2.extdeps.languages.dag { + dag_grammar_production_mvp1_fn_loop, + dag_mvp1_loop_source_text +} +import v2.test.complexity_gate.comprep_pipeline_bridge { comprep_source_resolved_root } + +// Inner body of the iteration: a Transform over an Atom leaf. +data nested_slow_body_leaf: Node = Node { + kind: TypeNode { connective: Atom { identity: ^nested_membership_body_atom } }, + children: [], + occurrence_id: SyntheticOccurrence +} + +data nested_slow_body: Node = Node { + kind: ComputationNode { behavior: Transform }, + children: [ + Edge { label: Positional, target: nested_slow_body_leaf } + ], + occurrence_id: SyntheticOccurrence +} + +// Inner Cardinality: O(n) iteration over the body. +data nested_inner_card: Node = Node { + kind: TypeNode { connective: Cardinality }, + children: [ + Edge { label: Positional, target: nested_slow_body } + ], + occurrence_id: SyntheticOccurrence +} + +// Outer Cardinality wrapping inner Cardinality: the nested-membership O(n²) pattern. +// No Named children → precondition holds → rewrite fires. +data nested_slow_input: Node = Node { + kind: TypeNode { connective: Cardinality }, + children: [ + Edge { label: Positional, target: nested_inner_card } + ], + occurrence_id: SyntheticOccurrence +} + +// Expected fast form: outer Cardinality with inner.children directly. +// inner.children = [Edge { Positional, nested_slow_body }] → single-level O(n). +data nested_fast_expected: Node = Node { + kind: TypeNode { connective: Cardinality }, + children: [ + Edge { label: Positional, target: nested_slow_body } + ], + occurrence_id: SyntheticOccurrence +} + +// Non-firing control: outer Cardinality with a Named edge (mutation coordinate present). +// Precondition check requires no Named children → rewrite must decline. +data nested_named_control_inner: Node = Node { + kind: TypeNode { connective: Cardinality }, + children: [], + occurrence_id: SyntheticOccurrence +} + +data nested_named_control_side: Node = Node { + kind: TypeNode { connective: Atom { identity: ^nested_membership_named_side_atom } }, + children: [], + occurrence_id: SyntheticOccurrence +} + +data nested_named_control: Node = Node { + kind: TypeNode { connective: Cardinality }, + children: [ + Edge { label: Positional, target: nested_named_control_inner }, + Edge { label: Named { name: ^nested_membership_mutation_coord }, target: nested_named_control_side } + ], + occurrence_id: SyntheticOccurrence +} + +// (a) Rewrite fires on the nested-Cardinality input. +test fn nested_membership_rewrite_fires() -> Bool { + match try_nested_membership_rewrite(n: nested_slow_input) { + Present { value: _ } => true + Absent => false + } +} + +// (b) Class drop: slow = ClassPolynomial(degree=2), fast = ClassLinear. +// Confirms the rewrite buys an asymptotic improvement (O(n²) → O(n)). +test fn nested_membership_class_drop() -> Bool { + match complexity_lens(n: nested_slow_input) { + Holds { value: slow_b } => + match complexity_lens(n: nested_fast_expected) { + Holds { value: fast_b } => + complexity_bound_class(b: slow_b) == ClassPolynomial { degree: polynomial_degree_two() } && + complexity_bound_class(b: fast_b) == ClassLinear + _ => false + } + _ => false + } +} + +// (c) Rewrite correctness: the rewritten node's children equal inner_card.children exactly. +// The hoist is not a copy-and-reconstruct — it threads inner.children through directly. +test fn nested_membership_rewrite_correct() -> Bool { + match try_nested_membership_rewrite(n: nested_slow_input) { + Present { value: rewritten } => rewritten.children == nested_inner_card.children + Absent => false + } +} + +// (d) Non-firing control: outer Cardinality with a Named edge returns Absent. +// Distinguishes the right precondition (no-mutation guard) from an over-eager fire. +test fn nested_membership_nofiring_control() -> Bool { + match try_nested_membership_rewrite(n: nested_named_control) { + Absent => true + Present { value: _ } => false + } +} + +// (e) End-to-end pipeline guard: comprep bridge resolves MVP1 loop source text (bridge proved live). +// Grammar gap: nested Cardinality source text not parseable in MVP1; single-level loop used here. +// The rewrite correctly declines on a single-level loop (Absent is expected — not a test failure). +fn nested_membership_loop_grammar_root() -> GrammarRoot { + GrammarRoot { + start: ^dag_production_mvp1_fn_loop, + productions: [dag_grammar_production_mvp1_fn_loop()], + sync_tokens: Set { member: fn(_) { false } } + } +} + +test fn nested_membership_pipeline_guard() -> Bool { + match comprep_source_resolved_root( + source_text: dag_mvp1_loop_source_text, + parse_root: nested_membership_loop_grammar_root(), + production_name: ^dag_production_mvp1_fn_loop, + file: ^nested_membership_pipeline_guard_file + ) { + Accepted { value: _, diagnostics: _ } => true + Rejected { diagnostics: _ } => false + } +}