use thiserror::Error;
pub type IrResult<T, E = IrError> = std::result::Result<T, E>;
#[derive(Debug, Clone, PartialEq, Eq, Error)]
#[non_exhaustive]
pub enum IrError {
#[error(
"IR inlining cycle at operation `{op_id}`. Fix: remove the recursive Expr::Call chain or split the recursive algorithm into an explicit bounded Loop."
)]
InlineCycle {
op_id: String,
},
#[error(
"IR inlining could not resolve operation `{op_id}`. Fix: register a Category A operation with this id before lowering or replace the call with inline IR."
)]
InlineUnknownOp {
op_id: String,
},
#[error(
"IR inlining rejected non-inlinable operation `{op_id}`. Fix: this op processes buffer inputs and must be dispatched as a separate kernel, not composed via Expr::Call."
)]
InlineNonInlinable {
op_id: String,
},
#[error(
"IR inlining argument count mismatch for operation `{op_id}`: expected {expected}, got {got}. Fix: pass exactly one argument for each ReadOnly or Uniform input buffer declared by the callee program."
)]
InlineArgCountMismatch {
op_id: String,
expected: usize,
got: usize,
},
#[error(
"IR inlining found no output write for operation `{op_id}`. Fix: Ensure the op's program() body writes to its output buffer at least once."
)]
InlineNoOutput {
op_id: String,
},
#[error(
"IR inlining found {got} declared output buffers for operation `{op_id}`. Fix: mark exactly one result buffer with BufferDecl::output(...)."
)]
InlineOutputCountMismatch {
op_id: String,
got: usize,
},
#[error("IR validation rejected the Program: {issues:?}")]
Validation {
issues: Vec<crate::validate::ValidationError>,
},
#[error(
"Wire-format validation failed: {message}. Fix: recompile the frontend program set and ensure the compiler only emits valid instructions."
)]
WireFormatValidation {
message: String,
},
#[error(
"vyre target-text lowering: {message}. Fix: inspect the Program shape, backend capability report, and emitted shader diagnostics before retrying."
)]
Lowering {
message: String,
},
#[error(
"Wire-format version mismatch: expected {expected}, found {found}. Fix: re-encode with a matching vyre version or upgrade this runtime."
)]
VersionMismatch {
expected: u32,
found: u32,
},
#[error(
"Unknown dialect `{name}` (requested version `{requested}`). Fix: link the dialect crate providing `{name}` into this runtime or drop the op that uses it before encoding."
)]
UnknownDialect {
name: String,
requested: String,
},
#[error(
"Unknown op `{op}` in dialect `{dialect}`. Fix: upgrade the runtime to a version that includes this op, or drop the op before encoding."
)]
UnknownOp {
dialect: String,
op: String,
},
}
impl IrError {
#[must_use]
pub fn lowering(message: impl Into<String>) -> Self {
Self::Lowering {
message: message.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lowering_helper_contains_fix_hint() {
let err = IrError::lowering("buffer too large");
let msg = err.to_string();
assert!(msg.contains("buffer too large"));
assert!(msg.contains("Fix:"));
}
#[test]
fn inline_cycle_display() {
let err = IrError::InlineCycle {
op_id: "math::add".into(),
};
assert!(err.to_string().contains("math::add"));
assert!(err.to_string().contains("cycle"));
}
#[test]
fn version_mismatch_display() {
let err = IrError::VersionMismatch {
expected: 6,
found: 5,
};
let msg = err.to_string();
assert!(msg.contains("6"));
assert!(msg.contains("5"));
}
#[test]
fn unknown_dialect_display() {
let err = IrError::UnknownDialect {
name: "my-dialect".into(),
requested: "1.0".into(),
};
assert!(err.to_string().contains("my-dialect"));
}
#[test]
fn error_is_clone_and_eq() {
let a = IrError::lowering("test");
let b = a.clone();
assert_eq!(a, b);
}
#[test]
fn inline_arg_count_mismatch_display() {
let err = IrError::InlineArgCountMismatch {
op_id: "test::op".into(),
expected: 3,
got: 1,
};
let msg = err.to_string();
assert!(msg.contains("expected 3"));
assert!(msg.contains("got 1"));
}
}