use crate::adapter::cst::MaterialiseError;
use crate::adapter::python::declared_path;
use crate::default_common::{discriminator_default_value, resolve_alias, selected_case};
use crate::emit::CodecTarget;
use crate::emit::python::Python;
use crate::encode::EncodeError;
use crate::fixed_literal::{fixed_text, zero_decimal};
use zerodds_ast::{
Ast, ConstExpr, Literal as AstLiteral, MemberNode, PrimitiveKind, TypeKind, TypeNode,
};
use zerodds_ast_core::Id;
use zerodds_lang_cst::common::{Doc, Ident, Literal, Path};
use zerodds_lang_cst::python::{self as py, escape_name};
const MAX_ALIAS_DEPTH: usize = 64;
#[must_use]
pub fn defaulter_name(ast: &Ast, id: Id<TypeNode>) -> String {
match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => {
format!("zd_def_p_{}", kind.as_str().replace(' ', "_"))
}
_ => format!("zd_def_{}", Python::type_name(ast, id)),
}
}
#[must_use]
pub fn defaulter_from_decoder(decoder: &str) -> String {
match decoder.strip_prefix("zd_dec_") {
Some(rest) => format!("zd_def_{rest}"),
None => format!("zd_def_{decoder}"),
}
}
#[must_use]
pub fn default_into(ast: &Ast, id: Id<TypeNode>, place: py::Expr) -> py::Statement {
set(place, call(&defaulter_name(ast, id), Vec::new()))
}
pub fn member_default(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
place: py::Expr,
) -> Result<Vec<py::Statement>, EncodeError> {
Ok(vec![set(place, member_value(ast, node, type_)?)])
}
pub fn python_defaulters_called_by(
ast: &Ast,
decls: &[py::Statement],
) -> Result<Vec<py::Statement>, EncodeError> {
let index: Vec<(String, Id<TypeNode>)> = ast
.types()
.map(|(id, _)| (defaulter_name(ast, id), id))
.collect();
let mut called: Vec<String> = Vec::new();
for decl in decls {
calls_in_statement(decl, &mut called);
}
let mut wanted: Vec<Id<TypeNode>> = Vec::new();
let mut have: Vec<String> = Vec::new();
for _ in 0..=index.len() {
let mut grew = false;
for name in &called {
if have.contains(name) {
continue;
}
have.push(name.clone());
if let Some(id) = index.iter().find(|(n, _)| n == name).map(|(_, id)| *id) {
wanted.push(id);
grew = true;
}
}
let produced = python_defaulters_over(ast, &wanted)?;
if !grew {
return Ok(produced);
}
for decl in &produced {
calls_in_statement(decl, &mut called);
}
}
python_defaulters_over(ast, &wanted)
}
pub fn python_defaulters_over(
ast: &Ast,
types: &[Id<TypeNode>],
) -> Result<Vec<py::Statement>, EncodeError> {
let mut out = Vec::new();
let mut emitted: Vec<String> = Vec::new();
for id in types.iter().copied() {
let name = defaulter_name(ast, id);
if emitted.contains(&name) {
continue;
}
let Some(value) = value_of(ast, id, &name)? else {
continue;
};
emitted.push(name.clone());
out.push(py::Statement::Function(py::Function {
doc: Doc::line(
"Table 9 of DDS-XTypes 1.3: what this type is where the stream did not \
carry it.",
),
decorators: Vec::new(),
name: Ident::new(&name),
params: Vec::new(),
returns: Some(Python::value_type(ast, id)),
body: vec![py::Statement::Return(Some(value))],
}));
}
Ok(out)
}
#[allow(clippy::too_many_lines)]
fn value_of(ast: &Ast, id: Id<TypeNode>, own_name: &str) -> Result<Option<py::Expr>, EncodeError> {
let id = resolve_alias(ast, id);
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: own_name.to_string(),
kind,
};
let Ok(node) = ast.type_(id) else {
return Err(refuse("a type the model does not hold"));
};
let value = match &node.kind {
TypeKind::Primitive(kind) => primitive_default(*kind),
TypeKind::String { .. } => text(""),
TypeKind::Fixed { scale, .. } => text(&zero_decimal(*scale)),
TypeKind::Alias { .. } => {
return Err(refuse("typedef with an unresolved target"));
}
TypeKind::Enumeration { .. } => {
let Some(enumerator) = crate::emit::default_enumerator_name(ast, id) else {
return Err(refuse(
"enumeration with no enumerator to take as its default",
));
};
Python::enumerator_constant(ast, id, &enumerator)
}
TypeKind::Bitmask { .. } => match declared_path(ast, id) {
Ok(path) => py::Expr::Call {
func: path,
args: vec![py::Arg::Positional(py::Expr::int(0))],
},
Err(_) => py::Expr::int(0),
},
TypeKind::Bitset { .. } => {
let mut args = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
if let Some(field_name) = field_node.name.as_ref() {
args.push((escape_name(field_name), py::Expr::int(0)));
}
}
construct(declared(ast, id, own_name)?, args)
}
TypeKind::Sequence { .. } | TypeKind::Map { .. } => py::Expr::List(Vec::new()),
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(refuse("array with an unresolved element"));
};
let mut lengths = Vec::new();
for dim in dims {
let length = ast
.eval_integral(*dim)
.map_err(|_| refuse("array whose length does not evaluate"))?;
lengths.push(
usize::try_from(length)
.map_err(|_| refuse("array of a length this column cannot spell"))?,
);
}
let mut out = call(&defaulter_name(ast, element), Vec::new());
for length in lengths.into_iter().rev() {
out = py::Expr::Tuple(vec![out; length]);
}
out
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let mut args = Vec::new();
if let Some(base) = crate::adapter::c::struct_base(ast, id) {
args.push((
escape_name("parent"),
call(&defaulter_name(ast, base), Vec::new()),
));
}
for member_id in ast.members_of(id) {
let Ok(member_node) = ast.member(*member_id) else {
continue;
};
if member_node.facets.is_optional() {
continue;
}
let Some(member_type) = member_node.type_.resolved() else {
return Err(EncodeError::NoWriter {
member: member_node.name.clone(),
kind: "member with an unresolved type",
});
};
args.push((
escape_name(&member_node.name),
member_value(ast, member_node, member_type)?,
));
}
construct(declared(ast, id, own_name)?, args)
}
TypeKind::Union { discriminator, .. } => {
let Some(discriminator) = discriminator.resolved() else {
return Err(refuse("union with an unresolved discriminator"));
};
let selector = discriminator_default_value(ast, discriminator);
let mut args = vec![(
"_d".to_string(),
discriminant_literal(ast, discriminator, selector.unwrap_or(0)),
)];
if let Some((member_type, field)) = selected_by_default(ast, id, selector)? {
args.push((field, call(&defaulter_name(ast, member_type), Vec::new())));
}
construct(declared(ast, id, own_name)?, args)
}
TypeKind::Native | TypeKind::ObjectRef { .. } | TypeKind::AnnotationType { .. } => {
return Ok(None);
}
TypeKind::ValueBox { .. } => {
return Err(refuse(
"boxed value, whose presence flag has no settled wire form",
));
}
TypeKind::Any => {
return Err(refuse(
"`any`, which DDS-XTypes has no encoding for; only CORBA CDR writes one, \
as a TypeCode ahead of the value",
));
}
TypeKind::ValueBase => {
return Err(refuse(
"`ValueBase`, which DDS-XTypes has no encoding for; only CORBA CDR writes \
one, as a chunked value with its repository id",
));
}
};
Ok(Some(value))
}
fn member_value(
ast: &Ast,
node: &MemberNode,
type_: Id<TypeNode>,
) -> Result<py::Expr, EncodeError> {
match node.facets.xtypes.as_ref().and_then(|x| x.default_value) {
Some(expr) => explicit(ast, expr, type_, &node.name),
None => Ok(call(&defaulter_name(ast, type_), Vec::new())),
}
}
fn primitive_default(kind: PrimitiveKind) -> py::Expr {
match kind {
PrimitiveKind::Boolean => py::Expr::Literal(Literal::Bool(false)),
PrimitiveKind::Char | PrimitiveKind::WChar => py::Expr::Literal(Literal::Char('\0')),
PrimitiveKind::Float32 | PrimitiveKind::Float64 => {
py::Expr::Literal(Literal::Float("0.0".to_string()))
}
PrimitiveKind::Float128 => py::Expr::Call {
func: Path::new(&["Float128", "zero"]),
args: Vec::new(),
},
PrimitiveKind::Octet
| PrimitiveKind::Int8
| PrimitiveKind::UInt8
| PrimitiveKind::Int16
| PrimitiveKind::UInt16
| PrimitiveKind::Int32
| PrimitiveKind::UInt32
| PrimitiveKind::Int64
| PrimitiveKind::UInt64 => py::Expr::int(0),
}
}
fn declared(ast: &Ast, id: Id<TypeNode>, own_name: &str) -> Result<Path, EncodeError> {
declared_path(ast, id).map_err(|_| EncodeError::NoWriter {
member: own_name.to_string(),
kind: "aggregate whose declared name the module chain will not answer for",
})
}
fn construct(path: Path, args: Vec<(String, py::Expr)>) -> py::Expr {
py::Expr::Call {
func: path,
args: args
.into_iter()
.map(|(name, value)| {
py::Arg::Keyword(py::Keyword {
name: Ident::new(&name),
value,
})
})
.collect(),
}
}
fn selected_by_default(
ast: &Ast,
id: Id<TypeNode>,
selector: Option<i128>,
) -> Result<Option<(Id<TypeNode>, String)>, EncodeError> {
let Some(selector) = selector else {
return Ok(None);
};
Ok(selected_case(ast, id, selector)?
.map(|(member, member_type)| (member_type, escape_name(&member.name))))
}
fn discriminant_literal(ast: &Ast, discriminator: Id<TypeNode>, value: i128) -> py::Expr {
let resolved = resolve_alias(ast, discriminator);
if matches!(
ast.type_(resolved).map(|node| node.kind.clone()),
Ok(TypeKind::Enumeration { .. })
) {
if let Some(enumerator) = crate::emit::default_enumerator_name(ast, resolved) {
return Python::enumerator_constant(ast, resolved, &enumerator);
}
}
match resolved_primitive(ast, resolved) {
Some(PrimitiveKind::Boolean) => py::Expr::Literal(Literal::Bool(value != 0)),
Some(PrimitiveKind::Char | PrimitiveKind::WChar) => {
char_literal(u32::try_from(value).unwrap_or(0))
}
_ => py::Expr::int(value),
}
}
fn resolved_primitive(ast: &Ast, id: Id<TypeNode>) -> Option<PrimitiveKind> {
let mut current = id;
for _ in 0..MAX_ALIAS_DEPTH {
match ast.type_(current).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => return Some(kind),
Ok(TypeKind::Alias { aliased }) => current = aliased.resolved()?,
_ => return None,
}
}
None
}
#[allow(clippy::too_many_lines)]
fn explicit(
ast: &Ast,
expr: Id<zerodds_ast::ConstExprNode>,
type_: Id<TypeNode>,
member_name: &str,
) -> Result<py::Expr, EncodeError> {
let refuse = |kind: &'static str| EncodeError::NoWriter {
member: member_name.to_string(),
kind,
};
let node = ast
.expr(expr)
.map_err(|_| refuse("a `@default` this frontend cannot follow"))?;
let resolved = resolve_alias(ast, type_);
let kind = ast.type_(resolved).map(|node| node.kind.clone());
let is_float128 = matches!(kind, Ok(TypeKind::Primitive(PrimitiveKind::Float128)));
match &node.expr {
ConstExpr::Literal(AstLiteral::Boolean(flag)) => {
Ok(py::Expr::Literal(Literal::Bool(*flag)))
}
ConstExpr::Literal(AstLiteral::Char(ch)) => Ok(char_literal(u32::from(*ch))),
ConstExpr::Literal(AstLiteral::WChar(code)) => Ok(char_literal(*code)),
ConstExpr::Literal(AstLiteral::String(value)) => Ok(text(value)),
ConstExpr::Literal(AstLiteral::WString(codes)) => {
let mut out = String::new();
for code in codes {
let Some(ch) = char::from_u32(*code) else {
return Err(refuse("a `@default` wide string that is not code points"));
};
out.push(ch);
}
Ok(text(&out))
}
ConstExpr::Literal(AstLiteral::Float { mantissa, exponent }) => {
let exponent = exponent
.as_ref()
.map_or(String::new(), |text| format!("e{text}"));
let written = format!("{mantissa}{exponent}");
if is_float128 {
return Ok(py::Expr::Call {
func: Path::new(&["Float128", "from_decimal"]),
args: vec![py::Arg::Positional(text(&written))],
});
}
Ok(py::Expr::Literal(Literal::Float(written)))
}
ConstExpr::Literal(AstLiteral::Fixed { digits, scale }) => {
Ok(text(&fixed_text(digits, *scale)))
}
ConstExpr::Literal(AstLiteral::Enumerator(reference)) => {
let target = reference
.resolved()
.ok_or_else(|| refuse("a `@default` enumerator that is unresolved"))?;
let enumerator = ast
.enumerator(target)
.map_err(|_| refuse("a `@default` enumerator this frontend cannot follow"))?;
Ok(Python::enumerator_constant(ast, resolved, &enumerator.name))
}
_ => {
let value = ast
.eval_integral(expr)
.map_err(|_| refuse("a `@default` this frontend cannot evaluate"))?;
Ok(match kind {
Ok(TypeKind::Primitive(PrimitiveKind::Boolean)) => {
py::Expr::Literal(Literal::Bool(value != 0))
}
Ok(TypeKind::Primitive(PrimitiveKind::Char | PrimitiveKind::WChar)) => {
let point = u32::try_from(value)
.ok()
.filter(|code| *code <= u32::from(u16::MAX))
.ok_or_else(|| {
refuse("a `@default` character outside the range of a UTF-16 code unit")
})?;
char_literal(point)
}
Ok(TypeKind::Primitive(PrimitiveKind::Float128)) => py::Expr::Call {
func: Path::new(&["Float128", "from_int"]),
args: vec![py::Arg::Positional(py::Expr::int(value))],
},
_ => py::Expr::int(value),
})
}
}
}
fn char_literal(code: u32) -> py::Expr {
match char::from_u32(code) {
Some(point) => py::Expr::Literal(Literal::Char(point)),
None => py::Expr::Call {
func: Path::new(&["chr"]),
args: vec![py::Arg::Positional(py::Expr::int(i128::from(code)))],
},
}
}
fn calls_in_statement(statement: &py::Statement, out: &mut Vec<String>) {
match statement {
py::Statement::Class(class) => calls_in_body(&class.body, out),
py::Statement::Function(function) => calls_in_body(&function.body, out),
py::Statement::Annotated(binding) => {
if let Some(value) = &binding.value {
calls_in_expr(value, out);
}
}
py::Statement::Assign(binding) => calls_in_expr(&binding.value, out),
py::Statement::Set { place, value } => {
calls_in_expr(place, out);
calls_in_expr(value, out);
}
py::Statement::Expr(value) => calls_in_expr(value, out),
py::Statement::If {
condition,
then,
otherwise,
} => {
calls_in_expr(condition, out);
calls_in_body(then, out);
if let Some(otherwise) = otherwise {
calls_in_body(otherwise, out);
}
}
py::Statement::For { bound, body, .. } => {
calls_in_expr(bound, out);
calls_in_body(body, out);
}
py::Statement::While { condition, body } => {
calls_in_expr(condition, out);
calls_in_body(body, out);
}
py::Statement::Return(value) => {
if let Some(value) = value {
calls_in_expr(value, out);
}
}
py::Statement::Break
| py::Statement::Comment(_)
| py::Statement::Pass
| py::Statement::Verbatim(_) => {}
}
}
fn calls_in_body(body: &[py::Statement], out: &mut Vec<String>) {
for statement in body {
calls_in_statement(statement, out);
}
}
fn calls_in_expr(expr: &py::Expr, out: &mut Vec<String>) {
match expr {
py::Expr::Call { func, args } => {
if func.segments().len() == 1 {
let name = func
.segments()
.first()
.map(|segment| segment.as_str().to_string())
.unwrap_or_default();
if name.starts_with("zd_def_") && !out.iter().any(|seen| seen == &name) {
out.push(name);
}
}
for arg in args {
match arg {
py::Arg::Positional(value) => calls_in_expr(value, out),
py::Arg::Keyword(keyword) => calls_in_expr(&keyword.value, out),
}
}
}
py::Expr::Tuple(values) | py::Expr::List(values) => {
for value in values {
calls_in_expr(value, out);
}
}
py::Expr::Dict(entries) => {
for (key, value) in entries {
calls_in_expr(key, out);
calls_in_expr(value, out);
}
}
py::Expr::Attribute { value, .. } => calls_in_expr(value, out),
py::Expr::Index { value, index } => {
calls_in_expr(value, out);
calls_in_expr(index, out);
}
py::Expr::Slice { start, end } => {
for side in [start, end].into_iter().flatten() {
calls_in_expr(side, out);
}
}
py::Expr::Unary { value, .. } | py::Expr::Paren(value) => calls_in_expr(value, out),
py::Expr::Binary { lhs, rhs, .. } => {
calls_in_expr(lhs, out);
calls_in_expr(rhs, out);
}
py::Expr::Literal(_) | py::Expr::Path(_) | py::Expr::Annotation(_) => {}
}
}
fn text(value: &str) -> py::Expr {
py::Expr::Literal(Literal::Str(value.to_string()))
}
fn call(callee: &str, args: Vec<py::Expr>) -> py::Expr {
py::Expr::Call {
func: Path::single(callee),
args: args.into_iter().map(py::Arg::Positional).collect(),
}
}
fn set(place: py::Expr, value: py::Expr) -> py::Statement {
py::Statement::Set { place, value }
}
const _: Option<MaterialiseError> = None;
#[cfg(test)]
mod tests {
#![allow(
clippy::expect_used,
clippy::panic,
clippy::print_stderr,
clippy::unwrap_used
)]
use super::*;
fn model(idl: &str) -> Ast {
let parsed = zerodds_idl_parse::parse(idl);
assert!(
parsed.diagnostics().is_empty(),
"the source does not parse: {:?}",
parsed.diagnostics()
);
let lowered = zerodds_idl_lower::lower(&parsed.syntax(), "test.idl");
assert!(
lowered.diagnostics.is_empty(),
"the source does not lower: {:?}",
lowered.diagnostics
);
let mut ast: Ast = lowered.ast;
ast.resolve_all_by_name().expect("the names resolve");
ast
}
fn root_of(ast: &Ast, type_name: &str) -> Id<TypeNode> {
ast.types()
.find(|(_, node)| node.name.as_str() == type_name)
.map(|(id, _)| id)
.expect("the named type")
}
fn rendered(decls: &[py::Statement]) -> String {
let text = decls
.iter()
.map(zerodds_lang_render::python::statement)
.collect::<Vec<_>>()
.join("\n");
parses(&text);
text
}
fn defaulter(idl: &str, type_name: &str) -> String {
let ast = model(idl);
let root = root_of(&ast, type_name);
rendered(&python_defaulters_over(&ast, &[root]).expect("Table 9 answers for the type"))
}
fn closure(idl: &str, type_name: &str) -> String {
let ast = model(idl);
let root = root_of(&ast, type_name);
let seed = python_defaulters_over(&ast, &[root]).expect("the root's own defaulter");
rendered(&python_defaulters_called_by(&ast, &seed).expect("the closure"))
}
fn parses(text: &str) {
let Ok(output) = std::process::Command::new("python3")
.arg("-c")
.arg("import sys; compile(sys.stdin.read(), '<t>', 'exec')")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.and_then(|mut child| {
use std::io::Write as _;
if let Some(stdin) = child.stdin.as_mut() {
stdin.write_all(text.as_bytes())?;
}
child.wait_with_output()
})
else {
eprintln!("no python3 on the path: the defaulter's syntax was not checked");
return;
};
assert!(
output.status.success(),
"the defaulter is not Python:\n{}\n--- source ---\n{text}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn a_structure_of_primitives_names_every_required_field() {
let text = defaulter("@final struct S { long x; double y; boolean z; };", "S");
assert!(
text.contains("def zd_def_S() -> S:"),
"the signature is not the Python one:\n{text}"
);
assert!(
text.contains("return S(x=zd_def_p_long(), y=zd_def_p_double(), z=zd_def_p_boolean())"),
"the fields are not named:\n{text}"
);
}
#[test]
fn a_nested_structure_reaches_its_members_defaulter_and_not_a_literal() {
let text = defaulter(
"@final struct Inner { long a; }; @final struct S { Inner i; long b; };",
"S",
);
assert!(
text.contains("return S(i=zd_def_Inner(), b=zd_def_p_long())"),
"the nested default is not a call:\n{text}"
);
}
#[test]
fn a_nested_module_is_written_from_the_module_root() {
let text = defaulter(
"module a { module b { @final struct S { long x; }; }; };",
"S",
);
assert!(
text.contains("return a.b.S(x=zd_def_p_long())"),
"the constructor is not qualified:\n{text}"
);
}
#[test]
fn an_optional_member_is_absent_from_the_constructor_call() {
let text = defaulter("@final struct S { @optional long a; long b; };", "S");
assert!(
text.contains("return S(b=zd_def_p_long())"),
"the optional member is not left out:\n{text}"
);
assert!(!text.contains("a="), "the optional member is set:\n{text}");
}
#[test]
fn a_union_whose_discriminators_default_selects_a_case_sets_both() {
let text = defaulter(
"@final union U switch (long) { case 0: long one; case 1: string other; };",
"U",
);
assert!(
text.contains("return U(_d=0, one=zd_def_p_long())"),
"the selected case is not set:\n{text}"
);
}
#[test]
fn a_union_whose_discriminators_default_selects_nothing_is_the_discriminator_alone() {
let text = defaulter(
"@final union U switch (long) { case 1: long one; case 2: long two; };",
"U",
);
assert!(
text.contains("return U(_d=0)"),
"a case was invented:\n{text}"
);
}
#[test]
fn a_unions_default_case_is_what_an_unlisted_discriminator_selects() {
let text = defaulter(
"@final union U switch (long) { case 1: long one; default: string other; };",
"U",
);
assert!(
text.contains("return U(_d=0, other=zd_def_string())"),
"the default case is not selected:\n{text}"
);
}
#[test]
fn an_enumeration_takes_its_first_literal_by_name() {
let text = defaulter("enum Colour { RED, GREEN, BLUE };", "Colour");
assert!(
text.contains("return Colour.RED"),
"the first literal is not the default:\n{text}"
);
}
#[test]
fn an_enumeration_with_a_default_literal_takes_that_one() {
let text = defaulter(
"enum Colour { RED, @default_literal GREEN, BLUE };",
"Colour",
);
assert!(
text.contains("return Colour.GREEN"),
"@default_literal is not honoured:\n{text}"
);
}
#[test]
fn a_sequence_is_an_empty_list_at_every_bound() {
for (idl, name) in [
(
"@final struct S { sequence<long> many; };",
"zd_def_seq_long",
),
(
"@final struct S { sequence<long, 4> many; };",
"zd_def_seq_b4_long",
),
] {
let text = closure(idl, "S");
assert!(
text.contains(&format!("def {name}() -> list[int]:")) && text.contains("return []"),
"the sequence default is not an empty list:\n{text}"
);
assert!(!text.contains("None"), "the sequence is absent:\n{text}");
}
}
#[test]
fn a_fixed_size_array_is_that_many_elements() {
let text = closure("@final struct S { long a[3]; };", "S");
assert!(
text.contains("return (zd_def_p_long(), zd_def_p_long(), zd_def_p_long())"),
"the elements are not spelled out as a tuple:\n{text}"
);
assert!(!text.contains(" * 3"), "the element is aliased:\n{text}");
}
#[test]
fn a_two_dimensional_array_is_one_tuple_per_dimension() {
let text = closure("@final struct S { long g[2][3]; };", "S");
assert!(
text.contains(
"((zd_def_p_long(), zd_def_p_long(), zd_def_p_long()), \
(zd_def_p_long(), zd_def_p_long(), zd_def_p_long()))"
),
"the dimensions are not nested outermost first:\n{text}"
);
}
#[test]
fn a_fixed_is_the_one_decimal_spelling_of_zero_for_its_scale() {
let text = defaulter("@final struct S { fixed<5,2> f; };", "S");
assert!(
text.contains("f=zd_def_fixed_5_2()"),
"the member does not reach the defaulter:\n{text}"
);
let text = defaulter(
"typedef fixed<5,2> Money; @final struct S { Money f; };",
"Money",
);
assert!(
text.contains("return \"0.00\""),
"the fixed default is not the decimal:\n{text}"
);
}
#[test]
fn a_long_double_is_a_float128_and_never_a_float() {
let text = defaulter("typedef long double LD; @final struct S { LD d; };", "LD");
assert!(
text.contains("return Float128.zero()"),
"the long double default is not the runtime's zero:\n{text}"
);
assert!(
!text.contains("0.0"),
"a float reached a binary128:\n{text}"
);
}
#[test]
fn a_char_is_a_one_character_string_and_not_an_integer() {
let text = closure("@final struct S { char c; };", "S");
assert!(
text.contains("return \"\\x00\""),
"the char default is not a str:\n{text}"
);
}
#[test]
fn a_bitset_is_the_dataclass_with_every_field_at_zero() {
let text = defaulter(
"bitset Packed { bitfield<3> low; bitfield<5> high; };",
"Packed",
);
assert!(
text.contains("return Packed(low=0, high=0)"),
"the bitset default is not the dataclass:\n{text}"
);
}
#[test]
fn a_typedef_and_its_target_are_one_function_and_it_does_not_call_itself() {
let text = defaulter(
"@final struct Point { long x; }; typedef Point Position;",
"Position",
);
assert!(
text.contains("return Point(x=zd_def_p_long())"),
"the typedef does not resolve to its target's table row:\n{text}"
);
assert!(
!text.contains("return zd_def_Point()"),
"the defaulter calls itself:\n{text}"
);
}
#[test]
fn the_closure_reaches_every_defaulter_a_batch_calls() {
let idl = "@final struct Leaf { long a; };
@final struct Middle { Leaf l; };
@final struct Top { Middle m; sequence<double> s; };";
let parsed = zerodds_idl_parse::parse(idl);
assert!(
parsed.diagnostics().is_empty(),
"{:?}",
parsed.diagnostics()
);
let lowered = zerodds_idl_lower::lower(&parsed.syntax(), "test.idl");
assert!(lowered.diagnostics.is_empty(), "{:?}", lowered.diagnostics);
let mut ast: Ast = lowered.ast;
ast.resolve_all_by_name().expect("the names resolve");
let top = ast
.types()
.find(|(_, node)| node.name.as_str() == "Top")
.map(|(id, _)| id)
.expect("the root type");
let seed = python_defaulters_over(&ast, &[top]).expect("the root's own defaulter");
let all = python_defaulters_called_by(&ast, &seed).expect("the closure");
let text = all
.iter()
.map(zerodds_lang_render::python::statement)
.collect::<Vec<_>>()
.join("\n");
for expected in [
"def zd_def_Middle()",
"def zd_def_Leaf()",
"def zd_def_p_long()",
"def zd_def_seq_double()",
] {
assert!(text.contains(expected), "{expected} is missing:\n{text}");
}
parses(&text);
}
}