use crate::adapter::constant;
use crate::apply::Applied;
use seesaw_tgg::ident::GhostId;
use zerodds_ast::{Ast, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, NodeId};
use zerodds_lang_cst::common::{Doc, Ident, Path, Visibility};
use zerodds_lang_cst::rust;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MaterialiseError {
UnknownType {
type_name: String,
},
NoOrigin {
type_name: String,
},
NoModule {
at: String,
},
NoNode {
kind: &'static str,
at: String,
},
Unevaluated {
construct: String,
at: String,
},
NoLanguageForm {
construct: String,
language: &'static str,
at: String,
},
SpellingCollision {
first: String,
second: String,
spelled: String,
language: &'static str,
at: String,
},
}
impl core::fmt::Display for MaterialiseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NoNode { kind, at } => {
write!(f, "{at}: the model lists a {kind} it will not hand over")
}
Self::Unevaluated { construct, at } => {
write!(f, "{at}: the {construct} does not evaluate")
}
Self::NoLanguageForm {
construct,
language,
at,
} => write!(f, "{at}: {language} has no form for `{construct}`"),
Self::UnknownType { type_name } => {
write!(
f,
"the rules created a `{type_name}`, which is not a CST node"
)
}
Self::NoOrigin { type_name } => {
write!(f, "a created `{type_name}` has no model node behind it")
}
Self::SpellingCollision {
first,
second,
spelled,
language,
at,
} => write!(
f,
"{at}: `{first}` and `{second}` both spell `{spelled}` in \
{language}, so one of them would be unreachable"
),
Self::NoModule { at } => {
write!(
f,
"the module {at} is not in the model, so the items it \
declares have no scope to nest in"
)
}
}
}
}
impl core::error::Error for MaterialiseError {}
pub fn no_spelling_collision<'a>(
names: impl IntoIterator<Item = (&'a str, &'a str)>,
language: &'static str,
at: &str,
) -> Result<(), MaterialiseError> {
let mut seen: Vec<(&str, &str)> = Vec::new();
for (spelled, declared) in names {
if let Some((_, first)) = seen.iter().find(|(other, _)| *other == spelled) {
return Err(MaterialiseError::SpellingCollision {
first: (*first).to_string(),
second: declared.to_string(),
spelled: spelled.to_string(),
language,
at: at.to_string(),
});
}
seen.push((spelled, declared));
}
Ok(())
}
pub fn rust_items(ast: &Ast, applied: &Applied) -> Result<Vec<rust::Item>, MaterialiseError> {
Ok(crate::verbatim::weave_file(
ast,
crate::verbatim::aliases::RUST,
nest_by_module(ast, rust_item_pairs(ast, applied)?)?,
))
}
fn weave_rust_verbatim(
ast: &Ast,
starts: &[(Id<TypeNode>, Id<zerodds_ast::ModuleNode>, usize)],
items: &mut Vec<(Id<zerodds_ast::ModuleNode>, rust::Item)>,
) {
let mut end = items.len();
for (id, module, start) in starts.iter().rev() {
let slots = crate::verbatim::declaration_slots(ast, *id, crate::verbatim::aliases::RUST);
let range_end = end;
end = *start;
if slots.is_empty() {
continue;
}
let name = ast
.type_(*id)
.map(|node| rust::escape_keyword(&node.name))
.unwrap_or_default();
let tail = items.split_off(range_end.min(items.len()));
let module = *module;
crate::verbatim::weave_with(
slots,
*start,
items,
|text| (module, rust::Item::Verbatim(text)),
|declared, begin, end| {
for (_, item) in declared {
if crate::verbatim::Carrier::take_into_body(item, &name, begin, end) {
return;
}
}
},
);
items.extend(tail);
}
}
impl crate::verbatim::Carrier for rust::Item {
fn verbatim(text: String) -> Self {
Self::Verbatim(text)
}
fn take_into_body(
&mut self,
name: &str,
begin: &mut Vec<String>,
end: &mut Vec<String>,
) -> bool {
let Self::Struct(node) = self else {
return false;
};
if node.name.as_str() != name || matches!(node.body, rust::StructBody::Tuple(_)) {
return false;
}
node.body_text.begin.append(begin);
node.body_text.end.append(end);
true
}
}
pub fn rust_item_pairs(
ast: &Ast,
applied: &Applied,
) -> Result<Vec<(Id<zerodds_ast::ModuleNode>, rust::Item)>, MaterialiseError> {
let mut items: Vec<(Id<zerodds_ast::ModuleNode>, rust::Item)> = Vec::new();
let mut starts: Vec<(Id<TypeNode>, Id<zerodds_ast::ModuleNode>, usize)> = Vec::new();
for (id, node) in ast.types() {
let from = node.module;
starts.push((id, from, items.len()));
match &node.kind {
TypeKind::Enumeration { enumerators } => {
if ghost_created_for(applied, node.node_id, "RustEnum").is_none() {
continue;
}
let mut variants = Vec::new();
for enumerator in enumerators {
let Ok(enum_node) = ast.enumerator(*enumerator) else {
continue;
};
if ghost_created_for(applied, enum_node.node_id, "RustVariant").is_none() {
continue;
}
let value = ast.effective_enumerator_value(*enumerator).ok_or_else(|| {
MaterialiseError::Unevaluated {
construct: "enumerator value".to_string(),
at: enum_node.name.clone(),
}
})?;
variants.push(rust::Variant {
doc: Doc::none(),
attrs: Vec::new(),
name: Ident::new(&rust::escape_keyword(&enum_node.name)),
body: rust::VariantBody::Unit,
discriminant: Some(rust::Expr::Literal(
zerodds_lang_cst::common::Literal::int(value),
)),
});
}
items.push((
node.module,
rust::Item::Enum(rust::Enum {
doc: Doc::none(),
attrs: Vec::new(),
derives: data_derives(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
variants,
}),
));
continue;
}
TypeKind::Alias { aliased } => {
if ghost_created_for(applied, node.node_id, "RustTypeAlias").is_none() {
continue;
}
if node.name.is_empty() {
continue;
}
items.push((
node.module,
rust::Item::TypeAlias(rust::TypeAlias {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
type_: rust_type_of(ast, from, aliased.resolved())?,
}),
));
continue;
}
TypeKind::Union { cases, .. } => {
if ghost_created_for(applied, node.node_id, "RustUnionEnum").is_none() {
continue;
}
let shape = crate::rust_union::shape(ast, id).map_err(|reason| {
MaterialiseError::NoLanguageForm {
construct: reason.to_string(),
language: "rust",
at: node.name.clone(),
}
})?;
let discriminator_type = rust_type_of(ast, from, Some(shape.discriminator))?;
let mut variants = Vec::new();
for case in cases {
let Ok(case_node) = ast.case(*case) else {
continue;
};
if ghost_created_for(applied, case_node.node_id, "RustCaseVariant").is_none() {
continue;
}
let Ok(member) = ast.member(case_node.member) else {
continue;
};
let carries = shape
.cases
.iter()
.find(|c| c.member.node_id == member.node_id)
.is_some_and(crate::rust_union::RustCase::carries_discriminator);
let mut fields = Vec::new();
if carries {
fields.push(discriminator_type.clone());
}
fields.push(field_type(ast, from, member)?);
variants.push(rust::Variant {
doc: Doc::none(),
attrs: Vec::new(),
name: Ident::new(&variant_name(&member.name)),
body: rust::VariantBody::Tuple(fields),
discriminant: None,
});
}
if shape.no_member {
variants.push(rust::Variant {
doc: Doc::none(),
attrs: Vec::new(),
name: Ident::new(crate::rust_union::NO_MEMBER),
body: rust::VariantBody::Tuple(vec![discriminator_type]),
discriminant: None,
});
}
items.push((
node.module,
rust::Item::Enum(rust::Enum {
doc: Doc::none(),
attrs: Vec::new(),
derives: data_derives(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
variants,
}),
));
continue;
}
TypeKind::Bitmask { bits } => {
if ghost_created_for(applied, node.node_id, "RustBitmask").is_none() {
continue;
}
let width = ast.effective_bit_bound(id);
items.push((
node.module,
rust::Item::TypeAlias(rust::TypeAlias {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
type_: rust::Type::Primitive(bitmask_primitive(width)),
}),
));
for bit in bits {
let Ok(bit_node) = ast.bit_value(*bit) else {
continue;
};
if ghost_created_for(applied, bit_node.node_id, "RustBitConst").is_none() {
continue;
}
let position = ast.effective_bit_position(*bit).ok_or_else(|| {
MaterialiseError::Unevaluated {
construct: "bit position".to_string(),
at: bit_node.name.clone(),
}
})?;
items.push((
node.module,
rust::Item::Const(rust::Const {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&bit_node.name)),
type_: rust::Type::Primitive(bitmask_primitive(width)),
value: rust::Expr::Binary {
op: rust::BinaryOp::Shl,
lhs: Box::new(rust::Expr::Literal(
zerodds_lang_cst::common::Literal::int(1),
)),
rhs: Box::new(rust::Expr::Literal(
zerodds_lang_cst::common::Literal::int(i128::from(position)),
)),
},
}),
));
}
continue;
}
TypeKind::Bitset { .. } => {
if ghost_created_for(applied, node.node_id, "RustBitset").is_none() {
continue;
}
let mut struct_fields = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
let Some(name) = field_node.name.as_ref() else {
continue;
};
struct_fields.push(rust::Field {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(name)),
type_: rust::Type::Primitive(bitmask_primitive(field_node.bits)),
});
}
items.push((
node.module,
rust::Item::Struct(rust::Struct {
body_text: zerodds_lang_cst::common::BodyText::none(),
doc: Doc::none(),
attrs: Vec::new(),
derives: data_derives(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
body: rust::StructBody::Named(struct_fields),
}),
));
continue;
}
TypeKind::Valuetype { state, .. } => {
if ghost_created_for(applied, node.node_id, "RustValueStruct").is_none() {
continue;
}
let mut struct_fields = Vec::new();
for member in state {
let Ok(member_node) = ast.member(*member) else {
continue;
};
struct_fields.push(rust::Field {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&member_node.name)),
type_: field_type(ast, from, member_node)?,
});
}
items.push((
node.module,
rust::Item::Struct(rust::Struct {
body_text: zerodds_lang_cst::common::BodyText::none(),
doc: Doc::none(),
attrs: Vec::new(),
derives: data_derives(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
body: rust::StructBody::Named(struct_fields),
}),
));
continue;
}
TypeKind::Native => {
if ghost_created_for(applied, node.node_id, "RustNativeAlias").is_none() {
continue;
}
items.push((
node.module,
rust::Item::TypeAlias(rust::TypeAlias {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
type_: native_type(ast, id),
}),
));
continue;
}
TypeKind::Structure { .. } => {}
_ => continue,
}
if ghost_created_for(applied, node.node_id, "RustStruct").is_none() {
continue;
}
let mut fields = Vec::new();
let mut spelled = Vec::new();
if let Some(base) = crate::adapter::c::struct_base(ast, id) {
spelled.push((PARENT.to_string(), PARENT.to_string()));
fields.push(rust::Field {
doc: Doc::line("The base structure, the first member (XTypes 1.3 §7.5.1.4.1.3)."),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(PARENT),
type_: rust_type_of(ast, from, Some(base))?,
});
}
for member in ast.members_of(id) {
let Ok(member_node) = ast.member(*member) else {
continue;
};
if ghost_created_for(applied, member_node.node_id, "RustField").is_none() {
continue;
}
let field = rust::escape_keyword(&member_node.name);
spelled.push((field.clone(), member_node.name.clone()));
fields.push(rust::Field {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&field),
type_: field_type(ast, from, member_node)?,
});
}
no_spelling_collision(
spelled
.iter()
.map(|(field, declared)| (field.as_str(), declared.as_str())),
"Rust",
&node.name,
)?;
items.push((
node.module,
rust::Item::Struct(rust::Struct {
body_text: zerodds_lang_cst::common::BodyText::none(),
doc: Doc::none(),
attrs: Vec::new(),
derives: data_derives(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
body: rust::StructBody::Named(fields),
}),
));
}
weave_rust_verbatim(ast, &starts, &mut items);
for (id, node) in ast.constants() {
let from = node.module;
if ghost_created_for(applied, node.node_id, "RustConst").is_none() {
continue;
}
let value = ast
.eval_const(node.expr)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "constant's value".to_string(),
at: node.name.clone(),
})?;
let declared = rust_type_of(ast, from, node.type_.resolved())?;
let (type_, literal) = rust_constant(&value, declared, &node.name)?;
items.push((
node.module,
rust::Item::Const(rust::Const {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
type_,
value: rust::Expr::Literal(literal),
}),
));
let _ = id;
}
for (id, node) in ast.exceptions() {
let from = node.module;
if ghost_created_for(applied, node.node_id, "RustErrorStruct").is_none() {
continue;
}
let mut fields = Vec::new();
for member in &node.members {
let Ok(member_node) = ast.member(*member) else {
continue;
};
fields.push(rust::Field {
doc: Doc::line(&format!("The IDL exception member `{}`.", member_node.name)),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&member_node.name)),
type_: rust_type_of(ast, from, member_node.type_.resolved())?,
});
}
items.push((
node.module,
rust::Item::Struct(rust::Struct {
body_text: zerodds_lang_cst::common::BodyText::none(),
doc: Doc::line(&format!(
"The IDL exception `{}`, as `Err` payload — DDS-RPC 1.0 \
§7.5.1.1.5 gives each raised exception its own case in \
the operation's `_Result` union.",
node.name
)),
attrs: Vec::new(),
derives: data_derives(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
body: rust::StructBody::Named(fields),
}),
));
let _ = id;
}
for (id, node) in ast.interfaces() {
let from = node.module;
if ghost_created_for(applied, node.node_id, "RustTrait").is_none() {
continue;
}
let mut methods = Vec::new();
for operation in &ast.all_operations_of(id) {
let Ok(op) = ast.operation(*operation) else {
continue;
};
let mut params = vec![rust::Param {
name: None,
type_: rust::Type::unit(),
receiver: Some(rust::Receiver::Ref),
}];
for param in &op.params {
let Ok(param_node) = ast.param(*param) else {
continue;
};
params.push(rust::Param {
name: Some(Ident::new(&rust::escape_keyword(¶m_node.name))),
type_: rust_type_of(ast, from, param_node.type_.resolved())?,
receiver: None,
});
}
methods.push(rust::Item::Function(rust::Function {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Private,
name: Ident::new(&rust::escape_keyword(&op.name)),
generics: Vec::new(),
params,
returns: op
.return_type
.as_ref()
.map(|type_| rust_type_of(ast, from, type_.resolved()))
.transpose()?,
body: None,
}));
}
items.push((
node.module,
rust::Item::Trait(rust::Trait {
doc: Doc::none(),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
generics: Vec::new(),
supertraits: Vec::new(),
items: methods,
}),
));
}
Ok(items)
}
pub fn nest_by_module(
ast: &Ast,
items: Vec<(Id<zerodds_ast::ModuleNode>, rust::Item)>,
) -> Result<Vec<rust::Item>, MaterialiseError> {
let mut entries: Vec<PathedItem> = Vec::with_capacity(items.len());
for (module, item) in items {
entries.push((module_path(ast, module)?, item));
}
fold_by_path(ast, entries, 0)
}
pub(crate) fn relative_path(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
node: &zerodds_ast::TypeNode,
) -> Result<Path, MaterialiseError> {
let here = module_path(ast, from)?;
let there = module_path(ast, node.module)?;
let shared = here
.iter()
.zip(there.iter())
.take_while(|(a, b)| a == b)
.count();
let mut segments: Vec<String> = Vec::new();
for _ in shared..here.len() {
segments.push(String::from("super"));
}
for module in there.iter().skip(shared) {
let enclosing = ast
.module(*module)
.map_err(|_| MaterialiseError::NoModule {
at: module_handle(*module),
})?;
segments.push(rust::escape_keyword(&enclosing.name));
}
segments.push(rust::escape_keyword(&node.name));
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
Ok(Path::new(&borrowed))
}
pub fn path_from_file_root(ast: &Ast, type_: Id<TypeNode>) -> Result<String, MaterialiseError> {
let node = ast.type_(type_).map_err(|_| MaterialiseError::NoOrigin {
type_name: format!("{type_:?}"),
})?;
let mut segments: Vec<String> = Vec::new();
for module in module_path(ast, node.module)? {
let enclosing = ast.module(module).map_err(|_| MaterialiseError::NoModule {
at: module_handle(module),
})?;
segments.push(rust::escape_keyword(&enclosing.name));
}
segments.push(rust::escape_keyword(&node.name));
Ok(segments.join("::"))
}
type PathedItem = (Vec<Id<zerodds_ast::ModuleNode>>, rust::Item);
pub(crate) fn module_path(
ast: &Ast,
module: Id<zerodds_ast::ModuleNode>,
) -> Result<Vec<Id<zerodds_ast::ModuleNode>>, MaterialiseError> {
let mut path = Vec::new();
let mut current = module;
for _ in 0..=MAX_MODULE_DEPTH {
let node = ast
.module(current)
.map_err(|_| MaterialiseError::NoModule {
at: module_handle(current),
})?;
let Some(parent) = node.parent else {
path.reverse();
return Ok(path);
};
path.push(current);
current = parent;
}
Err(MaterialiseError::NoModule {
at: format!(
"{} — the parent chain runs past {MAX_MODULE_DEPTH}",
module_handle(module)
),
})
}
fn fold_by_path(
ast: &Ast,
entries: Vec<PathedItem>,
depth: usize,
) -> Result<Vec<rust::Item>, MaterialiseError> {
if depth > MAX_MODULE_DEPTH {
return Err(MaterialiseError::NoModule {
at: format!("a module chain runs past {MAX_MODULE_DEPTH}"),
});
}
let mut here: Vec<rust::Item> = Vec::new();
let mut order: Vec<Id<zerodds_ast::ModuleNode>> = Vec::new();
let mut deeper: Vec<(Id<zerodds_ast::ModuleNode>, Vec<PathedItem>)> = Vec::new();
for (path, item) in entries {
match path.get(depth).copied() {
None => here.push(item),
Some(module) => match order.iter().position(|id| *id == module) {
Some(at) => deeper[at].1.push((path, item)),
None => {
order.push(module);
deeper.push((module, vec![(path, item)]));
}
},
}
}
let mut out = here;
for (module, nested) in deeper {
let node = ast.module(module).map_err(|_| MaterialiseError::NoModule {
at: module_handle(module),
})?;
out.push(rust::Item::Module(rust::Module {
doc: Doc::line(&format!("The IDL module `{}`.", node.name)),
attrs: Vec::new(),
visibility: Visibility::Public,
name: Ident::new(&rust::escape_keyword(&node.name)),
uses: vec![rust::Use {
path: Path::single("super"),
names: vec![Ident::new("*")],
visibility: Visibility::Private,
}],
items: fold_by_path(ast, nested, depth + 1)?,
}));
}
Ok(out)
}
const MAX_MODULE_DEPTH: usize = 64;
const MAX_TYPE_DEPTH: usize = 64;
pub(crate) fn module_handle(module: Id<zerodds_ast::ModuleNode>) -> String {
handle_of(module)
}
pub(crate) fn handle_of<T>(id: Id<T>) -> String {
format!("#{}@{}", id.slot(), id.generation())
}
fn field_type(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
member: &zerodds_ast::MemberNode,
) -> Result<rust::Type, MaterialiseError> {
let mut inner = rust_type_of(ast, from, member.type_.resolved())?;
if is_external(ast, member) {
inner = rust::Type::generic(Path::single("Box"), vec![inner]);
}
if member.facets.is_optional() {
return Ok(rust::Type::generic(Path::new(&["Option"]), vec![inner]));
}
Ok(inner)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RustMapForm {
#[default]
Entries,
BTreeMap,
}
thread_local! {
static MAP_FORM: std::cell::Cell<RustMapForm> = const { std::cell::Cell::new(RustMapForm::Entries) };
}
#[derive(Debug)]
pub struct RustMapFormScope {
previous: RustMapForm,
}
impl RustMapFormScope {
#[must_use]
pub fn enter(form: RustMapForm) -> Self {
let previous = MAP_FORM.with(|cell| cell.replace(form));
Self { previous }
}
}
impl Drop for RustMapFormScope {
fn drop(&mut self) {
MAP_FORM.with(|cell| cell.set(self.previous));
}
}
#[must_use]
pub fn rust_map_form() -> RustMapForm {
MAP_FORM.with(std::cell::Cell::get)
}
pub(crate) const PARENT: &str = "parent";
pub(crate) fn is_external(ast: &Ast, member: &zerodds_ast::MemberNode) -> bool {
super::held_externally(ast, member)
}
pub(crate) fn external_value(
ast: &Ast,
member: &zerodds_ast::MemberNode,
value: rust::Expr,
) -> rust::Expr {
if is_external(ast, member) {
rust::Expr::Call {
callee: Box::new(rust::Expr::Path(Path::new(&["Box", "new"]))),
args: vec![value],
}
} else {
value
}
}
pub(crate) fn variant_name(member: &str) -> String {
let mut chars = member.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
pub(crate) const fn bitmask_primitive(bits: u16) -> rust::Primitive {
match bits {
0..=8 => rust::Primitive::U8,
9..=16 => rust::Primitive::U16,
17..=32 => rust::Primitive::U32,
_ => rust::Primitive::U64,
}
}
fn native_type(ast: &Ast, id: zerodds_ast_core::Id<zerodds_ast::TypeNode>) -> rust::Type {
let mapping = ast
.any_of_type(id)
.ok()
.and_then(|any| ast.facets(any))
.and_then(|facets| facets.corba.as_ref())
.and_then(|corba| corba.native_mapping.clone());
match mapping {
Some(name) => rust::Type::named(Path::single(&name)),
None => rust::Type::Ref {
target: Box::new(rust::Type::Primitive(rust::Primitive::U8)),
mutable: false,
lifetime: None,
},
}
}
fn ghost_created_for(applied: &Applied, node: NodeId, type_name: &str) -> Option<GhostId> {
let anchor = applied
.origin
.iter()
.find(|(_, model)| **model == node)
.map(|(ghost, _)| *ghost)?;
let typ = applied.graph.types.lookup(type_name)?;
applied
.graph
.nodes_of_type(typ)
.map(|created| created.id)
.find(|created| {
applied.graph.connected(&anchor, created) || is_derived_from(applied, &anchor, created)
})
}
fn is_derived_from(applied: &Applied, anchor: &GhostId, created: &GhostId) -> bool {
applied
.graph
.parts(anchor)
.any(|part| applied.graph.connected(&part.other, created))
}
pub(crate) fn rust_type_of(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
target: Option<zerodds_ast_core::Id<zerodds_ast::TypeNode>>,
) -> Result<rust::Type, MaterialiseError> {
type_of_at(ast, from, target, 0)
}
fn type_of_at(
ast: &Ast,
from: Id<zerodds_ast::ModuleNode>,
target: Option<zerodds_ast_core::Id<zerodds_ast::TypeNode>>,
depth: usize,
) -> Result<rust::Type, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("type, whose Rust spelling nests past {MAX_TYPE_DEPTH}"),
at: target.map_or_else(|| "a member".to_string(), crate::adapter::cst::handle_of),
});
}
let Some(target) = target else {
return Ok(rust::Type::unit());
};
let Ok(node) = ast.type_(target) else {
return Ok(rust::Type::unit());
};
Ok(match &node.kind {
TypeKind::Primitive(PrimitiveKind::Float128) => {
rust::Type::named(Path::new(&["zerodds_float128", "Float128"]))
}
TypeKind::Primitive(kind) => rust::Type::Primitive(rust_primitive(*kind)),
TypeKind::String { .. } => rust::Type::Primitive(rust::Primitive::String),
TypeKind::Sequence { element, .. } => rust::Type::generic(
Path::new(&["Vec"]),
vec![type_of_at(ast, from, element.resolved(), depth + 1)?],
),
TypeKind::Array { element, dims } => {
let mut out = type_of_at(ast, from, element.resolved(), depth + 1)?;
for dim in dims.iter().rev() {
let length =
ast.eval_integral(*dim)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "array length".to_string(),
at: named(&node.name, node.node_id),
})?;
out = rust::Type::Array {
element: Box::new(out),
length: Box::new(rust::Expr::Literal(zerodds_lang_cst::common::Literal::int(
length,
))),
};
}
out
}
TypeKind::Map { key, value, .. } => {
let key = type_of_at(ast, from, key.resolved(), depth + 1)?;
let value = type_of_at(ast, from, value.resolved(), depth + 1)?;
match rust_map_form() {
RustMapForm::BTreeMap => {
rust::Type::generic(Path::new(&["BTreeMap"]), vec![key, value])
}
RustMapForm::Entries => rust::Type::generic(
Path::new(&["Vec"]),
vec![rust::Type::generic(
Path::new(&["zerodds_wire", "MapEntry"]),
vec![key, value],
)],
),
}
}
TypeKind::Alias { aliased } => type_of_at(ast, from, aliased.resolved(), depth + 1)?,
TypeKind::Structure { .. }
| TypeKind::Union { .. }
| TypeKind::Enumeration { .. }
| TypeKind::Bitmask { .. }
| TypeKind::Bitset { .. }
| TypeKind::Valuetype { .. }
| TypeKind::Native
| TypeKind::AnnotationType { .. } => rust::Type::named(relative_path(ast, from, node)?),
TypeKind::ValueBox { boxed } => rust::Type::generic(
Path::new(&["Box"]),
vec![type_of_at(ast, from, boxed.resolved(), depth + 1)?],
),
TypeKind::Fixed { .. } => rust::Type::Primitive(rust::Primitive::String),
TypeKind::ObjectRef { .. } => {
return Err(MaterialiseError::NoLanguageForm {
construct: "Object".to_string(),
language: "Rust",
at: handle_of(target),
});
}
TypeKind::Any => {
return Err(MaterialiseError::NoLanguageForm {
construct: "any".to_string(),
language: "Rust",
at: handle_of(target),
});
}
TypeKind::ValueBase => {
return Err(MaterialiseError::NoLanguageForm {
construct: "ValueBase".to_string(),
language: "Rust",
at: handle_of(target),
});
}
})
}
pub(crate) fn data_derives() -> Vec<rust::Derive> {
["Debug", "Clone", "PartialEq"]
.into_iter()
.map(|name| rust::Derive {
path: Path::single(name),
})
.collect()
}
fn named(name: &str, node: zerodds_ast_core::NodeId) -> String {
if name.is_empty() {
format!("{node:?}")
} else {
name.to_string()
}
}
const fn rust_primitive(kind: PrimitiveKind) -> rust::Primitive {
match kind {
PrimitiveKind::Boolean => rust::Primitive::Bool,
PrimitiveKind::Char => rust::Primitive::Char,
PrimitiveKind::WChar => rust::Primitive::Char,
PrimitiveKind::Octet | PrimitiveKind::UInt8 => rust::Primitive::U8,
PrimitiveKind::Int8 => rust::Primitive::I8,
PrimitiveKind::Int16 => rust::Primitive::I16,
PrimitiveKind::UInt16 => rust::Primitive::U16,
PrimitiveKind::Int32 => rust::Primitive::I32,
PrimitiveKind::UInt32 => rust::Primitive::U32,
PrimitiveKind::Int64 => rust::Primitive::I64,
PrimitiveKind::UInt64 => rust::Primitive::U64,
PrimitiveKind::Float32 => rust::Primitive::F32,
PrimitiveKind::Float64 | PrimitiveKind::Float128 => rust::Primitive::F64,
}
}
fn static_str() -> rust::Type {
rust::Type::Ref {
target: Box::new(rust::Type::Primitive(rust::Primitive::Str)),
mutable: false,
lifetime: Some(Ident::new("static")),
}
}
fn rust_constant(
value: &zerodds_ast::ConstValue,
declared: rust::Type,
at: &str,
) -> Result<(rust::Type, zerodds_lang_cst::common::Literal), MaterialiseError> {
use zerodds_ast::ConstValue;
use zerodds_lang_cst::common::Literal;
let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: "Rust",
at: at.to_string(),
};
let is_float128 = matches!(&declared, rust::Type::Named { path, .. } if path
.segments()
.last()
.is_some_and(|last| last.as_str() == "Float128"));
Ok(match value {
ConstValue::Boolean(flag) => (declared, Literal::Bool(*flag)),
ConstValue::Integer(number) => (declared, Literal::int(*number)),
ConstValue::Char(character) => (declared, Literal::Char(*character)),
ConstValue::WChar(point) => match constant::wchar_char(*point) {
Some(character) => (declared, Literal::Char(character)),
None => {
return Err(refuse(
"a `wchar` constant that is not a Unicode scalar value",
));
}
},
ConstValue::Float { mantissa, exponent } => {
if is_float128 {
return Err(refuse(
"a `long double` constant, which has no Rust literal",
));
}
(
declared,
Literal::Float(constant::float_text(mantissa, exponent.as_deref())),
)
}
ConstValue::Fixed { digits, scale } => (
static_str(),
Literal::Str(constant::fixed_decimal(digits, *scale)),
),
ConstValue::String(text) => (static_str(), Literal::Str(text.clone())),
ConstValue::WString(points) => (
static_str(),
Literal::Str(
constant::wstring_utf8(points)
.ok_or_else(|| refuse("a `wstring` constant holding a lone surrogate"))?,
),
),
_ => return Err(refuse("a constant form this binding has no literal for")),
})
}