Repository: 1Password/typeshare
Pull request #27 (2022-12-05 02:15:35)

Add Scala support
This PR adds Scala language support that passes all existing test cases.
As far as I tested, the generated codes support both Scala 2 and Scala 3.

Some notes on design:

- Scala does not support unsigned integers. But for many cases, the signed integers can fit. Java 8+ offers [operations to emulate unsigned, such as Long.compareUnsigned](https://docs.oracle.com/javase/8/docs/api/java/lang/Long.html#compareUnsigned-long-long-). So my impl converts unsigned ones to signed ones, with type aliases such as `type UInt = Int` to indicate it is unsigned. It is better than "unsigned integer is not supported" panic.
- Scala 2 does not allow type aliases defined in top-level. Since Scala 2 is still the majority, type aliases are put in `package object`. 
- Extracted `fn format_generic_parameters` since `T<A, B>` is the format of generic type for other supported languages but `T[A, B]` for Scala.
- Since Scala has no built-in support of renaming members on serialization or de-facto standard for it, raw names are encoded into `serialName: String` members. Users should use `serialName` on serializers.
- To generate the code in a single file, either or both of `package object FOO {}` and `package FOO {}` may appear in the generated file. See the below example for explanation:

```scala
package com.example // Scala allows multiple package statements in a single file

package object typeshare {  // Which means com.example.typeshare
  type UByte = Byte
  // type alias goes here since type aliases should be defined in class or (package) object in Scala 2.
}

package typeshare {  // Which also means com.example.typeshare
  sealed trait MyEnum {
    def serialName: String
  }  
  object MyEnum {
    case class Foo(age: UByte) extends MyEnum {
      val serialName: String = "foo"
   }
    case object Bar extends MyEnum {
      val serialName: String = "b_a_r"
    }
  }
}
```




Patch:
diff --git a/README.md b/README.md
index 33e323c..70152e9 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ _And in the darkness, compile them_ 💍
 
 
 Do you like manually managing types that need to be passed through an FFI layer, so that your code doesn't archaically break at runtime? Be honest, nobody does. Typeshare is here to take that burden away from you! Leveraging the power of the `serde` library, Typeshare is a tool that converts your
-Rust types into their equivalent forms in Swift, Go**, Kotlin, and Typescript, keeping
+Rust types into their equivalent forms in Swift, Go**, Kotlin, Scala and Typescript, keeping
 your cross-language codebase in sync. With automatic implementation for serialization and deserialization on both sides of the FFI, Typeshare does all the heavy lifting for you. It can even handle generics and convert effortlessly between standard libraries in different languages!
 
 **A few caveats. See [here](#a-quick-refresher-on-supported-languages) for more details.
@@ -48,6 +48,7 @@ To generate FFI definitions for a target language, run the `typeshare` command a
 ```
 typeshare ./my_rust_project --lang=kotlin --output-file=my_kotlin_definitions.kt
 typeshare ./my_rust_project --lang=swift --output-file=my_swift_definitions.swift
+typeshare ./my_rust_project --lang=scala --output-file=my_scala_definitions.scala
 typeshare ./my_rust_project --lang=typescript --output-file=my_typescript_definitions.ts
 ```
 
@@ -93,6 +94,7 @@ Are you getting weird deserialization issues? Did our procedural macro throw a c
 ## A Quick Refresher on Supported Languages
 
 - Kotlin
+- Scala
 - Swift
 - Typescript
 - Go**
diff --git a/cli/src/config.rs b/cli/src/config.rs
index b0a13b3..228d70a 100644
--- a/cli/src/config.rs
+++ b/cli/src/config.rs
@@ -17,6 +17,14 @@ pub struct KotlinParams {
     pub type_mappings: HashMap<String, String>,
 }
 
+#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq)]
+#[serde(default)]
+pub struct ScalaParams {
+    pub package: String,
+    pub module_name: String,
+    pub type_mappings: HashMap<String, String>,
+}
+
 #[derive(Default, Serialize, Deserialize, Debug, PartialEq, Eq)]
 #[serde(default)]
 pub struct SwiftParams {
@@ -47,6 +55,7 @@ pub(crate) struct Config {
     pub swift: SwiftParams,
     pub typescript: TypeScriptParams,
     pub kotlin: KotlinParams,
+    pub scala: ScalaParams,
     #[cfg(feature = "go")]
     pub go: GoParams,
 }
diff --git a/core/src/language/mod.rs b/core/src/language/mod.rs
index 36f4bbc..6f628c1 100644
--- a/core/src/language/mod.rs
+++ b/core/src/language/mod.rs
@@ -7,12 +7,14 @@ use std::{collections::HashMap, io::Write};
 
 mod go;
 mod kotlin;
+mod scala;
 mod swift;
 mod typescript;
 
 use crate::rust_types::{RustType, RustTypeFormatError, SpecialRustType};
 pub use go::Go;
 pub use kotlin::Kotlin;
+pub use scala::Scala;
 pub use swift::Swift;
 pub use typescript::TypeScript;
 
@@ -105,12 +107,18 @@ pub trait Language {
                 "{}{}",
                 self.format_simple_type(base, generic_types)?,
                 (!parameters.is_empty())
-                    .then(|| format!("<{}>", parameters.into_iter().join(", ")))
+                    .then(|| self.format_generic_parameters(parameters))
                     .unwrap_or_default()
             ))
         }
     }
 
+    /// Format generic parameters into A<T,R> which is common for many supported languages.
+    /// Reimplement if other notations is used.
+    fn format_generic_parameters(&mut self, parameters: Vec<String>) -> String {
+        format!("<{}>", parameters.into_iter().join(", "))
+    }
+
     /// Format a base type that is classified as a SpecialRustType.
     fn format_special_type(
         &mut self,
diff --git a/core/src/language/scala.rs b/core/src/language/scala.rs
new file mode 100644
index 0000000..c140709
--- /dev/null
+++ b/core/src/language/scala.rs
@@ -0,0 +1,449 @@
+use super::Language;
+use crate::parser::ParsedData;
+use crate::rust_types::{RustType, RustTypeFormatError, SpecialRustType};
+use crate::{
+    parser::remove_dash_from_identifier,
+    rust_types::{RustEnum, RustEnumVariant, RustField, RustStruct, RustTypeAlias},
+};
+use itertools::Itertools;
+use joinery::JoinableIterator;
+use lazy_format::lazy_format;
+use std::{collections::HashMap, io::Write};
+
+/// All information needed for Scala type-code
+#[derive(Default)]
+pub struct Scala {
+    /// Name of the Scala package
+    pub package: String,
+    /// Name of the Scala module
+    pub module_name: String,
+    /// Conversions from Rust type names to Scala type names.
+    pub type_mappings: HashMap<String, String>,
+}
+
+impl Language for Scala {
+    fn generate_types(
+        &mut self,
+        writable: &mut dyn Write,
+        data: &ParsedData,
+    ) -> std::io::Result<()> {
+        self.begin_file(writable)?;
+
+        // Package object to hold type aliases: aliases must be in class or object in Scala 2)
+        let unsigned_used = self.unsigned_integer_used(data);
+        if unsigned_used || !data.aliases.is_empty() {
+            self.begin_package_object(writable)?;
+            if unsigned_used {
+                self.write_unsigned_aliases(writable)?;
+            }
+            for a in data.aliases.iter() {
+                self.write_type_alias(writable, a)?;
+            }
+            self.end_package_object(writable)?;
+        }
+
+        if !data.structs.is_empty() || !data.enums.is_empty() {
+            self.begin_package(writable)?;
+            for s in data.structs.iter() {
+                self.write_struct(writable, s)?;
+            }
+            for e in data.enums.iter() {
+                self.write_enum(writable, e)?;
+            }
+            self.end_package(writable)?;
+        }
+
+        self.end_file(writable)?;
+
+        Ok(())
+    }
+
+    fn type_map(&mut self) -> &HashMap<String, String> {
+        &self.type_mappings
+    }
+
+    fn format_generic_parameters(&mut self, parameters: Vec<String>) -> String {
+        format!("[{}]", parameters.into_iter().join(", "))
+    }
+
+    fn format_special_type(
+        &mut self,
+        special_ty: &SpecialRustType,
+        generic_types: &[String],
+    ) -> Result<String, RustTypeFormatError> {
+        Ok(match special_ty {
+            SpecialRustType::Vec(rtype) => {
+                format!("Vector[{}]", self.format_type(rtype, generic_types)?)
+            }
+            SpecialRustType::Option(rtype) => {
+                format!("Option[{}]", self.format_type(rtype, generic_types)?)
+            }
+            SpecialRustType::HashMap(rtype1, rtype2) => {
+                format!(
+                    "Map[{}, {}]",
+                    self.format_type(rtype1, generic_types)?,
+                    self.format_type(rtype2, generic_types)?
+                )
+            }
+            SpecialRustType::Unit => "Unit".into(),
+            SpecialRustType::String => "String".into(),
+            // https://docs.scala-lang.org/scala3/book/first-look-at-types.html#scalas-value-types
+            SpecialRustType::I8 => "Byte".into(),
+            SpecialRustType::I16 => "Short".into(),
+            SpecialRustType::ISize | SpecialRustType::I32 => "Int".into(),
+            SpecialRustType::I54 | SpecialRustType::I64 => "Long".into(),
+            // Scala does not support unsigned integers, so upcast it to the closest one
+            SpecialRustType::U8 => "UByte".into(),
+            SpecialRustType::U16 => "UShort".into(),
+            SpecialRustType::USize | SpecialRustType::U32 => "UInt".into(),
+            SpecialRustType::U53 | SpecialRustType::U64 => "ULong".into(),
+            SpecialRustType::Bool => "Boolean".into(),
+            SpecialRustType::F32 => "Float".into(),
+            SpecialRustType::F64 => "Double".into(),
+        })
+    }
+
+    fn begin_file(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+        writeln!(w, "/**")?;
+        writeln!(w, " * Generated by typeshare {}", env!("CARGO_PKG_VERSION"))?;
+        writeln!(w, " */")?;
+        if self.package.is_empty() {
+            panic!("package name must be provided")
+        }
+        match self.package.rsplit_once('.') {
+            None => {}
+            Some((parent, _last)) => {
+                writeln!(w, "package {}", parent)?;
+                writeln!(w)?;
+            }
+        };
+        Ok(())
+    }
+
+    fn write_type_alias(&mut self, w: &mut dyn Write, ty: &RustTypeAlias) -> std::io::Result<()> {
+        self.write_comments(w, 0, &ty.comments)?;
+
+        writeln!(
+            w,
+            "type {}{} = {}\n",
+            ty.id.original,
+            (!ty.generic_types.is_empty())
+                .then(|| format!("[{}]", ty.generic_types.join(", ")))
+                .unwrap_or_default(),
+            self.format_type(&ty.r#type, ty.generic_types.as_slice())
+                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?
+        )?;
+
+        Ok(())
+    }
+
+    fn write_struct(&mut self, w: &mut dyn Write, rs: &RustStruct) -> std::io::Result<()> {
+        self.write_comments(w, 0, &rs.comments)?;
+
+        if !rs.fields.is_empty() {
+            writeln!(
+                w,
+                "case class {}{} (",
+                rs.id.renamed,
+                (!rs.generic_types.is_empty())
+                    .then(|| format!("[{}]", rs.generic_types.join(", ")))
+                    .unwrap_or_default()
+            )?;
+
+            if let Some((last, elements)) = rs.fields.split_last() {
+                for f in elements.iter() {
+                    self.write_element(w, f, rs.generic_types.as_slice())?;
+                    writeln!(w, ",")?;
+                }
+                self.write_element(w, last, rs.generic_types.as_slice())?;
+                writeln!(w)?;
+            }
+            writeln!(w, ")\n")?;
+        } else {
+            writeln!(w, "class {} extends Serializable\n", rs.id.renamed)?;
+        }
+        Ok(())
+    }
+
+    fn write_enum(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> {
+        // Generate named types for any anonymous struct variants of this enum
+        self.write_types_for_anonymous_structs(w, e, &|variant_name| {
+            format!("{}{}Inner", &e.shared().id.renamed, variant_name)
+        })?;
+
+        self.write_comments(w, 0, &e.shared().comments)?;
+
+        let generic_parameters = (!e.shared().generic_types.is_empty())
+            .then(|| format!("[{}]", e.shared().generic_types.join(", ")))
+            .unwrap_or_default();
+
+        match e {
+            RustEnum::Unit(shared) => {
+                writeln!(
+                    w,
+                    "sealed trait {}{} {{",
+                    shared.id.renamed, generic_parameters
+                )?;
+            }
+            RustEnum::Algebraic { shared, .. } => {
+                writeln!(
+                    w,
+                    "sealed trait {}{} {{",
+                    shared.id.renamed, generic_parameters
+                )?;
+            }
+        }
+        writeln!(w, "\tdef serialName: String")?;
+        writeln!(w, "}}")?;
+
+        writeln!(w, "object {} {{", &e.shared().id.renamed)?;
+        self.write_enum_variants(w, e)?;
+        writeln!(w, "}}\n")
+    }
+}
+
+impl Scala {
+    fn write_enum_variants(&mut self, w: &mut dyn Write, e: &RustEnum) -> std::io::Result<()> {
+        match e {
+            RustEnum::Unit(shared) => {
+                for v in shared.variants.iter() {
+                    self.write_comments(w, 1, &v.shared().comments)?;
+                    writeln!(
+                        w,
+                        "\tcase object {} extends {} {{",
+                        &v.shared().id.original,
+                        &e.shared().id.renamed
+                    )?;
+                    writeln!(
+                        w,
+                        "\t\tval serialName: String = {:?}",
+                        v.shared().id.renamed
+                    )?;
+                    writeln!(w, "\t}}")?;
+                }
+            }
+            RustEnum::Algebraic {
+                content_key,
+                shared,
+                ..
+            } => {
+                for v in shared.variants.iter() {
+                    let printed_value = format!(r##"{:?}"##, &v.shared().id.renamed);
+                    self.write_comments(w, 1, &v.shared().comments)?;
+
+                    let variant_name = {
+                        let mut variant_name = v.shared().id.original.to_string();
+
+                        if variant_name
+                            .chars()
+                            .next()
+                            .map(|c| c.is_ascii_digit())
+                            .unwrap_or(false)
+                        {
+                            // If the name starts with a digit just add an underscore
+                            // to the front and make it valid
+                            variant_name = format!("_{}", variant_name);
+                        }
+
+                        variant_name
+                    };
+
+                    match v {
+                        RustEnumVariant::Unit(_) => {
+                            write!(w, "\tcase object {}", variant_name)?;
+                        }
+                        RustEnumVariant::Tuple { ty, .. } => {
+                            write!(
+                                w,
+                                "\tcase class {}{}(",
+                                variant_name,
+                                (!e.shared().generic_types.is_empty())
+                                    .then(|| format!("[{}]", e.shared().generic_types.join(", ")))
+                                    .unwrap_or_default()
+                            )?;
+                            let variant_type = self
+                                .format_type(ty, e.shared().generic_types.as_slice())
+                                .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
+                            write!(w, "{}: {}", content_key, variant_type)?;
+                            write!(w, ")")?;
+                        }
+                        RustEnumVariant::AnonymousStruct { shared, fields } => {
+                            write!(
+                                w,
+                                "\tcase class {}{}(",
+                                variant_name,
+                                (!e.shared().generic_types.is_empty())
+                                    .then(|| format!("[{}]", e.shared().generic_types.join(", ")))
+                                    .unwrap_or_default()
+                            )?;
+
+                            // Builds the list of generic types (e.g [T, U, V]), by digging
+                            // through the fields recursively and comparing against the
+                            // enclosing enum's list of generic parameters.
+                            let generics = fields
+                                .iter()
+                                .flat_map(|field| {
+                                    e.shared()
+                                        .generic_types
+                                        .iter()
+                                        .filter(|g| field.ty.contains_type(g))
+                                })
+                                .unique()
+                                .collect_vec();
+
+                            // Sadly the parenthesis are required because of macro limitations
+                            let generics = lazy_format!(match (generics.is_empty()) {
+                                false => ("[{}]", generics.iter().join_with(", ")),
+                                true => (""),
+                            });
+
+                            write!(
+                                w,
+                                "{}: {}{}Inner{}",
+                                content_key,
+                                e.shared().id.original,
+                                shared.id.original,
+                                generics,
+                            )?;
+                            write!(w, ")")?;
+                        }
+                    }
+
+                    writeln!(
+                        w,
+                        " extends {}{} {{",
+                        e.shared().id.original,
+                        (!e.shared().generic_types.is_empty())
+                            .then(|| format!("[{}]", e.shared().generic_types.join(", ")))
+                            .unwrap_or_default()
+                    )?;
+                    writeln!(w, "\t\tval serialName: String = {}", printed_value)?;
+                    writeln!(w, "\t}}")?;
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    fn write_element(
+        &mut self,
+        w: &mut dyn Write,
+        f: &RustField,
+        generic_types: &[String],
+    ) -> std::io::Result<()> {
+        self.write_comments(w, 1, &f.comments)?;
+        let ty = self
+            .format_type(&f.ty, generic_types)
+            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
+        write!(
+            w,
+            "\t{}: {}{}",
+            remove_dash_from_identifier(&f.id.renamed),
+            ty,
+            (f.has_default && !f.ty.is_optional())
+                .then(|| " = _")
+                .or_else(|| f.ty.is_optional().then(|| " = None"))
+                .unwrap_or_default()
+        )
+    }
+
+    fn write_comment(
+        &mut self,
+        w: &mut dyn Write,
+        indent: usize,
+        comment: &str,
+    ) -> std::io::Result<()> {
+        writeln!(w, "{}// {}", "\t".repeat(indent), comment)?;
+        Ok(())
+    }
+
+    fn write_comments(
+        &mut self,
+        w: &mut dyn Write,
+        indent: usize,
+        comments: &[String],
+    ) -> std::io::Result<()> {
+        comments
+            .iter()
+            .try_for_each(|comment| self.write_comment(w, indent, comment))
+    }
+
+    fn begin_package_object(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+        match self.package.rsplit_once('.') {
+            None => {}
+            Some((_parent, last)) => {
+                writeln!(w, "package object {} {{", last)?;
+                writeln!(w)?;
+            }
+        };
+        Ok(())
+    }
+
+    fn begin_package(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+        match self.package.rsplit_once('.') {
+            None => {}
+            Some((_parent, last)) => {
+                writeln!(w, "package {} {{", last)?;
+                writeln!(w)?;
+            }
+        };
+        Ok(())
+    }
+
+    fn write_unsigned_aliases(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+        writeln!(w, "type UByte = Byte")?;
+        writeln!(w, "type UShort = Short")?;
+        writeln!(w, "type UInt = Int")?;
+        writeln!(w, "type ULong = Int")?;
+        writeln!(w)?;
+        Ok(())
+    }
+
+    fn end_package_object(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+        writeln!(w, "}}")?;
+        Ok(())
+    }
+
+    fn end_package(&mut self, w: &mut dyn Write) -> std::io::Result<()> {
+        writeln!(w, "}}")?;
+        Ok(())
+    }
+
+    fn unsigned_integer_used(&mut self, data: &ParsedData) -> bool {
+        let types_in_structs = data
+            .structs
+            .iter()
+            .flat_map(|f| f.fields.clone())
+            .map(|f| f.ty)
+            .collect_vec();
+        let types_in_enum = data
+            .enums
+            .iter()
+            .flat_map(|e| {
+                e.shared().variants.iter().flat_map(|v| match v {
+                    RustEnumVariant::Unit(_) => vec![],
+                    RustEnumVariant::Tuple { ty, .. } => vec![ty.clone()],
+                    RustEnumVariant::AnonymousStruct { fields, .. } => {
+                        fields.iter().map(|f| f.ty.clone()).collect_vec()
+                    }
+                })
+            })
+            .collect_vec();
+        itertools::concat(vec![types_in_structs, types_in_enum])
+            .iter()
+            .any(|ty| {
+                matches!(
+                    ty,
+                    RustType::Special(
+                        SpecialRustType::U8
+                            | SpecialRustType::U16
+                            | SpecialRustType::U32
+                            | SpecialRustType::U53
+                            | SpecialRustType::U64
+                            | SpecialRustType::USize,
+                    )
+                )
+            })
+    }
+}


Test patch:
diff --git a/core/data/tests/anonymous_struct_with_rename/output.scala b/core/data/tests/anonymous_struct_with_rename/output.scala
new file mode 100644
index 0000000..7020ad5
--- /dev/null
+++ b/core/data/tests/anonymous_struct_with_rename/output.scala
@@ -0,0 +1,42 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// Generated type representing the anonymous struct variant `List` of the `AnonymousStructWithRename` Rust enum
+case class AnonymousStructWithRenameListInner (
+	list: Vector[String]
+)
+
+// Generated type representing the anonymous struct variant `LongFieldNames` of the `AnonymousStructWithRename` Rust enum
+case class AnonymousStructWithRenameLongFieldNamesInner (
+	some_long_field_name: String,
+	and: Boolean,
+	but_one_more: Vector[String]
+)
+
+// Generated type representing the anonymous struct variant `KebabCase` of the `AnonymousStructWithRename` Rust enum
+case class AnonymousStructWithRenameKebabCaseInner (
+	another_list: Vector[String],
+	camelCaseStringField: String,
+	something_else: Boolean
+)
+
+sealed trait AnonymousStructWithRename {
+	def serialName: String
+}
+object AnonymousStructWithRename {
+	case class List(content: AnonymousStructWithRenameListInner) extends AnonymousStructWithRename {
+		val serialName: String = "list"
+	}
+	case class LongFieldNames(content: AnonymousStructWithRenameLongFieldNamesInner) extends AnonymousStructWithRename {
+		val serialName: String = "longFieldNames"
+	}
+	case class KebabCase(content: AnonymousStructWithRenameKebabCaseInner) extends AnonymousStructWithRename {
+		val serialName: String = "kebabCase"
+	}
+}
+
+}
diff --git a/core/data/tests/boxed_value/output.scala b/core/data/tests/boxed_value/output.scala
new file mode 100644
index 0000000..4454a11
--- /dev/null
+++ b/core/data/tests/boxed_value/output.scala
@@ -0,0 +1,24 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+sealed trait Colors {
+	def serialName: String
+}
+object Colors {
+	case object Red extends Colors {
+		val serialName: String = "Red"
+	}
+	case object Blue extends Colors {
+		val serialName: String = "Blue"
+	}
+	case class Green(content: String) extends Colors {
+		val serialName: String = "Green"
+	}
+}
+
+}
diff --git a/core/data/tests/can_apply_prefix_correctly/output.scala b/core/data/tests/can_apply_prefix_correctly/output.scala
new file mode 100644
index 0000000..57673e0
--- /dev/null
+++ b/core/data/tests/can_apply_prefix_correctly/output.scala
@@ -0,0 +1,36 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class ItemDetailsFieldValue (
+	hello: String
+)
+
+sealed trait AdvancedColors {
+	def serialName: String
+}
+object AdvancedColors {
+	case class String(c: String) extends AdvancedColors {
+		val serialName: String = "String"
+	}
+	case class Number(c: Int) extends AdvancedColors {
+		val serialName: String = "Number"
+	}
+	case class NumberArray(c: Vector[Int]) extends AdvancedColors {
+		val serialName: String = "NumberArray"
+	}
+	case class ReallyCoolType(c: ItemDetailsFieldValue) extends AdvancedColors {
+		val serialName: String = "ReallyCoolType"
+	}
+	case class ArrayReallyCoolType(c: Vector[ItemDetailsFieldValue]) extends AdvancedColors {
+		val serialName: String = "ArrayReallyCoolType"
+	}
+	case class DictionaryReallyCoolType(c: Map[String, ItemDetailsFieldValue]) extends AdvancedColors {
+		val serialName: String = "DictionaryReallyCoolType"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_algebraic_enum/input.rs b/core/data/tests/can_generate_algebraic_enum/input.rs
index 329885d..915ca74 100644
--- a/core/data/tests/can_generate_algebraic_enum/input.rs
+++ b/core/data/tests/can_generate_algebraic_enum/input.rs
@@ -9,6 +9,7 @@ pub enum AdvancedColors {
     /// This is a case comment
     String(String),
     Number(i32),
+    UnsignedNumber(u32),
     NumberArray(Vec<i32>),
     /// Comment on the last element
     ReallyCoolType(ItemDetailsFieldValue),
diff --git a/core/data/tests/can_generate_algebraic_enum/output.go b/core/data/tests/can_generate_algebraic_enum/output.go
index a261af4..d1c9969 100644
--- a/core/data/tests/can_generate_algebraic_enum/output.go
+++ b/core/data/tests/can_generate_algebraic_enum/output.go
@@ -12,6 +12,7 @@ const (
 	// This is a case comment
 	AdvancedColorsTypeVariantString AdvancedColorsTypes = "String"
 	AdvancedColorsTypeVariantNumber AdvancedColorsTypes = "Number"
+	AdvancedColorsTypeVariantUnsignedNumber AdvancedColorsTypes = "UnsignedNumber"
 	AdvancedColorsTypeVariantNumberArray AdvancedColorsTypes = "NumberArray"
 	// Comment on the last element
 	AdvancedColorsTypeVariantReallyCoolType AdvancedColorsTypes = "ReallyCoolType"
@@ -38,6 +39,9 @@ func (a *AdvancedColors) UnmarshalJSON(data []byte) error {
 	case AdvancedColorsTypeVariantNumber:
 		var res int
 		a.content = &res
+	case AdvancedColorsTypeVariantUnsignedNumber:
+		var res uint32
+		a.content = &res
 	case AdvancedColorsTypeVariantNumberArray:
 		var res []int
 		a.content = &res
@@ -71,6 +75,10 @@ func (a AdvancedColors) Number() int {
 	res, _ := a.content.(*int)
 	return *res
 }
+func (a AdvancedColors) UnsignedNumber() uint32 {
+	res, _ := a.content.(*uint32)
+	return *res
+}
 func (a AdvancedColors) NumberArray() []int {
 	res, _ := a.content.(*[]int)
 	return *res
@@ -92,6 +100,12 @@ func NewAdvancedColorsTypeVariantNumber(content int) AdvancedColors {
         content: &content,
     }
 }
+func NewAdvancedColorsTypeVariantUnsignedNumber(content uint32) AdvancedColors {
+    return AdvancedColors{
+        Type: AdvancedColorsTypeVariantUnsignedNumber,
+        content: &content,
+    }
+}
 func NewAdvancedColorsTypeVariantNumberArray(content []int) AdvancedColors {
     return AdvancedColors{
         Type: AdvancedColorsTypeVariantNumberArray,
diff --git a/core/data/tests/can_generate_algebraic_enum/output.kt b/core/data/tests/can_generate_algebraic_enum/output.kt
index f30ff39..c50c9ee 100644
--- a/core/data/tests/can_generate_algebraic_enum/output.kt
+++ b/core/data/tests/can_generate_algebraic_enum/output.kt
@@ -24,6 +24,9 @@ sealed class AdvancedColors {
 	@SerialName("Number")
 	data class Number(val content: Int): AdvancedColors()
 	@Serializable
+	@SerialName("UnsignedNumber")
+	data class UnsignedNumber(val content: UInt): AdvancedColors()
+	@Serializable
 	@SerialName("NumberArray")
 	data class NumberArray(val content: List<Int>): AdvancedColors()
 	/// Comment on the last element
diff --git a/core/data/tests/can_generate_algebraic_enum/output.scala b/core/data/tests/can_generate_algebraic_enum/output.scala
new file mode 100644
index 0000000..b368f39
--- /dev/null
+++ b/core/data/tests/can_generate_algebraic_enum/output.scala
@@ -0,0 +1,63 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+// Struct comment
+class ItemDetailsFieldValue extends Serializable
+
+// Enum comment
+sealed trait AdvancedColors {
+	def serialName: String
+}
+object AdvancedColors {
+	// This is a case comment
+	case class String(content: String) extends AdvancedColors {
+		val serialName: String = "String"
+	}
+	case class Number(content: Int) extends AdvancedColors {
+		val serialName: String = "Number"
+	}
+	case class UnsignedNumber(content: UInt) extends AdvancedColors {
+		val serialName: String = "UnsignedNumber"
+	}
+	case class NumberArray(content: Vector[Int]) extends AdvancedColors {
+		val serialName: String = "NumberArray"
+	}
+	// Comment on the last element
+	case class ReallyCoolType(content: ItemDetailsFieldValue) extends AdvancedColors {
+		val serialName: String = "ReallyCoolType"
+	}
+}
+
+sealed trait AdvancedColors2 {
+	def serialName: String
+}
+object AdvancedColors2 {
+	// This is a case comment
+	case class String(content: String) extends AdvancedColors2 {
+		val serialName: String = "string"
+	}
+	case class Number(content: Int) extends AdvancedColors2 {
+		val serialName: String = "number"
+	}
+	case class NumberArray(content: Vector[Int]) extends AdvancedColors2 {
+		val serialName: String = "number-array"
+	}
+	// Comment on the last element
+	case class ReallyCoolType(content: ItemDetailsFieldValue) extends AdvancedColors2 {
+		val serialName: String = "really-cool-type"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_algebraic_enum/output.swift b/core/data/tests/can_generate_algebraic_enum/output.swift
index 427f2bc..88534ca 100644
--- a/core/data/tests/can_generate_algebraic_enum/output.swift
+++ b/core/data/tests/can_generate_algebraic_enum/output.swift
@@ -14,6 +14,7 @@ public enum OPAdvancedColors: Codable {
 	/// This is a case comment
 	case string(String)
 	case number(Int32)
+	case unsignedNumber(UInt32)
 	case numberArray([Int32])
 	/// Comment on the last element
 	case reallyCoolType(OPItemDetailsFieldValue)
@@ -21,6 +22,7 @@ public enum OPAdvancedColors: Codable {
 	enum CodingKeys: String, CodingKey, Codable {
 		case string = "String",
 			number = "Number",
+			unsignedNumber = "UnsignedNumber",
 			numberArray = "NumberArray",
 			reallyCoolType = "ReallyCoolType"
 	}
@@ -43,6 +45,11 @@ public enum OPAdvancedColors: Codable {
 					self = .number(content)
 					return
 				}
+			case .unsignedNumber:
+				if let content = try? container.decode(UInt32.self, forKey: .content) {
+					self = .unsignedNumber(content)
+					return
+				}
 			case .numberArray:
 				if let content = try? container.decode([Int32].self, forKey: .content) {
 					self = .numberArray(content)
@@ -67,6 +74,9 @@ public enum OPAdvancedColors: Codable {
 		case .number(let content):
 			try container.encode(CodingKeys.number, forKey: .type)
 			try container.encode(content, forKey: .content)
+		case .unsignedNumber(let content):
+			try container.encode(CodingKeys.unsignedNumber, forKey: .type)
+			try container.encode(content, forKey: .content)
 		case .numberArray(let content):
 			try container.encode(CodingKeys.numberArray, forKey: .type)
 			try container.encode(content, forKey: .content)
diff --git a/core/data/tests/can_generate_algebraic_enum/output.ts b/core/data/tests/can_generate_algebraic_enum/output.ts
index a475d58..53dc4d3 100644
--- a/core/data/tests/can_generate_algebraic_enum/output.ts
+++ b/core/data/tests/can_generate_algebraic_enum/output.ts
@@ -11,6 +11,7 @@ export type AdvancedColors =
 	/** This is a case comment */
 	| { type: "String", content: string }
 	| { type: "Number", content: number }
+	| { type: "UnsignedNumber", content: number }
 	| { type: "NumberArray", content: number[] }
 	/** Comment on the last element */
 	| { type: "ReallyCoolType", content: ItemDetailsFieldValue };
diff --git a/core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.scala b/core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.scala
new file mode 100644
index 0000000..1ace566
--- /dev/null
+++ b/core/data/tests/can_generate_algebraic_enum_with_skipped_variants/output.scala
@@ -0,0 +1,20 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+sealed trait SomeEnum {
+	def serialName: String
+}
+object SomeEnum {
+	case object A extends SomeEnum {
+		val serialName: String = "A"
+	}
+	case class C(content: Int) extends SomeEnum {
+		val serialName: String = "C"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_bare_string_enum/output.scala b/core/data/tests/can_generate_bare_string_enum/output.scala
new file mode 100644
index 0000000..97e1732
--- /dev/null
+++ b/core/data/tests/can_generate_bare_string_enum/output.scala
@@ -0,0 +1,24 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+sealed trait Colors {
+	def serialName: String
+}
+object Colors {
+	case object Red extends Colors {
+		val serialName: String = "Red"
+	}
+	case object Blue extends Colors {
+		val serialName: String = "Blue"
+	}
+	case object Green extends Colors {
+		val serialName: String = "Green"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_empty_algebraic_enum/output.scala b/core/data/tests/can_generate_empty_algebraic_enum/output.scala
new file mode 100644
index 0000000..54aaada
--- /dev/null
+++ b/core/data/tests/can_generate_empty_algebraic_enum/output.scala
@@ -0,0 +1,22 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+class AddressDetails extends Serializable
+
+sealed trait Address {
+	def serialName: String
+}
+object Address {
+	case class FixedAddress(content: AddressDetails) extends Address {
+		val serialName: String = "FixedAddress"
+	}
+	case object NoFixedAddress extends Address {
+		val serialName: String = "NoFixedAddress"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_generic_enum/output.scala b/core/data/tests/can_generate_generic_enum/output.scala
new file mode 100644
index 0000000..bed40d1
--- /dev/null
+++ b/core/data/tests/can_generate_generic_enum/output.scala
@@ -0,0 +1,79 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class StructUsingGenericEnum (
+	enum_field: GenericEnum[String, Short]
+)
+
+sealed trait GenericEnum[A, B] {
+	def serialName: String
+}
+object GenericEnum {
+	case class VariantA[A, B](content: A) extends GenericEnum[A, B] {
+		val serialName: String = "VariantA"
+	}
+	case class VariantB[A, B](content: B) extends GenericEnum[A, B] {
+		val serialName: String = "VariantB"
+	}
+}
+
+sealed trait GenericEnumUsingGenericEnum[T] {
+	def serialName: String
+}
+object GenericEnumUsingGenericEnum {
+	case class VariantC[T](content: GenericEnum[T, T]) extends GenericEnumUsingGenericEnum[T] {
+		val serialName: String = "VariantC"
+	}
+	case class VariantD[T](content: GenericEnum[String, Map[String, T]]) extends GenericEnumUsingGenericEnum[T] {
+		val serialName: String = "VariantD"
+	}
+	case class VariantE[T](content: GenericEnum[String, UInt]) extends GenericEnumUsingGenericEnum[T] {
+		val serialName: String = "VariantE"
+	}
+}
+
+// Generated type representing the anonymous struct variant `VariantF` of the `GenericEnumsUsingStructVariants` Rust enum
+case class GenericEnumsUsingStructVariantsVariantFInner[T] (
+	action: T
+)
+
+// Generated type representing the anonymous struct variant `VariantG` of the `GenericEnumsUsingStructVariants` Rust enum
+case class GenericEnumsUsingStructVariantsVariantGInner[T, U] (
+	action: T,
+	response: U
+)
+
+// Generated type representing the anonymous struct variant `VariantH` of the `GenericEnumsUsingStructVariants` Rust enum
+case class GenericEnumsUsingStructVariantsVariantHInner (
+	non_generic: Int
+)
+
+// Generated type representing the anonymous struct variant `VariantI` of the `GenericEnumsUsingStructVariants` Rust enum
+case class GenericEnumsUsingStructVariantsVariantIInner[T, U] (
+	vec: Vector[T],
+	action: MyType[T, U]
+)
+
+sealed trait GenericEnumsUsingStructVariants[T, U] {
+	def serialName: String
+}
+object GenericEnumsUsingStructVariants {
+	case class VariantF[T, U](content: GenericEnumsUsingStructVariantsVariantFInner[T]) extends GenericEnumsUsingStructVariants[T, U] {
+		val serialName: String = "VariantF"
+	}
+	case class VariantG[T, U](content: GenericEnumsUsingStructVariantsVariantGInner[T, U]) extends GenericEnumsUsingStructVariants[T, U] {
+		val serialName: String = "VariantG"
+	}
+	case class VariantH[T, U](content: GenericEnumsUsingStructVariantsVariantHInner) extends GenericEnumsUsingStructVariants[T, U] {
+		val serialName: String = "VariantH"
+	}
+	case class VariantI[T, U](content: GenericEnumsUsingStructVariantsVariantIInner[T, U]) extends GenericEnumsUsingStructVariants[T, U] {
+		val serialName: String = "VariantI"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_generic_struct/output.scala b/core/data/tests/can_generate_generic_struct/output.scala
new file mode 100644
index 0000000..d6abced
--- /dev/null
+++ b/core/data/tests/can_generate_generic_struct/output.scala
@@ -0,0 +1,37 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class GenericStruct[A, B] (
+	field_a: A,
+	field_b: Vector[B]
+)
+
+case class GenericStructUsingGenericStruct[T] (
+	struct_field: GenericStruct[String, T],
+	second_struct_field: GenericStruct[T, String],
+	third_struct_field: GenericStruct[T, Vector[T]]
+)
+
+sealed trait EnumUsingGenericStruct {
+	def serialName: String
+}
+object EnumUsingGenericStruct {
+	case class VariantA(content: GenericStruct[String, Float]) extends EnumUsingGenericStruct {
+		val serialName: String = "VariantA"
+	}
+	case class VariantB(content: GenericStruct[String, Int]) extends EnumUsingGenericStruct {
+		val serialName: String = "VariantB"
+	}
+	case class VariantC(content: GenericStruct[String, Boolean]) extends EnumUsingGenericStruct {
+		val serialName: String = "VariantC"
+	}
+	case class VariantD(content: GenericStructUsingGenericStruct[Unit]) extends EnumUsingGenericStruct {
+		val serialName: String = "VariantD"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_generic_type_alias/output.scala b/core/data/tests/can_generate_generic_type_alias/output.scala
new file mode 100644
index 0000000..047d799
--- /dev/null
+++ b/core/data/tests/can_generate_generic_type_alias/output.scala
@@ -0,0 +1,12 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type GenericTypeAlias[T] = Vector[T]
+
+type NonGenericAlias = GenericTypeAlias[Option[String]]
+
+}
diff --git a/core/data/tests/can_generate_simple_enum/output.scala b/core/data/tests/can_generate_simple_enum/output.scala
new file mode 100644
index 0000000..59f0a82
--- /dev/null
+++ b/core/data/tests/can_generate_simple_enum/output.scala
@@ -0,0 +1,26 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+// Continued lovingly here
+sealed trait Colors {
+	def serialName: String
+}
+object Colors {
+	case object Red extends Colors {
+		val serialName: String = "Red"
+	}
+	case object Blue extends Colors {
+		val serialName: String = "Blue"
+	}
+	// Green is a cool color
+	case object Green extends Colors {
+		val serialName: String = "Green"
+	}
+}
+
+}
diff --git a/core/data/tests/can_generate_simple_struct_with_a_comment/output.scala b/core/data/tests/can_generate_simple_struct_with_a_comment/output.scala
new file mode 100644
index 0000000..95c7a39
--- /dev/null
+++ b/core/data/tests/can_generate_simple_struct_with_a_comment/output.scala
@@ -0,0 +1,28 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+class Location extends Serializable
+
+// This is a comment.
+case class Person (
+	// This is another comment
+	name: String,
+	age: UByte,
+	info: Option[String] = None,
+	emails: Vector[String],
+	location: Location
+)
+
+}
diff --git a/core/data/tests/can_generate_struct_with_skipped_fields/output.scala b/core/data/tests/can_generate_struct_with_skipped_fields/output.scala
new file mode 100644
index 0000000..b5fb073
--- /dev/null
+++ b/core/data/tests/can_generate_struct_with_skipped_fields/output.scala
@@ -0,0 +1,13 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class MyStruct (
+	a: Int,
+	c: Int
+)
+
+}
diff --git a/core/data/tests/can_generate_unit_structs/output.scala b/core/data/tests/can_generate_unit_structs/output.scala
new file mode 100644
index 0000000..a42e10c
--- /dev/null
+++ b/core/data/tests/can_generate_unit_structs/output.scala
@@ -0,0 +1,10 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+class UnitStruct extends Serializable
+
+}
diff --git a/core/data/tests/can_handle_anonymous_struct/output.scala b/core/data/tests/can_handle_anonymous_struct/output.scala
new file mode 100644
index 0000000..36e9d34
--- /dev/null
+++ b/core/data/tests/can_handle_anonymous_struct/output.scala
@@ -0,0 +1,73 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// Generated type representing the anonymous struct variant `Us` of the `AutofilledBy` Rust enum
+case class AutofilledByUsInner (
+	// The UUID for the fill
+	uuid: String
+)
+
+// Generated type representing the anonymous struct variant `SomethingElse` of the `AutofilledBy` Rust enum
+case class AutofilledBySomethingElseInner (
+	// The UUID for the fill
+	uuid: String,
+	// Some other thing
+	thing: Int
+)
+
+// Enum keeping track of who autofilled a field
+sealed trait AutofilledBy {
+	def serialName: String
+}
+object AutofilledBy {
+	// This field was autofilled by us
+	case class Us(content: AutofilledByUsInner) extends AutofilledBy {
+		val serialName: String = "Us"
+	}
+	// Something else autofilled this field
+	case class SomethingElse(content: AutofilledBySomethingElseInner) extends AutofilledBy {
+		val serialName: String = "SomethingElse"
+	}
+}
+
+// Generated type representing the anonymous struct variant `AnonVariant` of the `EnumWithManyVariants` Rust enum
+case class EnumWithManyVariantsAnonVariantInner (
+	uuid: String
+)
+
+// Generated type representing the anonymous struct variant `AnotherAnonVariant` of the `EnumWithManyVariants` Rust enum
+case class EnumWithManyVariantsAnotherAnonVariantInner (
+	uuid: String,
+	thing: Int
+)
+
+// This is a comment (yareek sameek wuz here)
+sealed trait EnumWithManyVariants {
+	def serialName: String
+}
+object EnumWithManyVariants {
+	case object UnitVariant extends EnumWithManyVariants {
+		val serialName: String = "UnitVariant"
+	}
+	case class TupleVariantString(content: String) extends EnumWithManyVariants {
+		val serialName: String = "TupleVariantString"
+	}
+	case class AnonVariant(content: EnumWithManyVariantsAnonVariantInner) extends EnumWithManyVariants {
+		val serialName: String = "AnonVariant"
+	}
+	case class TupleVariantInt(content: Int) extends EnumWithManyVariants {
+		val serialName: String = "TupleVariantInt"
+	}
+	case object AnotherUnitVariant extends EnumWithManyVariants {
+		val serialName: String = "AnotherUnitVariant"
+	}
+	case class AnotherAnonVariant(content: EnumWithManyVariantsAnotherAnonVariantInner) extends EnumWithManyVariants {
+		val serialName: String = "AnotherAnonVariant"
+	}
+}
+
+}
diff --git a/core/data/tests/can_handle_quote_in_serde_rename/output.scala b/core/data/tests/can_handle_quote_in_serde_rename/output.scala
new file mode 100644
index 0000000..5fd9bd7
--- /dev/null
+++ b/core/data/tests/can_handle_quote_in_serde_rename/output.scala
@@ -0,0 +1,18 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+sealed trait Colors {
+	def serialName: String
+}
+object Colors {
+	case object Green extends Colors {
+		val serialName: String = "Green\""
+	}
+}
+
+}
diff --git a/core/data/tests/can_handle_serde_rename/output.scala b/core/data/tests/can_handle_serde_rename/output.scala
new file mode 100644
index 0000000..0d67097
--- /dev/null
+++ b/core/data/tests/can_handle_serde_rename/output.scala
@@ -0,0 +1,28 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+class OtherType extends Serializable
+
+// This is a comment.
+case class Person (
+	name: String,
+	age: UByte,
+	extraSpecialFieldOne: Int,
+	extraSpecialFieldTwo: Option[Vector[String]] = None,
+	nonStandardDataType: OtherType,
+	nonStandardDataTypeInArray: Option[Vector[OtherType]] = None
+)
+
+}
diff --git a/core/data/tests/can_handle_serde_rename_all/output.scala b/core/data/tests/can_handle_serde_rename_all/output.scala
new file mode 100644
index 0000000..5464537
--- /dev/null
+++ b/core/data/tests/can_handle_serde_rename_all/output.scala
@@ -0,0 +1,32 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+// This is a Person struct with camelCase rename
+case class Person (
+	firstName: String,
+	lastName: String,
+	age: UByte,
+	extraSpecialField1: Int,
+	extraSpecialField2: Option[Vector[String]] = None
+)
+
+// This is a Person2 struct with UPPERCASE rename
+case class Person2 (
+	FIRST_NAME: String,
+	LAST_NAME: String,
+	AGE: UByte
+)
+
+}
diff --git a/core/data/tests/can_handle_serde_rename_on_top_level/output.scala b/core/data/tests/can_handle_serde_rename_on_top_level/output.scala
new file mode 100644
index 0000000..e5e51dd
--- /dev/null
+++ b/core/data/tests/can_handle_serde_rename_on_top_level/output.scala
@@ -0,0 +1,28 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+class OtherType extends Serializable
+
+// This is a comment.
+case class PersonTwo (
+	name: String,
+	age: UByte,
+	extraSpecialFieldOne: Int,
+	extraSpecialFieldTwo: Option[Vector[String]] = None,
+	nonStandardDataType: OtherType,
+	nonStandardDataTypeInArray: Option[Vector[OtherType]] = None
+)
+
+}
diff --git a/core/data/tests/can_handle_unit_type/output.scala b/core/data/tests/can_handle_unit_type/output.scala
new file mode 100644
index 0000000..7681f85
--- /dev/null
+++ b/core/data/tests/can_handle_unit_type/output.scala
@@ -0,0 +1,23 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This struct has a unit field
+case class StructHasVoidType (
+	thisIsAUnit: Unit
+)
+
+// This enum has a variant associated with unit data
+sealed trait EnumHasVoidType {
+	def serialName: String
+}
+object EnumHasVoidType {
+	case class HasAUnit(content: Unit) extends EnumHasVoidType {
+		val serialName: String = "hasAUnit"
+	}
+}
+
+}
diff --git a/core/data/tests/enum_is_properly_named_with_serde_overrides/output.scala b/core/data/tests/enum_is_properly_named_with_serde_overrides/output.scala
new file mode 100644
index 0000000..82c0aa0
--- /dev/null
+++ b/core/data/tests/enum_is_properly_named_with_serde_overrides/output.scala
@@ -0,0 +1,26 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+// Continued lovingly here
+sealed trait Colors {
+	def serialName: String
+}
+object Colors {
+	case object Red extends Colors {
+		val serialName: String = "red"
+	}
+	case object Blue extends Colors {
+		val serialName: String = "blue"
+	}
+	// Green is a cool color
+	case object Green extends Colors {
+		val serialName: String = "green-like"
+	}
+}
+
+}
diff --git a/core/data/tests/generate_types/output.scala b/core/data/tests/generate_types/output.scala
new file mode 100644
index 0000000..f21708f
--- /dev/null
+++ b/core/data/tests/generate_types/output.scala
@@ -0,0 +1,22 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+class CustomType extends Serializable
+
+case class Types (
+	s: String,
+	static_s: String,
+	int8: Byte,
+	float: Float,
+	double: Double,
+	array: Vector[String],
+	dictionary: Map[String, Int],
+	optional_dictionary: Option[Map[String, Int]] = None,
+	custom_type: CustomType
+)
+
+}
diff --git a/core/data/tests/generates_empty_structs_and_initializers/output.scala b/core/data/tests/generates_empty_structs_and_initializers/output.scala
new file mode 100644
index 0000000..061695f
--- /dev/null
+++ b/core/data/tests/generates_empty_structs_and_initializers/output.scala
@@ -0,0 +1,10 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+class MyEmptyStruct extends Serializable
+
+}
diff --git a/core/data/tests/kebab_case_rename/output.scala b/core/data/tests/kebab_case_rename/output.scala
new file mode 100644
index 0000000..35f43b2
--- /dev/null
+++ b/core/data/tests/kebab_case_rename/output.scala
@@ -0,0 +1,15 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+case class Things (
+	bla: String,
+	label: Option[String] = None,
+	label_left: Option[String] = None
+)
+
+}
diff --git a/core/data/tests/recursive_enum_decorator/output.scala b/core/data/tests/recursive_enum_decorator/output.scala
new file mode 100644
index 0000000..4dbc4d5
--- /dev/null
+++ b/core/data/tests/recursive_enum_decorator/output.scala
@@ -0,0 +1,48 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+sealed trait Options {
+	def serialName: String
+}
+object Options {
+	case class Red(content: Boolean) extends Options {
+		val serialName: String = "red"
+	}
+	case class Banana(content: String) extends Options {
+		val serialName: String = "banana"
+	}
+	case class Vermont(content: Options) extends Options {
+		val serialName: String = "vermont"
+	}
+}
+
+// Generated type representing the anonymous struct variant `Exactly` of the `MoreOptions` Rust enum
+case class MoreOptionsExactlyInner (
+	config: String
+)
+
+// Generated type representing the anonymous struct variant `Built` of the `MoreOptions` Rust enum
+case class MoreOptionsBuiltInner (
+	top: MoreOptions
+)
+
+sealed trait MoreOptions {
+	def serialName: String
+}
+object MoreOptions {
+	case class News(content: Boolean) extends MoreOptions {
+		val serialName: String = "news"
+	}
+	case class Exactly(content: MoreOptionsExactlyInner) extends MoreOptions {
+		val serialName: String = "exactly"
+	}
+	case class Built(content: MoreOptionsBuiltInner) extends MoreOptions {
+		val serialName: String = "built"
+	}
+}
+
+}
diff --git a/core/data/tests/resolves_qualified_type/output.scala b/core/data/tests/resolves_qualified_type/output.scala
new file mode 100644
index 0000000..13d200f
--- /dev/null
+++ b/core/data/tests/resolves_qualified_type/output.scala
@@ -0,0 +1,17 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class QualifiedTypes (
+	unqualified: String,
+	qualified: String,
+	qualified_vec: Vector[String],
+	qualified_hashmap: Map[String, String],
+	qualified_optional: Option[String] = None,
+	qualfied_optional_hashmap_vec: Option[Map[String, Vector[String]]] = None
+)
+
+}
diff --git a/core/data/tests/serialize_anonymous_field_as/output.scala b/core/data/tests/serialize_anonymous_field_as/output.scala
new file mode 100644
index 0000000..63e711d
--- /dev/null
+++ b/core/data/tests/serialize_anonymous_field_as/output.scala
@@ -0,0 +1,21 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+sealed trait SomeEnum {
+	def serialName: String
+}
+object SomeEnum {
+	// The associated String contains some opaque context
+	case class Context(content: String) extends SomeEnum {
+		val serialName: String = "Context"
+	}
+	case class Other(content: Int) extends SomeEnum {
+		val serialName: String = "Other"
+	}
+}
+
+}
diff --git a/core/data/tests/serialize_field_as/output.scala b/core/data/tests/serialize_field_as/output.scala
new file mode 100644
index 0000000..c7d7789
--- /dev/null
+++ b/core/data/tests/serialize_field_as/output.scala
@@ -0,0 +1,14 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class EditItemViewModelSaveRequest (
+	context: String,
+	values: Vector[EditItemSaveValue],
+	fill_action: Option[AutoFillItemActionRequest] = None
+)
+
+}
diff --git a/core/data/tests/serialize_type_alias/output.scala b/core/data/tests/serialize_type_alias/output.scala
new file mode 100644
index 0000000..ddf02b7
--- /dev/null
+++ b/core/data/tests/serialize_type_alias/output.scala
@@ -0,0 +1,17 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type AlsoString = String
+
+type Uuid = String
+
+// Unique identifier for an Account
+type AccountUuid = Uuid
+
+type ItemUuid = String
+
+}
diff --git a/core/data/tests/test_algebraic_enum_case_name_support/output.scala b/core/data/tests/test_algebraic_enum_case_name_support/output.scala
new file mode 100644
index 0000000..c2ccf1a
--- /dev/null
+++ b/core/data/tests/test_algebraic_enum_case_name_support/output.scala
@@ -0,0 +1,28 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+class ItemDetailsFieldValue extends Serializable
+
+sealed trait AdvancedColors {
+	def serialName: String
+}
+object AdvancedColors {
+	case class String(content: String) extends AdvancedColors {
+		val serialName: String = "string"
+	}
+	case class Number(content: Int) extends AdvancedColors {
+		val serialName: String = "number"
+	}
+	case class NumberArray(content: Vector[Int]) extends AdvancedColors {
+		val serialName: String = "number-array"
+	}
+	case class ReallyCoolType(content: ItemDetailsFieldValue) extends AdvancedColors {
+		val serialName: String = "reallyCoolType"
+	}
+}
+
+}
diff --git a/core/data/tests/test_i54_u53_type/output.scala b/core/data/tests/test_i54_u53_type/output.scala
new file mode 100644
index 0000000..da4e03e
--- /dev/null
+++ b/core/data/tests/test_i54_u53_type/output.scala
@@ -0,0 +1,21 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+case class Foo (
+	a: Long,
+	b: ULong
+)
+
+}
diff --git a/core/data/tests/test_optional_type_alias/output.scala b/core/data/tests/test_optional_type_alias/output.scala
new file mode 100644
index 0000000..ef65065
--- /dev/null
+++ b/core/data/tests/test_optional_type_alias/output.scala
@@ -0,0 +1,20 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type OptionalU32 = Option[UInt]
+
+type OptionalU16 = Option[UShort]
+
+}
+package onepassword {
+
+case class FooBar (
+	foo: OptionalU32,
+	bar: OptionalU16
+)
+
+}
diff --git a/core/data/tests/test_serde_default_struct/output.scala b/core/data/tests/test_serde_default_struct/output.scala
new file mode 100644
index 0000000..af71fa9
--- /dev/null
+++ b/core/data/tests/test_serde_default_struct/output.scala
@@ -0,0 +1,12 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class Foo (
+	bar: Boolean = _
+)
+
+}
diff --git a/core/data/tests/test_serde_iso8601/output.scala b/core/data/tests/test_serde_iso8601/output.scala
new file mode 100644
index 0000000..60c4023
--- /dev/null
+++ b/core/data/tests/test_serde_iso8601/output.scala
@@ -0,0 +1,12 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class Foo (
+	time: String
+)
+
+}
diff --git a/core/data/tests/test_serde_url/output.scala b/core/data/tests/test_serde_url/output.scala
new file mode 100644
index 0000000..8fbde14
--- /dev/null
+++ b/core/data/tests/test_serde_url/output.scala
@@ -0,0 +1,12 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+case class Foo (
+	url: String
+)
+
+}
diff --git a/core/data/tests/test_serialized_as/output.scala b/core/data/tests/test_serialized_as/output.scala
new file mode 100644
index 0000000..4d3e1f3
--- /dev/null
+++ b/core/data/tests/test_serialized_as/output.scala
@@ -0,0 +1,13 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type ItemId = String
+
+// Options that you could pick
+type Options = String
+
+}
diff --git a/core/data/tests/test_serialized_as_tuple/output.scala b/core/data/tests/test_serialized_as_tuple/output.scala
new file mode 100644
index 0000000..0cba137
--- /dev/null
+++ b/core/data/tests/test_serialized_as_tuple/output.scala
@@ -0,0 +1,10 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type ItemId = String
+
+}
diff --git a/core/data/tests/test_simple_enum_case_name_support/output.scala b/core/data/tests/test_simple_enum_case_name_support/output.scala
new file mode 100644
index 0000000..b7cc9a6
--- /dev/null
+++ b/core/data/tests/test_simple_enum_case_name_support/output.scala
@@ -0,0 +1,24 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+// This is a comment.
+sealed trait Colors {
+	def serialName: String
+}
+object Colors {
+	case object Red extends Colors {
+		val serialName: String = "red"
+	}
+	case object Blue extends Colors {
+		val serialName: String = "blue-ish"
+	}
+	case object Green extends Colors {
+		val serialName: String = "Green"
+	}
+}
+
+}
diff --git a/core/data/tests/test_type_alias/output.scala b/core/data/tests/test_type_alias/output.scala
new file mode 100644
index 0000000..3a7c08c
--- /dev/null
+++ b/core/data/tests/test_type_alias/output.scala
@@ -0,0 +1,17 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type Bar = String
+
+}
+package onepassword {
+
+case class Foo (
+	bar: Bar
+)
+
+}
diff --git a/core/data/tests/use_correct_decoded_variable_name/output.scala b/core/data/tests/use_correct_decoded_variable_name/output.scala
new file mode 100644
index 0000000..061695f
--- /dev/null
+++ b/core/data/tests/use_correct_decoded_variable_name/output.scala
@@ -0,0 +1,10 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package onepassword {
+
+class MyEmptyStruct extends Serializable
+
+}
diff --git a/core/data/tests/use_correct_integer_types/output.scala b/core/data/tests/use_correct_integer_types/output.scala
new file mode 100644
index 0000000..ff409de
--- /dev/null
+++ b/core/data/tests/use_correct_integer_types/output.scala
@@ -0,0 +1,26 @@
+/**
+ * Generated by typeshare 1.0.0
+ */
+package com.agilebits
+
+package object onepassword {
+
+type UByte = Byte
+type UShort = Short
+type UInt = Int
+type ULong = Int
+
+}
+package onepassword {
+
+// This is a comment.
+case class Foo (
+	a: Byte,
+	b: Short,
+	c: Int,
+	e: UByte,
+	f: UShort,
+	g: UInt
+)
+
+}
diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs
index 4c3ea34..52fd070 100644
--- a/core/tests/snapshot_tests.rs
+++ b/core/tests/snapshot_tests.rs
@@ -84,6 +84,9 @@ macro_rules! output_file_for_ident {
     (kotlin) => {
         "output.kt"
     };
+    (scala) => {
+        "output.scala"
+    };
     (swift) => {
         "output.swift"
     };
@@ -145,6 +148,24 @@ macro_rules! language_instance {
         })
     };
 
+    // Default scala
+    (scala) => {
+        language_instance!(scala {
+            package: "com.agilebits.onepassword".to_string(),
+            module_name: String::new(),
+            type_mappings: Default::default(),
+        })
+    };
+
+    // scala with configuration fields forwarded
+    (scala {$($field:ident: $val:expr),* $(,)?}) => {
+        #[allow(clippy::needless_update)]
+        Box::new(typeshare_core::language::Scala {
+            $($field: $val,)*
+            ..Default::default()
+        })
+    };
+
     // Default swift
     (swift) => {
         language_instance!(swift { })
@@ -295,6 +316,13 @@ static KOTLIN_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
         .collect()
 });
 
+static SCALA_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
+    [("Url", "String"), ("DateTime", "String")]
+        .iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect()
+});
+
 static SWIFT_MAPPINGS: Lazy<HashMap<String, String>> = Lazy::new(|| {
     [("Url", "String"), ("DateTime", "Date")]
         .iter()
@@ -326,6 +354,10 @@ tests! {
             package: "com.agilebits.onepassword".to_string(),
             module_name: "colorsModule".to_string(),
         },
+        scala {
+            package: "com.agilebits.onepassword".to_string(),
+            module_name: "colorsModule".to_string(),
+        },
         typescript,
         go
     ];
@@ -334,6 +366,7 @@ tests! {
             prefix: "Core".into(),
         },
         kotlin,
+        scala,
         typescript
     ];
     can_generate_generic_struct: [
@@ -341,6 +374,7 @@ tests! {
             prefix: "Core".into(),
         },
         kotlin,
+        scala,
         typescript
     ];
     can_generate_generic_type_alias: [
@@ -348,6 +382,7 @@ tests! {
             prefix: "Core".into()
         },
         kotlin,
+        scala,
         typescript
     ];
     can_generate_simple_enum: [
@@ -355,14 +390,15 @@ tests! {
             prefix: "TypeShare".to_string(),
         },
         kotlin,
+        scala,
         typescript,
         go
     ];
-    can_generate_bare_string_enum: [swift, kotlin, typescript];
+    can_generate_bare_string_enum: [swift, kotlin, scala, typescript];
     can_generate_double_option_pattern: [
         typescript
     ];
-    test_simple_enum_case_name_support: [swift, kotlin, typescript];
+    test_simple_enum_case_name_support: [swift, kotlin, scala, typescript];
     test_algebraic_enum_case_name_support: [
         swift {
             prefix: "OP".to_string(),
@@ -371,40 +407,46 @@ tests! {
             package: "com.agilebits.onepassword".to_string(),
             module_name: "colorModule".to_string(),
         },
+        scala {
+            package: "com.agilebits.onepassword".to_string(),
+            module_name: "colorModule".to_string(),
+        },
         typescript,
         go
     ];
-    can_apply_prefix_correctly: [ swift { prefix: "OP".to_string(), }, kotlin, typescript, go ];
-    can_generate_empty_algebraic_enum: [ swift { prefix: "OP".to_string(), }, kotlin, typescript, go ];
-    can_generate_algebraic_enum_with_skipped_variants: [swift, kotlin, typescript, go];
-    can_generate_struct_with_skipped_fields: [swift, kotlin, typescript, go];
-    enum_is_properly_named_with_serde_overrides: [swift, kotlin, typescript, go];
-    can_handle_quote_in_serde_rename: [swift, kotlin, typescript, go];
-    can_handle_anonymous_struct: [swift, kotlin, typescript, go];
+    can_apply_prefix_correctly: [ swift { prefix: "OP".to_string(), }, kotlin, scala,  typescript, go ];
+    can_generate_empty_algebraic_enum: [ swift { prefix: "OP".to_string(), }, kotlin, scala,  typescript, go ];
+    can_generate_algebraic_enum_with_skipped_variants: [swift, kotlin, scala,  typescript, go];
+    can_generate_struct_with_skipped_fields: [swift, kotlin, scala,  typescript, go];
+    enum_is_properly_named_with_serde_overrides: [swift, kotlin, scala,  typescript, go];
+    can_handle_quote_in_serde_rename: [swift, kotlin, scala,  typescript, go];
+    can_handle_anonymous_struct: [swift, kotlin, scala,  typescript, go];
     anonymous_struct_with_rename: [
         swift {
             prefix: "Core".to_string(),
         },
         kotlin,
+        scala,
         typescript,
         go
     ];
 
     /// Structs
-    can_generate_simple_struct_with_a_comment: [kotlin, swift, typescript, go];
-    generate_types: [kotlin, swift, typescript, go];
+    can_generate_simple_struct_with_a_comment: [kotlin, swift, typescript, scala,  go];
+    generate_types: [kotlin, swift, typescript, scala,  go];
     can_handle_serde_rename: [
         swift {
             prefix: "TypeShareX_".to_string(),
         },
         kotlin,
+        scala,
         typescript,
         go
     ];
     // TODO: kotlin and typescript don't appear to support this yet
-    generates_empty_structs_and_initializers: [swift, kotlin, typescript, go];
-    test_i54_u53_type: [swift, kotlin, typescript, go];
-    test_serde_default_struct: [swift, kotlin, typescript, go];
+    generates_empty_structs_and_initializers: [swift, kotlin, scala, typescript, go];
+    test_i54_u53_type: [swift, kotlin, scala,  typescript, go];
+    test_serde_default_struct: [swift, kotlin, scala,  typescript, go];
     test_serde_iso8601: [
         swift {
             prefix: String::new(),
@@ -415,6 +457,11 @@ tests! {
             module_name: "colorModule".to_string(),
             type_mappings: super::KOTLIN_MAPPINGS.clone()
         },
+        scala {
+            package: "com.agilebits.onepassword".to_string(),
+            module_name: "colorModule".to_string(),
+            type_mappings: super::KOTLIN_MAPPINGS.clone()
+        },
         typescript {
             type_mappings: super::TYPESCRIPT_MAPPINGS.clone(),
         },
@@ -432,6 +479,11 @@ tests! {
             module_name: "colorModule".to_string(),
             type_mappings: super::KOTLIN_MAPPINGS.clone()
         },
+        scala {
+            package: "com.agilebits.onepassword".to_string(),
+            module_name: "colorModule".to_string(),
+            type_mappings: super::SCALA_MAPPINGS.clone()
+        },
         typescript {
             type_mappings: super::TYPESCRIPT_MAPPINGS.clone(),
         },
@@ -440,41 +492,42 @@ tests! {
             uppercase_acronyms: vec!["URL".to_string()],
         },
     ];
-    test_type_alias: [ swift { prefix: "OP".to_string(), }, kotlin, typescript, go ];
-    test_optional_type_alias: [swift, kotlin, typescript, go];
-    test_serialized_as: [ swift { prefix: "OP".to_string(), }, kotlin, typescript ];
+    test_type_alias: [ swift { prefix: "OP".to_string(), }, kotlin, scala,  typescript, go ];
+    test_optional_type_alias: [swift, kotlin, scala, typescript, go];
+    test_serialized_as: [ swift { prefix: "OP".to_string(), }, kotlin, scala,  typescript];
     test_serialized_as_tuple: [
         swift {
             prefix: "OP".to_string(),
         },
         kotlin,
+        scala,
         typescript,
         go {
             uppercase_acronyms: vec!["ID".to_string()],
         },
     ];
-    can_handle_serde_rename_all: [swift, kotlin, typescript, go];
-    can_handle_serde_rename_on_top_level: [swift { prefix: "OP".to_string(), }, kotlin, typescript, go];
-    can_generate_unit_structs: [swift, kotlin, typescript, go];
-    kebab_case_rename: [swift, kotlin, typescript, go];
+    can_handle_serde_rename_all: [swift, kotlin, scala,  typescript, go];
+    can_handle_serde_rename_on_top_level: [swift { prefix: "OP".to_string(), }, kotlin, scala,  typescript, go];
+    can_generate_unit_structs: [swift, kotlin, scala, typescript, go];
+    kebab_case_rename: [swift, kotlin, scala,  typescript, go];
 
     /// Other
-    use_correct_integer_types: [swift, kotlin, typescript, go];
+    use_correct_integer_types: [swift, kotlin, scala,  typescript, go];
     // Only swift supports generating types with keywords
     generate_types_with_keywords: [swift];
     // TODO: how is this different from generates_empty_structs_and_initializers?
-    use_correct_decoded_variable_name: [swift, kotlin, typescript, go];
-    can_handle_unit_type: [swift, kotlin, typescript, go];
+    use_correct_decoded_variable_name: [swift, kotlin, scala,  typescript, go];
+    can_handle_unit_type: [swift, kotlin, scala,  typescript, go];
 
     //3 tests for adding decorators to enums and structs
     const_enum_decorator: [ swift{ prefix: "OP".to_string(), } ];
     algebraic_enum_decorator: [ swift{ prefix: "OP".to_string(), } ];
     struct_decorator: [ swift{ prefix: "OP".to_string(), } ];
-    serialize_field_as: [kotlin, swift, typescript, go];
-    serialize_type_alias: [kotlin, swift, typescript, go];
-    serialize_anonymous_field_as: [kotlin, swift, typescript, go];
-    boxed_value: [kotlin, swift, typescript, go];
-    recursive_enum_decorator: [kotlin, swift, typescript, go];
+    serialize_field_as: [kotlin, swift, typescript, scala,  go];
+    serialize_type_alias: [kotlin, swift, typescript, scala,  go];
+    serialize_anonymous_field_as: [kotlin, swift, typescript, scala,  go];
+    boxed_value: [kotlin, swift, typescript, scala,  go];
+    recursive_enum_decorator: [kotlin, swift, typescript, scala,  go];
 
     uppercase_go_acronyms: [
         go {
@@ -487,6 +540,7 @@ tests! {
         },
         typescript,
         kotlin,
+        scala,
         go
     ];
 }