Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
cb62436
WIP: T emit go/rust bounds
briansrls Apr 24, 2026
6fd61f4
chore: apply cargo fmt
briansrls Apr 24, 2026
cf763fc
feat(python): add mul/div/ne/lt/le/gt/ge operator realizations; lift …
briansrls Apr 24, 2026
0b85dba
chore: apply cargo fmt
briansrls Apr 24, 2026
8a52a8f
fix(sg0/parse-manifest): register m1_5_emit_omni_demo_test.rs; refres…
briansrls Apr 24, 2026
ad09b1b
fix(t-emit): honest lane-closure receipt — ignore gate + hard toolcha…
briansrls Apr 24, 2026
27e4590
fix(t-emit): pin floor-div semantic constraint; fix tmp-dir leakage
briansrls Apr 24, 2026
1dee193
fix(python): correct OrderedRing.div to truncate toward zero via __v3…
briansrls Apr 24, 2026
3936bc5
chore: apply cargo fmt
briansrls Apr 24, 2026
b276e55
Merge remote-tracking branch 'origin/main' into session/sharp-heron-47
briansrls Apr 24, 2026
cbb9777
refactor(emit): dissolve binary_op dual authority; carriers are sole …
briansrls Apr 24, 2026
a26eb13
Merge remote-tracking branch 'origin/main' into session/sharp-heron-47
briansrls Apr 24, 2026
176b7c3
feat(t-emit): lift recursive_function_call_six from Go/Python exclude…
briansrls Apr 24, 2026
00cda7b
fix(ci): update carrier snapshot in m1_3_prb_rust_dag_bootstrap_loads…
briansrls Apr 24, 2026
1194f9c
fix(emit): fail-closed on operator carriers missing {lhs}/{rhs} place…
briansrls Apr 24, 2026
db8e2df
test(python): always-on emit assertions for new Int operator carriers
briansrls Apr 24, 2026
022d9a5
WIP: T emit go/rust bounds
briansrls Apr 24, 2026
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
1,963 changes: 985 additions & 978 deletions src/v3/compiler/src/bootstrap_generated.rs

Large diffs are not rendered by default.

1,803 changes: 905 additions & 898 deletions src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions src/v3/compiler/src/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,6 @@ struct StatementSyntaxBinding {

#[derive(Debug, Clone)]
struct ExpressionSyntaxBinding {
binary_op: String,
field_access: String,
function_call: String,
}
Expand Down Expand Up @@ -571,6 +570,12 @@ impl RealizationIndexes {
}
RealizationCategory::Operator => {
let carrier = require_field_string(fields, "carrier", decl.id)?;
if !carrier.contains("{lhs}") || !carrier.contains("{rhs}") {
return Err(EmitError::MalformedRealization {
declaration: decl.id,
detail: "OperatorRealization carrier must be a full-expression template containing {lhs} and {rhs}",
});
}
let op = require_field_decl_ref(fields, "op", decl.id)?;
if operators.insert((target, op), carrier).is_some() {
return Err(EmitError::DuplicateRealization {
Expand Down Expand Up @@ -1349,8 +1354,8 @@ impl<'a> Ctx<'a> {
let lhs = self.render_port(t.inputs[0], locals)?;
let rhs = self.render_port(t.inputs[1], locals)?;
Ok(render_named_template(
&self.indexes.syntax.expressions.binary_op,
&[("lhs", &lhs), ("op", carrier), ("rhs", &rhs)],
carrier,
&[("lhs", &lhs), ("rhs", &rhs)],
))
}

Expand Down Expand Up @@ -2247,7 +2252,6 @@ fn parse_expression_syntax(
) -> Result<ExpressionSyntaxBinding, EmitError> {
let fields = structural_fields_for_decl(dag, declaration)?;
Ok(ExpressionSyntaxBinding {
binary_op: syntax_field_string(fields, "binary_op", declaration)?,
field_access: syntax_field_string(fields, "field_access", declaration)?,
function_call: syntax_field_string(fields, "function_call", declaration)?,
})
Expand Down
20 changes: 12 additions & 8 deletions src/v3/compiler/src/emit/python_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ enum CallableStrategyBinding {

#[derive(Debug, Clone)]
struct PythonSyntax {
binary_op: String,
field_access: String,
function_call: String,
closure: String,
Expand Down Expand Up @@ -249,6 +248,12 @@ impl PythonIndexes {
let target = require_field_decl_ref(fields, "target", decl.id)?;
let op = require_field_decl_ref(fields, "op", decl.id)?;
let carrier = require_field_string(fields, "carrier", decl.id)?;
if !carrier.contains("{lhs}") || !carrier.contains("{rhs}") {
return Err(EmitPythonError::MalformedSpec {
declaration: decl.id,
detail: "OperatorRealization carrier must be a full-expression template containing {lhs} and {rhs}",
});
}
if operators.insert((target, op), carrier).is_some() {
return Err(EmitPythonError::DuplicateRealization {
declaration: decl.id,
Expand Down Expand Up @@ -307,11 +312,6 @@ impl PythonIndexes {
})?;

let syntax = PythonSyntax {
binary_op: require_field_string(
structural_fields_for_decl(dag, expressions)?,
"binary_op",
expressions,
)?,
field_access: require_field_string(
structural_fields_for_decl(dag, expressions)?,
"field_access",
Expand Down Expand Up @@ -674,6 +674,10 @@ pub(crate) fn emit_python_with_mode(
"__T = typing.TypeVar(\"__T\")".to_string(),
"__U = typing.TypeVar(\"__U\")".to_string(),
"def __v3_fold(items: list[typing.Any], init: typing.Any, fn: typing.Callable[[typing.Any, typing.Any], typing.Any]) -> typing.Any:\n acc = init\n for item in items:\n acc = fn(acc, item)\n return acc".to_string(),
// Python `//` is floor division; `OrderedRing.div` requires truncation toward zero.
// `divmod` adjusts the floor quotient by +1 when the remainder is non-zero and
// operand signs differ — restoring C-style truncation without floating-point.
"def __v3_idiv(a: int, b: int) -> int:\n q, r = divmod(a, b)\n return q + (1 if r != 0 and (a < 0) != (b < 0) else 0)".to_string(),
"def __v3_unreachable(label: str) -> typing.NoReturn:\n raise ValueError(label)".to_string(),
];

Expand Down Expand Up @@ -797,8 +801,8 @@ impl<'a> Ctx<'a> {
let lhs = self.render_port(t.inputs[0], locals)?;
let rhs = self.render_port(t.inputs[1], locals)?;
Ok(render_named_template(
&self.indexes.syntax.binary_op,
&[("lhs", &lhs), ("op", carrier), ("rhs", &rhs)],
carrier,
&[("lhs", &lhs), ("rhs", &rhs)],
))
}

Expand Down
12 changes: 8 additions & 4 deletions src/v3/compiler/src/emit/rust_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@ struct StatementSyntaxBinding {

#[derive(Debug, Clone)]
struct ExpressionSyntaxBinding {
binary_op: String,
field_access: String,
function_call: String,
closure: String,
Expand Down Expand Up @@ -795,6 +794,12 @@ impl RealizationIndexes {
}
RealizationCategory::Operator => {
let carrier = require_field_string(fields, "carrier", decl.id)?;
if !carrier.contains("{lhs}") || !carrier.contains("{rhs}") {
return Err(EmitError::MalformedRealization {
declaration: decl.id,
detail: "OperatorRealization carrier must be a full-expression template containing {lhs} and {rhs}",
});
}
let op = require_field_decl_ref(fields, "op", decl.id)?;
if operators.insert((target, op), carrier).is_some() {
return Err(EmitError::DuplicateRealization {
Expand Down Expand Up @@ -1321,7 +1326,6 @@ fn parse_expression_syntax(
) -> Result<ExpressionSyntaxBinding, EmitError> {
let fields = structural_fields_for_decl(dag, declaration)?;
Ok(ExpressionSyntaxBinding {
binary_op: syntax_field_string(fields, "binary_op", declaration)?,
field_access: syntax_field_string(fields, "field_access", declaration)?,
function_call: syntax_field_string(fields, "function_call", declaration)?,
closure: syntax_field_string(fields, "closure", declaration)?,
Expand Down Expand Up @@ -3343,8 +3347,8 @@ impl<'a> Ctx<'a> {
locals,
)?;
Ok(render_named_template(
&self.indexes.syntax.expressions.binary_op,
&[("lhs", &lhs), ("op", &carrier), ("rhs", &rhs)],
&carrier,
&[("lhs", &lhs), ("rhs", &rhs)],
))
}

Expand Down
32 changes: 32 additions & 0 deletions src/v3/compiler/tests/boundary/m1_4_emit_python_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,38 @@ fn emit_python_bool_logical_ops_use_keyword_carriers() {
);
}

/// Verify that the Int arithmetic and comparison operators added in PR #681
/// emit the correct Python expression templates. These are always-on emit-level
/// checks; they do not require a Python toolchain.
#[test]
fn emit_python_int_operators_use_correct_expression_templates() {
let src = "fn f(a: Int, b: Int) -> Int = a * b\n";
let out = emit_python_module_from_source(src, "int_mul_py.v3");
assert!(
out.contains("* ") || out.contains(" *"),
"mul carrier must emit `*`; got:\n{out}"
);

let src = "fn f(a: Int, b: Int) -> Int = a / b\n";
let out = emit_python_module_from_source(src, "int_div_py.v3");
assert!(
out.contains("__v3_idiv("),
"div carrier must emit `__v3_idiv(...)`; got:\n{out}"
);

let src = "fn f(a: Int, b: Int) -> Bool = a != b\n";
let out = emit_python_module_from_source(src, "int_ne_py.v3");
assert!(out.contains("!="), "ne carrier must emit `!=`; got:\n{out}");

let src = "fn f(a: Int, b: Int) -> Bool = a < b\n";
let out = emit_python_module_from_source(src, "int_lt_py.v3");
assert!(out.contains(" < "), "lt carrier must emit `<`; got:\n{out}");

let src = "fn f(a: Int, b: Int) -> Bool = a > b\n";
let out = emit_python_module_from_source(src, "int_gt_py.v3");
assert!(out.contains(" > "), "gt carrier must emit `>`; got:\n{out}");
}

#[test]
fn emit_python_wrappers_match_shared_entrypoint() {
let program_source = "\
Expand Down
220 changes: 220 additions & 0 deletions src/v3/compiler/tests/boundary/m1_5_emit_omni_demo_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
//! **Layer:** boundary (TESTING.md § test layers — class-5 multi-target
//! toolchain roundtrip).
//!
//! Two tests live here:
//!
//! * `emit_omni_demo_rust_roundtrip` — non-ignored CI gate. Emits all
//! omni-set fixtures to Rust, compiles via rustc, and checks stdout.
//! Rustc is always available so this runs unconditionally.
//!
//! * `emit_omni_demo_fixtures_green` — T-Emit lane closure receipt.
//! Marked `#[ignore]` because it requires Go **and** Python toolchains
//! that are not present in CI. When run with `--ignored` it asserts
//! both toolchains are reachable and fails hard if either is absent;
//! a missing toolchain is an unmet receipt, not a skip.
//!
//! Run the full three-way proof locally:
//!
//! cargo test -p v3-compiler --test integration \
//! emit_omni_demo_fixtures_green -- --ignored --nocapture

use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicUsize, Ordering};

use crate::common::cached_compile_to_dag;
use crate::common::determinism_fixtures::{GO_EMIT_EXCLUDE, PROGRAM_FIXTURES, PYTHON_EMIT_EXCLUDE};
use v3_compiler::emit::{emit, EmitTarget};
use v3_compiler::emit_rust::emit_rust;

static ROUNDTRIP_ID: AtomicUsize = AtomicUsize::new(0);

struct TmpDir(PathBuf);

impl TmpDir {
fn new() -> Self {
let id = ROUNDTRIP_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"v3_emit_omni_roundtrip_{}_{}",
std::process::id(),
id
));
std::fs::create_dir_all(&path).expect("create tmp dir");
TmpDir(path)
}

fn path(&self) -> &Path {
&self.0
}
}

impl Drop for TmpDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}

fn omni_fixtures() -> Vec<&'static crate::common::determinism_fixtures::ProgramFixture> {
PROGRAM_FIXTURES
.iter()
.filter(|f| !GO_EMIT_EXCLUDE.contains(&f.name))
.filter(|f| !PYTHON_EMIT_EXCLUDE.contains(&f.name))
.collect()
}

/// Emit `source` as a Rust program, compile via rustc, run, and return
/// trimmed stdout. Panics if rustc compilation or execution fails.
fn rust_stdout(source: &str) -> String {
let dag = cached_compile_to_dag(source, "omni_parity.v3");
let rendered = emit_rust(&dag).expect("Rust emit succeeded");
let tmp_dir = TmpDir::new();
let src_path = tmp_dir.path().join("main.rs");
let bin_path = tmp_dir.path().join("main_bin");
std::fs::File::create(&src_path)
.and_then(|mut f| f.write_all(rendered.as_bytes()))
.expect("write rust source");

let compile = Command::new("rustc")
// Strip RUSTC_BOOTSTRAP so the ratchet CI step's libtest unlock
// does not leak into child rustc invocations.
.env_remove("RUSTC_BOOTSTRAP")
.arg(&src_path)
.arg("-o")
.arg(&bin_path)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.expect("invoke rustc");
assert!(
compile.success(),
"rustc failed on emitted source:\n{rendered}"
);

let run = Command::new(&bin_path).output().expect("run rust binary");
assert!(run.status.success(), "compiled rust binary exited non-zero");
String::from_utf8_lossy(&run.stdout).trim().to_string()
}

/// Emit `source` as a Go program, run via `go run`, and return trimmed stdout.
/// Panics (not `None`) — callers must only invoke this when `go` is confirmed present.
fn go_stdout(fixture_name: &str, source: &str) -> String {
let dag = cached_compile_to_dag(source, "omni_parity.v3");
let rendered = emit(&dag, EmitTarget::Go)
.unwrap_or_else(|e| panic!("Go emit failed for fixture `{fixture_name}`: {e:?}"))
.text;
let tmp_dir = TmpDir::new();
let src_path = tmp_dir.path().join("main.go");
std::fs::File::create(&src_path)
.and_then(|mut f| f.write_all(rendered.as_bytes()))
.expect("write go source");

let run = Command::new("go")
.arg("run")
.arg(&src_path)
.current_dir(tmp_dir.path())
.output()
.expect("invoke go run");
assert!(
run.status.success(),
"go run failed for fixture `{fixture_name}`:\nsource:\n{rendered}\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&run.stdout),
String::from_utf8_lossy(&run.stderr),
);
String::from_utf8_lossy(&run.stdout).trim().to_string()
}

/// Emit `source` as a Python program, run via `python3`, and return trimmed stdout.
/// Panics (not `None`) — callers must only invoke this when `python3` is confirmed present.
fn python_stdout(fixture_name: &str, source: &str) -> String {
let dag = cached_compile_to_dag(source, "omni_parity.v3");
let rendered = emit(&dag, EmitTarget::Python)
.unwrap_or_else(|e| panic!("Python emit failed for fixture `{fixture_name}`: {e:?}"))
.text;
let tmp_dir = TmpDir::new();
let src_path = tmp_dir.path().join("main.py");
std::fs::File::create(&src_path)
.and_then(|mut f| f.write_all(rendered.as_bytes()))
.expect("write python source");

let run = Command::new("python3")
.arg(&src_path)
.output()
.expect("invoke python3");
assert!(
run.status.success(),
"python3 failed for fixture `{fixture_name}`:\nsource:\n{rendered}\nstderr:\n{}",
String::from_utf8_lossy(&run.stderr),
);
String::from_utf8_lossy(&run.stdout).trim().to_string()
}

fn toolchain_available(cmd: &str, probe_arg: &str) -> bool {
Command::new(cmd)
.arg(probe_arg)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.ok()
.is_some_and(|s| s.success())
}

/// CI gate: Rust-only roundtrip over all omni fixtures.
///
/// Rustc is always available; this test runs unconditionally. It proves
/// that the omni fixture set emits valid, executable Rust — the minimum bar
/// for every commit.
#[test]
fn emit_omni_demo_rust_roundtrip() {
let fixtures = omni_fixtures();
assert!(
!fixtures.is_empty(),
"omni fixture set must not be empty — check exclude lists"
);
for fixture in &fixtures {
rust_stdout(fixture.source);
}
}

/// T-Emit lane closure receipt: all three targets produce identical stdout.
///
/// Marked `#[ignore]` because `go` and `python3` are not present in CI.
/// Run locally with `--ignored` to execute the full three-way proof. The
/// test asserts both toolchains are reachable and **fails hard** if either
/// is absent — a missing toolchain is an unmet receipt, not a skip.
#[test]
#[ignore = "requires go and python3 toolchains; run locally: cargo test ... -- --ignored --nocapture"]
fn emit_omni_demo_fixtures_green() {
assert!(
toolchain_available("go", "version"),
"go toolchain not found — this test requires go to be on PATH"
);
assert!(
toolchain_available("python3", "--version"),
"python3 toolchain not found — this test requires python3 to be on PATH"
);

let fixtures = omni_fixtures();
assert!(
!fixtures.is_empty(),
"omni fixture set must not be empty — check exclude lists"
);

for fixture in &fixtures {
let rust = rust_stdout(fixture.source);

let go = go_stdout(fixture.name, fixture.source);
assert_eq!(
go, rust,
"Go output diverged from Rust baseline for fixture `{}`",
fixture.name
);

let py = python_stdout(fixture.name, fixture.source);
assert_eq!(
py, rust,
"Python output diverged from Rust baseline for fixture `{}`",
fixture.name
);
}
}
Loading
Loading