#![allow(clippy::too_many_lines)]
use std::{
collections::{BTreeMap, BTreeSet},
io::{self, Result, Write},
sync::Arc,
};
use heck::ToLowerCamelCase as _;
use indoc::formatdoc;
use heck::ToUpperCamelCase as _;
use crate::generation::CodeGeneratorConfig;
use crate::{
Registry,
generation::{
Container, Emitter,
indent::{IndentWrite, Newlines},
module::Module,
plugin::{EmitContext, EmitterPlugin},
swift::generator::{compute_equatable_types, compute_hashable_types},
},
reflection::format::{
ContainerFormat, Doc, Format, Named, Namespace, QualifiedTypeName, VariantFormat,
},
};
#[derive(Debug, Clone)]
pub struct Swift {
pub(crate) config: CodeGeneratorConfig,
pub(crate) local_types: BTreeSet<QualifiedTypeName>,
pub(crate) hashable_types: BTreeSet<QualifiedTypeName>,
pub(crate) equatable_types: BTreeSet<QualifiedTypeName>,
pub(crate) plugins: Vec<Arc<dyn EmitterPlugin<Self>>>,
}
impl Swift {
#[must_use]
pub fn new(config: &CodeGeneratorConfig, registry: &Registry) -> Self {
Self {
config: config.clone(),
local_types: registry.keys().cloned().collect(),
hashable_types: compute_hashable_types(registry),
equatable_types: compute_equatable_types(registry),
plugins: vec![],
}
}
#[must_use]
pub const fn config(&self) -> &CodeGeneratorConfig {
&self.config
}
#[must_use]
pub fn plugins(&self) -> &[Arc<dyn EmitterPlugin<Self>>] {
&self.plugins
}
#[must_use]
pub fn with_plugin(mut self, plugin: Arc<dyn EmitterPlugin<Self>>) -> Self {
self.plugins.push(plugin);
self
}
}
pub fn is_hashable(format: &Format, lang: &Swift) -> bool {
match format {
Format::Variable(_)
| Format::Unit => false,
Format::Map { key, value } => {
is_hashable(key, lang) && is_hashable(value, lang)
},
Format::TypeName(qtn) => {
!lang.local_types.contains(qtn) || lang.hashable_types.contains(qtn)
}
Format::Bool
| Format::I8
| Format::I16
| Format::I32
| Format::I64
| Format::I128
| Format::U8
| Format::U16
| Format::U32
| Format::U64
| Format::U128
| Format::F32
| Format::F64
| Format::Char
| Format::Str
| Format::Bytes
| Format::Uuid => true,
Format::Option(inner)
| Format::Set(inner)
| Format::Seq(inner)
| Format::TupleArray { content: inner, .. } => is_hashable(inner, lang),
Format::Tuple(formats) => {
formats.len() == 1 && is_hashable(&formats[0], lang)
}
}
}
fn variant_is_hashable(format: &VariantFormat, lang: &Swift) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => is_hashable(fmt, lang),
VariantFormat::Tuple(fmts) => fmts.iter().all(|f| is_hashable(f, lang)),
VariantFormat::Struct(nameds) => nameds.iter().all(|n| is_hashable(&n.value, lang)),
}
}
enum Usage {
Field,
IndirectField,
Parameter,
Assignment,
}
fn needs_indirect(format: &Format, struct_name: &str) -> bool {
match format {
Format::TypeName(qtn) => qtn.name == struct_name,
Format::Option(inner) => needs_indirect(inner, struct_name),
Format::Tuple(formats) => formats.iter().any(|f| needs_indirect(f, struct_name)),
_ => false,
}
}
fn is_equatable_auto(format: &Format, lang: &Swift) -> bool {
match format {
Format::TypeName(qtn) => {
!lang.local_types.contains(qtn) || lang.equatable_types.contains(qtn)
}
Format::Variable(_) | Format::Unit => false,
Format::Bool
| Format::I8
| Format::I16
| Format::I32
| Format::I64
| Format::I128
| Format::U8
| Format::U16
| Format::U32
| Format::U64
| Format::U128
| Format::F32
| Format::F64
| Format::Char
| Format::Str
| Format::Bytes
| Format::Uuid => true,
Format::Option(inner) | Format::Set(inner) => is_equatable_auto(inner, lang),
Format::Seq(inner) | Format::TupleArray { content: inner, .. } => {
is_equatable_auto(inner, lang)
}
Format::Map { key, value } => {
is_equatable_auto(key, lang) && is_equatable_auto(value, lang)
}
Format::Tuple(formats) => formats.len() == 1 && is_equatable_auto(&formats[0], lang),
}
}
fn can_use_eq_operator(format: &Format, lang: &Swift) -> bool {
match format {
Format::Tuple(formats) if formats.len() > 1 => {
formats.iter().all(|f| is_equatable_auto(f, lang))
}
_ => is_equatable_auto(format, lang),
}
}
fn variant_is_equatable_auto(format: &VariantFormat, lang: &Swift) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => is_equatable_auto(fmt, lang),
VariantFormat::Tuple(formats) => formats.iter().all(|f| is_equatable_auto(f, lang)),
VariantFormat::Struct(nameds) => nameds.iter().all(|n| is_equatable_auto(&n.value, lang)),
}
}
fn variant_can_use_eq_operator(format: &VariantFormat, lang: &Swift) -> bool {
match format {
VariantFormat::Variable(_) => false,
VariantFormat::Unit => true,
VariantFormat::NewType(fmt) => can_use_eq_operator(fmt, lang),
VariantFormat::Tuple(formats) => formats.iter().all(|f| can_use_eq_operator(f, lang)),
VariantFormat::Struct(nameds) => nameds.iter().all(|n| can_use_eq_operator(&n.value, lang)),
}
}
impl Emitter<Swift> for Module {
fn write<W: IndentWrite>(&self, w: &mut W, lang: &Swift) -> Result<()> {
let mut imports = vec![];
for ns in self.config().external_definitions.keys() {
imports.push(ns.to_upper_camel_case());
}
for plugin in lang.plugins() {
imports.extend(plugin.imports(self.config()));
}
imports.sort();
imports.dedup();
for import in &imports {
writeln!(w, "import {import}")?;
}
for plugin in lang.plugins() {
plugin.module_helpers(w, self.config())?;
}
Ok(())
}
}
impl Emitter<Swift> for Container<'_> {
fn write<W: IndentWrite>(&self, w: &mut W, lang: &Swift) -> Result<()> {
let Container { format, .. } = self;
match format {
ContainerFormat::UnitStruct(doc) => struct_(w, self, &[], doc, lang),
ContainerFormat::NewTypeStruct(format, doc) => struct_(
w,
self,
&[&Named::new(format, "value".to_string())],
doc,
lang,
),
ContainerFormat::TupleStruct(formats, doc) => {
let formats = named(formats, "field");
struct_(w, self, &formats.iter().collect::<Vec<_>>(), doc, lang)
}
ContainerFormat::Struct(nameds, doc) => {
struct_(w, self, &nameds.iter().collect::<Vec<_>>(), doc, lang)
}
ContainerFormat::Enum(variants, doc) => enum_(w, self, variants, doc, lang),
}
}
}
fn render_type_name(qtn: &QualifiedTypeName, config: &CodeGeneratorConfig) -> String {
match &qtn.namespace {
Namespace::Root => qtn.name.clone(),
Namespace::Named(ns) if ns == config.module_name() => qtn.name.clone(),
Namespace::Named(ns) => format!("{}.{}", heck::AsUpperCamelCase(ns), qtn.name),
}
}
impl Emitter<Swift> for Format {
fn write<W: IndentWrite>(&self, w: &mut W, lang: &Swift) -> Result<()> {
match &self {
Self::Variable(_variable) => unreachable!("placeholders should not get this far"),
Self::TypeName(qualified_type_name) => {
write!(w, "{}", render_type_name(qualified_type_name, &lang.config))
}
Self::Unit => write!(w, "Void"),
Self::Bool => write!(w, "Bool"),
Self::I8 => write!(w, "Int8"),
Self::I16 => write!(w, "Int16"),
Self::I32 => write!(w, "Int32"),
Self::I64 => write!(w, "Int64"),
Self::I128 => write!(w, "Int128"),
Self::U8 => write!(w, "UInt8"),
Self::U16 => write!(w, "UInt16"),
Self::U32 => write!(w, "UInt32"),
Self::U64 => write!(w, "UInt64"),
Self::U128 => write!(w, "UInt128"),
Self::F32 => write!(w, "Float"),
Self::F64 => write!(w, "Double"),
Self::Char => write!(w, "Character"),
Self::Str => write!(w, "String"),
Self::Bytes => write!(w, "[UInt8]"),
Self::Uuid => write!(w, "UUID"),
Self::Option(format) => {
format.write(w, lang)?;
write!(w, "?")
}
Self::Seq(format)
| Self::TupleArray {
content: format,
size: _,
} => {
write!(w, "[")?;
format.write(w, lang)?;
write!(w, "]")
}
Self::Set(format) => {
if !is_hashable(format, lang) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
formatdoc!(
"Set element type is not Hashable in Swift; \
native tuples and dictionaries do not conform to Hashable"
),
));
}
write!(w, "Set<")?;
format.write(w, lang)?;
write!(w, ">")
}
Self::Map { key, value } => {
if !is_hashable(key, lang) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
formatdoc!(
"Map key type is not Hashable in Swift; \
native tuples and dictionaries do not conform to Hashable"
),
));
}
write!(w, "[")?;
key.write(w, lang)?;
write!(w, ": ")?;
value.write(w, lang)?;
write!(w, "]")
}
Self::Tuple(formats) => {
let len = formats.len();
if len == 1 {
formats[0].write(w, lang)
} else {
write!(w, "(")?;
for (i, format) in formats.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
format.write(w, lang)?;
}
write!(w, ")")
}
}
}
}
}
impl Emitter<Swift> for (&Named<Format>, Usage) {
fn write<W: IndentWrite>(&self, w: &mut W, lang: &Swift) -> Result<()> {
let (Named { name, doc, value }, usage) = self;
let name = &name.to_lower_camel_case();
match usage {
Usage::Field => {
doc.write(w, lang)?;
write!(w, "public var {name}: ")?;
value.write(w, lang)?;
writeln!(w)
}
Usage::IndirectField => {
doc.write(w, lang)?;
write!(w, "@Indirect public var {name}: ")?;
value.write(w, lang)?;
writeln!(w)
}
Usage::Parameter => {
write!(w, "{name}: ")?;
value.write(w, lang)
}
Usage::Assignment => writeln!(w, "self.{name} = {name}"),
}
}
}
impl Emitter<Swift> for (&Named<VariantFormat>, Usage) {
fn write<W: IndentWrite>(&self, w: &mut W, lang: &Swift) -> Result<()> {
let (
Named {
name,
doc,
value: format,
},
usage,
) = self;
let name = name.to_lower_camel_case();
doc.write(w, lang)?;
match usage {
Usage::IndirectField => {
unreachable!("@Indirect is only used for struct fields, not enum variants")
}
Usage::Field => match format {
VariantFormat::Variable(_variable) => {
unreachable!("placeholders should not get this far")
}
VariantFormat::Unit => writeln!(w, "case {name}"),
VariantFormat::NewType(format) => {
write!(w, "case {name}(")?;
format.write(w, lang)?;
writeln!(w, ")")
}
VariantFormat::Tuple(formats) => {
write!(w, "case {name}(")?;
for (i, format) in formats.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
format.write(w, lang)?;
}
writeln!(w, ")")
}
VariantFormat::Struct(nameds) => {
write!(w, "case {name}(")?;
for (i, format) in nameds.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
(format, Usage::Parameter).write(w, lang)?;
}
writeln!(w, ")")
}
},
Usage::Parameter | Usage::Assignment => Ok(()),
}
}
}
impl Emitter<Swift> for Doc {
fn write<W: IndentWrite>(&self, w: &mut W, _lang: &Swift) -> Result<()> {
for comment in self.comments() {
writeln!(w, "/// {comment}")?;
}
Ok(())
}
}
fn struct_<W: IndentWrite>(
w: &mut W,
container: &Container<'_>,
fields: &[&Named<Format>],
doc: &Doc,
lang: &Swift,
) -> Result<()> {
let name = &container.name.name;
doc.write(w, lang)?;
let has_plugins = !lang.plugins().is_empty();
let all_hashable = fields.iter().all(|f| is_hashable(&f.value, lang));
let all_equatable_auto = fields.iter().all(|f| is_equatable_auto(&f.value, lang));
let all_can_eq = fields.iter().all(|f| can_use_eq_operator(&f.value, lang));
let mut implements = vec![];
if all_hashable {
implements.push("Hashable");
}
if all_equatable_auto || all_can_eq {
implements.push("Equatable");
}
if has_plugins && !implements.is_empty() {
write!(w, "public struct {name}: {} ", implements.join(", "))?;
} else {
write!(w, "public struct {name} ")?;
}
let mut w = w.block(Newlines::BOTH)?;
for field in fields {
let usage = if has_plugins && needs_indirect(&field.value, name) {
Usage::IndirectField
} else {
Usage::Field
};
(*field, usage).write(&mut w, lang)?;
}
if !fields.is_empty() {
writeln!(w)?;
}
write!(w, "public init(")?;
for (i, field) in fields.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
(*field, Usage::Parameter).write(&mut w, lang)?;
}
write!(w, ") ")?;
{
let mut w = w.block(Newlines::BOTH)?;
for field in fields {
(*field, Usage::Assignment).write(&mut w, lang)?;
}
}
let ctx = EmitContext::top_level(container, &lang.config);
for plugin in lang.plugins() {
plugin.type_body(&mut w as &mut dyn IndentWrite, &ctx)?;
}
if has_plugins && !all_hashable && !all_equatable_auto && all_can_eq {
write_struct_eq(&mut w, name, fields)?;
}
Ok(())
}
fn write_struct_eq<W: IndentWrite>(w: &mut W, name: &str, fields: &[&Named<Format>]) -> Result<()> {
writeln!(w)?;
write!(
w,
"public static func == (lhs: {name}, rhs: {name}) -> Bool "
)?;
let mut w = w.block(Newlines::BOTH)?;
if fields.is_empty() {
writeln!(w, "return true")?;
} else {
write!(w, "return ")?;
for (i, field) in fields.iter().enumerate() {
let fname = field.name.to_lower_camel_case();
if i > 0 {
writeln!(w)?;
write!(w, " && ")?;
}
write!(w, "lhs.{fname} == rhs.{fname}")?;
}
writeln!(w)?;
}
Ok(())
}
fn enum_<W: IndentWrite>(
w: &mut W,
container: &Container<'_>,
variants: &BTreeMap<u32, Named<VariantFormat>>,
doc: &Doc,
lang: &Swift,
) -> Result<()> {
let name = &container.name.name;
doc.write(w, lang)?;
let has_plugins = !lang.plugins().is_empty();
let all_hashable = variants
.values()
.all(|v| variant_is_hashable(&v.value, lang));
let all_equatable_auto = variants
.values()
.all(|v| variant_is_equatable_auto(&v.value, lang));
let all_can_eq = variants
.values()
.all(|v| variant_can_use_eq_operator(&v.value, lang));
let mut implements = vec![];
if all_hashable {
implements.push("Hashable");
}
if all_equatable_auto || all_can_eq {
implements.push("Equatable");
}
if has_plugins && !implements.is_empty() {
write!(w, "indirect public enum {name}: {} ", implements.join(", "))?;
} else {
write!(w, "indirect public enum {name} ")?;
}
let mut w = w.block(Newlines::BOTH)?;
for variant in variants.values() {
(variant, Usage::Field).write(&mut w, lang)?;
}
let ctx = EmitContext::top_level(container, &lang.config);
for plugin in lang.plugins() {
plugin.type_body(&mut w as &mut dyn IndentWrite, &ctx)?;
}
if has_plugins && !all_hashable && !all_equatable_auto && all_can_eq {
write_enum_eq(&mut w, name, &variants.values().collect::<Vec<_>>())?;
}
Ok(())
}
fn write_enum_eq<W: IndentWrite>(
w: &mut W,
name: &str,
variants: &[&Named<VariantFormat>],
) -> Result<()> {
writeln!(w)?;
write!(
w,
"public static func == (lhs: {name}, rhs: {name}) -> Bool "
)?;
let mut w = w.block(Newlines::BOTH)?;
write!(w, "switch (lhs, rhs) ")?;
{
let mut w = w.block(Newlines::BOTH)?;
w.unindent();
for variant in variants {
let variant_name = variant.name.to_lower_camel_case();
match &variant.value {
VariantFormat::Unit => {
writeln!(w, "case (.{variant_name}, .{variant_name}): return true")?;
}
VariantFormat::NewType(_) => {
writeln!(
w,
"case (.{variant_name}(let l), .{variant_name}(let r)): return l == r"
)?;
}
VariantFormat::Tuple(formats) => {
write!(w, "case (.{variant_name}(")?;
for (i, _) in formats.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
write!(w, "let l{i}")?;
}
write!(w, "), .{variant_name}(")?;
for (i, _) in formats.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
write!(w, "let r{i}")?;
}
write!(w, ")): return ")?;
for (i, _) in formats.iter().enumerate() {
if i > 0 {
write!(w, " && ")?;
}
write!(w, "l{i} == r{i}")?;
}
writeln!(w)?;
}
VariantFormat::Struct(nameds) => {
write!(w, "case (.{variant_name}(")?;
for (i, n) in nameds.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
let fname = n.name.to_lower_camel_case();
write!(w, "{fname}: let l{i}")?;
}
write!(w, "), .{variant_name}(")?;
for (i, n) in nameds.iter().enumerate() {
if i > 0 {
write!(w, ", ")?;
}
let fname = n.name.to_lower_camel_case();
write!(w, "{fname}: let r{i}")?;
}
write!(w, ")): return ")?;
for (i, _) in nameds.iter().enumerate() {
if i > 0 {
write!(w, " && ")?;
}
write!(w, "l{i} == r{i}")?;
}
writeln!(w)?;
}
VariantFormat::Variable(_) => {
unreachable!("placeholders should not get this far")
}
}
}
writeln!(w, "default: return false")?;
w.indent();
}
Ok(())
}
fn named<Format: Clone>(formats: &[Format], prefix: &str) -> Vec<Named<Format>> {
formats
.iter()
.enumerate()
.map(|(i, f)| Named::new(f, format!("{prefix}{i}")))
.collect()
}
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_bincode;
#[cfg(test)]
mod tests_json;