Skip to content

Commit f20ea57

Browse files
authored
Continuous improvements: Tuples, TypeUnions, descriptor extraction (#21)
* change tuple constructor to use an iterator, so it's not indexed based (and so a little less fragile) * distinct the given serializers, before generating * make a distinction between a type alias (to one type) and type union (helps with the 'brand typing' option') - change the formatting of type unions - * add kotest * fix extracting SerialDescriptors from within subclasses * additional test for serializer extraction from sealed classes * document polymorphic descriptor extraction
1 parent 38d9f64 commit f20ea57

File tree

15 files changed

+196
-52
lines changed

15 files changed

+196
-52
lines changed

buildSrc/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ object Versions {
1717
const val kotlinxSerialization = "1.3.2"
1818
const val ksp = "1.6.10-1.0.4"
1919

20-
const val kotest = "5.1.0"
20+
const val kotest = "5.2.3"
2121
}
2222

2323

docs/code/example/example-tuple-01.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ data class SimpleTypes(
2727
element(SimpleTypes::privateMember)
2828
}
2929
) {
30-
override fun tupleConstructor(elements: List<*>): SimpleTypes {
30+
override fun tupleConstructor(elements: Iterator<*>): SimpleTypes {
3131
// When deserializing, the elements will be available as a list, in the order defined
3232
return SimpleTypes(
33-
elements[0] as String,
34-
elements[1] as Int,
35-
elements[2] as Double,
36-
elements[3] as Boolean,
37-
elements[4] as String,
33+
elements.next() as String,
34+
elements.next() as Int,
35+
elements.next() as Double,
36+
elements.next() as Boolean,
37+
elements.next() as String,
3838
)
3939
}
4040
}

docs/code/example/example-tuple-02.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ data class OptionalFields(
2222
element(OptionalFields::nullableOptionalString)
2323
}
2424
) {
25-
override fun tupleConstructor(elements: List<*>): OptionalFields {
25+
override fun tupleConstructor(elements: Iterator<*>): OptionalFields {
2626
val iter = elements.iterator()
2727
return OptionalFields(
2828
iter.next() as String,

docs/code/example/example-tuple-03.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ data class Coordinates(
2020
element(Coordinates::z)
2121
}
2222
) {
23-
override fun tupleConstructor(elements: List<*>): Coordinates {
23+
override fun tupleConstructor(elements: Iterator<*>): Coordinates {
2424
return Coordinates(
25-
elements[0] as Int,
26-
elements[1] as Int,
27-
elements[2] as Int,
25+
elements.next() as Int,
26+
elements.next() as Int,
27+
elements.next() as Int,
2828
)
2929
}
3030
}

docs/code/test/PolymorphismTest.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ class PolymorphismTest {
7575
.shouldBe(
7676
// language=TypeScript
7777
"""
78-
|export type Project = Project.DeprecatedProject | Project.OProj;
78+
|export type Project =
79+
| | Project.DeprecatedProject
80+
| | Project.OProj;
7981
|
8082
|export namespace Project {
8183
| export enum Type {
@@ -108,7 +110,10 @@ class PolymorphismTest {
108110
.shouldBe(
109111
// language=TypeScript
110112
"""
111-
|export type Dog = Dog.Golden | Dog.Mutt | Dog.NovaScotia;
113+
|export type Dog =
114+
| | Dog.Golden
115+
| | Dog.Mutt
116+
| | Dog.NovaScotia;
112117
|
113118
|export namespace Dog {
114119
| export enum Type {
@@ -185,7 +190,9 @@ class PolymorphismTest {
185190
.shouldBe(
186191
// language=TypeScript
187192
"""
188-
|export type Response = Response.EmptyResponse | Response.TextResponse;
193+
|export type Response =
194+
| | Response.EmptyResponse
195+
| | Response.TextResponse;
189196
|
190197
|export namespace Response {
191198
| export enum Type {

docs/polymorphism.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,9 @@ fun main() {
163163
> You can get the full code [here](./code/example/example-polymorphic-sealed-class-01.kt).
164164
165165
```typescript
166-
export type Project = Project.DeprecatedProject | Project.OProj;
166+
export type Project =
167+
| Project.DeprecatedProject
168+
| Project.OProj;
167169

168170
export namespace Project {
169171
export enum Type {
@@ -231,7 +233,10 @@ fun main() {
231233
> You can get the full code [here](./code/example/example-polymorphic-sealed-class-02.kt).
232234
233235
```typescript
234-
export type Dog = Dog.Golden | Dog.Mutt | Dog.NovaScotia;
236+
export type Dog =
237+
| Dog.Golden
238+
| Dog.Mutt
239+
| Dog.NovaScotia;
235240

236241
export namespace Dog {
237242
export enum Type {
@@ -322,7 +327,9 @@ fun main() {
322327
> You can get the full code [here](./code/example/example-polymorphic-objects-01.kt).
323328
324329
```typescript
325-
export type Response = Response.EmptyResponse | Response.TextResponse;
330+
export type Response =
331+
| Response.EmptyResponse
332+
| Response.TextResponse;
326333

327334
export namespace Response {
328335
export enum Type {

docs/tuples.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ data class SimpleTypes(
5858
element(SimpleTypes::privateMember)
5959
}
6060
) {
61-
override fun tupleConstructor(elements: List<*>): SimpleTypes {
61+
override fun tupleConstructor(elements: Iterator<*>): SimpleTypes {
6262
// When deserializing, the elements will be available as a list, in the order defined
6363
return SimpleTypes(
64-
elements[0] as String,
65-
elements[1] as Int,
66-
elements[2] as Double,
67-
elements[3] as Boolean,
68-
elements[4] as String,
64+
elements.next() as String,
65+
elements.next() as Int,
66+
elements.next() as Double,
67+
elements.next() as Boolean,
68+
elements.next() as String,
6969
)
7070
}
7171
}
@@ -111,7 +111,7 @@ data class OptionalFields(
111111
element(OptionalFields::nullableOptionalString)
112112
}
113113
) {
114-
override fun tupleConstructor(elements: List<*>): OptionalFields {
114+
override fun tupleConstructor(elements: Iterator<*>): OptionalFields {
115115
val iter = elements.iterator()
116116
return OptionalFields(
117117
iter.next() as String,
@@ -154,11 +154,11 @@ data class Coordinates(
154154
element(Coordinates::z)
155155
}
156156
) {
157-
override fun tupleConstructor(elements: List<*>): Coordinates {
157+
override fun tupleConstructor(elements: Iterator<*>): Coordinates {
158158
return Coordinates(
159-
elements[0] as Int,
160-
elements[1] as Int,
161-
elements[2] as Int,
159+
elements.next() as Int,
160+
elements.next() as Int,
161+
elements.next() as Int,
162162
)
163163
}
164164
}

modules/kxs-ts-gen-core/build.gradle.kts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ plugins {
66
buildsrc.convention.`maven-publish`
77
kotlin("plugin.serialization")
88
// id("org.jetbrains.reflekt")
9+
id("io.kotest.multiplatform")
910
}
1011

1112
val kotlinxSerializationVersion = "1.3.2"
13+
val kotestVersion = "5.2.2"
1214

1315
kotlin {
1416

@@ -70,6 +72,12 @@ kotlin {
7072
val commonTest by getting {
7173
dependencies {
7274
implementation(kotlin("test"))
75+
76+
implementation("io.kotest:kotest-assertions-core:$kotestVersion")
77+
implementation("io.kotest:kotest-assertions-json:$kotestVersion")
78+
implementation("io.kotest:kotest-property:$kotestVersion")
79+
implementation("io.kotest:kotest-framework-engine:$kotestVersion")
80+
implementation("io.kotest:kotest-framework-datatest:$kotestVersion")
7381
}
7482
}
7583
// val nativeMain by getting
@@ -81,6 +89,11 @@ kotlin {
8189
implementation(kotlin("reflect"))
8290
}
8391
}
84-
// val jvmTest by getting
92+
93+
val jvmTest by getting {
94+
dependencies {
95+
implementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion")
96+
}
97+
}
8598
}
8699
}

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsGenerator.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ open class KxsTsGenerator(
6868

6969
open fun generate(vararg serializers: KSerializer<*>): String {
7070
return serializers
71+
.toSet()
7172

7273
// 1. get all SerialDescriptors from a KSerializer
7374
.flatMap { serializer ->

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ abstract class KxsTsSourceCodeGenerator(
1717
is TsDeclaration.TsEnum -> generateEnum(element)
1818
is TsDeclaration.TsInterface -> generateInterface(element)
1919
is TsDeclaration.TsNamespace -> generateNamespace(element)
20-
is TsDeclaration.TsTypeAlias -> generateType(element)
20+
is TsDeclaration.TsTypeUnion -> generateTypeUnion(element)
21+
is TsDeclaration.TsTypeAlias -> generateTypeAlias(element)
2122
is TsDeclaration.TsTuple -> generateTuple(element)
2223
}
2324
}
2425

2526
abstract fun generateEnum(enum: TsDeclaration.TsEnum): String
2627
abstract fun generateInterface(element: TsDeclaration.TsInterface): String
2728
abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String
28-
abstract fun generateType(element: TsDeclaration.TsTypeAlias): String
29+
abstract fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String
30+
abstract fun generateTypeUnion(element: TsDeclaration.TsTypeUnion): String
2931
abstract fun generateTuple(tuple: TsDeclaration.TsTuple): String
3032

3133
abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String
@@ -117,17 +119,13 @@ abstract class KxsTsSourceCodeGenerator(
117119
}
118120

119121

120-
override fun generateType(element: TsDeclaration.TsTypeAlias): String {
121-
val aliases =
122-
element.typeRefs
123-
.map { generateTypeReference(it) }
124-
.sorted()
125-
.joinToString(" | ")
122+
override fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String {
123+
val aliasedRef = generateTypeReference(element.typeRef)
126124

127125
return when (config.typeAliasTyping) {
128126
KxsTsConfig.TypeAliasTypingConfig.None ->
129127
"""
130-
|export type ${element.id.name} = ${aliases};
128+
|export type ${element.id.name} = ${aliasedRef};
131129
""".trimMargin()
132130
KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> {
133131

@@ -141,12 +139,31 @@ abstract class KxsTsSourceCodeGenerator(
141139
}.joinToString("")
142140

143141
"""
144-
|export type ${element.id.name} = $aliases & { __${brandType}__: void };
142+
|export type ${element.id.name} = $aliasedRef & { __${brandType}__: void };
145143
""".trimMargin()
146144
}
147145
}
148146
}
149147

148+
override fun generateTypeUnion(element: TsDeclaration.TsTypeUnion): String {
149+
return if (element.typeRefs.isEmpty()) {
150+
"""
151+
|export type ${element.id.name} = ${generatePrimitive(TsLiteral.Primitive.TsUnknown)};
152+
""".trimMargin()
153+
} else {
154+
val aliases = element.typeRefs
155+
.map { "${config.indent}| ${generateTypeReference(it)}" }
156+
.sorted()
157+
.joinToString("\n")
158+
159+
"""
160+
¦export type ${element.id.name} =
161+
¦$aliases;
162+
""".trimMargin("¦")
163+
}
164+
}
165+
166+
150167
override fun generateTuple(tuple: TsDeclaration.TsTuple): String {
151168

152169
val types = tuple.elements.joinToString(separator = ", ") {

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,25 @@ fun interface SerializerDescriptorsExtractor {
6666
StructureKind.OBJECT -> descriptor.elementDescriptors
6767

6868
PolymorphicKind.SEALED,
69-
PolymorphicKind.OPEN -> descriptor
70-
.elementDescriptors
71-
.filter { it.kind is PolymorphicKind }
72-
.flatMap { it.elementDescriptors }
69+
PolymorphicKind.OPEN ->
70+
// Polymorphic descriptors have 2 elements, the 'type' and 'value' - we don't need either
71+
// for generation, they're metadata that will be used later.
72+
// The elements of 'value' are similarly unneeded, but their elements might contain new
73+
// descriptors - so extract them
74+
descriptor.elementDescriptors
75+
.flatMap { it.elementDescriptors }
76+
.flatMap { it.elementDescriptors }
77+
78+
// Example:
79+
// com.application.Polymorphic<MySealedClass>
80+
// ├── 'type' descriptor (ignore / it's a String, so check its elements, it doesn't hurt)
81+
// └── 'value' descriptor (check elements...)
82+
// ├── com.application.Polymorphic<Subclass1> (ignore)
83+
// │ ├── Double (extract!)
84+
// │ └── com.application.SomeOtherClass (extract!)
85+
// └── com.application.Polymorphic<Subclass2> (ignore)
86+
// ├── UInt (extract!)
87+
// └── List<com.application.AnotherClass (extract!
7388
}
7489
}
7590
}

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ fun interface TsElementConverter {
132132
}
133133

134134
// create type union and namespace
135-
val subInterfaceTypeUnion = TsDeclaration.TsTypeAlias(
135+
val subInterfaceTypeUnion = TsDeclaration.TsTypeUnion(
136136
namespaceId,
137137
subInterfaceRefs.keys
138138
)

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ abstract class TupleSerializer<T>(
9696
}
9797
private val indexedTupleElements = tupleElements.associateBy { it.index }
9898

99-
abstract fun tupleConstructor(elements: List<*>): T
99+
abstract fun tupleConstructor(elements: Iterator<*>): T
100100

101101
override val descriptor: SerialDescriptor = buildSerialDescriptor(
102102
serialName = serialName,
@@ -123,7 +123,7 @@ abstract class TupleSerializer<T>(
123123
generateSequence {
124124
val index = decodeElementIndex(descriptor)
125125
indexedTupleElements[index]?.decodeElement(this)
126-
}.toList()
126+
}.iterator()
127127
}
128128
return tupleConstructor(elements)
129129
}

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,18 @@ sealed interface TsDeclaration : TsElement {
3838
val id: TsElementId
3939

4040

41-
/** A named reference to one or more other types. */
41+
/** A named reference to another type. */
4242
data class TsTypeAlias(
43+
override val id: TsElementId,
44+
val typeRef: TsTypeRef,
45+
) : TsDeclaration
46+
47+
48+
/** A named reference to one or more other types. */
49+
data class TsTypeUnion(
4350
override val id: TsElementId,
4451
val typeRefs: Set<TsTypeRef>,
45-
) : TsDeclaration {
46-
constructor(id: TsElementId, typeRef: TsTypeRef, vararg typeRefs: TsTypeRef) :
47-
this(id, typeRefs.toSet() + typeRef)
48-
}
52+
) : TsDeclaration
4953

5054

5155
/** A [tuple type](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types). */

0 commit comments

Comments
 (0)