diff --git a/scripts/regen_runtime_mirrors.py b/scripts/regen_runtime_mirrors.py new file mode 100644 index 00000000000..2d5c3df9bd4 --- /dev/null +++ b/scripts/regen_runtime_mirrors.py @@ -0,0 +1,593 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import re +import sys +from dataclasses import dataclass +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +COMPILER_DIR = ROOT / "src" / "v3" / "compiler" +SRC_DIR = COMPILER_DIR / "src" +AUTHORITY_PATH = COMPILER_DIR / "runtime_mirrors.dag" +SUBSTRATE_PATH = ROOT / "src" / "v3" / "std" / "substrate.dag" + + +TYPE_SHAPE_IMPL_TEMPLATE = """impl TypeShape { + pub fn new(declaration: DeclarationId) -> Self { + Self { declaration } + } +} + +impl From for TypeShape { + fn from(declaration: DeclarationId) -> Self { + Self { declaration } + } +} +""" + + +SOURCE_SPAN_TEMPLATE = """#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SourceSpan { + pub file: String, + pub byte_start: u32, + pub byte_end: u32, +} + +impl SourceSpan { + pub fn new(file: impl Into, byte_start: u32, byte_end: u32) -> Self { + Self { + file: file.into(), + byte_start, + byte_end, + } + } +} +""" + + +CORRECTION_TEMPLATE = """#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Correction { + pub description: String, + pub span: SourceSpan, + pub new_source: String, +} +""" + + +DAG_COST_TEMPLATE = """#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DegreeAtLeastTwo { + DegreeTwo, + DegreeSuccessor { previous: Box }, +} + +impl DegreeAtLeastTwo { + pub const TWO: Self = Self::DegreeTwo; + + pub fn new(value: i64) -> Option { + match value { + 2 => Some(Self::DegreeTwo), + v if v > 2 => Some(Self::DegreeSuccessor { + previous: Box::new(Self::new(v - 1)?), + }), + _ => None, + } + } + + pub fn raw(&self) -> i64 { + match self { + Self::DegreeTwo => 2, + Self::DegreeSuccessor { previous } => previous.raw() + 1, + } + } +} + +type BoxedSymbolicCostList = NonSingletonList>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymbolicCost { + ConstantCost { _0: i64 }, + LinearCost { _0: SizeVariable }, + PolynomialCost { + var: SizeVariable, + degree: DegreeAtLeastTwo, + }, + ProductCost { _0: BoxedSymbolicCostList }, + SumCost { _0: BoxedSymbolicCostList }, + LogCost { _0: SizeVariable }, + UnknownCost { _0: String }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SizeVariable { + pub source_port: PortId, +} + +pub fn sequential(a: SymbolicCost, b: SymbolicCost) -> SymbolicCost { + normalize(SymbolicCost::SumCost { + _0: boxed_cost_list_pair(a, b), + }) +} + +pub fn iterate(bound: SymbolicCost, body: SymbolicCost) -> SymbolicCost { + normalize(SymbolicCost::ProductCost { + _0: boxed_cost_list_pair(bound, body), + }) +} + +pub fn max_path(paths: &[SymbolicCost]) -> SymbolicCost { + paths + .iter() + .fold(SymbolicCost::ConstantCost { _0: 0 }, |acc, candidate| { + if dominates(candidate, &acc) { + candidate.clone() + } else if dominates(&acc, candidate) { + acc + } else { + sequential(acc, candidate.clone()) + } + }) +} + +pub fn normalize(cost: SymbolicCost) -> SymbolicCost { + match cost { + SymbolicCost::SumCost { _0: terms } => { + reduce_sum(drop_zero_terms(boxed_terms_to_vec(&terms))) + } + SymbolicCost::ProductCost { _0: terms } => { + reduce_product(drop_zero_terms(boxed_terms_to_vec(&terms))) + } + other => other, + } +} + +fn boxed_cost_list_pair(a: SymbolicCost, b: SymbolicCost) -> BoxedSymbolicCostList { + BoxedSymbolicCostList { + first: Box::new(a), + second: Box::new(b), + rest: Vec::new(), + } +} + +fn boxed_terms_to_vec(terms: &BoxedSymbolicCostList) -> Vec { + terms.iter().map(|term| term.as_ref().clone()).collect() +} + +fn drop_zero_terms(terms: Vec) -> Vec { + terms + .into_iter() + .filter(|t| !matches!(t, SymbolicCost::ConstantCost { _0: 0 })) + .collect() +} + +fn reduce_sum(mut terms: Vec) -> SymbolicCost { + terms = drop_dominated_in_sum(terms); + match terms.len() { + 0 => SymbolicCost::ConstantCost { _0: 0 }, + 1 => terms.into_iter().next().unwrap(), + _ => SymbolicCost::SumCost { + _0: boxed_cost_list_from_vec(terms), + }, + } +} + +fn reduce_product(terms: Vec) -> SymbolicCost { + match terms.len() { + 0 => SymbolicCost::ConstantCost { _0: 0 }, + 1 => terms.into_iter().next().unwrap(), + 2 => { + let mut iter = terms.into_iter(); + let a = iter.next().unwrap(); + let b = iter.next().unwrap(); + combine_binary_product(a, b) + } + _ => SymbolicCost::ProductCost { + _0: boxed_cost_list_from_vec(terms), + }, + } +} + +fn boxed_cost_list_from_vec(terms: Vec) -> BoxedSymbolicCostList { + NonSingletonList::from_vec(terms.into_iter().map(Box::new).collect()).unwrap() +} + +fn combine_binary_product(a: SymbolicCost, b: SymbolicCost) -> SymbolicCost { + if let (SymbolicCost::LinearCost { _0: va }, SymbolicCost::LinearCost { _0: vb }) = (&a, &b) { + if va == vb { + return SymbolicCost::PolynomialCost { + var: va.clone(), + degree: DegreeAtLeastTwo::TWO, + }; + } + } + SymbolicCost::ProductCost { + _0: boxed_cost_list_pair(a, b), + } +} + +fn drop_dominated_in_sum(terms: Vec) -> Vec { + let mut keep: Vec = Vec::with_capacity(terms.len()); + for term in terms { + let term_dominated = keep.iter().any(|k| dominates(k, &term)); + if term_dominated { + continue; + } + keep.retain(|k| !dominates(&term, k)); + keep.push(term); + } + keep +} + +pub fn dominates(a: &SymbolicCost, b: &SymbolicCost) -> bool { + match a { + SymbolicCost::UnknownCost { .. } => true, + SymbolicCost::ConstantCost { .. } => matches!(b, SymbolicCost::ConstantCost { .. }), + SymbolicCost::LinearCost { _0: va } => match b { + SymbolicCost::ConstantCost { .. } | SymbolicCost::LogCost { .. } => true, + SymbolicCost::LinearCost { _0: vb } => va == vb, + SymbolicCost::PolynomialCost { var: _, degree: _ } => false, + _ => false, + }, + SymbolicCost::PolynomialCost { + var: va, + degree: ka, + } => match b { + SymbolicCost::ConstantCost { .. } | SymbolicCost::LogCost { .. } => true, + SymbolicCost::LinearCost { _0: vb } => va == vb, + SymbolicCost::PolynomialCost { + var: vb, + degree: kb, + } => va == vb && ka.raw() >= kb.raw(), + _ => false, + }, + SymbolicCost::LogCost { _0: va } => match b { + SymbolicCost::ConstantCost { .. } => true, + SymbolicCost::LogCost { _0: vb } => va == vb, + _ => false, + }, + SymbolicCost::ProductCost { _0: terms } | SymbolicCost::SumCost { _0: terms } => { + terms.iter().any(|child| dominates(child.as_ref(), b)) + } + } +} +""" + + +HEADER_TEMPLATE = "// AUTO-GENERATED from `{authority}`.\n// Regenerate instead of hand-editing.\n\n{body}\n" + + +@dataclass +class RecordDef: + name: str + fields: list[tuple[str, str]] + + +@dataclass +class VariantDef: + name: str + kind: str + payload: str | None = None + fields: list[tuple[str, str]] | None = None + + +def parse_inline_fields(source: str) -> list[tuple[str, str]]: + source = source.strip() + if not source: + return [] + fields: list[tuple[str, str]] = [] + for raw_field in source.split(","): + field = raw_field.strip() + if not field: + continue + label, ty = field.split(":", 1) + fields.append((label.strip(), ty.strip())) + return fields + + +def parse_types(path: Path) -> tuple[dict[str, RecordDef], dict[str, list[VariantDef]]]: + lines = path.read_text().splitlines() + records: dict[str, RecordDef] = {} + sums: dict[str, list[VariantDef]] = {} + i = 0 + while i < len(lines): + line = lines[i].strip() + if not line.startswith("type "): + i += 1 + continue + if "{" in line and "=" not in line: + name = re.match(r"type\s+(\w+)(?:<[^>]+>)?\s*\{", line).group(1) + i += 1 + fields: list[tuple[str, str]] = [] + while lines[i].strip() != "}": + field_line = lines[i].strip() + if field_line and ":" in field_line: + label, ty = field_line.split(":", 1) + fields.append((label.strip(), ty.strip())) + i += 1 + records[name] = RecordDef(name=name, fields=fields) + i += 1 + continue + + name = re.match(r"type\s+(\w+)(?:<[^>]+>)?", line).group(1) + variants: list[VariantDef] = [] + i += 1 + while i < len(lines): + stripped = lines[i].strip() + if not stripped or stripped.startswith("type "): + break + if not stripped.startswith(("=", "|")): + i += 1 + continue + variant_src = stripped[1:].strip() + tuple_match = re.match(r"(\w+)\((.+)\)", variant_src) + record_match = re.match(r"(\w+)\s*\{", variant_src) + unit_match = re.match(r"(\w+)$", variant_src) + if tuple_match: + variants.append( + VariantDef( + name=tuple_match.group(1), + kind="tuple", + payload=tuple_match.group(2).strip(), + ) + ) + i += 1 + elif record_match: + variant_name = record_match.group(1) + inline_fields = re.match(r"\w+\s*\{(.+)\}\s*$", variant_src) + if inline_fields: + fields = parse_inline_fields(inline_fields.group(1)) + i += 1 + else: + i += 1 + fields = [] + while lines[i].strip() != "}": + field_line = lines[i].strip() + if field_line and ":" in field_line: + label, ty = field_line.split(":", 1) + fields.append((label.strip(), ty.strip())) + i += 1 + i += 1 + variants.append(VariantDef(name=variant_name, kind="record", fields=fields)) + elif unit_match: + variants.append(VariantDef(name=unit_match.group(1), kind="unit")) + i += 1 + else: + raise ValueError(f"unparsed variant line: {lines[i]}") + sums[name] = variants + return records, sums + + +def parse_runtime_mirrors() -> tuple[dict[str, RecordDef], dict[str, list[VariantDef]]]: + return parse_types(AUTHORITY_PATH) + + +def rust_type(source: str, overrides: dict[str, str] | None = None) -> str: + overrides = overrides or {} + if source in overrides: + return overrides[source] + mapping = { + "String": "String", + "Bool": "bool", + "Int": "i64", + "NodeId": "NodeId", + "PortId": "PortId", + "ClusterId": "ClusterId", + "DeclarationId": "DeclarationId", + "ParamRef": "ParamRef", + "TransformRef": "TransformRef", + "TypeShape": "TypeShape", + "CompilerSourceSpan": "SourceSpan", + "CompilerCorrection": "Correction", + } + if source in mapping: + return mapping[source] + if source.endswith("?"): + return f"Option<{rust_type(source[:-1], overrides)}>" + generic_match = re.fullmatch(r"(\w+)<(.+)>", source) + if generic_match: + container = generic_match.group(1) + inner = rust_type(generic_match.group(2), overrides) + if container == "List": + return f"Vec<{inner}>" + return f"{container}<{inner}>" + return source + + +def render_record( + record: RecordDef, + output_name: str | None = None, + derives: str = "#[derive(Debug, Clone, PartialEq, Eq)]", + field_name_overrides: dict[str, str] | None = None, +) -> str: + output_name = output_name or record.name + field_name_overrides = field_name_overrides or {} + lines = [derives, f"pub struct {output_name} {{"] + for label, ty in record.fields: + output_label = field_name_overrides.get(label, label) + lines.append(f" pub {output_label}: {rust_type(ty)},") + lines.append("}") + return "\n".join(lines) + + +def render_sum( + name: str, + variants: list[VariantDef], + derives: str, + output_name: str | None = None, + overrides: dict[str, str] | None = None, + variant_name_overrides: dict[str, str] | None = None, +) -> str: + output_name = output_name or name + variant_name_overrides = variant_name_overrides or {} + lines = [derives, f"pub enum {output_name} {{"] + for variant in variants: + variant_name = variant_name_overrides.get(variant.name, variant.name) + if variant.kind == "unit": + lines.append(f" {variant_name},") + elif variant.kind == "tuple": + lines.append(f" {variant_name}({rust_type(variant.payload, overrides)}),") + elif variant.kind == "record": + lines.append(f" {variant_name} {{") + for label, ty in variant.fields or []: + lines.append(f" {label}: {rust_type(ty, overrides)},") + lines.append(" },") + else: + raise ValueError(f"unsupported variant kind {variant.kind}") + lines.append("}") + return "\n".join(lines) + + +def render_diagnostics_module(records: dict[str, RecordDef], sums: dict[str, list[VariantDef]]) -> str: + del records, sums + return "\n\n".join([SOURCE_SPAN_TEMPLATE.strip(), CORRECTION_TEMPLATE.strip()]) + + +def render_types_module(substrate_records: dict[str, RecordDef]) -> str: + return "\n\n".join( + [ + render_record( + substrate_records["TypeShape"], + derives="#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]", + ), + TYPE_SHAPE_IMPL_TEMPLATE.strip(), + ] + ) + + +def render_dag_scalar_module(records: dict[str, RecordDef], sums: dict[str, list[VariantDef]]) -> str: + parts = [ + render_sum( + "LiteralBits", + sums["LiteralBits"], + "#[derive(Debug, Clone, PartialEq, Eq)]", + output_name="LiteralBits", + variant_name_overrides={"LitInt": "Int", "LitBool": "Bool", "LitString": "String"}, + ), + render_sum( + "CardinalityBound", + sums["CardinalityBound"], + "#[derive(Debug, Clone, PartialEq, Eq)]", + output_name="CardinalityBound", + overrides={"Int": "u32"}, + ), + render_record( + records["TemplateArgument"], + output_name="TemplateArgument", + derives="#[derive(Debug, Clone)]", + ), + render_sum( + "PortState", + sums["PortState"], + "#[derive(Debug, Clone, PartialEq, Eq)]", + output_name="PortState", + ), + ] + return "\n\n".join(parts) + + +def render_dag_branch_module(records: dict[str, RecordDef], sums: dict[str, list[VariantDef]]) -> str: + parts = [ + render_sum( + "BranchPattern", + sums["BranchPattern"], + "#[derive(Debug, Clone)]", + output_name="BranchPattern", + ), + render_record( + records["PayloadBinding"], + output_name="PayloadBinding", + derives="#[derive(Debug, Clone)]", + ), + render_record( + records["BranchPath"], + output_name="Path", + derives="#[derive(Debug, Clone)]", + field_name_overrides={"result_port": "output"}, + ), + ] + return "\n\n".join(parts) + + +def render_dag_cluster_module(records: dict[str, RecordDef], sums: dict[str, list[VariantDef]]) -> str: + parts = [ + render_record( + records["MemberDescent"], + output_name="MemberDescent", + ), + render_record( + records["IntraClusterCall"], + output_name="IntraClusterCall", + ), + render_record(records["Cluster"], output_name="Cluster"), + render_sum( + "LoopBound", + sums["LoopBound"], + "#[derive(Debug, Clone, Copy, PartialEq, Eq)]", + output_name="LoopBound", + ), + ] + return "\n\n".join(parts) + + +def format_with_header(authority: str, body: str) -> str: + return HEADER_TEMPLATE.format(authority=authority, body=body.rstrip()) + + +def expected_outputs() -> dict[Path, str]: + records, sums = parse_runtime_mirrors() + substrate_records, _ = parse_types(SUBSTRATE_PATH) + _, substrate_sums = parse_types(SUBSTRATE_PATH) + return { + SRC_DIR / "types_generated.rs": format_with_header( + "src/v3/std/substrate.dag", render_types_module(substrate_records) + ), + SRC_DIR / "diagnostics_generated.rs": format_with_header( + "src/v3/std/substrate.dag, src/v3/std/diagnostics.dag", + render_diagnostics_module(records, sums), + ), + SRC_DIR / "serialize_generated.rs": format_with_header( + "src/v3/compiler/runtime_mirrors.dag", + render_record(records["DagDifference"]), + ), + SRC_DIR / "dag_scalar_generated.rs": format_with_header( + "src/v3/std/substrate.dag", + render_dag_scalar_module(substrate_records, substrate_sums), + ), + SRC_DIR / "dag_branch_generated.rs": format_with_header( + "src/v3/std/substrate.dag", + render_dag_branch_module(substrate_records, substrate_sums), + ), + SRC_DIR / "dag_cluster_generated.rs": format_with_header( + "src/v3/std/substrate.dag", + render_dag_cluster_module(substrate_records, substrate_sums), + ), + SRC_DIR / "dag_cost_generated.rs": format_with_header( + "src/v3/std/algebra.dag", DAG_COST_TEMPLATE + ), + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--check", action="store_true", help="fail if generated files are stale") + args = parser.parse_args() + + outputs = expected_outputs() + stale = False + for path, expected in outputs.items(): + if args.check: + actual = path.read_text() + if actual != expected: + print(f"stale: {path.relative_to(ROOT)}", file=sys.stderr) + stale = True + else: + path.write_text(expected) + print(f"wrote {path}") + return 1 if stale else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/v3/compiler/runtime_mirrors.dag b/src/v3/compiler/runtime_mirrors.dag new file mode 100644 index 00000000000..e440df2726e --- /dev/null +++ b/src/v3/compiler/runtime_mirrors.dag @@ -0,0 +1,5 @@ +module v3.compiler.runtime_mirrors + +type DagDifference { + detail: String +} diff --git a/src/v3/compiler/src/dag.rs b/src/v3/compiler/src/dag.rs index da7ea3a946e..a83c98b6883 100644 --- a/src/v3/compiler/src/dag.rs +++ b/src/v3/compiler/src/dag.rs @@ -445,58 +445,9 @@ impl AtomPayload { } } -/// Terminal literal payload. 3 variants, each corresponds to a distinct -/// user-input boundary (integer literal, boolean literal, string literal -/// from source text). Pattern-check is trivial: fact placement, variant- -/// is-data, algebraic form, and dimensional all fail on disjoint payload -/// types. Any future `Float`/`Char` additions go through §8.10's audit. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum LiteralBits { - Int(i64), - Bool(bool), - String(String), -} - -/// Dissolution ledger: CardinalityBound is a 3-variant coproduct that -/// encodes the "how many?" dimension of field/element repetition. -/// Required/Optional/List distinctions that v2 carried in separate -/// attributes collapse into this one axis. -/// -/// 4-pattern check: -/// - Pattern 1 (fact placement): fails. Callers dispatch on bound. -/// - Pattern 2 (variant-is-data): partial. `Exact(n)` differs from -/// `AtMostOne`/`Unbounded` structurally (carries a u32), but -/// `AtMostOne` is distinct from `Exact(1)` because it admits the -/// zero case. -/// - Pattern 3 (algebraic form): passes. The three variants cover the -/// `{n}` / `[0..1]` / `[0..∞)` range algebra. -/// - Pattern 4 (dimensional): fails. No orthogonal coordinate space. -/// -/// Verdict: terminal at M1(2.5). Adding `AtLeast(n)` / `Range(lo, hi)` -/// would be a variant extension subject to §8.10's audit; neither is -/// motivated by current std/ or shell.dag consumers. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum CardinalityBound { - /// Exact count. Required = Exact(1); fixed-size arrays = Exact(n); argv - /// literals = Exact(3). - Exact(u32), - /// Zero or one. Distinct from Unbounded so that `T?` and `List` are - /// structurally unrepresentable as the same thing. - AtMostOne, - /// Zero or more. `List` and friends. - Unbounded, -} - -#[derive(Debug, Clone)] -pub struct TemplateArgument { - /// The template parameter being bound. References a TypeParam Atom declared - /// as a child of the template. - pub parameter: DeclarationId, - /// The concrete declaration the parameter binds to. This is a type - /// declaration for ordinary generics and may also be a callable - /// declaration for higher-order-function instantiation. - pub value: DeclarationId, -} +// Terminal literal/cardinality/template/port-state mirrors are generated +// from `std/substrate.dag`; keep the substrate authority there, not here. +include!("dag_scalar_generated.rs"); /// Dissolution ledger (per M1_DESIGN.md §Q7 "ArrowBody dissolution ledger"): /// ArrowBody is a **mixed-lifecycle coproduct**. Terminal shape is 2 @@ -683,13 +634,6 @@ pub enum ArrowBody { /// state == Unresolved iff diagnostics.contains(port.id()) /// /// **Dissolution receipt: TERMINAL.** PortState is substrate, not an annotation. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PortState { - Uninferred, - Resolved(TypeShape), - Unresolved, -} - /// A Port carries a typed value forward in time. /// /// `state` has a single authoritative location: the Port struct stored in @@ -859,74 +803,9 @@ impl BranchNode { } } -/// Per-arm pattern on a `Path`. Encodes which variant of the -/// scrutinee's Disj this arm handles. Unifies `if`/`else` with -/// `match` — both lower to `Branch` with one `Path` per arm, and -/// the discriminator lives on the pattern instead of on positional -/// convention. -/// -/// **Phase coproduct — M1(2.8).** Lowering emits -/// `UnresolvedVariant { name, span }`; inference walks the -/// scrutinee's Disj children, matches the arm's variant name -/// scoped against that Disj, and mutates the Path's pattern -/// in-place to `ResolvedVariant(DeclarationId)`. The resolved -/// form is the stable post-infer shape; the unresolved form is -/// the transient lowering-output shape. Same pattern the substrate -/// uses for `AtomPayload::Unresolved/ResolvedIdentifier`. -/// -/// 4-pattern check: -/// - Pattern 1 (fact placement): fails. Variant identity is a -/// per-arm fact that downstream code (exhaustiveness checks, -/// emission) must read per-Path. -/// - Pattern 2 (variant-is-data): fails. Unresolved carries a -/// name + span; Resolved carries a DeclarationId. -/// - Pattern 3 (algebraic form): fails. The two variants are -/// two phases of the same fact, not an algebra. -/// - Pattern 4 (dimensional): fails. -/// -/// Verdict: terminal at M1(2.8). Future pattern extensions -/// (wildcard, record destructure, nested patterns) go through -/// §8.10's substrate-extension audit. -#[derive(Debug, Clone)] -pub enum BranchPattern { - /// Arm pattern as written in surface syntax. Populated by - /// lowering; the name is the variant identifier the user wrote - /// (or `"True"` / `"False"` for an `if`/`else`'s two branches). - /// Must be resolved by the end of inference. - UnresolvedVariant { name: String, span: SourceSpan }, - /// Arm pattern resolved to a variant declaration. The - /// `DeclarationId` points at the anonymous variant child of - /// the scrutinee's Disj. - ResolvedVariant(DeclarationId), -} - -#[derive(Debug, Clone)] -pub struct PayloadBinding { - /// Authored arm-local name from the surface pattern - /// (`Some(payload)` -> `"payload"`). Lowering consumes it to - /// extend the arm-local scope. It remains on the substrate as - /// carry-forward for readable downstream rendering. - pub binding_name: String, - /// Port carrying the variant payload value for this arm. - /// Lowering allocates the port so the binding can exist in - /// arm-local scope immediately; inference later validates the - /// matched variant shape and populates the payload type. - pub payload_port: PortId, -} - -#[derive(Debug, Clone)] -pub struct Path { - pub body: NodeId, - pub output: PortId, - /// Which variant of the scrutinee's Disj this path handles. - /// Discriminator for both `if`/`else` (on Bool) and `match` - /// (on any Disj). See `BranchPattern`. - pub pattern: BranchPattern, - /// Optional payload extraction for this arm. Present for - /// `Variant(binding)` surface patterns; absent for bare-variant - /// arms and `if`/`else`. - pub binding: Option, -} +// Branch-pattern/path mirrors are generated from `std/substrate.dag`. +// The host keeps only the `Path.output` field rename plus impl behavior below. +include!("dag_branch_generated.rs"); impl Path { pub fn result_port(&self) -> PortId { @@ -1301,38 +1180,9 @@ pub enum WorkflowParallelismReport { // Cluster / loop-bound carriers below are Track 9 mutual-recursion // witnesses — not part of the Lane 2 Stage 2b effects algebra. -/// 🟢 **TERMINAL.** Single cluster member's descent parameter — typed -/// `ParamRef` witness (see `docs/design-mutual-recursion-lowering.md`). -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct MemberDescent { - pub param: ParamRef, -} - -/// 🟢 **TERMINAL.** One intra-cluster `Transform` call edge inside the SCC. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct IntraClusterCall { - pub transform: TransformRef, -} - -/// 🟢 **TERMINAL.** Typed index over authoritative member/call topology for -/// `LoopBound::Descent` — not a parallel copy of the Dag call graph. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Cluster { - pub members: NonSingletonList, - pub intra_cluster_calls: NonEmptyList, -} - -/// 🟢 TERMINAL. `LoopBound` records the irreducible witness that makes -/// a `Behavior::Loop` honest at the substrate layer: either an -/// explicit runtime count port or a proved mutual-recursion cluster. -/// Collapsing the variants would either fabricate a count fact or -/// erase a structural termination proof. See -/// `docs/design-mutual-recursion-lowering.md` for the full receipt. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum LoopBound { - Cardinality { count: PortId }, - Descent { cluster: ClusterId }, -} +// Cluster/loop-bound mirrors are generated from `std/substrate.dag`; +// keep only host helper behavior in Rust. +include!("dag_cluster_generated.rs"); impl LoopBound { pub fn count_port(&self) -> Option { @@ -1343,290 +1193,7 @@ impl LoopBound { } } -/// 🟢 TERMINAL at Stage 2d scope. Rust mirror of the -/// `SymbolicCost` coproduct declared in `src/v3/std/algebra.dag`. -/// The .dag declaration is the authority — this mirror exists -/// because `emit_rust_module` filters declarations from -/// `src/v3/std/` as bootstrap-resident, so the generated -/// `lens_cost_symbolic_generated.rs` references `SymbolicCost` -/// by name without re-declaring it. Keeping the Rust shape -/// adjacent to the other substrate carriers (`Behavior`, -/// `LoopBound`, `Cluster`) matches the existing pattern used for -/// every other .dag type the generated lenses consume. -/// -/// The `SizeVariable` name field the DB-7 design doc sketches is -/// deliberately not mirrored at this stage: the MVP render path -/// pins structural equality on `source_port` alone (two -/// `LinearCost` terms collapse to `PolynomialCost(var, 2)` when -/// their ports match), and pulling a user-facing name through -/// would require an InternTable lookup the lens doesn't yet run. -/// Name-rendering lands when a concrete display consumer pins -/// the missing piece — tracked in DB-7 Open Question §1's -/// size-variable normalization. -/// -/// Fields mirror the .dag variants exactly (anonymous tuple -/// payloads render as `{ _0: ... }`, record payloads render as -/// `{ field: ... }`); kept in sync by hand for now and tracked as -/// Lane 1e scheduled-deletion work when substrate emission grows -/// a cross-file type-mirror pass. -/// -/// See `docs/design-symbolic-cost-algebra.md` (DB-7) for the -/// dissolution receipt and variant rationale. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum DegreeAtLeastTwo { - DegreeTwo, - DegreeSuccessor { previous: Box }, -} - -impl DegreeAtLeastTwo { - pub const TWO: Self = Self::DegreeTwo; - - pub fn new(value: i64) -> Option { - match value { - 2 => Some(Self::DegreeTwo), - v if v > 2 => Some(Self::DegreeSuccessor { - previous: Box::new(Self::new(v - 1)?), - }), - _ => None, - } - } - - pub fn raw(&self) -> i64 { - match self { - Self::DegreeTwo => 2, - Self::DegreeSuccessor { previous } => previous.raw() + 1, - } - } -} - -type BoxedSymbolicCostList = NonSingletonList>; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum SymbolicCost { - ConstantCost { - _0: i64, - }, - LinearCost { - _0: SizeVariable, - }, - PolynomialCost { - var: SizeVariable, - degree: DegreeAtLeastTwo, - }, - ProductCost { - _0: BoxedSymbolicCostList, - }, - SumCost { - _0: BoxedSymbolicCostList, - }, - LogCost { - _0: SizeVariable, - }, - UnknownCost { - _0: String, - }, -} - -/// 🟢 TERMINAL at Stage 2d scope. Rust mirror of `SizeVariable` -/// from `src/v3/std/algebra.dag`. Structural equality on -/// `source_port` is how two `LinearCost` terms collapse into -/// `PolynomialCost(var, 2)` through -/// `std.algebra::combine_binary_product` — the nested-fold -/// fingerprint DB-7's `all_pairs` acceptance fixture exercises. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SizeVariable { - pub source_port: PortId, -} - -// Composition functions mirroring `src/v3/std/algebra.dag`. -// `emit_rust_module` filters declarations under `src/v3/std/` as -// bootstrap-resident, so the functions there never emit their own -// Rust projection; `lens_cost_symbolic_generated.rs` references -// `sequential`, `iterate`, `max_path` by name and resolves them -// through `use crate::dag::*`. Kept in sync with the .dag source -// by hand for now, same cadence as the `SymbolicCost` variant -// mirror above. -// -// Algorithmic equivalence receipts: -// - `sequential` / `iterate` follow DB-7 §"Composition -// operations": sequential is a normalized sum, iterate is a -// normalized product. -// - `normalize` implements the Sum/Product reductions DB-7 -// §"Dominance / normalization" specifies: drop `ConstantCost(0)` -// out of sums, collapse singleton wrappers, fold two identical -// `LinearCost` terms into `PolynomialCost(var, 2)`. -// - `dominates` implements the dominance partial order: -// Unknown dominates everything (safest over-approximation), -// Constant dominated by every other variant, Polynomial degrees -// strictly-order, Linear≡Polynomial(v, 1), Sum/Product use -// dominant-child summary. - -pub fn sequential(a: SymbolicCost, b: SymbolicCost) -> SymbolicCost { - normalize(SymbolicCost::SumCost { - _0: boxed_cost_list_pair(a, b), - }) -} - -pub fn iterate(bound: SymbolicCost, body: SymbolicCost) -> SymbolicCost { - normalize(SymbolicCost::ProductCost { - _0: boxed_cost_list_pair(bound, body), - }) -} - -pub fn max_path(paths: &[SymbolicCost]) -> SymbolicCost { - // Three-way step: candidate-wins / acc-wins / keep-both. Fixes - // PR #537 review (Facts Flow Forward violation): pairing paths - // via a two-way `if dominates(c, acc) then c else acc` silently - // dropped whichever path was ordered later when the two were - // incomparable (e.g. `Linear(n)` vs `Linear(m)` with distinct - // size variables). When neither dominates, preserve both via - // `sequential`, which normalizes through `drop_dominated`; - // `Big-O(f + g) = Big-O(max(f, g))` makes the sum asymptotically - // identical to the max. - paths - .iter() - .fold(SymbolicCost::ConstantCost { _0: 0 }, |acc, candidate| { - if dominates(candidate, &acc) { - candidate.clone() - } else if dominates(&acc, candidate) { - acc - } else { - sequential(acc, candidate.clone()) - } - }) -} - -pub fn normalize(cost: SymbolicCost) -> SymbolicCost { - match cost { - SymbolicCost::SumCost { _0: terms } => { - reduce_sum(drop_zero_terms(boxed_terms_to_vec(&terms))) - } - SymbolicCost::ProductCost { _0: terms } => { - reduce_product(drop_zero_terms(boxed_terms_to_vec(&terms))) - } - other => other, - } -} - -fn boxed_cost_list_pair(a: SymbolicCost, b: SymbolicCost) -> BoxedSymbolicCostList { - BoxedSymbolicCostList { - first: Box::new(a), - second: Box::new(b), - rest: Vec::new(), - } -} - -fn boxed_terms_to_vec(terms: &BoxedSymbolicCostList) -> Vec { - terms.iter().map(|term| term.as_ref().clone()).collect() -} - -fn drop_zero_terms(terms: Vec) -> Vec { - terms - .into_iter() - .filter(|t| !matches!(t, SymbolicCost::ConstantCost { _0: 0 })) - .collect() -} - -fn reduce_sum(mut terms: Vec) -> SymbolicCost { - terms = drop_dominated_in_sum(terms); - match terms.len() { - 0 => SymbolicCost::ConstantCost { _0: 0 }, - 1 => terms.into_iter().next().unwrap(), - _ => SymbolicCost::SumCost { - _0: boxed_cost_list_from_vec(terms), - }, - } -} - -fn reduce_product(terms: Vec) -> SymbolicCost { - match terms.len() { - 0 => SymbolicCost::ConstantCost { _0: 0 }, - 1 => terms.into_iter().next().unwrap(), - 2 => { - let mut iter = terms.into_iter(); - let a = iter.next().unwrap(); - let b = iter.next().unwrap(); - combine_binary_product(a, b) - } - _ => SymbolicCost::ProductCost { - _0: boxed_cost_list_from_vec(terms), - }, - } -} - -fn boxed_cost_list_from_vec(terms: Vec) -> BoxedSymbolicCostList { - NonSingletonList::from_vec(terms.into_iter().map(Box::new).collect()).unwrap() -} - -fn combine_binary_product(a: SymbolicCost, b: SymbolicCost) -> SymbolicCost { - if let (SymbolicCost::LinearCost { _0: va }, SymbolicCost::LinearCost { _0: vb }) = (&a, &b) { - if va == vb { - return SymbolicCost::PolynomialCost { - var: va.clone(), - degree: DegreeAtLeastTwo::TWO, - }; - } - } - SymbolicCost::ProductCost { - _0: boxed_cost_list_pair(a, b), - } -} - -fn drop_dominated_in_sum(terms: Vec) -> Vec { - let mut keep: Vec = Vec::with_capacity(terms.len()); - for term in terms { - let term_dominated = keep.iter().any(|k| dominates(k, &term)); - if term_dominated { - continue; - } - keep.retain(|k| !dominates(&term, k)); - keep.push(term); - } - keep -} - -pub fn dominates(a: &SymbolicCost, b: &SymbolicCost) -> bool { - match a { - SymbolicCost::UnknownCost { .. } => true, - SymbolicCost::ConstantCost { .. } => matches!(b, SymbolicCost::ConstantCost { .. }), - SymbolicCost::LinearCost { _0: va } => match b { - SymbolicCost::ConstantCost { .. } | SymbolicCost::LogCost { .. } => true, - SymbolicCost::LinearCost { _0: vb } => va == vb, - SymbolicCost::PolynomialCost { var: _, degree: _ } => false, - _ => false, - }, - SymbolicCost::PolynomialCost { - var: va, - degree: ka, - } => match b { - SymbolicCost::ConstantCost { .. } | SymbolicCost::LogCost { .. } => true, - SymbolicCost::LinearCost { _0: vb } => va == vb, - SymbolicCost::PolynomialCost { - var: vb, - degree: kb, - } => va == vb && ka.raw() >= kb.raw(), - _ => false, - }, - SymbolicCost::LogCost { _0: va } => match b { - SymbolicCost::ConstantCost { .. } => true, - SymbolicCost::LogCost { _0: vb } => va == vb, - _ => false, - }, - // Composite dominance via "dominant child summary" (DB-7 - // §Dominance). Both `Sum([A, B])` and `Product([A, B])` bound - // each child from below — sum and product of non-negative - // terms are ≥ any single term — so the composite dominates - // `b` iff *any* child does. Short-circuit evaluation keeps - // this O(n) worst-case over the (post-normalize) ≤ few-term - // lists. Fixes PR #537 review (codex): prior code hardcoded - // `LinearCost(_) => True` / `LogCost(_) => True` without - // inspecting terms, so e.g. `Product([Log(n)])` incorrectly - // dominated `Linear(n)`. - SymbolicCost::ProductCost { _0: terms } | SymbolicCost::SumCost { _0: terms } => { - terms.iter().any(|child| dominates(child.as_ref(), b)) - } - } -} +include!("dag_cost_generated.rs"); #[derive(Debug, Clone)] pub struct LoopNode { diff --git a/src/v3/compiler/src/dag_branch_generated.rs b/src/v3/compiler/src/dag_branch_generated.rs new file mode 100644 index 00000000000..6abf5d36fd0 --- /dev/null +++ b/src/v3/compiler/src/dag_branch_generated.rs @@ -0,0 +1,25 @@ +// AUTO-GENERATED from `src/v3/std/substrate.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone)] +pub enum BranchPattern { + UnresolvedVariant { + name: String, + span: SourceSpan, + }, + ResolvedVariant(DeclarationId), +} + +#[derive(Debug, Clone)] +pub struct PayloadBinding { + pub binding_name: String, + pub payload_port: PortId, +} + +#[derive(Debug, Clone)] +pub struct Path { + pub body: NodeId, + pub output: PortId, + pub pattern: BranchPattern, + pub binding: Option, +} diff --git a/src/v3/compiler/src/dag_cluster_generated.rs b/src/v3/compiler/src/dag_cluster_generated.rs new file mode 100644 index 00000000000..952a0ce8142 --- /dev/null +++ b/src/v3/compiler/src/dag_cluster_generated.rs @@ -0,0 +1,28 @@ +// AUTO-GENERATED from `src/v3/std/substrate.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct MemberDescent { + pub param: ParamRef, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IntraClusterCall { + pub transform: TransformRef, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Cluster { + pub members: NonSingletonList, + pub intra_cluster_calls: NonEmptyList, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LoopBound { + Cardinality { + count: PortId, + }, + Descent { + cluster: ClusterId, + }, +} diff --git a/src/v3/compiler/src/dag_cost_generated.rs b/src/v3/compiler/src/dag_cost_generated.rs new file mode 100644 index 00000000000..f847b50f132 --- /dev/null +++ b/src/v3/compiler/src/dag_cost_generated.rs @@ -0,0 +1,198 @@ +// AUTO-GENERATED from `src/v3/std/algebra.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum DegreeAtLeastTwo { + DegreeTwo, + DegreeSuccessor { previous: Box }, +} + +impl DegreeAtLeastTwo { + pub const TWO: Self = Self::DegreeTwo; + + pub fn new(value: i64) -> Option { + match value { + 2 => Some(Self::DegreeTwo), + v if v > 2 => Some(Self::DegreeSuccessor { + previous: Box::new(Self::new(v - 1)?), + }), + _ => None, + } + } + + pub fn raw(&self) -> i64 { + match self { + Self::DegreeTwo => 2, + Self::DegreeSuccessor { previous } => previous.raw() + 1, + } + } +} + +type BoxedSymbolicCostList = NonSingletonList>; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SymbolicCost { + ConstantCost { _0: i64 }, + LinearCost { _0: SizeVariable }, + PolynomialCost { + var: SizeVariable, + degree: DegreeAtLeastTwo, + }, + ProductCost { _0: BoxedSymbolicCostList }, + SumCost { _0: BoxedSymbolicCostList }, + LogCost { _0: SizeVariable }, + UnknownCost { _0: String }, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SizeVariable { + pub source_port: PortId, +} + +pub fn sequential(a: SymbolicCost, b: SymbolicCost) -> SymbolicCost { + normalize(SymbolicCost::SumCost { + _0: boxed_cost_list_pair(a, b), + }) +} + +pub fn iterate(bound: SymbolicCost, body: SymbolicCost) -> SymbolicCost { + normalize(SymbolicCost::ProductCost { + _0: boxed_cost_list_pair(bound, body), + }) +} + +pub fn max_path(paths: &[SymbolicCost]) -> SymbolicCost { + paths + .iter() + .fold(SymbolicCost::ConstantCost { _0: 0 }, |acc, candidate| { + if dominates(candidate, &acc) { + candidate.clone() + } else if dominates(&acc, candidate) { + acc + } else { + sequential(acc, candidate.clone()) + } + }) +} + +pub fn normalize(cost: SymbolicCost) -> SymbolicCost { + match cost { + SymbolicCost::SumCost { _0: terms } => { + reduce_sum(drop_zero_terms(boxed_terms_to_vec(&terms))) + } + SymbolicCost::ProductCost { _0: terms } => { + reduce_product(drop_zero_terms(boxed_terms_to_vec(&terms))) + } + other => other, + } +} + +fn boxed_cost_list_pair(a: SymbolicCost, b: SymbolicCost) -> BoxedSymbolicCostList { + BoxedSymbolicCostList { + first: Box::new(a), + second: Box::new(b), + rest: Vec::new(), + } +} + +fn boxed_terms_to_vec(terms: &BoxedSymbolicCostList) -> Vec { + terms.iter().map(|term| term.as_ref().clone()).collect() +} + +fn drop_zero_terms(terms: Vec) -> Vec { + terms + .into_iter() + .filter(|t| !matches!(t, SymbolicCost::ConstantCost { _0: 0 })) + .collect() +} + +fn reduce_sum(mut terms: Vec) -> SymbolicCost { + terms = drop_dominated_in_sum(terms); + match terms.len() { + 0 => SymbolicCost::ConstantCost { _0: 0 }, + 1 => terms.into_iter().next().unwrap(), + _ => SymbolicCost::SumCost { + _0: boxed_cost_list_from_vec(terms), + }, + } +} + +fn reduce_product(terms: Vec) -> SymbolicCost { + match terms.len() { + 0 => SymbolicCost::ConstantCost { _0: 0 }, + 1 => terms.into_iter().next().unwrap(), + 2 => { + let mut iter = terms.into_iter(); + let a = iter.next().unwrap(); + let b = iter.next().unwrap(); + combine_binary_product(a, b) + } + _ => SymbolicCost::ProductCost { + _0: boxed_cost_list_from_vec(terms), + }, + } +} + +fn boxed_cost_list_from_vec(terms: Vec) -> BoxedSymbolicCostList { + NonSingletonList::from_vec(terms.into_iter().map(Box::new).collect()).unwrap() +} + +fn combine_binary_product(a: SymbolicCost, b: SymbolicCost) -> SymbolicCost { + if let (SymbolicCost::LinearCost { _0: va }, SymbolicCost::LinearCost { _0: vb }) = (&a, &b) { + if va == vb { + return SymbolicCost::PolynomialCost { + var: va.clone(), + degree: DegreeAtLeastTwo::TWO, + }; + } + } + SymbolicCost::ProductCost { + _0: boxed_cost_list_pair(a, b), + } +} + +fn drop_dominated_in_sum(terms: Vec) -> Vec { + let mut keep: Vec = Vec::with_capacity(terms.len()); + for term in terms { + let term_dominated = keep.iter().any(|k| dominates(k, &term)); + if term_dominated { + continue; + } + keep.retain(|k| !dominates(&term, k)); + keep.push(term); + } + keep +} + +pub fn dominates(a: &SymbolicCost, b: &SymbolicCost) -> bool { + match a { + SymbolicCost::UnknownCost { .. } => true, + SymbolicCost::ConstantCost { .. } => matches!(b, SymbolicCost::ConstantCost { .. }), + SymbolicCost::LinearCost { _0: va } => match b { + SymbolicCost::ConstantCost { .. } | SymbolicCost::LogCost { .. } => true, + SymbolicCost::LinearCost { _0: vb } => va == vb, + SymbolicCost::PolynomialCost { var: _, degree: _ } => false, + _ => false, + }, + SymbolicCost::PolynomialCost { + var: va, + degree: ka, + } => match b { + SymbolicCost::ConstantCost { .. } | SymbolicCost::LogCost { .. } => true, + SymbolicCost::LinearCost { _0: vb } => va == vb, + SymbolicCost::PolynomialCost { + var: vb, + degree: kb, + } => va == vb && ka.raw() >= kb.raw(), + _ => false, + }, + SymbolicCost::LogCost { _0: va } => match b { + SymbolicCost::ConstantCost { .. } => true, + SymbolicCost::LogCost { _0: vb } => va == vb, + _ => false, + }, + SymbolicCost::ProductCost { _0: terms } | SymbolicCost::SumCost { _0: terms } => { + terms.iter().any(|child| dominates(child.as_ref(), b)) + } + } +} diff --git a/src/v3/compiler/src/dag_scalar_generated.rs b/src/v3/compiler/src/dag_scalar_generated.rs new file mode 100644 index 00000000000..11b771c7c39 --- /dev/null +++ b/src/v3/compiler/src/dag_scalar_generated.rs @@ -0,0 +1,29 @@ +// AUTO-GENERATED from `src/v3/std/substrate.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum LiteralBits { + Int(i64), + Bool(bool), + String(String), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CardinalityBound { + Exact(u32), + AtMostOne, + Unbounded, +} + +#[derive(Debug, Clone)] +pub struct TemplateArgument { + pub parameter: DeclarationId, + pub value: DeclarationId, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PortState { + Uninferred, + Resolved(TypeShape), + Unresolved, +} diff --git a/src/v3/compiler/src/diagnostics.rs b/src/v3/compiler/src/diagnostics.rs index df9b01973bb..ab2630eb918 100644 --- a/src/v3/compiler/src/diagnostics.rs +++ b/src/v3/compiler/src/diagnostics.rs @@ -60,33 +60,11 @@ use crate::dag::{ }; use crate::types::TypeShape; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct SourceSpan { - pub file: String, - pub byte_start: u32, - pub byte_end: u32, -} - -impl SourceSpan { - pub fn new(file: impl Into, byte_start: u32, byte_end: u32) -> Self { - Self { - file: file.into(), - byte_start, - byte_end, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Correction { - pub description: String, - // Retained even though the current source-level renderer only - // prints replacement text. Future fix surfaces can use this to - // point at the precise range the correction applies to without - // changing the carrier shape. - pub span: SourceSpan, - pub new_source: String, -} +// `SourceSpan` / `Correction` are generated mirrors of std-owned +// substrate/diagnostic carriers. The compiler-local `Diagnostic` +// taxonomy below remains a narrow host shim until the staged compiler +// adopts the shared std diagnostic record directly. +include!("diagnostics_generated.rs"); #[derive(Debug, Clone)] pub enum Diagnostic { @@ -118,7 +96,6 @@ pub enum Diagnostic { span: SourceSpan, fixes: Vec, }, - /// DB-18 R2 — branch condition port did not resolve to `Bool` at lowering. BranchConditionNotBool { port: PortId, actual_type: Option, @@ -194,7 +171,6 @@ pub enum DiagnosticRenderError { detail: &'static str, }, } - #[derive(Debug, Clone, PartialEq, Eq)] struct CorrectionStyleBinding { indent_unit: String, diff --git a/src/v3/compiler/src/diagnostics_generated.rs b/src/v3/compiler/src/diagnostics_generated.rs new file mode 100644 index 00000000000..7f2e30c7d0b --- /dev/null +++ b/src/v3/compiler/src/diagnostics_generated.rs @@ -0,0 +1,26 @@ +// AUTO-GENERATED from `src/v3/std/substrate.dag, src/v3/std/diagnostics.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SourceSpan { + pub file: String, + pub byte_start: u32, + pub byte_end: u32, +} + +impl SourceSpan { + pub fn new(file: impl Into, byte_start: u32, byte_end: u32) -> Self { + Self { + file: file.into(), + byte_start, + byte_end, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Correction { + pub description: String, + pub span: SourceSpan, + pub new_source: String, +} diff --git a/src/v3/compiler/src/serialize.rs b/src/v3/compiler/src/serialize.rs index a1acec61a18..8e7e2569858 100644 --- a/src/v3/compiler/src/serialize.rs +++ b/src/v3/compiler/src/serialize.rs @@ -1,10 +1,7 @@ use crate::dag::{Behavior, Dag}; use crate::diagnostics::Diagnostic; -#[derive(Debug, Clone)] -pub struct DagDifference { - pub detail: String, -} +include!("serialize_generated.rs"); pub fn serialize_dag(dag: &Dag) -> Vec { let mut out = String::new(); diff --git a/src/v3/compiler/src/serialize_generated.rs b/src/v3/compiler/src/serialize_generated.rs new file mode 100644 index 00000000000..be16939b1a3 --- /dev/null +++ b/src/v3/compiler/src/serialize_generated.rs @@ -0,0 +1,7 @@ +// AUTO-GENERATED from `src/v3/compiler/runtime_mirrors.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DagDifference { + pub detail: String, +} diff --git a/src/v3/compiler/src/types.rs b/src/v3/compiler/src/types.rs index 0d1e6139018..3d9ad5dd4d0 100644 --- a/src/v3/compiler/src/types.rs +++ b/src/v3/compiler/src/types.rs @@ -32,22 +32,4 @@ use crate::dag::DeclarationId; -/// A port's type, carried as the identity of the declaration the port -/// refers to. Structural equality (`==`) is declaration-id equality; -/// no name comparison, no string bridging. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TypeShape { - pub declaration: DeclarationId, -} - -impl TypeShape { - pub fn new(declaration: DeclarationId) -> Self { - Self { declaration } - } -} - -impl From for TypeShape { - fn from(declaration: DeclarationId) -> Self { - Self { declaration } - } -} +include!("types_generated.rs"); diff --git a/src/v3/compiler/src/types_generated.rs b/src/v3/compiler/src/types_generated.rs new file mode 100644 index 00000000000..512643c7087 --- /dev/null +++ b/src/v3/compiler/src/types_generated.rs @@ -0,0 +1,19 @@ +// AUTO-GENERATED from `src/v3/std/substrate.dag`. +// Regenerate instead of hand-editing. + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TypeShape { + pub declaration: DeclarationId, +} + +impl TypeShape { + pub fn new(declaration: DeclarationId) -> Self { + Self { declaration } + } +} + +impl From for TypeShape { + fn from(declaration: DeclarationId) -> Self { + Self { declaration } + } +} diff --git a/src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs b/src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs index dbc7cb36768..677ec13b495 100644 --- a/src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs +++ b/src/v3/compiler/tests/integration/m2_substrate_inhabitance_test.rs @@ -354,3 +354,21 @@ fn rust_dag_realizes_reflected_substrate_types() { ); } } + +#[test] +fn runtime_mirror_snapshots_are_fresh() { + let repo_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(3) + .expect("repo root"); + let status = std::process::Command::new("python3") + .arg("scripts/regen_runtime_mirrors.py") + .arg("--check") + .current_dir(repo_root) + .status() + .expect("run runtime mirror freshness check"); + assert!( + status.success(), + "runtime mirror snapshots are stale; run scripts/regen_runtime_mirrors.py" + ); +} diff --git a/src/v3/spec/python.dag b/src/v3/spec/python.dag index 4274bfe8b02..fd3ac7f6229 100644 --- a/src/v3/spec/python.dag +++ b/src/v3/spec/python.dag @@ -451,6 +451,19 @@ data python_list_instantiation: TypeInstantiationRealization = { } // List structural-sum → Python list surface; pattern-role + templates are spec-owned. +data python_non_empty_list_instantiation: TypeInstantiationRealization = { + language: python_language + target: NonEmptyList + carrier: "NonEmptyList[{element}]" + cost: 1 +} + +data python_non_singleton_list_instantiation: TypeInstantiationRealization = { + language: python_language + target: NonSingletonList + carrier: "NonSingletonList[{element}]" + cost: 1 +} data python_list_pattern: PatternRealization = { language: python_language target: List