use std::fmt::Write as _;
#[derive(Debug, Clone, PartialEq)]
pub enum RustItem {
Fn(RustFn),
Struct(RustStruct),
Enum(RustEnum),
Trait(RustTrait),
Impl(RustImpl),
Mod(RustMod),
Const(RustConst),
Use(String),
Comment(String),
InnerAttr(String),
Raw(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustFn {
pub name: String,
pub type_params: Vec<String>,
pub params: Vec<RustParam>,
pub ret: Option<RustType>,
pub body: Vec<RustStmt>,
pub is_pub: bool,
pub is_unsafe: bool,
pub doc: Vec<String>,
pub attrs: Vec<String>,
pub is_abstract: bool,
}
impl Default for RustFn {
fn default() -> Self {
Self {
name: String::new(),
type_params: Vec::new(),
params: Vec::new(),
ret: None,
body: Vec::new(),
is_pub: true,
is_unsafe: false,
doc: Vec::new(),
attrs: Vec::new(),
is_abstract: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustParam {
pub name: String,
pub ty: RustType,
}
#[derive(Debug, Clone, PartialEq)]
pub enum RustType {
Named(String),
Generic(String, Vec<RustType>),
Ref(Box<RustType>),
RefMut(Box<RustType>),
Tuple(Vec<RustType>),
Unit,
Never,
Raw(String),
}
impl RustType {
pub fn i64() -> Self {
Self::Named("i64".into())
}
pub fn u64() -> Self {
Self::Named("u64".into())
}
pub fn f64() -> Self {
Self::Named("f64".into())
}
pub fn bool() -> Self {
Self::Named("bool".into())
}
pub fn string() -> Self {
Self::Named("String".into())
}
pub fn bytes() -> Self {
Self::Generic("Vec".into(), vec![Self::Named("u8".into())])
}
pub fn result(ok: RustType, err: RustType) -> Self {
Self::Generic("Result".into(), vec![ok, err])
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum RustStmt {
Let {
name: String,
ty: Option<RustType>,
init: RustExpr,
},
Assert { cond: String, label: String },
Comment(String),
Expr(RustExpr),
Raw(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum RustExpr {
Ident(String),
Literal(String),
Call(String, Vec<RustExpr>),
MethodCall(Box<RustExpr>, String, Vec<RustExpr>),
Todo(String),
Ok(Box<RustExpr>),
Clone(Box<RustExpr>),
Raw(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustStruct {
pub name: String,
pub type_params: Vec<String>,
pub fields: Vec<RustField>,
pub derives: Vec<String>,
pub is_pub: bool,
pub doc: Vec<String>,
}
impl Default for RustStruct {
fn default() -> Self {
Self {
name: String::new(),
type_params: Vec::new(),
fields: Vec::new(),
derives: vec!["Debug".into(), "Clone".into(), "PartialEq".into()],
is_pub: true,
doc: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustField {
pub name: String,
pub ty: RustType,
pub is_pub: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustEnum {
pub name: String,
pub type_params: Vec<String>,
pub variants: Vec<RustVariant>,
pub derives: Vec<String>,
pub is_pub: bool,
pub doc: Vec<String>,
pub attrs: Vec<String>,
}
impl Default for RustEnum {
fn default() -> Self {
Self {
name: String::new(),
type_params: Vec::new(),
variants: Vec::new(),
derives: vec!["Debug".into(), "Clone".into(), "PartialEq".into()],
is_pub: true,
doc: Vec::new(),
attrs: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustVariant {
pub name: String,
pub fields: Vec<RustType>,
pub attrs: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustTrait {
pub name: String,
pub type_params: Vec<String>,
pub supertraits: Vec<String>,
pub methods: Vec<RustFn>,
pub is_pub: bool,
pub doc: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustImpl {
pub trait_name: Option<String>,
pub target: String,
pub type_params: Vec<String>,
pub methods: Vec<RustFn>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustMod {
pub name: String,
pub items: Vec<RustItem>,
pub is_pub: bool,
pub doc: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct RustConst {
pub name: String,
pub ty: RustType,
pub value: String,
pub is_pub: bool,
pub doc: Vec<String>,
}
pub fn render_items(items: &[RustItem]) -> String {
let mut code = String::new();
for item in items {
render_item(item, &mut code);
}
match syn::parse_file(&code) {
Ok(syntax_tree) => prettyplease::unparse(&syntax_tree),
Err(e) => {
eprintln!("warning: HIR-generated Rust has syntax errors, skipping formatting: {e}");
format!("// WARNING: prettyplease formatting skipped (parse error: {e})\n\n{code}")
}
}
}
fn render_item(item: &RustItem, out: &mut String) {
match item {
RustItem::Fn(f) => render_fn(f, out),
RustItem::Struct(s) => render_struct(s, out),
RustItem::Enum(e) => render_enum(e, out),
RustItem::Trait(t) => render_trait(t, out),
RustItem::Impl(i) => render_impl(i, out),
RustItem::Mod(m) => render_mod(m, out),
RustItem::Const(c) => render_const(c, out),
RustItem::Use(path) => {
let _ = writeln!(out, "use {path};");
}
RustItem::Comment(text) => {
let _ = writeln!(out, "// {text}");
}
RustItem::InnerAttr(attr) => {
let _ = writeln!(out, "#![{attr}]");
}
RustItem::Raw(code) => {
out.push_str(code);
if !code.ends_with('\n') {
out.push('\n');
}
}
}
}
fn render_fn(f: &RustFn, out: &mut String) {
render_fn_opts(f, out, &RenderOpts::default());
}
fn render_fn_opts(f: &RustFn, out: &mut String, opts: &RenderOpts) {
for doc in &f.doc {
let _ = writeln!(out, "/// {doc}");
}
for attr in &f.attrs {
let _ = writeln!(out, "#[{attr}]");
}
let vis = if f.is_pub { "pub " } else { "" };
let unsafe_kw = if f.is_unsafe { "unsafe " } else { "" };
let tps = if f.type_params.is_empty() {
String::new()
} else {
format!("<{}>", f.type_params.join(", "))
};
let params: Vec<String> = f
.params
.iter()
.map(|p| {
if p.name == "&self" || p.name == "self" || p.name == "&mut self" {
p.name.clone()
} else {
format!("{}: {}", p.name, render_type(&p.ty))
}
})
.collect();
let params_s = params.join(", ");
let ret = match &f.ret {
Some(ty) => format!(" -> {}", render_type(ty)),
None => String::new(),
};
if f.is_abstract {
let _ = writeln!(
out,
"{vis}{unsafe_kw}fn {}{tps}({params_s}){ret};\n",
f.name
);
} else {
let _ = writeln!(
out,
"{vis}{unsafe_kw}fn {}{tps}({params_s}){ret} {{",
f.name
);
for stmt in &f.body {
render_stmt_opts(stmt, out, 1, opts);
}
out.push_str("}\n\n");
}
}
fn render_struct(s: &RustStruct, out: &mut String) {
for doc in &s.doc {
let _ = writeln!(out, "/// {doc}");
}
if !s.derives.is_empty() {
let _ = writeln!(out, "#[derive({})]", s.derives.join(", "));
}
let vis = if s.is_pub { "pub " } else { "" };
let tps = if s.type_params.is_empty() {
String::new()
} else {
format!("<{}>", s.type_params.join(", "))
};
if s.fields.is_empty() {
let _ = writeln!(out, "{vis}struct {}{tps};\n", s.name);
} else {
let _ = writeln!(out, "{vis}struct {}{tps} {{", s.name);
for field in &s.fields {
let fvis = if field.is_pub { "pub " } else { "" };
let _ = writeln!(out, " {fvis}{}: {},", field.name, render_type(&field.ty));
}
out.push_str("}\n\n");
}
}
fn render_enum(e: &RustEnum, out: &mut String) {
for doc in &e.doc {
let _ = writeln!(out, "/// {doc}");
}
if !e.derives.is_empty() {
let _ = writeln!(out, "#[derive({})]", e.derives.join(", "));
}
for attr in &e.attrs {
let _ = writeln!(out, "#[{attr}]");
}
let vis = if e.is_pub { "pub " } else { "" };
let tps = if e.type_params.is_empty() {
String::new()
} else {
format!("<{}>", e.type_params.join(", "))
};
let _ = writeln!(out, "{vis}enum {}{tps} {{", e.name);
for variant in &e.variants {
for attr in &variant.attrs {
let _ = writeln!(out, " #[{attr}]");
}
if variant.fields.is_empty() {
let _ = writeln!(out, " {},", variant.name);
} else {
let fields: Vec<String> = variant.fields.iter().map(render_type).collect();
let _ = writeln!(out, " {}({}),", variant.name, fields.join(", "));
}
}
out.push_str("}\n\n");
}
fn render_trait(t: &RustTrait, out: &mut String) {
for doc in &t.doc {
let _ = writeln!(out, "/// {doc}");
}
let vis = if t.is_pub { "pub " } else { "" };
let tps = if t.type_params.is_empty() {
String::new()
} else {
format!("<{}>", t.type_params.join(", "))
};
let bounds = if t.supertraits.is_empty() {
String::new()
} else {
format!(": {}", t.supertraits.join(" + "))
};
let _ = writeln!(out, "{vis}trait {}{tps}{bounds} {{", t.name);
for method in &t.methods {
render_fn(method, out);
}
out.push_str("}\n\n");
}
fn render_impl(i: &RustImpl, out: &mut String) {
let tps = if i.type_params.is_empty() {
String::new()
} else {
format!("<{}>", i.type_params.join(", "))
};
match &i.trait_name {
Some(trait_name) => {
let _ = writeln!(out, "impl{tps} {trait_name} for {}{tps} {{", i.target);
}
None => {
let _ = writeln!(out, "impl{tps} {}{tps} {{", i.target);
}
}
for method in &i.methods {
render_fn(method, out);
}
out.push_str("}\n\n");
}
fn render_mod(m: &RustMod, out: &mut String) {
for doc in &m.doc {
let _ = writeln!(out, "/// {doc}");
}
let vis = if m.is_pub { "pub " } else { "" };
let _ = writeln!(out, "{vis}mod {} {{", m.name);
for item in &m.items {
render_item(item, out);
}
out.push_str("}\n\n");
}
fn render_const(c: &RustConst, out: &mut String) {
for doc in &c.doc {
let _ = writeln!(out, "/// {doc}");
}
let vis = if c.is_pub { "pub " } else { "" };
let _ = writeln!(
out,
"{vis}const {}: {} = {};",
c.name,
render_type(&c.ty),
c.value
);
}
fn render_type(ty: &RustType) -> String {
match ty {
RustType::Named(name) => name.clone(),
RustType::Generic(name, args) => {
let args_s: Vec<String> = args.iter().map(render_type).collect();
format!("{name}<{}>", args_s.join(", "))
}
RustType::Ref(inner) => format!("&{}", render_type(inner)),
RustType::RefMut(inner) => format!("&mut {}", render_type(inner)),
RustType::Tuple(items) => {
let items_s: Vec<String> = items.iter().map(render_type).collect();
format!("({})", items_s.join(", "))
}
RustType::Unit => "()".into(),
RustType::Never => "!".into(),
RustType::Raw(s) => s.clone(),
}
}
#[cfg(test)]
pub(crate) fn render_stmt(stmt: &RustStmt, out: &mut String, indent: usize) {
render_stmt_opts(stmt, out, indent, &RenderOpts::default());
}
#[derive(Debug, Clone, Default)]
pub(crate) struct RenderOpts {
pub runtime_checks: bool,
pub contract_name: String,
}
pub(crate) fn render_stmt_opts(
stmt: &RustStmt,
out: &mut String,
indent: usize,
opts: &RenderOpts,
) {
let pad = " ".repeat(indent);
match stmt {
RustStmt::Let { name, ty, init } => {
let ty_s = match ty {
Some(t) => format!(": {}", render_type(t)),
None => String::new(),
};
let _ = writeln!(out, "{pad}let {name}{ty_s} = {};", render_expr(init));
}
RustStmt::Assert { cond, label } => {
if crate::has_deep_field_access(cond) {
let _ = writeln!(out, "{pad}// {label}: {}", cond.replace('"', "\\\""));
} else if opts.runtime_checks {
let escaped_cond = cond.replace('"', "\\\"");
let contract = opts.contract_name.replace('"', "\\\"");
if cond.contains('\n') {
let flat_cond = cond.replace('\n', " ");
let _ = writeln!(
out,
"{pad}if !({{ {cond} }}) {{ assura_runtime::contract_violation(\"{contract}\", \"{label}\", \"{escaped_cond}\", file!(), line!()); }}"
);
let _ = flat_cond.len(); } else {
let _ = writeln!(
out,
"{pad}if !({cond}) {{ assura_runtime::contract_violation(\"{contract}\", \"{label}\", \"{escaped_cond}\", file!(), line!()); }}"
);
}
} else if cond.contains('\n') {
let msg = cond.replace('\n', " ").replace('"', "\\\"");
let _ = writeln!(out, "{pad}debug_assert!({{ {cond} }}, \"{label}: {msg}\");");
} else {
let escaped_cond = cond.replace('"', "\\\"");
let _ = writeln!(
out,
"{pad}debug_assert!({cond}, \"{label}: {escaped_cond}\");"
);
}
}
RustStmt::Comment(text) => {
let _ = writeln!(out, "{pad}// {text}");
}
RustStmt::Expr(expr) => {
let _ = writeln!(out, "{pad}{}", render_expr(expr));
}
RustStmt::Raw(code) => {
for line in code.lines() {
let _ = writeln!(out, "{pad}{line}");
}
}
}
}
fn render_expr(expr: &RustExpr) -> String {
match expr {
RustExpr::Ident(name) => name.clone(),
RustExpr::Literal(lit) => lit.clone(),
RustExpr::Call(func, args) => {
let args_s: Vec<String> = args.iter().map(render_expr).collect();
format!("{func}({})", args_s.join(", "))
}
RustExpr::MethodCall(receiver, method, args) => {
let args_s: Vec<String> = args.iter().map(render_expr).collect();
format!("{}.{method}({})", render_expr(receiver), args_s.join(", "))
}
RustExpr::Todo(msg) => format!("todo!(\"{msg}\")"),
RustExpr::Ok(inner) => format!("Ok({})", render_expr(inner)),
RustExpr::Clone(inner) => format!("{}.clone()", render_expr(inner)),
RustExpr::Raw(code) => code.clone(),
}
}
pub fn build_error_enum(contract_name: &str, variants: &[String]) -> RustItem {
let enum_name = format!("{contract_name}Error");
RustItem::Enum(RustEnum {
name: enum_name,
variants: variants
.iter()
.map(|v| RustVariant {
name: v.clone(),
fields: vec![],
attrs: vec![format!("error(\"{v}\")")],
})
.collect(),
derives: vec!["Debug".into(), "thiserror::Error".into()],
..RustEnum::default()
})
}
pub fn build_enum_def(e: &assura_ast::EnumDef) -> Vec<RustItem> {
let mut items = Vec::new();
let tps: Vec<String> = e.type_params.clone();
let rust_enum = RustEnum {
name: e.name.clone(),
type_params: tps.clone(),
variants: e
.variants
.iter()
.map(|v| RustVariant {
name: v.name.clone(),
fields: v
.fields
.iter()
.map(|f| RustType::Raw(super::map_type_token(f).to_string()))
.collect(),
attrs: vec![],
})
.collect(),
..RustEnum::default()
};
items.push(RustItem::Enum(rust_enum));
if tps.is_empty() {
let arms: Vec<String> = e
.variants
.iter()
.map(|v| {
if v.fields.is_empty() {
format!("{}::{} => write!(f, \"{}\")", e.name, v.name, v.name)
} else {
let underscores: Vec<&str> = (0..v.fields.len()).map(|_| "_").collect();
format!(
"{}::{}({}) => write!(f, \"{}(...)\")",
e.name,
v.name,
underscores.join(", "),
v.name
)
}
})
.collect();
let match_body = format!("match self {{ {} }}", arms.join(", "));
items.push(RustItem::Impl(RustImpl {
trait_name: Some("std::fmt::Display".into()),
target: e.name.clone(),
type_params: vec![],
methods: vec![RustFn {
name: "fmt".into(),
params: vec![
RustParam {
name: "&self".into(),
ty: RustType::Raw("&Self".into()),
},
RustParam {
name: "f".into(),
ty: RustType::Raw("&mut std::fmt::Formatter<'_>".into()),
},
],
ret: Some(RustType::Raw("std::fmt::Result".into())),
body: vec![RustStmt::Raw(match_body)],
is_pub: false,
..RustFn::default()
}],
}));
}
if !e.variants.is_empty() && tps.is_empty() {
let arms: Vec<String> = e
.variants
.iter()
.map(|v| {
if v.fields.is_empty() {
format!("{}::{} => \"{}\"", e.name, v.name, v.name)
} else {
let underscores: Vec<&str> = (0..v.fields.len()).map(|_| "_").collect();
format!(
"{}::{}({}) => \"{}\"",
e.name,
v.name,
underscores.join(", "),
v.name
)
}
})
.collect();
let match_body = format!("match v {{ {} }}", arms.join(", "));
items.push(RustItem::Fn(RustFn {
name: format!("__exhaustive_check_{}", e.name.to_lowercase()),
params: vec![RustParam {
name: "v".into(),
ty: RustType::Ref(Box::new(RustType::Named(e.name.clone()))),
}],
ret: Some(RustType::Raw("&'static str".into())),
body: vec![RustStmt::Raw(match_body)],
is_pub: false,
attrs: vec!["allow(dead_code)".into()],
doc: vec![
format!("Compile-time exhaustiveness check for `{}`.", e.name),
"Adding a variant without updating all match sites causes a build error.".into(),
],
..RustFn::default()
}));
}
items
}
pub fn render_item_raw(item: &RustItem) -> String {
let mut out = String::new();
render_item(item, &mut out);
out
}
pub(crate) fn render_item_raw_with_opts(item: &RustItem, opts: &RenderOpts) -> String {
let mut out = String::new();
render_item_with_opts(item, &mut out, opts);
out
}
fn render_item_with_opts(item: &RustItem, out: &mut String, opts: &RenderOpts) {
match item {
RustItem::Fn(f) => render_fn_opts(f, out, opts),
other => render_item(other, out),
}
}
pub fn build_type_def(t: &assura_ast::TypeDef) -> Vec<RustItem> {
use assura_ast::TypeBody;
let tps = t.type_params.clone();
match &t.body {
TypeBody::Struct(fields) => {
let rust_fields: Vec<RustField> = fields
.iter()
.map(|f| {
let ty_tokens = f.ty.as_ref().map(|t| t.to_tokens()).unwrap_or_default();
RustField {
name: f.name.clone(),
ty: RustType::Raw(crate::map_type_tokens(&ty_tokens)),
is_pub: f.is_pub,
}
})
.collect();
vec![RustItem::Struct(RustStruct {
name: t.name.clone(),
type_params: tps,
fields: rust_fields,
derives: vec!["Debug".into(), "Clone".into(), "PartialEq".into()],
..RustStruct::default()
})]
}
TypeBody::Alias(tokens) => {
let rust_ty = crate::map_type_tokens(tokens);
vec![RustItem::Raw(format!("pub type {} = {rust_ty};\n", t.name))]
}
TypeBody::Refined(tokens) => {
let base = crate::extract_base_type_from_refined(tokens);
let tps_str = if tps.is_empty() {
String::new()
} else {
format!("<{}>", tps.join(", "))
};
vec![RustItem::Raw(format!(
"#[derive(Debug, Clone, PartialEq)]\npub struct {}{tps_str}(pub {base});\n",
t.name
))]
}
TypeBody::Empty => {
let tps_str = if tps.is_empty() {
String::new()
} else {
format!("<{}>", tps.join(", "))
};
if tps.is_empty() {
vec![RustItem::Raw(format!(
"#[derive(Debug, Clone, PartialEq)]\npub struct {}{};\n",
t.name, tps_str
))]
} else {
let phantoms: Vec<String> = tps
.iter()
.map(|p| format!("std::marker::PhantomData<{p}>"))
.collect();
vec![RustItem::Raw(format!(
"#[derive(Debug, Clone, PartialEq)]\npub struct {}{tps_str}({});\n",
t.name,
phantoms.join(", ")
))]
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_simple_fn() {
let f = RustFn {
name: "add".into(),
params: vec![
RustParam {
name: "a".into(),
ty: RustType::i64(),
},
RustParam {
name: "b".into(),
ty: RustType::i64(),
},
],
ret: Some(RustType::i64()),
body: vec![RustStmt::Expr(RustExpr::Raw("a + b".into()))],
..RustFn::default()
};
let code = render_items(&[RustItem::Fn(f)]);
assert!(code.contains("pub fn add(a: i64, b: i64) -> i64"));
assert!(code.contains("a + b"));
}
#[test]
fn render_unit_struct() {
let s = RustStruct {
name: "Marker".into(),
..RustStruct::default()
};
let code = render_items(&[RustItem::Struct(s)]);
assert!(code.contains("#[derive(Debug, Clone, PartialEq)]"));
assert!(code.contains("pub struct Marker;"));
}
#[test]
fn render_struct_with_fields() {
let s = RustStruct {
name: "Point".into(),
fields: vec![
RustField {
name: "x".into(),
ty: RustType::f64(),
is_pub: true,
},
RustField {
name: "y".into(),
ty: RustType::f64(),
is_pub: true,
},
],
..RustStruct::default()
};
let code = render_items(&[RustItem::Struct(s)]);
assert!(code.contains("pub struct Point"));
assert!(code.contains("pub x: f64"));
assert!(code.contains("pub y: f64"));
}
#[test]
fn render_error_enum() {
let e = RustEnum {
name: "SafeDivisionError".into(),
variants: vec![
RustVariant {
name: "DivByZero".into(),
fields: vec![],
attrs: vec!["error(\"DivByZero\")".into()],
},
RustVariant {
name: "Overflow".into(),
fields: vec![],
attrs: vec!["error(\"Overflow\")".into()],
},
],
derives: vec!["Debug".into(), "thiserror::Error".into()],
..RustEnum::default()
};
let code = render_items(&[RustItem::Enum(e)]);
assert!(code.contains("#[derive(Debug, thiserror::Error)]"));
assert!(code.contains("pub enum SafeDivisionError"));
assert!(code.contains("#[error(\"DivByZero\")]"));
assert!(code.contains("DivByZero,"));
assert!(code.contains("#[error(\"Overflow\")]"));
assert!(code.contains("Overflow,"));
}
#[test]
fn render_fn_with_assert() {
let f = RustFn {
name: "check".into(),
params: vec![RustParam {
name: "x".into(),
ty: RustType::i64(),
}],
ret: Some(RustType::i64()),
body: vec![
RustStmt::Assert {
cond: "(x > 0)".into(),
label: "requires".into(),
},
RustStmt::Let {
name: "__assura_result".into(),
ty: Some(RustType::i64()),
init: RustExpr::Todo("implementation provided by AI agent".into()),
},
RustStmt::Expr(RustExpr::Ident("__assura_result".into())),
],
..RustFn::default()
};
let code = render_items(&[RustItem::Fn(f)]);
assert!(code.contains("debug_assert!"));
assert!(code.contains("requires"));
assert!(code.contains("todo!"));
}
#[test]
fn render_const() {
let c = RustConst {
name: "MAX_SIZE".into(),
ty: RustType::u64(),
value: "1024".into(),
is_pub: true,
doc: vec![],
};
let code = render_items(&[RustItem::Const(c)]);
assert!(code.contains("pub const MAX_SIZE: u64 = 1024;"));
}
#[test]
fn render_generic_type() {
let ty = RustType::result(RustType::i64(), RustType::Named("MyError".into()));
assert_eq!(render_type(&ty), "Result<i64, MyError>");
}
#[test]
fn render_module() {
let m = RustMod {
name: "contract_foo".into(),
items: vec![RustItem::Comment("inner".into())],
is_pub: true,
doc: vec!["Contract: Foo".into()],
};
let code = render_items(&[RustItem::Mod(m)]);
assert!(code.contains("/// Contract: Foo"));
assert!(code.contains("pub mod contract_foo"));
}
#[test]
fn render_impl_block() {
let i = RustImpl {
trait_name: Some("Display".into()),
target: "Color".into(),
type_params: vec![],
methods: vec![RustFn {
name: "fmt".into(),
params: vec![
RustParam {
name: "&self".into(),
ty: RustType::Raw("&Self".into()),
},
RustParam {
name: "f".into(),
ty: RustType::Raw("&mut std::fmt::Formatter<'_>".into()),
},
],
ret: Some(RustType::Raw("std::fmt::Result".into())),
body: vec![RustStmt::Raw(
"match self { _ => write!(f, \"Color\") }".into(),
)],
is_pub: false,
..RustFn::default()
}],
};
let code = render_items(&[RustItem::Impl(i)]);
assert!(code.contains("impl Display for Color"));
}
#[test]
fn type_shorthand_helpers() {
assert_eq!(render_type(&RustType::i64()), "i64");
assert_eq!(render_type(&RustType::u64()), "u64");
assert_eq!(render_type(&RustType::f64()), "f64");
assert_eq!(render_type(&RustType::bool()), "bool");
assert_eq!(render_type(&RustType::string()), "String");
assert_eq!(render_type(&RustType::bytes()), "Vec<u8>");
assert_eq!(render_type(&RustType::Unit), "()");
assert_eq!(render_type(&RustType::Never), "!");
}
#[test]
fn render_trait_def() {
let t = RustTrait {
name: "Serializable".into(),
type_params: vec![],
supertraits: vec!["Clone".into()],
methods: vec![RustFn {
name: "serialize".into(),
params: vec![RustParam {
name: "&self".into(),
ty: RustType::Raw("&Self".into()),
}],
ret: Some(RustType::bytes()),
body: vec![],
is_pub: false,
..RustFn::default()
}],
is_pub: true,
doc: vec![],
};
let code = render_items(&[RustItem::Trait(t)]);
assert!(code.contains("pub trait Serializable: Clone"));
}
#[test]
fn count_asserts_in_fn() {
let f = RustFn {
name: "check".into(),
params: vec![RustParam {
name: "x".into(),
ty: RustType::i64(),
}],
ret: Some(RustType::i64()),
body: vec![
RustStmt::Assert {
cond: "(x > 0)".into(),
label: "requires".into(),
},
RustStmt::Assert {
cond: "(x < 100)".into(),
label: "requires".into(),
},
RustStmt::Expr(RustExpr::Ident("x".into())),
],
..RustFn::default()
};
let assert_count = f
.body
.iter()
.filter(|s| matches!(s, RustStmt::Assert { .. }))
.count();
assert_eq!(assert_count, 2, "should have exactly 2 assert statements");
}
#[test]
fn build_error_enum_structure() {
let item = build_error_enum("SafeDivision", &["DivByZero".into(), "Overflow".into()]);
if let RustItem::Enum(e) = &item {
assert_eq!(e.name, "SafeDivisionError");
assert_eq!(e.variants.len(), 2);
assert_eq!(e.variants[0].name, "DivByZero");
assert_eq!(e.variants[1].name, "Overflow");
assert!(e.derives.contains(&"thiserror::Error".to_string()));
for v in &e.variants {
assert_eq!(v.attrs.len(), 1);
assert!(v.attrs[0].starts_with("error(\""));
}
} else {
panic!("Expected RustItem::Enum");
}
}
#[test]
fn build_enum_def_structure() {
let enum_def = assura_ast::EnumDef {
name: "Color".into(),
type_params: vec![],
variants: vec![
assura_ast::EnumVariant {
name: "Red".into(),
fields: vec![],
},
assura_ast::EnumVariant {
name: "Blue".into(),
fields: vec![],
},
assura_ast::EnumVariant {
name: "Custom".into(),
fields: vec!["Int".into()],
},
],
};
let items = build_enum_def(&enum_def);
assert_eq!(items.len(), 3, "enum + Display + exhaustive");
if let RustItem::Enum(e) = &items[0] {
assert_eq!(e.name, "Color");
assert_eq!(e.variants.len(), 3);
assert_eq!(e.variants[2].name, "Custom");
assert_eq!(e.variants[2].fields.len(), 1);
} else {
panic!("Expected RustItem::Enum");
}
assert!(
matches!(&items[1], RustItem::Impl(i) if i.trait_name.as_deref() == Some("std::fmt::Display"))
);
assert!(matches!(&items[2], RustItem::Fn(f) if f.name == "__exhaustive_check_color"));
}
#[test]
fn build_enum_def_generic_no_display() {
let enum_def = assura_ast::EnumDef {
name: "Option".into(),
type_params: vec!["T".into()],
variants: vec![
assura_ast::EnumVariant {
name: "Some".into(),
fields: vec!["T".into()],
},
assura_ast::EnumVariant {
name: "None".into(),
fields: vec![],
},
],
};
let items = build_enum_def(&enum_def);
assert_eq!(
items.len(),
1,
"only enum, no Display or exhaustive for generic"
);
assert!(matches!(&items[0], RustItem::Enum(e) if e.type_params == vec!["T"]));
}
#[test]
fn round_trip_parse_format() {
let items = vec![
RustItem::Fn(RustFn {
name: "add".into(),
params: vec![
RustParam {
name: "a".into(),
ty: RustType::i64(),
},
RustParam {
name: "b".into(),
ty: RustType::i64(),
},
],
ret: Some(RustType::i64()),
body: vec![RustStmt::Expr(RustExpr::Raw("a + b".into()))],
..RustFn::default()
}),
RustItem::Struct(RustStruct {
name: "Point".into(),
fields: vec![
RustField {
name: "x".into(),
ty: RustType::f64(),
is_pub: true,
},
RustField {
name: "y".into(),
ty: RustType::f64(),
is_pub: true,
},
],
..RustStruct::default()
}),
];
let rendered = render_items(&items);
let parsed = syn::parse_file(&rendered);
assert!(
parsed.is_ok(),
"Rendered HIR should parse as valid Rust: {:?}",
parsed.err()
);
let reparsed = prettyplease::unparse(&parsed.unwrap());
assert_eq!(rendered, reparsed, "Formatting should be idempotent");
}
#[test]
fn type_rendering_coverage() {
let cases = vec![
(RustType::Named("MyStruct".into()), "MyStruct"),
(
RustType::Generic("Vec".into(), vec![RustType::Named("u8".into())]),
"Vec<u8>",
),
(
RustType::Ref(Box::new(RustType::Named("str".into()))),
"&str",
),
(RustType::RefMut(Box::new(RustType::i64())), "&mut i64"),
(
RustType::Tuple(vec![RustType::i64(), RustType::bool()]),
"(i64, bool)",
),
(RustType::Unit, "()"),
(RustType::Never, "!"),
(RustType::Raw("Box<dyn Fn()>".into()), "Box<dyn Fn()>"),
];
for (ty, expected) in cases {
assert_eq!(
render_type(&ty),
expected,
"type rendering mismatch for {:?}",
ty
);
}
}
#[test]
fn malformed_hir_produces_warning() {
let items = vec![RustItem::Fn(RustFn {
name: "bad".into(),
body: vec![RustStmt::Raw("this is {{ not valid {{ rust".into())],
..RustFn::default()
})];
let rendered = render_items(&items);
assert!(
rendered.contains("WARNING"),
"Invalid HIR should produce a warning in output"
);
}
}