Repository: 1Password/typeshare (rust)

Issue:
Typeshare panics when encountering slice of user type.
For example, a struct like so:
```rust
#[typeshare]
#[derive(Serialize)]
pub struct Video<'a> {
    pub tags: &'a [Tag],
}
```
Running typeshare gives an error like so:
```
thread 'main' panicked at 'failed to parse a rust type: Unexpected token when parsing type: `[Tag]`. This is an internal error, please ping a typeshare developer to resolve this problem.', /home/louisgariepy/.cargo/registry/src/index.crates.io-6f17d22bba15001f/typeshare-cli-1.6.0/src/main.rs:267:17
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

`Tag` is a simple enum which is also annotated with  `#[typeshare]`.

According to the error message, the error happens because of the slice type `[Tag]`.

Note that changing `&'a [Tag]` for `&'a Vec<Tag>` makes things work.

Pull request description:
Support Rust slice type
Adds support for the Rust slice type e.g.:

```rust
#[typeshare]
#[derive(Serialize)]
pub struct Video<'a> {
    pub tags: &'a [Tag],
}
```

@LouisGariepy please verify if it works in your case.

Closes #126

Patch:
diff --git a/core/src/language/go.rs b/core/src/language/go.rs
index 2ecf055..1639389 100644
--- a/core/src/language/go.rs
+++ b/core/src/language/go.rs
@@ -84,6 +84,9 @@ 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)?)
             }
diff --git a/core/src/language/kotlin.rs b/core/src/language/kotlin.rs
index ac38e6a..d8633aa 100644
--- a/core/src/language/kotlin.rs
+++ b/core/src/language/kotlin.rs
@@ -42,6 +42,9 @@ 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)?)
             }
diff --git a/core/src/language/scala.rs b/core/src/language/scala.rs
index 9ce0d52..aaefe7f 100644
--- a/core/src/language/scala.rs
+++ b/core/src/language/scala.rs
@@ -83,6 +83,9 @@ 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)?)
             }
diff --git a/core/src/language/swift.rs b/core/src/language/swift.rs
index a1d86bc..c9cf26e 100644
--- a/core/src/language/swift.rs
+++ b/core/src/language/swift.rs
@@ -177,6 +177,9 @@ 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)?)
             }
diff --git a/core/src/language/typescript.rs b/core/src/language/typescript.rs
index cffea1f..68cbe7d 100644
--- a/core/src/language/typescript.rs
+++ b/core/src/language/typescript.rs
@@ -41,6 +41,9 @@ 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!(
diff --git a/core/src/rust_types.rs b/core/src/rust_types.rs
index e14d13c..cb8f127 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};
+use syn::{Expr, ExprLit, Lit, TypeArray, TypeSlice};
 use thiserror::Error;
 
 use crate::language::SupportedLanguage;
@@ -135,6 +135,8 @@ 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
@@ -280,6 +282,12 @@ 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(),
@@ -372,7 +380,9 @@ 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::Option(rty) => rty.contains_type(ty),
+            Self::Vec(rty) | Self::Array(rty, _) | Self::Slice(rty) | Self::Option(rty) => {
+                rty.contains_type(ty)
+            }
             Self::HashMap(rty1, rty2) => rty1.contains_type(ty) || rty2.contains_type(ty),
             Self::Unit
             | Self::String
@@ -402,6 +412,7 @@ impl SpecialRustType {
             Self::F32 => "f32",
             Self::Vec(_) => "Vec",
             Self::Array(_, _) => "[]",
+            Self::Slice(_) => "&[]",
             Self::Option(_) => "Option",
             Self::HashMap(_, _) => "HashMap",
             Self::String => "String",
@@ -424,7 +435,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::Option(rtype) => {
+            Self::Vec(rtype) | Self::Array(rtype, _) | Self::Slice(rtype) | Self::Option(rtype) => {
                 Box::new(std::iter::once(rtype.as_ref()))
             }
             Self::HashMap(rtype1, rtype2) => {


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
new file mode 100644
index 0000000..9950eb2
--- /dev/null
+++ b/core/data/tests/can_generate_slice_of_user_type/input.rs
@@ -0,0 +1,5 @@
+#[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
new file mode 100644
index 0000000..bd4ceee
--- /dev/null
+++ b/core/data/tests/can_generate_slice_of_user_type/output.go
@@ -0,0 +1,7 @@
+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.kt b/core/data/tests/can_generate_slice_of_user_type/output.kt
new file mode 100644
index 0000000..8bc019a
--- /dev/null
+++ b/core/data/tests/can_generate_slice_of_user_type/output.kt
@@ -0,0 +1,12 @@
+@file:NoLiveLiterals
+
+package com.agilebits.onepassword
+
+import androidx.compose.runtime.NoLiveLiterals
+import kotlinx.serialization.*
+
+@Serializable
+data class Video (
+	val tags: List<Tag>
+)
+
diff --git a/core/data/tests/can_generate_slice_of_user_type/output.scala b/core/data/tests/can_generate_slice_of_user_type/output.scala
new file mode 100644
index 0000000..11888b1
--- /dev/null
+++ b/core/data/tests/can_generate_slice_of_user_type/output.scala
@@ -0,0 +1,9 @@
+package com.agilebits
+
+package onepassword {
+
+case class Video (
+	tags: Vector[Tag]
+)
+
+}
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
new file mode 100644
index 0000000..6c9a5fe
--- /dev/null
+++ b/core/data/tests/can_generate_slice_of_user_type/output.swift
@@ -0,0 +1,9 @@
+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
new file mode 100644
index 0000000..0b82b7f
--- /dev/null
+++ b/core/data/tests/can_generate_slice_of_user_type/output.ts
@@ -0,0 +1,4 @@
+export interface Video {
+	tags: Tag[];
+}
+
diff --git a/core/tests/snapshot_tests.rs b/core/tests/snapshot_tests.rs
index b8e1e06..99f3887 100644
--- a/core/tests/snapshot_tests.rs
+++ b/core/tests/snapshot_tests.rs
@@ -390,6 +390,7 @@ tests! {
         scala,
         typescript
     ];
+    can_generate_slice_of_user_type: [swift, kotlin, scala, typescript, go];
     can_generate_readonly_fields: [
         typescript
     ];