opy-rs 0.1.12

Standalone OverPy-compatible .opy implementation with parsing, tooling, and bounded Workshop compilation.
Documentation
//! Source-semantic failure-frontier evidence for issue #162.

use std::path::Path;

use crate::{CompileFailureClass, CompileStatus, Compiler};
use workshop_rs::catalog::Locale;

#[test]
fn semantic_boundary_errors_are_reported_before_lowering() {
    let cases = [
        (
            "invalid-range-binder.opy",
            "rule \"invalid binder\":\n    @Event global\n    for 1 in range(3):\n        pass\n",
            "invalid-range-binder",
            "Expected variable for 1st argument",
            3,
            9,
        ),
        (
            "four-dimensional-assignment.opy",
            "globalvar nested\n\nrule \"four-dimensional indexed assignment\":\n    @Event global\n    nested[0][0][0][0] = 1\n",
            "four-dimensional-assignment",
            "Cannot assign to 4d array",
            5,
            5,
        ),
    ];
    let compiler = Compiler::new().expect("released Workshop contract must load");

    for (source_name, source, code, message, line, col) in cases {
        let report = compiler.compile_source_report_with_locale(
            source,
            source_name,
            Path::new("."),
            &Locale::new("en-US"),
        );
        assert_eq!(
            report.compile.status,
            CompileStatus::Failure,
            "{source_name}"
        );
        assert_eq!(
            report.compile.failure_class,
            Some(CompileFailureClass::Frontend),
            "{source_name}"
        );
        let diagnostic = report
            .compile
            .diagnostics
            .first()
            .unwrap_or_else(|| panic!("missing diagnostic for {source_name}"));
        assert_eq!(diagnostic.code, code, "{source_name}");
        assert!(diagnostic.message.contains(message), "{source_name}");
        let span = diagnostic
            .span
            .as_ref()
            .unwrap_or_else(|| panic!("missing source span for {source_name}"));
        assert_eq!(span.path, source_name, "{source_name}");
        assert_eq!(span.start.line, line, "{source_name}");
        assert_eq!(span.start.col, col, "{source_name}");
    }
}