use std::collections::HashSet;
use facet_core::{ScalarType, Shape};
use heck::ToLowerCamelCase;
use vox_types::{
EnumInfo, ServiceDescriptor, ShapeKind, StructInfo, VariantKind, classify_shape,
classify_variant, is_bytes, is_rx, is_tx,
};
pub fn collect_named_types(service: &ServiceDescriptor) -> Vec<(String, &'static Shape)> {
let mut seen: HashSet<String> = HashSet::new();
let mut types = Vec::new();
fn visit(
shape: &'static Shape,
seen: &mut HashSet<String>,
types: &mut Vec<(String, &'static Shape)>,
) {
match classify_shape(shape) {
ShapeKind::Struct(StructInfo {
name: Some(name),
fields,
..
}) if seen.insert(name.to_string()) => {
for field in fields {
visit(field.shape(), seen, types);
}
types.push((name.to_string(), shape));
}
ShapeKind::Enum(EnumInfo {
name: Some(name),
variants,
}) if seen.insert(name.to_string()) => {
for variant in variants {
match classify_variant(variant) {
VariantKind::Newtype { inner } => visit(inner, seen, types),
VariantKind::Struct { fields } | VariantKind::Tuple { fields } => {
for field in fields {
visit(field.shape(), seen, types);
}
}
VariantKind::Unit => {}
}
}
types.push((name.to_string(), shape));
}
ShapeKind::List { element }
| ShapeKind::Slice { element }
| ShapeKind::Option { inner: element }
| ShapeKind::Array { element, .. }
| ShapeKind::Set { element } => visit(element, seen, types),
ShapeKind::Map { key, value } => {
visit(key, seen, types);
visit(value, seen, types);
}
ShapeKind::Tuple { elements } => {
for param in elements {
visit(param.shape, seen, types);
}
}
ShapeKind::Tx { inner } | ShapeKind::Rx { inner } => visit(inner, seen, types),
ShapeKind::Pointer { pointee } => visit(pointee, seen, types),
ShapeKind::Result { ok, err } => {
visit(ok, seen, types);
visit(err, seen, types);
}
_ => {}
}
}
for method in service.methods {
for arg in method.args {
visit(arg.shape, &mut seen, &mut types);
}
visit(method.return_shape, &mut seen, &mut types);
}
types
}
pub fn generate_named_types(named_types: &[(String, &'static Shape)]) -> String {
let mut out = String::new();
for (name, shape) in named_types {
match classify_shape(shape) {
ShapeKind::Struct(StructInfo { fields, .. }) => {
out.push_str(&format!("public struct {name}: Codable, Sendable {{\n"));
for field in fields {
let field_name = swift_field_name(field.name);
let field_type = swift_type_base(field.shape());
out.push_str(&format!(" public var {field_name}: {field_type}\n"));
}
out.push('\n');
out.push_str(" nonisolated public init(");
for (i, field) in fields.iter().enumerate() {
if i > 0 {
out.push_str(", ");
}
let field_name = swift_field_name(field.name);
let field_type = swift_type_base(field.shape());
out.push_str(&format!("{field_name}: {field_type}"));
}
out.push_str(") {\n");
for field in fields {
let field_name = swift_field_name(field.name);
out.push_str(&format!(" self.{field_name} = {field_name}\n"));
}
out.push_str(" }\n");
out.push_str("}\n\n");
}
ShapeKind::Enum(EnumInfo { variants, .. }) => {
let protocols = if name.ends_with("Error") {
"Codable, Sendable, Error"
} else {
"Codable, Sendable"
};
out.push_str(&format!("public enum {name}: {protocols} {{\n"));
for variant in variants {
let variant_name = swift_field_name(variant.name);
match classify_variant(variant) {
VariantKind::Unit => {
out.push_str(&format!(" case {variant_name}\n"));
}
VariantKind::Newtype { inner } => {
let inner_type = swift_type_base(inner);
out.push_str(&format!(" case {variant_name}({inner_type})\n"));
}
VariantKind::Tuple { fields } => {
let field_types: Vec<_> =
fields.iter().map(|f| swift_type_base(f.shape())).collect();
out.push_str(&format!(
" case {variant_name}({})\n",
field_types.join(", ")
));
}
VariantKind::Struct { fields } => {
let field_decls: Vec<_> = fields
.iter()
.map(|f| {
format!(
"{}: {}",
swift_field_name(f.name),
swift_type_base(f.shape())
)
})
.collect();
out.push_str(&format!(
" case {variant_name}({})\n",
field_decls.join(", ")
));
}
}
}
out.push_str("}\n\n");
}
_ => {}
}
}
out
}
pub fn swift_field_name(name: &str) -> String {
if name.chars().next().is_some_and(|c| c.is_ascii_digit()) {
return format!("_{name}");
}
let lower = name.to_lower_camel_case();
if SWIFT_RESERVED.binary_search(&lower.as_str()).is_ok() {
format!("`{lower}`")
} else {
lower
}
}
const SWIFT_RESERVED: &[&str] = &[
"Any",
"Self",
"as",
"associatedtype",
"break",
"case",
"catch",
"class",
"continue",
"default",
"defer",
"deinit",
"do",
"else",
"enum",
"extension",
"fallthrough",
"false",
"fileprivate",
"for",
"func",
"guard",
"if",
"import",
"in",
"init",
"inout",
"internal",
"is",
"let",
"nil",
"open",
"operator",
"precedencegroup",
"private",
"protocol",
"public",
"repeat",
"rethrows",
"return",
"self",
"static",
"struct",
"subscript",
"super",
"switch",
"throw",
"throws",
"true",
"try",
"typealias",
"var",
"where",
"while",
];
pub fn swift_scalar_type(scalar: ScalarType) -> String {
match scalar {
ScalarType::Bool => "Bool".into(),
ScalarType::U8 => "UInt8".into(),
ScalarType::U16 => "UInt16".into(),
ScalarType::U32 => "UInt32".into(),
ScalarType::U64 => "UInt64".into(),
ScalarType::U128 => "UInt128".into(),
ScalarType::USize => "UInt".into(),
ScalarType::I8 => "Int8".into(),
ScalarType::I16 => "Int16".into(),
ScalarType::I32 => "Int32".into(),
ScalarType::I64 => "Int64".into(),
ScalarType::I128 => "Int128".into(),
ScalarType::ISize => "Int".into(),
ScalarType::F32 => "Float".into(),
ScalarType::F64 => "Double".into(),
ScalarType::Char | ScalarType::Str | ScalarType::String | ScalarType::CowStr => {
"String".into()
}
ScalarType::Unit => "Void".into(),
_ => "Data".into(),
}
}
pub fn swift_type_base(shape: &'static Shape) -> String {
if is_bytes(shape) {
return "Data".into();
}
match classify_shape(shape) {
ShapeKind::Scalar(scalar) => swift_scalar_type(scalar),
ShapeKind::List { element } => format!("[{}]", swift_type_base(element)),
ShapeKind::Slice { element } => format!("[{}]", swift_type_base(element)),
ShapeKind::Option { inner } => format!("{}?", swift_type_base(inner)),
ShapeKind::Array { element, .. } => format!("[{}]", swift_type_base(element)),
ShapeKind::Map { key, value } => {
format!("[{}: {}]", swift_type_base(key), swift_type_base(value))
}
ShapeKind::Set { element } => format!("Set<{}>", swift_type_base(element)),
ShapeKind::Tuple { elements } => {
if elements.is_empty() {
"Void".into()
} else {
let types: Vec<_> = elements.iter().map(|p| swift_type_base(p.shape)).collect();
format!("({})", types.join(", "))
}
}
ShapeKind::Tx { inner } => format!("Tx<{}>", swift_type_base(inner)),
ShapeKind::Rx { inner } => format!("Rx<{}>", swift_type_base(inner)),
ShapeKind::Struct(StructInfo {
name: Some(name), ..
}) => name.to_string(),
ShapeKind::Enum(EnumInfo {
name: Some(name), ..
}) => name.to_string(),
ShapeKind::Struct(StructInfo {
name: None, fields, ..
}) => {
let types: Vec<_> = fields.iter().map(|f| swift_type_base(f.shape())).collect();
format!("({})", types.join(", "))
}
ShapeKind::Enum(EnumInfo {
name: None,
variants,
}) => {
let _ = variants; "Any".into()
}
ShapeKind::Pointer { pointee } => swift_type_base(pointee),
ShapeKind::Result { ok, err } => {
format!("Result<{}, {}>", swift_type_base(ok), swift_type_base(err))
}
ShapeKind::TupleStruct { fields } => {
let types: Vec<_> = fields.iter().map(|f| swift_type_base(f.shape())).collect();
format!("({})", types.join(", "))
}
ShapeKind::Opaque => "Data".into(),
}
}
pub fn swift_type_client_arg(shape: &'static Shape) -> String {
match classify_shape(shape) {
ShapeKind::Tx { inner } => format!("UnboundTx<{}>", swift_type_base(inner)),
ShapeKind::Rx { inner } => format!("UnboundRx<{}>", swift_type_base(inner)),
_ => swift_type_base(shape),
}
}
pub fn swift_type_client_return(shape: &'static Shape) -> String {
assert_no_channels_in_return_shape(shape);
match classify_shape(shape) {
ShapeKind::Scalar(ScalarType::Unit) => "Void".into(),
ShapeKind::Tuple { elements: [] } => "Void".into(),
_ => swift_type_base(shape),
}
}
pub fn swift_type_server_arg(shape: &'static Shape) -> String {
match classify_shape(shape) {
ShapeKind::Tx { inner } => format!("Tx<{}>", swift_type_base(inner)),
ShapeKind::Rx { inner } => format!("Rx<{}>", swift_type_base(inner)),
_ => swift_type_base(shape),
}
}
pub fn swift_type_server_return(shape: &'static Shape) -> String {
assert_no_channels_in_return_shape(shape);
match classify_shape(shape) {
ShapeKind::Scalar(ScalarType::Unit) => "Void".into(),
ShapeKind::Tuple { elements: [] } => "Void".into(),
_ => swift_type_base(shape),
}
}
pub fn is_channel(shape: &'static Shape) -> bool {
is_tx(shape) || is_rx(shape)
}
pub fn format_doc(doc: &str, indent: &str) -> String {
doc.lines()
.map(|line| format!("{indent}/// {line}\n"))
.collect()
}
pub fn assert_no_channels_in_return_shape(shape: &'static Shape) {
fn has_channel(shape: &'static Shape) -> bool {
matches!(
classify_shape(shape),
ShapeKind::Tx { .. } | ShapeKind::Rx { .. }
)
}
assert!(
!has_channel(shape),
"channels are not allowed in return types"
);
}