Repository: 1Password/typeshare (rust)

Issue:
Support for `char`
👋 Thanks for building and open sourcing typeshare, I'm just trying it out for the first time and I've noticed the `char` type get's output as `char`, should/could this be output as `string`? 

Pull request:
Support char type
Add support for the Rust `char` type.

Closes #103 

Patch:
diff --git a/cli/src/main.rs b/cli/src/main.rs
index 847c9d6..6a34b98 100644
--- a/cli/src/main.rs
+++ b/cli/src/main.rs
@@ -261,8 +261,7 @@ fn main() {
     let parsed_data = glob_paths
         .par_iter()
         .map(|filepath| {
-            let data = std::fs::read_to_string(filepath)
-                .unwrap_or_else(|e| panic!("failed to read file at {filepath:?}: {e}"));
+            let data = std::fs::read_to_string(filepath).unwrap();
             let parsed_data = typeshare_core::parser::parse(&data);
             if parsed_data.is_err() {
                 panic!("{}", parsed_data.err().unwrap());
diff --git a/core/src/language/go.rs b/core/src/language/go.rs
index 1639389..feb84ab 100644
--- a/core/src/language/go.rs
+++ b/core/src/language/go.rs
@@ -84,9 +84,6 @@ impl Language for Go {
             SpecialRustType::Array(rtype, len) => {
                 format!("[{}]{}", len, self.format_type(rtype, generic_types)?)
             }
-            SpecialRustType::Slice(rtype) => {
-                format!("[]{}", self.format_type(rtype, generic_types)?)
-            }
             SpecialRustType::Option(rtype) => {
                 format!("*{}", self.format_type(rtype, generic_types)?)
             }
@@ -97,6 +94,7 @@ impl Language for Go {
             ),
             SpecialRustType::Unit => "struct{}".into(),
             SpecialRustType::String => "string".into(),
+            SpecialRustType::Char => "rune".into(),
             SpecialRustType::I8
             | SpecialRustType::U8
             | SpecialRustType::U16
diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs
index d8633aa..d82d850 100644
--- a/core/src/language/kotlin.rs
+++ b/core/src/language/kotlin.rs
@@ -42,9 +42,6 @@ impl Language for Kotlin {
             SpecialRustType::Array(rtype, _) => {
                 format!("List<{}>", self.format_type(rtype, generic_types)?)
             }
-            SpecialRustType::Slice(rtype) => {
-                format!("List<{}>", self.format_type(rtype, generic_types)?)
-            }
             SpecialRustType::Option(rtype) => {
                 format!("{}?", self.format_type(rtype, generic_types)?)
             }
@@ -57,6 +54,8 @@ impl Language for Kotlin {
             }
             SpecialRustType::Unit => "Unit".into(),
             SpecialRustType::String => "String".into(),
+            // Char in Kotlin is 16 bits long, so we need to use String
+            SpecialRustType::Char => "String".into(),
             // https://kotlinlang.org/docs/basic-types.html#integer-types
             SpecialRustType::I8 => "Byte".into(),
             SpecialRustType::I16 => "Short".into(),
diff --git a/core/src/language/scala.rs b/core/src/language/scala.rs
index aaefe7f..0cfaadd 100644
--- a/core/src/language/scala.rs
+++ b/core/src/language/scala.rs
@@ -83,9 +83,6 @@ impl Language for Scala {
             SpecialRustType::Array(rtype, _) => {
                 format!("Vector[{}]", self.format_type(rtype, generic_types)?)
             }
-            SpecialRustType::Slice(rtype) => {
-                format!("Vector[{}]", self.format_type(rtype, generic_types)?)
-            }
             SpecialRustType::Option(rtype) => {
                 format!("Option[{}]", self.format_type(rtype, generic_types)?)
             }
@@ -98,6 +95,8 @@ impl Language for Scala {
             }
             SpecialRustType::Unit => "Unit".into(),
             SpecialRustType::String => "String".into(),
+            // Char in Scala is 16 bits long, so we need to use String
+            SpecialRustType::Char => "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(),
diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs
index c9cf26e..324a617 100644
--- a/core/src/language/swift.rs
+++ b/core/src/language/swift.rs
@@ -177,9 +177,6 @@ impl Language for Swift {
             SpecialRustType::Array(rtype, _) => {
                 format!("[{}]", self.format_type(rtype, generic_types)?)
             }
-            SpecialRustType::Slice(rtype) => {
-                format!("[{}]", self.format_type(rtype, generic_types)?)
-            }
             SpecialRustType::Option(rtype) => {
                 format!("{}?", self.format_type(rtype, generic_types)?)
             }
@@ -193,6 +190,7 @@ impl Language for Swift {
                 "CodableVoid".into()
             }
             SpecialRustType::String => "String".into(),
+            SpecialRustType::Char => "Unicode.Scalar".into(),
             SpecialRustType::I8 => "Int8".into(),
             SpecialRustType::U8 => "UInt8".into(),
             SpecialRustType::I16 => "Int16".into(),
diff --git a/core/src/language/typescript.rs b/core/src/language/typescript.rs
index 68cbe7d..4472a05 100644
--- a/core/src/language/typescript.rs
+++ b/core/src/language/typescript.rs
@@ -41,9 +41,6 @@ impl Language for TypeScript {
                         .join_with(", ")
                 ))
             }
-            SpecialRustType::Slice(rtype) => {
-                Ok(format!("{}[]", self.format_type(rtype, generic_types)?))
-            }
             // We add optionality above the type formatting level
             SpecialRustType::Option(rtype) => self.format_type(rtype, generic_types),
             SpecialRustType::HashMap(rtype1, rtype2) => Ok(format!(
@@ -58,6 +55,7 @@ impl Language for TypeScript {
             )),
             SpecialRustType::Unit => Ok("undefined".into()),
             SpecialRustType::String => Ok("string".into()),
+            SpecialRustType::Char => Ok("string".into()),
             SpecialRustType::I8
             | SpecialRustType::U8
             | SpecialRustType::I16
diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs
index cb8f127..5e4a42b 100644
--- a/core/src/rust_types.rs
+++ b/core/src/rust_types.rs
@@ -2,7 +2,7 @@ use quote::ToTokens;
 use std::collections::BTreeSet;
 use std::str::FromStr;
 use std::{collections::HashMap, convert::TryFrom};
-use syn::{Expr, ExprLit, Lit, TypeArray, TypeSlice};
+use syn::{Expr, ExprLit, Lit, TypeArray};
 use thiserror::Error;
 
 use crate::language::SupportedLanguage;
@@ -135,8 +135,6 @@ pub enum SpecialRustType {
     Vec(Box<RustType>),
     /// Represents `[T; N]` from the standard library
     Array(Box<RustType>, usize),
-    /// Represents `&[T]` from the standard library
-    Slice(Box<RustType>),
     /// Represents `HashMap<K, V>` from the standard library
     HashMap(Box<RustType>, Box<RustType>),
     /// Represents `Option<T>` from the standard library
@@ -145,6 +143,8 @@ pub enum SpecialRustType {
     Unit,
     /// Represents `String` from the standard library
     String,
+    /// Represents `char`
+    Char,
     /// Represents `i8`
     I8,
     /// Represents `i16`
@@ -246,6 +246,7 @@ impl TryFrom<&syn::Type> for RustType {
                     // as its inner type.
                     "Box" => parameters.into_iter().next().unwrap(),
                     "bool" => Self::Special(SpecialRustType::Bool),
+                    "char" => Self::Special(SpecialRustType::Char),
                     "u8" => Self::Special(SpecialRustType::U8),
                     "u16" => Self::Special(SpecialRustType::U16),
                     "u32" => Self::Special(SpecialRustType::U32),
@@ -282,12 +283,6 @@ impl TryFrom<&syn::Type> for RustType {
                     .base10_parse()
                     .map_err(RustTypeParseError::NumericLiteral)?,
             )),
-            syn::Type::Slice(TypeSlice {
-                bracket_token: _,
-                elem,
-            }) => Self::Special(SpecialRustType::Slice(
-                Self::try_from(elem.as_ref())?.into(),
-            )),
             _ => {
                 return Err(RustTypeParseError::UnexpectedToken(
                     ty.to_token_stream().to_string(),
@@ -380,12 +375,11 @@ impl SpecialRustType {
     /// Check if this type is equivalent to or contains `ty` in one of its generic parameters.
     pub fn contains_type(&self, ty: &str) -> bool {
         match &self {
-            Self::Vec(rty) | Self::Array(rty, _) | Self::Slice(rty) | Self::Option(rty) => {
-                rty.contains_type(ty)
-            }
+            Self::Vec(rty) | Self::Array(rty, _) | Self::Option(rty) => rty.contains_type(ty),
             Self::HashMap(rty1, rty2) => rty1.contains_type(ty) || rty2.contains_type(ty),
             Self::Unit
             | Self::String
+            | Self::Char
             | Self::I8
             | Self::I16
             | Self::I32
@@ -412,10 +406,10 @@ impl SpecialRustType {
             Self::F32 => "f32",
             Self::Vec(_) => "Vec",
             Self::Array(_, _) => "[]",
-            Self::Slice(_) => "&[]",
             Self::Option(_) => "Option",
             Self::HashMap(_, _) => "HashMap",
             Self::String => "String",
+            Self::Char => "char",
             Self::Bool => "bool",
             Self::I8 => "i8",
             Self::I16 => "i16",
@@ -435,7 +429,7 @@ impl SpecialRustType {
     /// if there are none.
     pub fn parameters(&self) -> Box<dyn Iterator<Item = &RustType> + '_> {
         match &self {
-            Self::Vec(rtype) | Self::Array(rtype, _) | Self::Slice(rtype) | Self::Option(rtype) => {
+            Self::Vec(rtype) | Self::Array(rtype, _) | Self::Option(rtype) => {
                 Box::new(std::iter::once(rtype.as_ref()))
             }
             Self::HashMap(rtype1, rtype2) => {
@@ -443,6 +437,7 @@ impl SpecialRustType {
             }
             Self::Unit
             | Self::String
+            | Self::Char
             | Self::I8
             | Self::I16
             | Self::I32


Test patch:
diff --git a/core/data/tests/can_generate_slice_of_user_type/input.rs b/core/data/tests/can_generate_slice_of_user_type/input.rs
deleted file mode 100644
index 9950eb2..0000000
--- a/core/data/tests/can_generate_slice_of_user_type/input.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-#[typeshare]
-#[derive(Serialize)]
-pub struct Video<'a> {
-    pub tags: &'a [Tag],
-}
diff --git a/core/data/tests/can_generate_slice_of_user_type/output.go b/core/data/tests/can_generate_slice_of_user_type/output.go
deleted file mode 100644
index bd4ceee..0000000
--- a/core/data/tests/can_generate_slice_of_user_type/output.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package proto
-
-import "encoding/json"
-
-type Video struct {
-	Tags []Tag `json:"tags"`
-}
diff --git a/core/data/tests/can_generate_slice_of_user_type/output.swift b/core/data/tests/can_generate_slice_of_user_type/output.swift
deleted file mode 100644
index 6c9a5fe..0000000
--- a/core/data/tests/can_generate_slice_of_user_type/output.swift
+++ /dev/null
@@ -1,9 +0,0 @@
-import Foundation
-
-public struct Video: Codable {
-	public let tags: [Tag]
-
-	public init(tags: [Tag]) {
-		self.tags = tags
-	}
-}
diff --git a/core/data/tests/can_generate_slice_of_user_type/output.ts b/core/data/tests/can_generate_slice_of_user_type/output.ts
deleted file mode 100644
index 0b82b7f..0000000
--- a/core/data/tests/can_generate_slice_of_user_type/output.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export interface Video {
-	tags: Tag[];
-}
-
diff --git a/core/data/tests/test_generate_char/input.rs b/core/data/tests/test_generate_char/input.rs
new file mode 100644
index 0000000..889d8eb
--- /dev/null
+++ b/core/data/tests/test_generate_char/input.rs
@@ -0,0 +1,4 @@
+#[typeshare]
+struct MyType {
+    field: char,
+}
diff --git a/core/data/tests/test_generate_char/output.go b/core/data/tests/test_generate_char/output.go
new file mode 100644
index 0000000..4029f98
--- /dev/null
+++ b/core/data/tests/test_generate_char/output.go
@@ -0,0 +1,7 @@
+package proto
+
+import "encoding/json"
+
+type MyType struct {
+	Field rune `json:"field"`
+}
diff --git a/core/data/tests/can_generate_slice_of_user_type/output.kt b/core/data/tests/test_generate_char/output.kt
similarity index 79%
rename from core/data/tests/can_generate_slice_of_user_type/output.kt
rename to core/data/tests/test_generate_char/output.kt
index 8bc019a..5c3cc23 100644
--- a/core/data/tests/can_generate_slice_of_user_type/output.kt
+++ b/core/data/tests/test_generate_char/output.kt
@@ -6,7 +6,7 @@ import androidx.compose.runtime.NoLiveLiterals
 import kotlinx.serialization.*
 
 @Serializable
-data class Video (
-	val tags: List<Tag>
+data class MyType (
+	val field: String
 )
 
diff --git a/core/data/tests/can_generate_slice_of_user_type/output.scala b/core/data/tests/test_generate_char/output.scala
similarity index 57%
rename from core/data/tests/can_generate_slice_of_user_type/output.scala
rename to core/data/tests/test_generate_char/output.scala
index 11888b1..7f5e140 100644
--- a/core/data/tests/can_generate_slice_of_user_type/output.scala
+++ b/core/data/tests/test_generate_char/output.scala
@@ -2,8 +2,8 @@ package com.agilebits
 
 package onepassword {
 
-case class Video (
-	tags: Vector[Tag]
+case class MyType (
+	field: String
 )
 
 }
diff --git a/core/data/tests/test_generate_char/output.swift b/core/data/tests/test_generate_char/output.swift
new file mode 100644
index 0000000..6491c4d
--- /dev/null
+++ b/core/data/tests/test_generate_char/output.swift
@@ -0,0 +1,9 @@
+import Foundation
+
+public struct MyType: Codable {
+	public let field: Unicode.Scalar
+
+	public init(field: Unicode.Scalar) {
+		self.field = field
+	}
+}
diff --git a/core/data/tests/test_generate_char/output.ts b/core/data/tests/test_generate_char/output.ts
new file mode 100644
index 0000000..a865321
--- /dev/null
+++ b/core/data/tests/test_generate_char/output.ts
@@ -0,0 +1,4 @@
+export interface MyType {
+	field: string;
+}
+
diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs
index 99f3887..5b1583c 100644
--- a/core/tests/snapshot_tests.rs
+++ b/core/tests/snapshot_tests.rs
@@ -390,7 +390,6 @@ tests! {
         scala,
         typescript
     ];
-    can_generate_slice_of_user_type: [swift, kotlin, scala, typescript, go];
     can_generate_readonly_fields: [
         typescript
     ];
@@ -433,6 +432,7 @@ tests! {
     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];
+    test_generate_char: [swift, kotlin, scala, typescript, go];
     anonymous_struct_with_rename: [
         swift {
             prefix: "Core".to_string(),