Skip to content
This repository was archived by the owner on Nov 6, 2019. It is now read-only.

Commit e2bdd29

Browse files
committed
Add and use EnhancedKtType
1 parent 5995c70 commit e2bdd29

17 files changed

+277
-130
lines changed

src/main/kotlin/converter/typeUtils.kt

Lines changed: 66 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,117 +8,109 @@ import typescriptServices.ts.*
88
import kotlin.collections.Map
99

1010
fun ObjectTypeToKotlinTypeMapper.mapType(type: Node): KtType {
11-
val resolvedType: Type? = typeChecker.getTypeAtLocation(type)
12-
return if (resolvedType != null) {
13-
mapType(resolvedType, type)
14-
}
15-
else {
16-
(type.unsafeCast<TypeNode>()).toKotlinType(this)
17-
}
11+
return mapToEnhancedType(type).singleType
1812
}
1913

20-
fun ObjectTypeToKotlinTypeMapper.mapTypeToUnion(type: Node): KtTypeUnion {
14+
fun ObjectTypeToKotlinTypeMapper.mapToEnhancedType(type: Node): EnhancedKtType {
2115
val resolvedType: Type? = typeChecker.getTypeAtLocation(type)
2216
return if (resolvedType != null) {
23-
mapTypeToUnion(resolvedType, type)
17+
mapToEnhancedType(resolvedType, type)
2418
}
2519
else {
26-
(type.unsafeCast<TypeNode>()).toKotlinTypeUnion(this)
20+
(type.unsafeCast<TypeNode>()).toEnhancedType(this)
2721
}
2822
}
2923

3024
private fun ObjectTypeToKotlinTypeMapper.mapType(type: Type, declaration: Node?): KtType =
31-
mapTypeToUnion(type, declaration).singleType
25+
mapToEnhancedType(type, declaration).singleType
3226

33-
private fun ObjectTypeToKotlinTypeMapper.mapTypeToUnion(type: Type, declaration: Node?): KtTypeUnion {
27+
private fun ObjectTypeToKotlinTypeMapper.mapToEnhancedType(type: Type, declaration: Node?): EnhancedKtType {
3428
val resultingDeclaration = declaration ?: type.symbol?.declarations?.singleOrNull()
3529

3630
val flags = type.getFlags()
3731
if (resultingDeclaration != null && type.symbol == null && TypeFlags.Any in flags) {
38-
return resultingDeclaration.unsafeCast<TypeNode>().toKotlinTypeUnion(this)
32+
return resultingDeclaration.unsafeCast<TypeNode>().toEnhancedType(this)
3933
}
4034

4135
if (resultingDeclaration?.kind == SyntaxKind.UnionType && TypeFlags.Union !in flags) {
42-
return resultingDeclaration.unsafeCast<UnionTypeNode>().toKotlinTypeUnion(this)
36+
return resultingDeclaration.unsafeCast<UnionTypeNode>().toEnhancedType(this)
4337
}
4438

4539
if (type in typesInMappingProcess) {
4640
report(
4741
"Recursion is detected when resolve type: \"${type.symbol?.name}\" for the declaration at ${declaration?.location()}",
4842
maxLevelToShow = DiagnosticLevel.WARNING_WITH_STACKTRACE
4943
)
50-
return KtTypeUnion(KtType(DYNAMIC))
44+
return SingleKtType(KtType(DYNAMIC))
5145
}
5246

5347
typesInMappingProcess += type
5448

5549
val mappedType = when {
56-
declaration?.kind == SyntaxKind.ThisType -> {
57-
val possibleTypes = mapTypeToUnion(type.unsafeCast<TypeParameter>().constraint!!, null).possibleTypes
58-
.map { it.copy(comment = "this") }
59-
KtTypeUnion(possibleTypes)
60-
}
50+
declaration?.kind == SyntaxKind.ThisType -> mapToEnhancedType(type.unsafeCast<TypeParameter>().constraint!!, null).withComment("this")
6151

62-
TypeFlags.Any in flags -> KtTypeUnion(KtType(ANY))
63-
TypeFlags.String in flags -> KtTypeUnion(KtType(STRING))
64-
TypeFlags.Boolean in flags -> KtTypeUnion(KtType(BOOLEAN))
65-
TypeFlags.Number in flags -> KtTypeUnion(KtType(NUMBER))
66-
TypeFlags.Void in flags -> KtTypeUnion(KtType(UNIT))
52+
TypeFlags.Any in flags -> SingleKtType(KtType(ANY))
53+
TypeFlags.String in flags -> SingleKtType(KtType(STRING))
54+
TypeFlags.Boolean in flags -> SingleKtType(KtType(BOOLEAN))
55+
TypeFlags.Number in flags -> SingleKtType(KtType(NUMBER))
56+
TypeFlags.Void in flags -> SingleKtType(KtType(UNIT))
6757

6858
TypeFlags.Undefined in flags ||
69-
TypeFlags.Null in flags -> KtTypeUnion(KtType(NOTHING, isNullable = true))
59+
TypeFlags.Null in flags -> SingleKtType(KtType(NOTHING, isNullable = true))
7060

7161
TypeFlags.StringLiteral in flags -> {
72-
KtTypeUnion(KtType(STRING, comment = "\"" + type.unsafeCast<LiteralType>().value + "\""))
62+
SingleKtType(KtType(STRING, comment = "\"" + type.unsafeCast<LiteralType>().value + "\""))
7363
}
7464
// TODO: add test if it's allowed
7565
TypeFlags.NumberLiteral in flags -> {
76-
KtTypeUnion(KtType(NUMBER, comment = type.unsafeCast<LiteralType>().value))
66+
SingleKtType(KtType(NUMBER, comment = type.unsafeCast<LiteralType>().value))
7767
}
7868
// TODO: add test if it's allowed
7969
TypeFlags.BooleanLiteral in flags -> {
80-
KtTypeUnion(KtType(BOOLEAN, comment = type.unsafeCast<LiteralType>().value))
70+
SingleKtType(KtType(BOOLEAN, comment = type.unsafeCast<LiteralType>().value))
8171
}
8272

8373
TypeFlags.Union in flags -> mapUnionType(type.unsafeCast<UnionType>())
8474
TypeFlags.Intersection in flags -> mapIntersectionType(type.unsafeCast<IntersectionType>())
8575

86-
TypeFlags.TypeParameter in flags -> KtTypeUnion(KtType(KtQualifiedName(unescapeIdentifier(type.getSymbol()!!.name))))
76+
TypeFlags.TypeParameter in flags -> SingleKtType(KtType(KtQualifiedName(unescapeIdentifier(type.getSymbol()!!.name))))
8777

8878
TypeFlags.Object in flags -> {
8979
val objectFlags = (type as ObjectType).objectFlags
9080
when {
91-
ObjectFlags.Anonymous in objectFlags -> KtTypeUnion(mapAnonymousType(type, declaration))
92-
ObjectFlags.ClassOrInterface in objectFlags -> KtTypeUnion(mapInterfaceType(type.unsafeCast<InterfaceType>(), declaration))
93-
ObjectFlags.Reference in objectFlags -> KtTypeUnion(mapTypeReference(type.unsafeCast<TypeReference>(), declaration))
81+
ObjectFlags.Anonymous in objectFlags -> SingleKtType(mapAnonymousType(type, declaration))
82+
ObjectFlags.ClassOrInterface in objectFlags -> SingleKtType(mapInterfaceType(type.unsafeCast<InterfaceType>(), declaration))
83+
ObjectFlags.Reference in objectFlags -> SingleKtType(mapTypeReference(type.unsafeCast<TypeReference>(), declaration))
9484

95-
else -> KtTypeUnion(KtType(ANY, isNullable = true))
85+
else -> SingleKtType(KtType(ANY, isNullable = true))
9686
}
9787
}
9888

99-
TypeFlags.Enum in flags -> KtTypeUnion(mapObjectType(type.unsafeCast<ObjectType>()))
89+
TypeFlags.Enum in flags -> SingleKtType(mapObjectType(type.unsafeCast<ObjectType>()))
10090

101-
else -> KtTypeUnion(KtType(ANY, isNullable = true))
91+
else -> SingleKtType(KtType(ANY, isNullable = true))
10292
}
10393

10494
typesInMappingProcess -= type
10595

10696
return mappedType
10797
}
10898

109-
private fun ObjectTypeToKotlinTypeMapper.mapUnionType(type: UnionOrIntersectionType): KtTypeUnion {
110-
val notNullTypes = type.types.filter {
111-
TypeFlags.Undefined !in it.getFlags() &&
112-
TypeFlags.Null !in it.getFlags()
113-
}
114-
val nullable = notNullTypes.size != type.types.size || type.containsNull || type.containsUndefined
99+
private fun ObjectTypeToKotlinTypeMapper.mapUnionType(type: UnionType): EnhancedKtType {
100+
val forceNullable = type.containsNull || type.containsUndefined
101+
return mapUnionType(type.types.map { mapToEnhancedType(it, null) }).forceNullable(forceNullable)
102+
}
115103

116-
val mappedTypes = notNullTypes.map { mapType(it, null) }
117-
return KtTypeUnion(when {
118-
!nullable -> mappedTypes.mergeToPreventCompilationConflicts()
119-
notNullTypes.size == 1 -> mappedTypes.map { it.copy(isNullable = true) }
120-
else -> (mappedTypes + KtType(NOTHING, isNullable = true)).distinct()
121-
})
104+
/**
105+
* Normalize to a KtTypeUnion such that equals will be true if the KotlinJS compiler would consider them
106+
* conflicting if both were the only parameter to identically named functions.
107+
*/
108+
private fun EnhancedKtType.normalizeToDetectCompilationConflicts(): EnhancedKtType {
109+
return when (this) {
110+
is KtTypeUnion -> copy(possibleTypes = possibleTypes.map { it.normalizeToDetectCompilationConflicts() })
111+
is KtTypeIntersection -> copy(requiredTypes = requiredTypes.map { it.normalizeToDetectCompilationConflicts() })
112+
is SingleKtType -> copy(singleType = singleType.normalizeToDetectCompilationConflicts())
113+
}
122114
}
123115

124116
/**
@@ -134,7 +126,19 @@ private fun KtType.normalizeToDetectCompilationConflicts(): KtType {
134126
* Comments are not taken into account for the comparison, but are preserved by concatenation using "|" since a "union".
135127
* This is especially useful for Typescript string literal unions.
136128
*/
137-
private fun List<KtType>.mergeToPreventCompilationConflicts(): List<KtType> {
129+
fun List<EnhancedKtType>.mergeToPreventCompilationConflicts(): List<EnhancedKtType> {
130+
return groupBy { it.normalizeToDetectCompilationConflicts() }.map { entry ->
131+
val comments = entry.value.mapNotNull { it.comment }
132+
entry.key.withComment(comment = if (comments.isEmpty()) null else comments.joinToString(" | "))
133+
}
134+
}
135+
136+
/**
137+
* Handle the case where a function is overloaded with the same Kotlin parameter types by merging them.
138+
* Comments are not taken into account for the comparison, but are preserved by concatenation using "|" since a "union".
139+
* This is especially useful for Typescript string literal unions.
140+
*/
141+
fun List<KtType>.mergeToPreventCompilationConflicts(): List<KtType> {
138142
return groupBy { it.normalizeToDetectCompilationConflicts() }.map { entry ->
139143
val comments = entry.value.mapNotNull { it.comment }
140144
entry.key.copy(comment = if (comments.isEmpty()) null else comments.joinToString(" | "))
@@ -153,10 +157,8 @@ private inline val UnionOrIntersectionType.containsNull: Boolean
153157
return jsTypeOf(array.containsNull) == "boolean" && array.containsNull.unsafeCast<Boolean>()
154158
}
155159

156-
private fun ObjectTypeToKotlinTypeMapper.mapIntersectionType(type: IntersectionType): KtTypeUnion {
157-
return KtTypeUnion(mapType(type.types.first(), null).copy(
158-
comment = type.types.joinToString(" & ") { mapType(it, null).stringify() }
159-
))
160+
private fun ObjectTypeToKotlinTypeMapper.mapIntersectionType(type: IntersectionType): EnhancedKtType {
161+
return KtTypeIntersection(type.types.map { SingleKtType(mapType(it, null)) })
160162
}
161163

162164
private fun ObjectTypeToKotlinTypeMapper.mapTypeReference(type: TypeReference, declaration: Node?): KtType {
@@ -184,7 +186,7 @@ private fun ObjectTypeToKotlinTypeMapper.mapInterfaceType(type: InterfaceType, d
184186

185187
private fun ObjectTypeToKotlinTypeMapper.mapTypeArguments(
186188
typeArguments: Array<Type>?, declaration: Node?
187-
): Sequence<KtType> {
189+
): Sequence<EnhancedKtType> {
188190
val typeArgsFromDeclaration = if (declaration != null) {
189191
when (declaration.kind as Any) {
190192
SyntaxKind.ExpressionWithTypeArguments,
@@ -211,14 +213,14 @@ private fun ObjectTypeToKotlinTypeMapper.mapTypeArguments(
211213
.asSequence()
212214
.zip(typeArgsFromDeclaration + generateSequence { 0 }.map { null })
213215
return typeArgsWithDeclarations.map { (argType, arg) ->
214-
mapType(argType, arg)
216+
mapToEnhancedType(argType, arg)
215217
}
216218
}
217219

218220
// TODO: is it correct name???
219221
private fun ObjectTypeToKotlinTypeMapper.mapObjectType(type: Type): KtType {
220222
val fqn = buildFqn(type.getSymbol()!!)
221-
if (fqn == KtQualifiedName("Function")) return KtType(KtQualifiedName("Function"), typeArgs = listOf(starType()))
223+
if (fqn == KtQualifiedName("Function")) return KtType(KtQualifiedName("Function"), typeArgs = listOf(SingleKtType(starType())))
222224
return KtType(when (fqn) {
223225
KtQualifiedName("Object") -> ANY
224226
else -> {
@@ -326,6 +328,14 @@ fun KtType.replaceTypeParameters(substitution: kotlin.collections.Map<String, Kt
326328
)
327329
}
328330

331+
fun EnhancedKtType.replaceTypeParameters(substitution: kotlin.collections.Map<String, KtType>): EnhancedKtType {
332+
return when (this) {
333+
is KtTypeUnion -> this.copy(possibleTypes = this.possibleTypes.map { it.replaceTypeParameters(substitution) })
334+
is KtTypeIntersection -> this.copy(requiredTypes = this.requiredTypes.map { it.replaceTypeParameters(substitution) })
335+
is SingleKtType -> copy(singleType = singleType.replaceTypeParameters(substitution))
336+
}
337+
}
338+
329339
private fun KtCallSignature.replaceTypeParameters(substitution: Map<String, KtType>): KtCallSignature =
330340
copy(
331341
params = params.map { it.replaceTypeParameters(substitution) },

src/main/kotlin/ts2kt/TsClassifierToKt.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package ts2kt
22

33
import converter.mapType
4-
import converter.mapTypeToUnion
4+
import converter.mapToEnhancedType
55
import ts2kt.kotlin.ast.*
66
import ts2kt.utils.assert
77
import typescriptServices.ts.*
@@ -32,12 +32,17 @@ abstract class TsClassifierToKt(
3232
private fun translateAccessor(node: IndexSignatureDeclaration, isGetter: Boolean, extendsType: KtType?) {
3333
// TODO type params?
3434
node.parameters.toKotlinParamsOverloads(typeMapper).forEach { params ->
35-
val propTypeUnion = if (isGetter) {
36-
KtTypeUnion(node.type?.let { typeMapper.mapType(it) } ?: KtType(ANY))
35+
val enhancedType = if (isGetter) {
36+
SingleKtType(node.type?.let { typeMapper.mapType(it) } ?: KtType(ANY))
3737
} else {
38-
node.type?.let { typeMapper.mapTypeToUnion(it) } ?: KtTypeUnion(KtType(ANY))
38+
node.type?.let { typeMapper.mapToEnhancedType(it) } ?: SingleKtType(KtType(ANY))
3939
}
40-
propTypeUnion.possibleTypes.forEach { propType ->
40+
val possibleTypes = if (enhancedType is KtTypeUnion) {
41+
enhancedType.possibleTypes
42+
} else {
43+
listOf(enhancedType.singleType)
44+
}
45+
possibleTypes.forEach { propType ->
4146
val callSignature: KtCallSignature
4247
val accessorName: String
4348
val annotation: KtAnnotation?

src/main/kotlin/ts2kt/TsInterfaceToKtExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class TsInterfaceToKtExtensions(
1616

1717
val cachedExtendsType by lazy { getExtendsType(typeParams) }
1818

19-
private fun getExtendsType(typeParams: List<KtTypeParam>?) = KtType(KtQualifiedName(name!!), typeParams?.map { KtType(KtQualifiedName(it.name)) } ?: emptyList())
19+
private fun getExtendsType(typeParams: List<KtTypeParam>?) = KtType(KtQualifiedName(name!!), typeParams?.map { SingleKtType(KtType(KtQualifiedName(it.name))) } ?: emptyList())
2020

2121
fun List<KtTypeParam>?.fixIfClashWith(another: List<KtTypeParam>?): List<KtTypeParam>? {
2222
if (this == null || another == null) return this

src/main/kotlin/ts2kt/kotlin/ast/KtVisitor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface KtVisitor {
1313
fun visitTypeParam(typeParam: KtTypeParam)
1414
fun visitTypeAnnotation(typeAnnotation: KtTypeAnnotation)
1515
fun visitType(type: KtType)
16+
fun visitTypeIntersection(typeIntersection: KtTypeIntersection)
1617
fun visitTypeUnion(typeUnion: KtTypeUnion)
1718
fun visitHeritageType(heritageType: KtHeritageType)
1819
fun visitArgument(argument: KtArgument)

src/main/kotlin/ts2kt/kotlin/ast/Stringify.kt

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ private val VAL = "val"
2222
private val FUN = "fun"
2323
private val VARARG = "vararg"
2424

25-
2625
class Stringify(
2726
private val packagePartPrefix: String?,
2827
private val topLevel: Boolean,
2928
private val additionalImports: List<String> = listOf(),
3029
private val suppressedDiagnostics: List<String> = listOf(),
30+
private val allowEnhanced: Boolean = false,
3131
private val out: Output = Output()
3232
) : KtVisitor {
3333
val result: String
@@ -346,9 +346,11 @@ class Stringify(
346346
enumEntry.value?.let { out.print(" /* = $it */") }
347347
}
348348

349+
private val EnhancedKtType.singleTypeIfNeeded: KtNode get() = if (allowEnhanced) this else singleType
350+
349351
override fun visitTypeParam(typeParam: KtTypeParam) {
350352
out.print(typeParam.name.asString())
351-
typeParam.upperBound?.let {
353+
typeParam.upperBound?.singleTypeIfNeeded?.let {
352354
out.print(" : ")
353355
it.accept(this)
354356
}
@@ -383,7 +385,7 @@ class Stringify(
383385
else {
384386
out.print(qualifiedName.asString())
385387

386-
typeArgs.acceptForEach(this@Stringify, ", ", startWithIfNotEmpty = "<", endWithIfNotEmpty = ">")
388+
typeArgs.map { it.singleTypeIfNeeded }.acceptForEach(this@Stringify, ", ", startWithIfNotEmpty = "<", endWithIfNotEmpty = ">")
387389
}
388390

389391
if (isNullable && qualifiedName != DYNAMIC) {
@@ -396,6 +398,20 @@ class Stringify(
396398
}
397399
}
398400

401+
override fun visitTypeIntersection(typeIntersection: KtTypeIntersection) {
402+
if (typeIntersection.requiredTypes.size > 1 && typeIntersection.isNullable) {
403+
out.print("(")
404+
typeIntersection.requiredTypes.acceptForEach(this, " & ")
405+
out.print(")?")
406+
} else {
407+
typeIntersection.requiredTypes.acceptForEach(this, " & ")
408+
if (typeIntersection.isNullable) {
409+
out.print("?")
410+
}
411+
}
412+
}
413+
414+
399415
override fun visitTypeUnion(typeUnion: KtTypeUnion) {
400416
typeUnion.possibleTypes.acceptForEach(this, " | ")
401417
}
@@ -415,5 +431,5 @@ class Stringify(
415431
}
416432

417433
private fun innerStringifier() =
418-
Stringify(packagePartPrefix, /*topLevel = */false, additionalImports, suppressedDiagnostics, out)
434+
Stringify(packagePartPrefix, /*topLevel = */false, additionalImports, suppressedDiagnostics, allowEnhanced, out)
419435
}

0 commit comments

Comments
 (0)