use crate::adapter::constant;
use crate::adapter::cst::{MaterialiseError, no_spelling_collision};
use crate::adapter::interface;
use crate::apply::Applied;
use crate::verbatim;
use seesaw_tgg::ident::GhostId;
use zerodds_ast::{Ast, CharWidth, PrimitiveKind, TypeKind, TypeNode};
use zerodds_ast_core::{Id, NodeId};
use zerodds_lang_cst::common::{Doc, Ident, Literal, Visibility};
use zerodds_lang_cst::swift;
const LANGUAGE: &str = "Swift";
const INVENTED_PREFIX: &str = "ZdMapEntry_";
const EXTERNAL_BOX: &str = "ZdExternal";
const EXTERNAL_BOX_PARAM: &str = "Value";
pub const DISCRIMINATOR: &str = "_d";
const SELF_LABEL: &str = "`self`";
const SELF_PARAM: &str = "zdSelf";
const MAX_MODULE_DEPTH: usize = 64;
const MAX_TYPE_DEPTH: usize = 64;
pub fn swift_items(ast: &Ast, applied: &Applied) -> Result<Vec<swift::Decl>, MaterialiseError> {
let mut out = Vec::new();
let mut emitted: Vec<(String, String)> = Vec::new();
for (id, _) in ast.types() {
let start = out.len();
declare(ast, applied, id, &mut out, &mut emitted)?;
verbatim::weave(
verbatim::declaration_slots(ast, id, verbatim::aliases::SWIFT),
&swift_type_name(ast, id),
start,
&mut out,
);
}
for (_, node) in ast.constants() {
if ghost_created_for(applied, node.node_id, "SwiftLet").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 = node.type_.resolved();
let type_ = match declared {
Some(target) => swift_value_type(ast, target)?,
None => swift::Type::Primitive(swift::Primitive::Int32),
};
let name = declared_name(ast, &node.name, node.module);
remember(&mut emitted, &name, &node.name)?;
out.push(swift::Decl::Constant(swift::ConstDecl {
doc: idl_note(&name, &node.name),
visibility: Visibility::Public,
name: Ident::new(&name),
type_: Some(type_.clone()),
value: swift_constant(&value, &type_, &node.name)?,
}));
}
for (_, node) in ast.exceptions() {
if ghost_created_for(applied, node.node_id, "SwiftError").is_none() {
continue;
}
let name = declared_name(ast, &node.name, node.module);
remember(&mut emitted, &name, &node.name)?;
let mut pairs = Vec::new();
for member in &node.members {
if let Ok(member_node) = ast.member(*member) {
pairs.push((property_name(&member_node.name), member_node.name.clone()));
}
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut properties = Vec::new();
for member in &node.members {
let Ok(member_node) = ast.member(*member) else {
continue;
};
let Some(target) = member_node.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "exception member type".to_string(),
at: member_node.name.clone(),
});
};
properties.push(swift::Property {
doc: array_note(ast, target),
visibility: Visibility::Public,
mutable: true,
name: Ident::new(&property_name(&member_node.name)),
type_: member_shape(ast, member_node, target)?,
});
}
out.push(aggregate_with_conformances(
with_idl_note(
Doc::line(
"An exception, as the structure its members make — and `Swift.Error`, \
so an operation whose `raises` names it can literally `throw` one.",
),
&name,
&node.name,
),
&name,
properties,
vec![Ident::new("Error")],
));
}
for (id, node) in ast.interfaces() {
if ghost_created_for(applied, node.node_id, "SwiftProtocol").is_none() {
continue;
}
let name = declared_name(ast, &node.name, node.module);
remember(&mut emitted, &name, &node.name)?;
let mut methods = Vec::new();
let mut signatures = Vec::new();
let operations = interface::all_operations_flattened(ast, id)
.map_err(|error| interface::interface_error_to_materialise(error, &node.name))?;
for op in &operations {
let (exported, idl_spelling) = match op.origin {
interface::Origin::AttributeGetter => {
let attr = op.attribute_name();
(property_name(attr), op.name.clone())
}
interface::Origin::AttributeSetter => {
let attr = op.attribute_name();
(
format!("set{}", capitalise(&property_name(attr))),
op.name.clone(),
)
}
interface::Origin::Declared => (property_name(&op.name), op.name.clone()),
};
signatures.push((exported.clone(), idl_spelling));
let mut params = Vec::new();
for param in &op.params {
params.push(swift::Param {
label: Ident::new(&property_name(¶m.name)),
internal_name: None,
type_: swift_member_type(ast, param.type_, ¶m.name)?,
});
}
let returns = match op.returns {
Some(target) => Some(swift_member_type(ast, target, &op.name)?),
None => None,
};
let doc = op
.raises_line()
.map_or_else(Doc::none, |line| Doc::line(&line));
methods.push(swift::Method {
doc,
name: Ident::new(&exported),
params,
throws: !op.raises.is_empty(),
returns,
});
}
no_spelling_collision(as_pairs(&signatures), LANGUAGE, &name)?;
out.push(swift::Decl::Protocol(swift::ProtocolDecl {
doc: with_idl_note(
Doc::line(
"An interface, as the requirements its operations and attributes \
make — an attribute a `name()`/`setName(_:)` pair, `readonly` only \
the first. A derived interface's bases are flattened in rather than \
inherited, so these are all of them; the transport is the RPC arc's \
business, and the shape is the IDL's. A `raises` clause is a real \
`throws`, over the `Swift.Error`-conforming struct each exception \
becomes.",
),
&name,
&node.name,
),
visibility: Visibility::Public,
name: Ident::new(&name),
methods,
}));
}
if out.iter().any(decl_uses_external_box) {
out.insert(0, external_box());
}
Ok(verbatim::weave_file(ast, verbatim::aliases::SWIFT, out))
}
impl verbatim::Carrier for swift::Decl {
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::Aggregate(node) = self else {
return false;
};
if node.name.as_str() != name {
return false;
}
node.body_text.begin.append(begin);
node.body_text.end.append(end);
true
}
}
pub fn swift_file(ast: &Ast, applied: &Applied) -> Result<swift::File, MaterialiseError> {
Ok(swift::File {
doc: Doc::lines(&[
"Generated by ZeroDDS from an IDL source. Do not edit.",
"",
"Every declaration is `public`: Swift's default access level is",
"`internal`, which is the defining module only, and a generated type",
"no consumer can name has no consumer. The memberwise initialiser is",
"written out for the same reason — the synthesised one is `internal`",
"even when every property is `public`.",
"",
"A member whose IDL name is a Swift keyword is written in back quotes,",
"which do not rename it: the IDL spelling is the Swift spelling, which",
"is what @autoid(HASH) hashes (DDS-XTypes 1.3 7.3.1.2.1.1).",
]),
imports: Vec::new(),
decls: swift_items(ast, applied)?,
})
}
#[allow(clippy::too_many_lines)]
fn declare(
ast: &Ast,
applied: &Applied,
id: Id<TypeNode>,
out: &mut Vec<swift::Decl>,
emitted: &mut Vec<(String, String)>,
) -> Result<(), MaterialiseError> {
let Ok(node) = ast.type_(id) else {
return Ok(());
};
match &node.kind {
TypeKind::Primitive(_)
| TypeKind::String { .. }
| TypeKind::Fixed { .. }
| TypeKind::Sequence { .. }
| TypeKind::Array { .. } => {}
TypeKind::Map { key, value, .. } => {
let (Some(key), Some(value)) = (key.resolved(), value.resolved()) else {
return Err(MaterialiseError::Unevaluated {
construct: "map key or value".to_string(),
at: swift_type_name(ast, id),
});
};
let name = map_entry_name_at(ast, id, 0)?;
if emitted.iter().any(|(spelled, _)| *spelled == name) {
return Ok(());
}
emitted.push((name.clone(), name.clone()));
out.push(aggregate(
Doc::line(
"One entry of a map. DDS-XTypes 1.3 7.4.1.1.4 defines a map as a \
sequence of these, and the rules write the two halves with no \
framing between them.",
),
&name,
vec![
swift::Property {
doc: Doc::none(),
visibility: Visibility::Public,
mutable: true,
name: Ident::new("key"),
type_: swift_member_type(ast, key, &name)?,
},
swift::Property {
doc: Doc::none(),
visibility: Visibility::Public,
mutable: true,
name: Ident::new("value"),
type_: swift_member_type(ast, value, &name)?,
},
],
));
}
TypeKind::Alias { aliased } => {
if node.name.is_empty() {
return Ok(());
}
if ghost_created_for(applied, node.node_id, "SwiftTypealias").is_none() {
return Ok(());
}
let Some(target) = aliased.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "typedef target".to_string(),
at: node.name.clone(),
});
};
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
out.push(swift::Decl::TypeAlias(swift::TypeAliasDecl {
doc: idl_note(&name, &node.name),
visibility: Visibility::Public,
name: Ident::new(&name),
type_: swift_member_type(ast, target, &node.name)?,
}));
}
TypeKind::Enumeration { enumerators } => {
if ghost_created_for(applied, node.node_id, "SwiftEnum").is_none() {
return Ok(());
}
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
let mut cases = Vec::new();
let mut pairs = Vec::new();
for enumerator in enumerators {
let Ok(enum_node) = ast.enumerator(*enumerator) else {
continue;
};
if ghost_created_for(applied, enum_node.node_id, "SwiftCase").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(),
}
})?;
pairs.push((property_name(&enum_node.name), enum_node.name.clone()));
cases.push(swift::EnumCase {
doc: Doc::none(),
name: Ident::new(&property_name(&enum_node.name)),
raw_value: Some(swift::Expr::int(value)),
});
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
out.push(swift::Decl::Enum(swift::EnumDecl {
doc: with_idl_note(
Doc::line(
"An enumeration. The raw type is signed because DDS-XTypes 1.3 \
7.4.3.5.3 makes it so; the width on the wire is the \
@bit_bound's and is applied by the runtime.",
),
&name,
&node.name,
),
visibility: Visibility::Public,
name: Ident::new(&name),
raw_type: if cases.is_empty() {
None
} else {
Some(swift::Type::Primitive(swift::Primitive::Int32))
},
cases,
}));
}
TypeKind::Bitmask { bits } => {
if ghost_created_for(applied, node.node_id, "SwiftOptionSet").is_none() {
return Ok(());
}
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
let bound = ast.effective_bit_bound(id);
let holder = swift::Type::Primitive(swift_holder(bound));
let mut statics = Vec::new();
let mut pairs = Vec::new();
for bit in bits {
let Ok(bit_node) = ast.bit_value(*bit) else {
continue;
};
if ghost_created_for(applied, bit_node.node_id, "SwiftFlag").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(),
}
})?;
pairs.push((property_name(&bit_node.name), bit_node.name.clone()));
statics.push(swift::StaticLet {
doc: Doc::none(),
visibility: Visibility::Public,
name: Ident::new(&property_name(&bit_node.name)),
type_: None,
value: swift::Expr::Call {
callee: Ident::new(&name),
args: vec![swift::Argument {
label: Some(Ident::new("rawValue")),
value: swift::Expr::Binary {
op: "<<",
lhs: Box::new(swift::Expr::int(1)),
rhs: Box::new(swift::Expr::int(i128::from(position))),
},
}],
},
});
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
out.push(swift::Decl::Aggregate(swift::AggregateDecl {
doc: with_idl_note(
Doc::line(
"A bitmask, as the unsigned integer Table 33 of DDS-XTypes 1.3 \
7.4.1.1.3.2 gives for its bound.",
),
&name,
&node.name,
),
visibility: Visibility::Public,
kind: swift::Aggregate::Struct,
name: Ident::new(&name),
generics: Vec::new(),
conformances: vec![Ident::new("OptionSet")],
properties: vec![swift::Property {
doc: Doc::none(),
visibility: Visibility::Public,
mutable: false,
name: Ident::new("rawValue"),
type_: holder.clone(),
}],
statics,
body_text: zerodds_lang_cst::common::BodyText::none(),
inits: vec![swift::Init {
doc: Doc::line(
"`RawRepresentable`'s requirement, written out because the \
synthesised memberwise initialiser is internal.",
),
visibility: Visibility::Public,
params: vec![swift::Param {
label: Ident::new("rawValue"),
internal_name: None,
type_: holder,
}],
body: vec![swift::Stmt::AssignProperty {
property: Ident::new("rawValue"),
value: swift::Expr::Name(Ident::new("rawValue")),
}],
}],
}));
}
TypeKind::Bitset { .. } => {
if ghost_created_for(applied, node.node_id, "SwiftBitset").is_none() {
return Ok(());
}
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
let mut pairs = Vec::new();
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
let Some(field_ident) = field_node.name.as_ref() else {
continue;
};
pairs.push((property_name(field_ident), field_ident.clone()));
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut properties = Vec::new();
let mut index = 0;
for field in &ast.all_bitfields_of(id) {
let Ok(field_node) = ast.bitfield(*field) else {
continue;
};
if field_node.name.is_none() {
continue;
}
properties.push(swift::Property {
doc: Doc::none(),
visibility: Visibility::Public,
mutable: true,
name: Ident::new(&pairs[index].0),
type_: swift::Type::Primitive(swift_holder(field_node.bits)),
});
index += 1;
}
out.push(aggregate(
with_idl_note(
Doc::line(
"A bitset. The fields are separate integers here; the packing \
into the anchor is the codec's, because the order on the wire \
is the declaration's.",
),
&name,
&node.name,
),
&name,
properties,
));
}
TypeKind::Structure { .. } | TypeKind::Valuetype { .. } => {
let created = if matches!(node.kind, TypeKind::Valuetype { .. }) {
"SwiftValue"
} else {
"SwiftStruct"
};
if ghost_created_for(applied, node.node_id, created).is_none() {
return Ok(());
}
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
let members = ast.members_of(id).to_vec();
let base = super::c::struct_base(ast, id);
let mut pairs = Vec::new();
if base.is_some() {
pairs.push((property_name("parent"), "parent".to_string()));
}
for member in &members {
if let Ok(member_node) = ast.member(*member) {
pairs.push((property_name(&member_node.name), member_node.name.clone()));
}
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut properties = Vec::new();
if let Some(base) = base {
properties.push(swift::Property {
doc: Doc::line(
"The base structure, the first member (XTypes 1.3 7.5.1.4.1.3).",
),
visibility: Visibility::Public,
mutable: true,
name: Ident::new(&property_name("parent")),
type_: swift_member_type(ast, base, "parent")?,
});
}
for member in &members {
let Ok(member_node) = ast.member(*member) else {
continue;
};
let Some(target) = member_node.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "member type".to_string(),
at: member_node.name.clone(),
});
};
let mut doc = array_note(ast, target);
if member_node.facets.is_optional() {
doc.push(
"Optional. `nil` when the member is absent — DDS-XTypes 1.3 \
7.2.2.4.4.4.7. In a @mutable type an absent member is not \
written at all; outside one its absence is written.",
);
}
if super::held_externally(ast, member_node) {
doc.push(
"Stored out of line (`@external`, DDS-XTypes 1.3 Table 21). The \
box is a reference, so two copies of this value share it until \
one of them replaces it.",
);
}
properties.push(swift::Property {
doc,
visibility: Visibility::Public,
mutable: true,
name: Ident::new(&property_name(&member_node.name)),
type_: member_shape(ast, member_node, target)?,
});
}
out.push(aggregate(idl_note(&name, &node.name), &name, properties));
}
TypeKind::Union { discriminator, .. } => {
if ghost_created_for(applied, node.node_id, "SwiftUnion").is_none() {
return Ok(());
}
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
let Some(disc) = discriminator.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "union discriminator".to_string(),
at: name,
});
};
let cases = ast.cases_of(id);
let mut pairs = vec![(DISCRIMINATOR.to_string(), "<discriminator>".to_string())];
for case in cases {
if let Ok(case_node) = ast.case(*case)
&& let Ok(member) = ast.member(case_node.member)
{
pairs.push((property_name(&member.name), member.name.clone()));
}
}
no_spelling_collision(as_pairs(&pairs), LANGUAGE, &name)?;
let mut properties = vec![swift::Property {
doc: Doc::line("Which branch carries the value."),
visibility: Visibility::Public,
mutable: true,
name: Ident::new(DISCRIMINATOR),
type_: swift_member_type(ast, disc, &name)?,
}];
for case in cases {
let Ok(case_node) = ast.case(*case) else {
continue;
};
if ghost_created_for(applied, case_node.node_id, "SwiftBranch").is_none() {
continue;
}
let Ok(member) = ast.member(case_node.member) else {
continue;
};
let Some(target) = member.type_.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "union case type".to_string(),
at: member.name.clone(),
});
};
let mut doc = Doc::line("`nil` unless `_d` selects this branch.");
if super::held_externally(ast, member) {
doc.push(
"Stored out of line (`@external`, DDS-XTypes 1.3 Table 21). The \
box is a reference, so two copies of this value share it until \
one of them replaces it.",
);
}
properties.push(swift::Property {
doc,
visibility: Visibility::Public,
mutable: true,
name: Ident::new(&property_name(&member.name)),
type_: swift::Type::optional(member_shape(ast, member, target)?),
});
}
out.push(aggregate(
with_idl_note(
Doc::lines(&[
"A union. Swift's enum with associated values is the shape this",
"looks like and cannot be: an IDL discriminator may take a value",
"that selects no branch (IDL 4.2 7.4.1.4.4), and DDS-XTypes 1.3",
"7.4.1.1.5.2 then writes the discriminator and nothing else. A",
"struct has that state; an enum has no case for it.",
]),
&name,
&node.name,
),
&name,
properties,
));
}
TypeKind::Native => {
if ghost_created_for(applied, node.node_id, "SwiftNative").is_none() {
return Ok(());
}
let name = swift_type_name(ast, id);
remember(emitted, &name, &node.name)?;
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());
out.push(swift::Decl::TypeAlias(swift::TypeAliasDecl {
doc: with_idl_note(
Doc::line("A native type. Opaque unless a facet named its mapping."),
&name,
&node.name,
),
visibility: Visibility::Public,
name: Ident::new(&name),
type_: match mapping {
Some(spelling) => swift::Type::named(&spelling),
None => swift::Type::Primitive(swift::Primitive::Any),
},
}));
}
TypeKind::ValueBox { boxed } => {
let Some(target) = boxed.resolved() else {
return Ok(());
};
let name = swift_type_name(ast, id);
if emitted.iter().any(|(spelled, _)| *spelled == name) {
return Ok(());
}
remember(emitted, &name, &node.name)?;
out.push(swift::Decl::TypeAlias(swift::TypeAliasDecl {
doc: with_idl_note(
Doc::line("A boxed value: an `Optional`, `nil` when absent."),
&name,
&node.name,
),
visibility: Visibility::Public,
name: Ident::new(&name),
type_: swift::Type::optional(swift_member_type(ast, target, &name)?),
}));
}
TypeKind::ObjectRef { .. }
| TypeKind::AnnotationType { .. }
| TypeKind::Any
| TypeKind::ValueBase => {}
}
Ok(())
}
fn aggregate(doc: Doc, name: &str, properties: Vec<swift::Property>) -> swift::Decl {
aggregate_with_conformances(doc, name, properties, Vec::new())
}
fn aggregate_with_conformances(
doc: Doc,
name: &str,
properties: Vec<swift::Property>,
conformances: Vec<Ident>,
) -> swift::Decl {
let init = memberwise_init(&properties);
swift::Decl::Aggregate(swift::AggregateDecl {
doc,
visibility: Visibility::Public,
kind: swift::Aggregate::Struct,
name: Ident::new(name),
generics: Vec::new(),
conformances,
properties,
statics: Vec::new(),
body_text: zerodds_lang_cst::common::BodyText::none(),
inits: vec![init],
})
}
fn memberwise_init(properties: &[swift::Property]) -> swift::Init {
let mut params = Vec::new();
let mut body = Vec::new();
for property in properties {
let internal = if property.name.as_str() == SELF_LABEL {
Some(Ident::new(SELF_PARAM))
} else {
None
};
let value = Ident::new(
internal
.as_ref()
.map_or_else(|| property.name.as_str(), Ident::as_str),
);
params.push(swift::Param {
label: property.name.clone(),
internal_name: internal,
type_: property.type_.clone(),
});
body.push(swift::Stmt::AssignProperty {
property: property.name.clone(),
value: swift::Expr::Name(value),
});
}
swift::Init {
doc: Doc::line(
"The memberwise initialiser, written out: the synthesised one is \
internal even when every property is public, so a consumer in \
another module could hold this type and not construct one.",
),
visibility: Visibility::Public,
params,
body,
}
}
fn external_box() -> swift::Decl {
swift::Decl::Aggregate(swift::AggregateDecl {
doc: Doc::lines(&[
"A member the IDL declared @external — stored out of line",
"(DDS-XTypes 1.3 Table 21).",
"",
"A class and not a struct, because Swift's only stored form that does",
"not sit inside its owner is a reference: `struct N { var next: N? }`",
"is rejected with `value type 'N' cannot have a stored property that",
"recursively contains it`, and a self-recursive type made finite by an",
"absent @optional member is exactly that shape.",
"",
"The consequence is that two copies of the enclosing value share this",
"box until one of them replaces it, which is what storing a member out",
"of line means.",
]),
visibility: Visibility::Public,
kind: swift::Aggregate::Class,
name: Ident::new(EXTERNAL_BOX),
generics: vec![Ident::new(EXTERNAL_BOX_PARAM)],
conformances: Vec::new(),
properties: vec![swift::Property {
doc: Doc::none(),
visibility: Visibility::Public,
mutable: true,
name: Ident::new("value"),
type_: swift::Type::named(EXTERNAL_BOX_PARAM),
}],
statics: Vec::new(),
body_text: zerodds_lang_cst::common::BodyText::none(),
inits: vec![swift::Init {
doc: Doc::none(),
visibility: Visibility::Public,
params: vec![swift::Param {
label: Ident::new("_"),
internal_name: Some(Ident::new("value")),
type_: swift::Type::named(EXTERNAL_BOX_PARAM),
}],
body: vec![swift::Stmt::AssignProperty {
property: Ident::new("value"),
value: swift::Expr::Name(Ident::new("value")),
}],
}],
})
}
fn decl_uses_external_box(decl: &swift::Decl) -> bool {
match decl {
swift::Decl::Aggregate(node) => {
node.name.as_str() != EXTERNAL_BOX
&& node
.properties
.iter()
.any(|property| type_uses_external_box(&property.type_))
}
swift::Decl::TypeAlias(node) => type_uses_external_box(&node.type_),
swift::Decl::Protocol(node) => node.methods.iter().any(|method| {
method
.params
.iter()
.any(|param| type_uses_external_box(¶m.type_))
|| method.returns.as_ref().is_some_and(type_uses_external_box)
}),
swift::Decl::Enum(_)
| swift::Decl::Constant(_)
| swift::Decl::Func(_)
| swift::Decl::Comment(_)
| swift::Decl::Verbatim(_) => false,
}
}
fn type_uses_external_box(type_: &swift::Type) -> bool {
match type_ {
swift::Type::Generic { base, arguments } => {
base.as_str() == EXTERNAL_BOX || arguments.iter().any(type_uses_external_box)
}
swift::Type::Array(inner) | swift::Type::Optional(inner) => type_uses_external_box(inner),
swift::Type::Primitive(_) | swift::Type::Named(_) => false,
}
}
fn member_shape(
ast: &Ast,
member: &zerodds_ast::MemberNode,
target: Id<TypeNode>,
) -> Result<swift::Type, MaterialiseError> {
let mut type_ = swift_member_type(ast, target, &member.name)?;
if super::held_externally(ast, member) {
type_ = swift::Type::generic(EXTERNAL_BOX, vec![type_]);
}
if member.facets.is_optional() {
type_ = swift::Type::optional(type_);
}
Ok(type_)
}
fn array_note(ast: &Ast, target: Id<TypeNode>) -> Doc {
let Some(dims) = array_dims(ast, target) else {
return Doc::none();
};
let written: Vec<String> = dims.iter().map(dim_text).collect();
Doc::line(&format!(
"A fixed-size array of {} elements. Swift's `[T]` carries no length, \
so the bound is checked by the codec and not by the compiler.",
written.join(" x ")
))
}
fn dim_text(value: &i128) -> String {
format!("{value}")
}
fn array_dims(ast: &Ast, target: Id<TypeNode>) -> Option<Vec<i128>> {
let mut current = target;
for _ in 0..MAX_TYPE_DEPTH {
let node = ast.type_(current).ok()?;
match &node.kind {
TypeKind::Array { dims, .. } => {
return Some(
dims.iter()
.map(|dim| ast.eval_integral(*dim).unwrap_or(0))
.collect(),
);
}
TypeKind::Alias { aliased } if node.name.is_empty() => current = aliased.resolved()?,
_ => return None,
}
}
None
}
pub fn swift_member_type(
ast: &Ast,
target: Id<TypeNode>,
at: &str,
) -> Result<swift::Type, MaterialiseError> {
let construct = match ast.type_(target).map(|node| &node.kind) {
Ok(TypeKind::Any) => Some("any"),
Ok(TypeKind::ValueBase) => Some("ValueBase"),
Ok(TypeKind::ObjectRef { .. }) => Some("Object"),
_ => None,
};
if let Some(construct) = construct {
return Err(MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: LANGUAGE,
at: at.to_string(),
});
}
swift_value_type(ast, target)
}
pub fn swift_value_type(ast: &Ast, id: Id<TypeNode>) -> Result<swift::Type, MaterialiseError> {
value_type_at(ast, id, 0)
}
fn value_type_at(
ast: &Ast,
id: Id<TypeNode>,
depth: usize,
) -> Result<swift::Type, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("type, whose Swift spelling nests past {MAX_TYPE_DEPTH}"),
at: swift_type_name(ast, id),
});
}
let Ok(node) = ast.type_(id) else {
return Ok(swift::Type::Primitive(swift::Primitive::Any));
};
Ok(match &node.kind {
TypeKind::Primitive(kind) => swift_primitive_type(*kind),
TypeKind::String { .. } | TypeKind::Fixed { .. } => {
swift::Type::Primitive(swift::Primitive::String)
}
TypeKind::Sequence { element, .. } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "sequence element".to_string(),
at: node.name.clone(),
});
};
swift::Type::array(value_type_at(ast, element, depth + 1)?)
}
TypeKind::Array { element, dims } => {
let Some(element) = element.resolved() else {
return Err(MaterialiseError::Unevaluated {
construct: "array element".to_string(),
at: node.name.clone(),
});
};
let mut type_ = value_type_at(ast, element, depth + 1)?;
for dim in dims {
ast.eval_integral(*dim)
.map_err(|_| MaterialiseError::Unevaluated {
construct: "array length".to_string(),
at: node.name.clone(),
})?;
type_ = swift::Type::array(type_);
}
type_
}
TypeKind::Map { .. } => {
swift::Type::array(swift::Type::named(&map_entry_name_at(ast, id, depth + 1)?))
}
TypeKind::Alias { aliased } if node.name.is_empty() => match aliased.resolved() {
Some(target) => value_type_at(ast, target, depth + 1)?,
None => swift::Type::Primitive(swift::Primitive::Any),
},
_ => swift::Type::named(&swift_type_name(ast, id)),
})
}
#[must_use]
pub fn swift_primitive_type(kind: PrimitiveKind) -> swift::Type {
let primitive = match kind {
PrimitiveKind::Boolean => swift::Primitive::Bool,
PrimitiveKind::Octet | PrimitiveKind::UInt8 | PrimitiveKind::Char => {
swift::Primitive::UInt8
}
PrimitiveKind::Int8 => swift::Primitive::Int8,
PrimitiveKind::WChar | PrimitiveKind::UInt16 => swift::Primitive::UInt16,
PrimitiveKind::Int16 => swift::Primitive::Int16,
PrimitiveKind::Int32 => swift::Primitive::Int32,
PrimitiveKind::UInt32 => swift::Primitive::UInt32,
PrimitiveKind::Int64 => swift::Primitive::Int64,
PrimitiveKind::UInt64 => swift::Primitive::UInt64,
PrimitiveKind::Float32 => swift::Primitive::Float,
PrimitiveKind::Float64 => swift::Primitive::Double,
PrimitiveKind::Float128 => swift::Primitive::String,
};
swift::Type::Primitive(primitive)
}
#[must_use]
pub const fn swift_holder(bits: u16) -> swift::Primitive {
match bits {
0..=8 => swift::Primitive::UInt8,
9..=16 => swift::Primitive::UInt16,
17..=32 => swift::Primitive::UInt32,
_ => swift::Primitive::UInt64,
}
}
#[must_use]
pub fn swift_type_name(ast: &Ast, id: Id<TypeNode>) -> String {
let Ok(any) = ast.any_of_type(id) else {
return "ZdUnnamed".to_string();
};
match ast.scoped_name_of(any) {
Some(scoped) => flatten(scoped.segments()),
None => "ZdUnnamed".to_string(),
}
}
pub fn swift_map_entry_name(ast: &Ast, id: Id<TypeNode>) -> Result<String, MaterialiseError> {
map_entry_name_at(ast, id, 0)
}
fn map_entry_name_at(
ast: &Ast,
id: Id<TypeNode>,
depth: usize,
) -> Result<String, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("map entry name, which nests past {MAX_TYPE_DEPTH}"),
at: swift_type_name(ast, id),
});
}
let Ok(node) = ast.type_(id) else {
return Ok(format!("{INVENTED_PREFIX}Unknown"));
};
let TypeKind::Map { key, value, .. } = &node.kind else {
return Ok(format!("{INVENTED_PREFIX}Unknown"));
};
let key = match key.resolved() {
Some(k) => type_tag(ast, k, depth + 1)?,
None => "Unknown".to_string(),
};
let value = match value.resolved() {
Some(v) => type_tag(ast, v, depth + 1)?,
None => "Unknown".to_string(),
};
Ok(format!("{INVENTED_PREFIX}{key}_{value}"))
}
fn type_tag(ast: &Ast, id: Id<TypeNode>, depth: usize) -> Result<String, MaterialiseError> {
if depth > MAX_TYPE_DEPTH {
return Err(MaterialiseError::Unevaluated {
construct: format!("type tag, which nests past {MAX_TYPE_DEPTH}"),
at: swift_type_name(ast, id),
});
}
Ok(match ast.type_(id).map(|node| node.kind.clone()) {
Ok(TypeKind::Primitive(kind)) => capitalise(&kind.as_str().replace(' ', "_")),
Ok(TypeKind::String {
width: CharWidth::Char,
..
}) => "String".to_string(),
Ok(TypeKind::String {
width: CharWidth::WChar,
..
}) => "WString".to_string(),
Ok(TypeKind::Sequence { element, .. }) => match element.resolved() {
Some(element) => format!("SeqOf{}", type_tag(ast, element, depth + 1)?),
None => "SeqOfUnknown".to_string(),
},
Ok(TypeKind::Array { element, dims }) => match element.resolved() {
Some(element) => {
let mut name = format!("ArrOf{}", type_tag(ast, element, depth + 1)?);
for dim in &dims {
match ast.eval_integral(*dim) {
Ok(length) => name.push_str(&format!("_{length}")),
Err(_) => name.push_str("_x"),
}
}
name
}
None => "ArrOfUnknown".to_string(),
},
Ok(TypeKind::Map { .. }) => map_entry_name_at(ast, id, depth + 1)?,
_ => swift_type_name(ast, id),
})
}
fn capitalise(name: &str) -> String {
let mut chars = name.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
fn declared_name(ast: &Ast, name: &str, module: Id<zerodds_ast::ModuleNode>) -> String {
let mut segments: Vec<String> = Vec::new();
let mut current = Some(module);
let mut depth = 0;
while let Some(id) = current {
if depth > MAX_MODULE_DEPTH {
break;
}
depth += 1;
let Ok(node) = ast.module(id) else { break };
if node.name.is_empty() {
break;
}
segments.push(node.name.clone());
current = node.parent;
}
segments.reverse();
segments.push(name.to_string());
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
flatten_str(&borrowed)
}
fn flatten(segments: &[String]) -> String {
let borrowed: Vec<&str> = segments.iter().map(String::as_str).collect();
flatten_str(&borrowed)
}
fn flatten_str(segments: &[&str]) -> String {
swift::escape_type_name(&segments.join("_"))
}
#[must_use]
pub fn property_name(name: &str) -> String {
swift::escape_name(name)
}
fn idl_note(spelled: &str, declared: &str) -> Doc {
if spelled == declared {
return Doc::none();
}
Doc::line(&format!("IDL: `{declared}`."))
}
fn with_idl_note(mut doc: Doc, spelled: &str, declared: &str) -> Doc {
if spelled != declared {
doc.push(&format!("IDL: `{declared}`."));
}
doc
}
fn remember(
emitted: &mut Vec<(String, String)>,
spelled: &str,
declared: &str,
) -> Result<(), MaterialiseError> {
if let Some((_, first)) = emitted.iter().find(|(other, _)| other == spelled) {
return Err(MaterialiseError::SpellingCollision {
first: first.clone(),
second: declared.to_string(),
spelled: spelled.to_string(),
language: LANGUAGE,
at: "the generated module".to_string(),
});
}
emitted.push((spelled.to_string(), declared.to_string()));
Ok(())
}
fn as_pairs(pairs: &[(String, String)]) -> Vec<(&str, &str)> {
pairs
.iter()
.map(|(spelled, declared)| (spelled.as_str(), declared.as_str()))
.collect()
}
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)
|| applied
.graph
.parts(&anchor)
.any(|part| applied.graph.connected(&part.other, created))
})
}
fn swift_constant(
value: &zerodds_ast::ConstValue,
type_: &swift::Type,
at: &str,
) -> Result<swift::Expr, MaterialiseError> {
use zerodds_ast::ConstValue;
let refuse = |construct: &str| MaterialiseError::NoLanguageForm {
construct: construct.to_string(),
language: LANGUAGE,
at: at.to_string(),
};
let is_string = matches!(type_, swift::Type::Primitive(swift::Primitive::String));
Ok(match value {
ConstValue::Boolean(flag) => swift::Expr::Literal(Literal::Bool(*flag)),
ConstValue::Integer(number) => swift::Expr::Literal(Literal::int(*number)),
ConstValue::Char(character) => {
swift::Expr::Literal(Literal::int(i128::from(u32::from(*character))))
}
ConstValue::WChar(point) => swift::Expr::Literal(Literal::int(i128::from(*point))),
ConstValue::Float { mantissa, exponent } => {
if is_string {
return Err(refuse(
"a `long double` constant, which has no Swift literal",
));
}
swift::Expr::Literal(Literal::Float(constant::float_text(
mantissa,
exponent.as_deref(),
)))
}
ConstValue::Fixed { digits, scale } => {
swift::Expr::Literal(Literal::Str(constant::fixed_decimal(digits, *scale)))
}
ConstValue::String(text) => swift::Expr::Literal(Literal::Str(text.clone())),
ConstValue::WString(points) => swift::Expr::Literal(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")),
})
}