pub mod cross_product;
pub mod emit;
pub mod independence;
pub mod naming;
pub mod provenance;
pub mod seed;
use std::fmt;
use std::path::{Path, PathBuf};
use crate::generate::archetypes::Archetype;
use crate::generate::templates::TemplateError;
use crate::spec::types::OpSpec;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum TemplateKind {
OpCorrectness,
Law,
BackendEquiv,
Archetype,
Validation,
MutationKill,
}
pub struct GenerationPlan {
pub ops: Vec<&'static OpSpec>,
pub archetypes: Vec<&'static dyn Archetype>,
pub templates: Vec<TemplateKind>,
pub seed: u64,
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct GenerationReport {
pub files: Vec<GeneratedFile>,
pub skipped_empty_inputs: usize,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GeneratedFile {
pub path: PathBuf,
pub test_name: String,
pub op: &'static str,
pub archetype: &'static str,
pub oracle: String,
pub template: TemplateKind,
pub seed: u64,
}
#[derive(Debug)]
pub enum GenError {
InvalidPlan {
reason: String,
},
Template(
TemplateError,
),
InvalidRust {
test_name: String,
reason: String,
},
Io(
std::io::Error,
),
NameCollision(
String,
),
TautologicalTest {
test_name: String,
op: String,
line: usize,
call_path: String,
hint: String,
},
Multiple(
Vec<GenError>,
),
ArityMismatch {
expected: usize,
actual: usize,
op: &'static str,
},
}
impl fmt::Display for GenError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidPlan { reason } => write!(f, "invalid generation plan. Fix: {reason}"),
Self::Template(err) => write!(f, "template rendering failed. Fix: {err}"),
Self::InvalidRust { test_name, reason } => write!(
f,
"generated Rust for `{test_name}` is invalid. Fix: repair the template or generator context. {reason}"
),
Self::Io(err) => write!(f, "generated file write failed. Fix: {err}"),
Self::NameCollision(name) => write!(
f,
"deterministic test name collision for `{name}`. Fix: add distinguishing tuple data to the seed hash."
),
Self::TautologicalTest { test_name, op, line, call_path, hint } => write!(
f,
"generated test `{test_name}` for op `{op}` is tautological: \
line {line} binds an expected value to `{call_path}`, which \
resolves into the op under test. Fix: {hint}"
),
Self::Multiple(errors) => {
write!(f, "multiple generation errors ({}):", errors.len())?;
for err in errors {
write!(f, " [{}]", err)?;
}
Ok(())
}
Self::ArityMismatch { expected, actual, op } => write!(
f,
"arity mismatch for op `{op}`: expected {expected} inputs, got {actual}. \
Fix: ensure archetype inputs match the op signature."
),
}
}
}
impl std::error::Error for GenError {}
impl From<std::io::Error> for GenError {
fn from(value: std::io::Error) -> Self {
Self::Io(value)
}
}
impl From<TemplateError> for GenError {
fn from(value: TemplateError) -> Self {
Self::Template(value)
}
}
#[inline]
pub fn generate(plan: &GenerationPlan, out_dir: &Path) -> Result<GenerationReport, GenError> {
if plan.ops.is_empty() {
return Err(GenError::InvalidPlan {
reason: "provide at least one OpSpec in GenerationPlan::ops.".to_string(),
});
}
if plan.archetypes.is_empty() {
return Err(GenError::InvalidPlan {
reason: "provide at least one archetype in GenerationPlan::archetypes.".to_string(),
});
}
if plan.templates.is_empty() {
return Err(GenError::InvalidPlan {
reason: "provide at least one TemplateKind in GenerationPlan::templates.".to_string(),
});
}
cross_product::generate_cross_product(plan, out_dir)
}