diff --git a/src/main/kotlin/converter/typeUtils.kt b/src/main/kotlin/converter/typeUtils.kt index 87a67ab..c79304c 100644 --- a/src/main/kotlin/converter/typeUtils.kt +++ b/src/main/kotlin/converter/typeUtils.kt @@ -8,38 +8,36 @@ import typescriptServices.ts.* import kotlin.collections.Map fun ObjectTypeToKotlinTypeMapper.mapType(type: Node): KtType { - val resolvedType: Type? = typeChecker.getTypeAtLocation(type) - return if (resolvedType != null) { - mapType(resolvedType, type) - } - else { - (type.unsafeCast()).toKotlinType(this) - } + return mapToEnhancedType(type).singleType } -fun ObjectTypeToKotlinTypeMapper.mapTypeToUnion(type: Node): KtTypeUnion { +fun ObjectTypeToKotlinTypeMapper.mapToEnhancedType(type: Node): EnhancedKtType { val resolvedType: Type? = typeChecker.getTypeAtLocation(type) return if (resolvedType != null) { - mapTypeToUnion(resolvedType, type) + mapToEnhancedType(resolvedType, type) } else { - (type.unsafeCast()).toKotlinTypeUnion(this) + (type.unsafeCast()).toEnhancedType(this) } } private fun ObjectTypeToKotlinTypeMapper.mapType(type: Type, declaration: Node?): KtType = - mapTypeToUnion(type, declaration).singleType + mapToEnhancedType(type, declaration).singleType -private fun ObjectTypeToKotlinTypeMapper.mapTypeToUnion(type: Type, declaration: Node?): KtTypeUnion { +private fun ObjectTypeToKotlinTypeMapper.mapToEnhancedType(type: Type, declaration: Node?): EnhancedKtType { val resultingDeclaration = declaration ?: type.symbol?.declarations?.singleOrNull() val flags = type.getFlags() if (resultingDeclaration != null && type.symbol == null && TypeFlags.Any in flags) { - return resultingDeclaration.unsafeCast().toKotlinTypeUnion(this) + return resultingDeclaration.unsafeCast().toEnhancedType(this) } - if (resultingDeclaration?.kind == SyntaxKind.UnionType && TypeFlags.Union !in flags) { - return resultingDeclaration.unsafeCast().toKotlinTypeUnion(this) + if (resultingDeclaration?.kind == SyntaxKind.UnionType) { + return resultingDeclaration.unsafeCast().toEnhancedType(this) + } + + if (TypeFlags.Union in flags) { + return mapUnionType(type.unsafeCast()) } if (type in typesInMappingProcess) { @@ -47,58 +45,53 @@ private fun ObjectTypeToKotlinTypeMapper.mapTypeToUnion(type: Type, declaration: "Recursion is detected when resolve type: \"${type.symbol?.name}\" for the declaration at ${declaration?.location()}", maxLevelToShow = DiagnosticLevel.WARNING_WITH_STACKTRACE ) - return KtTypeUnion(KtType(DYNAMIC)) + return SingleKtType(KtType(DYNAMIC)) } typesInMappingProcess += type val mappedType = when { - declaration?.kind == SyntaxKind.ThisType -> { - val possibleTypes = mapTypeToUnion(type.unsafeCast().constraint!!, null).possibleTypes - .map { it.copy(comment = "this") } - KtTypeUnion(possibleTypes) - } + declaration?.kind == SyntaxKind.ThisType -> mapToEnhancedType(type.unsafeCast().constraint!!, null).withComment("this") - TypeFlags.Any in flags -> KtTypeUnion(KtType(ANY)) - TypeFlags.String in flags -> KtTypeUnion(KtType(STRING)) - TypeFlags.Boolean in flags -> KtTypeUnion(KtType(BOOLEAN)) - TypeFlags.Number in flags -> KtTypeUnion(KtType(NUMBER)) - TypeFlags.Void in flags -> KtTypeUnion(KtType(UNIT)) + TypeFlags.Any in flags -> SingleKtType(KtType(ANY)) + TypeFlags.String in flags -> SingleKtType(KtType(STRING)) + TypeFlags.Boolean in flags -> SingleKtType(KtType(BOOLEAN)) + TypeFlags.Number in flags -> SingleKtType(KtType(NUMBER)) + TypeFlags.Void in flags -> SingleKtType(KtType(UNIT)) TypeFlags.Undefined in flags || - TypeFlags.Null in flags -> KtTypeUnion(KtType(NOTHING, isNullable = true)) + TypeFlags.Null in flags -> SingleKtType(KtType(NOTHING, isNullable = true)) TypeFlags.StringLiteral in flags -> { - KtTypeUnion(KtType(STRING, comment = "\"" + type.unsafeCast().value + "\"")) + SingleKtType(KtType(STRING, comment = "\"" + type.unsafeCast().value + "\"")) } // TODO: add test if it's allowed TypeFlags.NumberLiteral in flags -> { - KtTypeUnion(KtType(NUMBER, comment = type.unsafeCast().value)) + SingleKtType(KtType(NUMBER, comment = type.unsafeCast().value)) } // TODO: add test if it's allowed TypeFlags.BooleanLiteral in flags -> { - KtTypeUnion(KtType(BOOLEAN, comment = type.unsafeCast().value)) + SingleKtType(KtType(BOOLEAN, comment = type.unsafeCast().value)) } - TypeFlags.Union in flags -> mapUnionType(type.unsafeCast()) TypeFlags.Intersection in flags -> mapIntersectionType(type.unsafeCast()) - TypeFlags.TypeParameter in flags -> KtTypeUnion(KtType(KtQualifiedName(unescapeIdentifier(type.getSymbol()!!.name)))) + TypeFlags.TypeParameter in flags -> SingleKtType(KtType(KtQualifiedName(unescapeIdentifier(type.getSymbol()!!.name)))) TypeFlags.Object in flags -> { val objectFlags = (type as ObjectType).objectFlags when { - ObjectFlags.Anonymous in objectFlags -> KtTypeUnion(mapAnonymousType(type, declaration)) - ObjectFlags.ClassOrInterface in objectFlags -> KtTypeUnion(mapInterfaceType(type.unsafeCast(), declaration)) - ObjectFlags.Reference in objectFlags -> KtTypeUnion(mapTypeReference(type.unsafeCast(), declaration)) + ObjectFlags.Anonymous in objectFlags -> SingleKtType(mapAnonymousType(type, declaration)) + ObjectFlags.ClassOrInterface in objectFlags -> SingleKtType(mapInterfaceType(type.unsafeCast(), declaration)) + ObjectFlags.Reference in objectFlags -> SingleKtType(mapTypeReference(type.unsafeCast(), declaration)) - else -> KtTypeUnion(KtType(ANY, isNullable = true)) + else -> SingleKtType(KtType(ANY, isNullable = true)) } } - TypeFlags.Enum in flags -> KtTypeUnion(mapObjectType(type.unsafeCast())) + TypeFlags.Enum in flags -> SingleKtType(mapObjectType(type.unsafeCast())) - else -> KtTypeUnion(KtType(ANY, isNullable = true)) + else -> SingleKtType(KtType(ANY, isNullable = true)) } typesInMappingProcess -= type @@ -106,19 +99,53 @@ private fun ObjectTypeToKotlinTypeMapper.mapTypeToUnion(type: Type, declaration: return mappedType } -private fun ObjectTypeToKotlinTypeMapper.mapUnionType(type: UnionOrIntersectionType): KtTypeUnion { - val notNullTypes = type.types.filter { - TypeFlags.Undefined !in it.getFlags() && - TypeFlags.Null !in it.getFlags() +private fun ObjectTypeToKotlinTypeMapper.mapUnionType(type: UnionType): EnhancedKtType { + val forceNullable = type.containsNull || type.containsUndefined + return mapUnionType(type.types.map { mapToEnhancedType(it, null) }).forceNullable(forceNullable) +} + +/** + * Normalize to a KtTypeUnion such that equals will be true if the KotlinJS compiler would consider them + * conflicting if both were the only parameter to identically named functions. + */ +private fun EnhancedKtType.normalizeToDetectCompilationConflicts(): EnhancedKtType { + return when (this) { + is KtTypeUnion -> copy(possibleTypes = possibleTypes.map { it.normalizeToDetectCompilationConflicts() }) + is KtTypeIntersection -> copy(requiredTypes = requiredTypes.map { it.normalizeToDetectCompilationConflicts() }) + is SingleKtType -> copy(singleType = singleType.normalizeToDetectCompilationConflicts()) } - val nullable = notNullTypes.size != type.types.size || type.containsNull || type.containsUndefined +} - val mappedTypes = notNullTypes.map { mapType(it, null) } - return KtTypeUnion(when { - !nullable -> mappedTypes.distinct() - notNullTypes.size == 1 -> mappedTypes.map { it.copy(isNullable = true) } - else -> (mappedTypes + KtType(NOTHING, isNullable = true)).distinct() - }) +/** + * Normalize to a KtType such that equals will be true if the KotlinJS compiler would consider them + * conflicting if both were the only parameter to identically named functions. + */ +private fun KtType.normalizeToDetectCompilationConflicts(): KtType { + return copy(comment = null, typeArgs = typeArgs.map { it.normalizeToDetectCompilationConflicts() }) +} + +/** + * Handle the case where a function is overloaded with the same Kotlin parameter types by merging them. + * Comments are not taken into account for the comparison, but are preserved by concatenation using "|" since a "union". + * This is especially useful for Typescript string literal unions. + */ +fun List.mergeToPreventCompilationConflicts(): List { + return groupBy { it.normalizeToDetectCompilationConflicts() }.map { entry -> + val comments = entry.value.mapNotNull { it.comment } + entry.key.withComment(comment = if (comments.isEmpty()) null else comments.joinToString(" | ")) + } +} + +/** + * Handle the case where a function is overloaded with the same Kotlin parameter types by merging them. + * Comments are not taken into account for the comparison, but are preserved by concatenation using "|" since a "union". + * This is especially useful for Typescript string literal unions. + */ +fun List.mergeToPreventCompilationConflicts(): List { + return groupBy { it.normalizeToDetectCompilationConflicts() }.map { entry -> + val comments = entry.value.mapNotNull { it.comment } + entry.key.copy(comment = if (comments.isEmpty()) null else comments.joinToString(" | ")) + } } private inline val UnionOrIntersectionType.containsUndefined: Boolean @@ -133,10 +160,8 @@ private inline val UnionOrIntersectionType.containsNull: Boolean return jsTypeOf(array.containsNull) == "boolean" && array.containsNull.unsafeCast() } -private fun ObjectTypeToKotlinTypeMapper.mapIntersectionType(type: IntersectionType): KtTypeUnion { - return KtTypeUnion(mapType(type.types.first(), null).copy( - comment = type.types.joinToString(" & ") { mapType(it, null).stringify() } - )) +private fun ObjectTypeToKotlinTypeMapper.mapIntersectionType(type: IntersectionType): EnhancedKtType { + return KtTypeIntersection(type.types.map { SingleKtType(mapType(it, null)) }) } private fun ObjectTypeToKotlinTypeMapper.mapTypeReference(type: TypeReference, declaration: Node?): KtType { @@ -164,7 +189,7 @@ private fun ObjectTypeToKotlinTypeMapper.mapInterfaceType(type: InterfaceType, d private fun ObjectTypeToKotlinTypeMapper.mapTypeArguments( typeArguments: Array?, declaration: Node? -): Sequence { +): Sequence { val typeArgsFromDeclaration = if (declaration != null) { when (declaration.kind as Any) { SyntaxKind.ExpressionWithTypeArguments, @@ -191,14 +216,14 @@ private fun ObjectTypeToKotlinTypeMapper.mapTypeArguments( .asSequence() .zip(typeArgsFromDeclaration + generateSequence { 0 }.map { null }) return typeArgsWithDeclarations.map { (argType, arg) -> - mapType(argType, arg) + mapToEnhancedType(argType, arg) } } // TODO: is it correct name??? private fun ObjectTypeToKotlinTypeMapper.mapObjectType(type: Type): KtType { val fqn = buildFqn(type.getSymbol()!!) - if (fqn == KtQualifiedName("Function")) return KtType(KtQualifiedName("Function"), typeArgs = listOf(starType())) + if (fqn == KtQualifiedName("Function")) return KtType(KtQualifiedName("Function"), typeArgs = listOf(SingleKtType(starType()))) return KtType(when (fqn) { KtQualifiedName("Object") -> ANY else -> { @@ -306,6 +331,14 @@ fun KtType.replaceTypeParameters(substitution: kotlin.collections.Map): EnhancedKtType { + return when (this) { + is KtTypeUnion -> this.copy(possibleTypes = this.possibleTypes.map { it.replaceTypeParameters(substitution) }) + is KtTypeIntersection -> this.copy(requiredTypes = this.requiredTypes.map { it.replaceTypeParameters(substitution) }) + is SingleKtType -> copy(singleType = singleType.replaceTypeParameters(substitution)) + } +} + private fun KtCallSignature.replaceTypeParameters(substitution: Map): KtCallSignature = copy( params = params.map { it.replaceTypeParameters(substitution) }, diff --git a/src/main/kotlin/ts2kt/TsClassifierToKt.kt b/src/main/kotlin/ts2kt/TsClassifierToKt.kt index 79bca81..e38e8af 100644 --- a/src/main/kotlin/ts2kt/TsClassifierToKt.kt +++ b/src/main/kotlin/ts2kt/TsClassifierToKt.kt @@ -1,7 +1,7 @@ package ts2kt import converter.mapType -import converter.mapTypeToUnion +import converter.mapToEnhancedType import ts2kt.kotlin.ast.* import ts2kt.utils.assert import typescriptServices.ts.* @@ -32,12 +32,17 @@ abstract class TsClassifierToKt( private fun translateAccessor(node: IndexSignatureDeclaration, isGetter: Boolean, extendsType: KtType?) { // TODO type params? node.parameters.toKotlinParamsOverloads(typeMapper).forEach { params -> - val propTypeUnion = if (isGetter) { - KtTypeUnion(node.type?.let { typeMapper.mapType(it) } ?: KtType(ANY)) + val propEnhancedType = if (isGetter) { + SingleKtType(node.type?.let { typeMapper.mapType(it) } ?: KtType(ANY)) } else { - node.type?.let { typeMapper.mapTypeToUnion(it) } ?: KtTypeUnion(KtType(ANY)) + node.type?.let { typeMapper.mapToEnhancedType(it) } ?: SingleKtType(KtType(ANY)) } - propTypeUnion.possibleTypes.forEach { propType -> + val possibleTypes = if (propEnhancedType is KtTypeUnion) { + propEnhancedType.possibleTypes + } else { + listOf(propEnhancedType.singleType) + } + possibleTypes.forEach { propType -> val callSignature: KtCallSignature val accessorName: String val annotation: KtAnnotation? diff --git a/src/main/kotlin/ts2kt/TsInterfaceToKtExtensions.kt b/src/main/kotlin/ts2kt/TsInterfaceToKtExtensions.kt index 5c43295..0de8639 100644 --- a/src/main/kotlin/ts2kt/TsInterfaceToKtExtensions.kt +++ b/src/main/kotlin/ts2kt/TsInterfaceToKtExtensions.kt @@ -16,7 +16,7 @@ class TsInterfaceToKtExtensions( val cachedExtendsType by lazy { getExtendsType(typeParams) } - private fun getExtendsType(typeParams: List?) = KtType(KtQualifiedName(name!!), typeParams?.map { KtType(KtQualifiedName(it.name)) } ?: emptyList()) + private fun getExtendsType(typeParams: List?) = KtType(KtQualifiedName(name!!), typeParams?.map { SingleKtType(KtType(KtQualifiedName(it.name))) } ?: emptyList()) fun List?.fixIfClashWith(another: List?): List? { if (this == null || another == null) return this diff --git a/src/main/kotlin/ts2kt/kotlin/ast/KtVisitor.kt b/src/main/kotlin/ts2kt/kotlin/ast/KtVisitor.kt index 9159bae..c04e476 100644 --- a/src/main/kotlin/ts2kt/kotlin/ast/KtVisitor.kt +++ b/src/main/kotlin/ts2kt/kotlin/ast/KtVisitor.kt @@ -13,6 +13,7 @@ interface KtVisitor { fun visitTypeParam(typeParam: KtTypeParam) fun visitTypeAnnotation(typeAnnotation: KtTypeAnnotation) fun visitType(type: KtType) + fun visitTypeIntersection(typeIntersection: KtTypeIntersection) fun visitTypeUnion(typeUnion: KtTypeUnion) fun visitHeritageType(heritageType: KtHeritageType) fun visitArgument(argument: KtArgument) diff --git a/src/main/kotlin/ts2kt/kotlin/ast/Stringify.kt b/src/main/kotlin/ts2kt/kotlin/ast/Stringify.kt index fdece56..1071da6 100644 --- a/src/main/kotlin/ts2kt/kotlin/ast/Stringify.kt +++ b/src/main/kotlin/ts2kt/kotlin/ast/Stringify.kt @@ -28,6 +28,7 @@ class Stringify( private val topLevel: Boolean, private val additionalImports: List = listOf(), private val suppressedDiagnostics: List = listOf(), + private val allowEnhanced: Boolean = false, private val out: Output = Output() ) : KtVisitor { val result: String @@ -346,9 +347,11 @@ class Stringify( enumEntry.value?.let { out.print(" /* = $it */") } } + private val EnhancedKtType.singleTypeIfNeeded: KtNode get() = if (allowEnhanced) this else singleType + override fun visitTypeParam(typeParam: KtTypeParam) { out.print(typeParam.name.asString()) - typeParam.upperBound?.let { + typeParam.upperBound?.singleTypeIfNeeded?.let { out.print(" : ") it.accept(this) } @@ -383,7 +386,7 @@ class Stringify( else { out.print(qualifiedName.asString()) - typeArgs.acceptForEach(this@Stringify, ", ", startWithIfNotEmpty = "<", endWithIfNotEmpty = ">") + typeArgs.map { it.singleTypeIfNeeded }.acceptForEach(this@Stringify, ", ", startWithIfNotEmpty = "<", endWithIfNotEmpty = ">") } if (isNullable && qualifiedName != DYNAMIC) { @@ -396,6 +399,20 @@ class Stringify( } } + override fun visitTypeIntersection(typeIntersection: KtTypeIntersection) { + if (typeIntersection.requiredTypes.size > 1 && typeIntersection.isNullable) { + out.print("(") + typeIntersection.requiredTypes.acceptForEach(this, " & ") + out.print(")?") + } else { + typeIntersection.requiredTypes.acceptForEach(this, " & ") + if (typeIntersection.isNullable) { + out.print("?") + } + } + } + + override fun visitTypeUnion(typeUnion: KtTypeUnion) { typeUnion.possibleTypes.acceptForEach(this, " | ") } @@ -415,5 +432,5 @@ class Stringify( } private fun innerStringifier() = - Stringify(packagePartPrefix, /*topLevel = */false, additionalImports, suppressedDiagnostics, out) + Stringify(packagePartPrefix, /*topLevel = */false, additionalImports, suppressedDiagnostics, allowEnhanced, out) } diff --git a/src/main/kotlin/ts2kt/kotlin/ast/ast.kt b/src/main/kotlin/ts2kt/kotlin/ast/ast.kt index b51d1f1..4f88895 100644 --- a/src/main/kotlin/ts2kt/kotlin/ast/ast.kt +++ b/src/main/kotlin/ts2kt/kotlin/ast/ast.kt @@ -16,9 +16,12 @@ package ts2kt.kotlin.ast +import converter.mergeToPreventCompilationConflicts import ts2kt.DYNAMIC import ts2kt.UNIT import ts2kt.escapeIfNeed +import ts2kt.mapLast +import ts2kt.utils.assert val MODULE = KtName("module") private val FAKE = KtName("fake") @@ -197,17 +200,94 @@ data class KtHeritageType(var type: KtType, val byExpression: String? = null) : } } -fun KtTypeUnion(vararg possibleTypes: KtType): KtTypeUnion = KtTypeUnion(possibleTypes.toList()) +fun toKtTypeUnionOrSingleKtType(possibleTypes: List): EnhancedKtType { + return if (possibleTypes.size > 1) KtTypeUnion(possibleTypes) else SingleKtType(possibleTypes.single()) +} + +fun toKtTypeUnionOrSingleKtType(possibleTypes: List): EnhancedKtType { + val mergedEnhancedTypes = possibleTypes.mergeToPreventCompilationConflicts() + if (mergedEnhancedTypes.size == 1) { + return mergedEnhancedTypes.single() + } + val flattenedPossibleTypes = mergedEnhancedTypes.flatMap { if (it is KtTypeUnion) it.possibleTypes else listOf(it.singleType) } + val mergedPossibleTypes = flattenedPossibleTypes.mergeToPreventCompilationConflicts() + return if (mergedPossibleTypes.size > 1) KtTypeUnion(mergedPossibleTypes) else mergedEnhancedTypes.first() +} + +sealed class EnhancedKtType : AbstractKtNode() { + abstract val singleType: KtType + abstract val isNullable: Boolean + abstract val toNullable: EnhancedKtType + abstract val comment: String? + + abstract fun withComment(comment: String?): EnhancedKtType + fun forceNullable(forceNullable: Boolean): EnhancedKtType = if (forceNullable) this.toNullable else this +} + +data class SingleKtType(override val singleType: KtType) : EnhancedKtType() { + override fun accept(visitor: KtVisitor) { + visitor.visitType(singleType) + } + + override val isNullable: Boolean + get() = singleType.isNullable + + override val toNullable: EnhancedKtType + get() = SingleKtType(singleType.copy(isNullable = true)) + + override val comment: String? + get() = singleType.comment + + override fun withComment(comment: String?): EnhancedKtType = copy(singleType = singleType.copy(comment = comment)) +} + +data class KtTypeIntersection(val requiredTypes: List, override val isNullable: Boolean = false) : EnhancedKtType() { + init { + assert(requiredTypes.size > 1, "KtTypeIntersection must have size > 1 (instead of ${requiredTypes.size})") + } + + override fun accept(visitor: KtVisitor) { + visitor.visitTypeIntersection(this) + } + + override val singleType: KtType by lazy { + val firstType = requiredTypes.first().singleType + firstType.copy(isNullable = firstType.isNullable || isNullable, comment = stringify(allowEnhanced = true)) + } + + override val toNullable: EnhancedKtType + get() = copy(isNullable = true) + + override val comment: String? + get() = null + + override fun withComment(comment: String?): EnhancedKtType { + return copy(requiredTypes.mapLast { it.withComment(comment) }) + } +} + +data class KtTypeUnion(val possibleTypes: List) : EnhancedKtType() { + init { + assert(possibleTypes.size > 1, "KtTypeUnion must have size > 1 (instead of ${possibleTypes.size})") + } -data class KtTypeUnion(val possibleTypes: List) : AbstractKtNode() { override fun accept(visitor: KtVisitor) { visitor.visitTypeUnion(this) } - val singleType: KtType = if (possibleTypes.size == 1) possibleTypes.single() else { + override val singleType: KtType = if (possibleTypes.size == 1) possibleTypes.single() else { // TODO should it be `Any`? - KtType(DYNAMIC, comment = stringify(), isNullable = possibleTypes.first().isNullable) + KtType(DYNAMIC, comment = stringify(allowEnhanced = true), isNullable = isNullable) } + override val isNullable: Boolean + get() = possibleTypes.any { it.isNullable } + + override val toNullable: KtTypeUnion by lazy { copy(possibleTypes = possibleTypes.map { it.copy(isNullable = true) }) } + + override val comment: String? + get() = null + + override fun withComment(comment: String?): EnhancedKtType = copy(possibleTypes.mapLast { it.copy(comment = comment) }) } /** @@ -218,7 +298,7 @@ data class KtTypeUnion(val possibleTypes: List) : AbstractKtNode() { */ data class KtType( var qualifiedName: KtQualifiedName, - val typeArgs: List = emptyList(), + val typeArgs: List = emptyList(), val comment: String? = null, val isNullable: Boolean = false, val callSignature: KtCallSignature? = null @@ -236,7 +316,12 @@ data class KtType( fun starType() = KtType(KtQualifiedName("*")) -data class KtTypeParam(override var name: KtName, val upperBound: KtType? = null) : KtNamed, AbstractKtNode() { +/** + * The upper bound is an EnhancedKtType since TypeScript allows unions of type params and Kotlin should defer + * forcing into a single type as late as possible since it may end up in a comment as a union. + * @param upperBound an upper bound for a type param. + */ +data class KtTypeParam(override var name: KtName, val upperBound: EnhancedKtType? = null) : KtNamed, AbstractKtNode() { override fun accept(visitor: KtVisitor) { visitor.visitTypeParam(this) } diff --git a/src/main/kotlin/ts2kt/kotlin/ast/astUtils.kt b/src/main/kotlin/ts2kt/kotlin/ast/astUtils.kt index 6a314c7..aa37c4e 100644 --- a/src/main/kotlin/ts2kt/kotlin/ast/astUtils.kt +++ b/src/main/kotlin/ts2kt/kotlin/ast/astUtils.kt @@ -2,9 +2,13 @@ package ts2kt.kotlin.ast // TODO: review usages -fun KtNode.stringify() = stringify(null, false) -fun KtNode.stringify(packagePartPrefix: String?, topLevel: Boolean = true, additionalImports: List = listOf(), suppressedDiagnostics: List = listOf()) = - Stringify(packagePartPrefix, topLevel, additionalImports, suppressedDiagnostics).also { this.accept(it) }.result +fun KtNode.stringify(allowEnhanced: Boolean = false) = stringify(null, false, allowEnhanced = allowEnhanced) +fun KtNode.stringify(packagePartPrefix: String?, + topLevel: Boolean = true, + additionalImports: List = listOf(), + suppressedDiagnostics: List = listOf(), + allowEnhanced: Boolean = false) = + Stringify(packagePartPrefix, topLevel, additionalImports, suppressedDiagnostics, allowEnhanced).also { this.accept(it) }.result fun KtAnnotation.getFirstParamAsString(): String? { if (this.parameters.isEmpty()) return null diff --git a/src/main/kotlin/ts2kt/mappingObjectTypeToKotlinType.kt b/src/main/kotlin/ts2kt/mappingObjectTypeToKotlinType.kt index 3a35a84..1d4eb19 100644 --- a/src/main/kotlin/ts2kt/mappingObjectTypeToKotlinType.kt +++ b/src/main/kotlin/ts2kt/mappingObjectTypeToKotlinType.kt @@ -85,7 +85,7 @@ data class ObjectTypeToKotlinTypeMapperImpl( .map { it.identifierName.text } val traitName = "T$${n++}" - val traitType = KtType(KtQualifiedName(traitName), typeParamNames.map { KtType(KtQualifiedName(it)) }) + val traitType = KtType(KtQualifiedName(traitName), typeParamNames.map { SingleKtType(KtType(KtQualifiedName(it))) }) translator.name = traitName translator.typeParams = typeParamNames.map { KtTypeParam(KtName(it)) } diff --git a/src/main/kotlin/ts2kt/typeScriptAstUtils.kt b/src/main/kotlin/ts2kt/typeScriptAstUtils.kt index e2af129..cb13dae 100644 --- a/src/main/kotlin/ts2kt/typeScriptAstUtils.kt +++ b/src/main/kotlin/ts2kt/typeScriptAstUtils.kt @@ -17,7 +17,7 @@ package ts2kt import converter.mapType -import converter.mapTypeToUnion +import converter.mapToEnhancedType import ts2kt.kotlin.ast.* import ts2kt.utils.* import typescriptServices.ts.* @@ -91,14 +91,16 @@ fun ParameterDeclaration.toKotlinParam(typeMapper: ObjectTypeToKotlinTypeMapper) fun ParameterDeclaration.toKotlinParamOverloads(typeMapper: ObjectTypeToKotlinTypeMapper): List { val nodeType: TypeNode? = getNodeTypeConsideringVararg() - val unionType = nodeType?.let { typeMapper.mapTypeToUnion(it) } ?: KtTypeUnion(KtType(ANY)) + val enhancedType = nodeType?.let { typeMapper.mapToEnhancedType(it) } ?: SingleKtType(KtType(ANY)) - if (unionType.possibleTypes.size > OVERLOAD_GEN_THRESHOLD_FOR_TYPE_COUNT_ON_ONE_PARAMETER) { - return listOf(toKotlinParam(KtType(DYNAMIC, comment = unionType.stringify()))) - } + if (enhancedType is KtTypeUnion) { + if (enhancedType.possibleTypes.size > OVERLOAD_GEN_THRESHOLD_FOR_TYPE_COUNT_ON_ONE_PARAMETER) { + return listOf(toKotlinParam(enhancedType.singleType)) + } - return unionType.possibleTypes.map { type -> - toKotlinParam(nodeType, type) + return enhancedType.possibleTypes.map { type -> toKotlinParam(nodeType, type) } + } else { + return listOf(toKotlinParam(nodeType, enhancedType.singleType)) } } @@ -169,8 +171,8 @@ private fun NodeArray.toKotlinParamsOverloads(typeMapper: var paramOverloads = parameterDeclaration.toKotlinParamOverloads(typeMapper) if (overloadsOfPriorParams.size * paramOverloads.size > OVERLOAD_GEN_THRESHOLD_FOR_TOTAL_COUNT) { - val comment = KtTypeUnion(paramOverloads.map { it.type.type }).stringify() - paramOverloads = listOf(parameterDeclaration.toKotlinParam(KtType(DYNAMIC, comment = comment))) + val paramEnhancedType = toKtTypeUnionOrSingleKtType(paramOverloads.map { it.type.type }) + paramOverloads = listOf(parameterDeclaration.toKotlinParam(paramEnhancedType.singleType)) } return overloadsOfPriorParams.flatMap { priorParams -> @@ -187,7 +189,7 @@ fun NodeArray.toKotlinTypeParams(typeMapper: ObjectTyp fun TypeParameterDeclaration.toKotlinTypeParam(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeParam { val type = typeMapper.mapType(this) - val upperBound = constraint?.let { typeMapper.mapType(it) } + val upperBound = constraint?.let { typeMapper.mapToEnhancedType(it) } assert(type.qualifiedName.qualifier == null, "type.qualifiedName.qualifier expected to be null, but ${type.qualifiedName.qualifier}") @@ -214,7 +216,7 @@ fun SignatureDeclaration.toKotlinCallSignature(typeMapper: ObjectTypeToKotlinTyp } fun ArrayTypeNode.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): KtType { - val typeArg = typeMapper.mapType(elementType) + val typeArg = typeMapper.mapToEnhancedType(elementType) return KtType(ARRAY, listOf(typeArg)) } @@ -226,8 +228,8 @@ fun SignatureDeclaration.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): } //TODO: do we need LambdaType??? -private fun SignatureDeclaration.toKotlinTypeUnion(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeUnion { - return KtTypeUnion(parameters.toKotlinParamsOverloads(typeMapper).map { +private fun SignatureDeclaration.toEnhancedType(typeMapper: ObjectTypeToKotlinTypeMapper): EnhancedKtType { + return toKtTypeUnionOrSingleKtType(parameters.toKotlinParamsOverloads(typeMapper).map { createFunctionType(it, type?.let { typeMapper.mapType(it) } ?: KtType(ANY)) }) } @@ -236,15 +238,15 @@ private fun SignatureDeclaration.toKotlinTypeUnion(typeMapper: ObjectTypeToKotli private fun TypeLiteralNode.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): KtType = typeMapper.getKotlinTypeForObjectType(this) -fun TypeNode.toKotlinTypeUnion(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeUnion { +fun TypeNode.toEnhancedType(typeMapper: ObjectTypeToKotlinTypeMapper): EnhancedKtType { return when (this.kind as Any) { SyntaxKind.ConstructorType, - SyntaxKind.FunctionType -> (this.cast()).toKotlinTypeUnion(typeMapper) + SyntaxKind.FunctionType -> (this.cast()).toEnhancedType(typeMapper) - SyntaxKind.TypeReference -> (this.cast()).toKotlinTypeUnion(typeMapper) - SyntaxKind.UnionType -> (this.cast()).toKotlinTypeUnion(typeMapper) - SyntaxKind.IntersectionType -> (this.cast()).toKotlinTypeUnion(typeMapper) - else -> KtTypeUnion(toKotlinType(typeMapper)) + SyntaxKind.TypeReference -> (this.cast()).toEnhancedType(typeMapper) + SyntaxKind.UnionType -> (this.cast()).toEnhancedType(typeMapper) + SyntaxKind.IntersectionType -> (this.cast()).toEnhancedType(typeMapper) + else -> SingleKtType(toKotlinType(typeMapper)) } } @@ -264,15 +266,15 @@ fun TypeNode.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): KtType { SyntaxKind.ConstructorType, SyntaxKind.FunctionType -> (this.cast()).toKotlinType(typeMapper) - SyntaxKind.TypeReference -> (this.cast()).toKotlinTypeUnion(typeMapper).singleType + SyntaxKind.TypeReference -> (this.cast()).toEnhancedType(typeMapper).singleType SyntaxKind.ExpressionWithTypeArguments -> (this.cast()).toKotlinType(typeMapper) SyntaxKind.Identifier -> KtType(KtQualifiedName((this.cast()).unescapedText)) SyntaxKind.TypeLiteral -> (this.cast()).toKotlinType(typeMapper) - SyntaxKind.UnionType -> (this.cast()).toKotlinTypeUnion(typeMapper).singleType + SyntaxKind.UnionType -> (this.cast()).toEnhancedType(typeMapper).singleType - SyntaxKind.IntersectionType -> (this.cast()).toKotlinTypeUnion(typeMapper).singleType + SyntaxKind.IntersectionType -> (this.cast()).toEnhancedType(typeMapper).singleType SyntaxKind.ParenthesizedType -> (this.cast()).type.toKotlinType(typeMapper) @@ -297,8 +299,8 @@ fun EntityName.toKotlinTypeName(): KtQualifiedName { } } -fun TypeReferenceNode.toKotlinTypeUnion(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeUnion { - return KtTypeUnion(toKotlinTypeIgnoringTypeAliases(typeMapper)) +fun TypeReferenceNode.toEnhancedType(typeMapper: ObjectTypeToKotlinTypeMapper): EnhancedKtType { + return SingleKtType(toKotlinTypeIgnoringTypeAliases(typeMapper)) } private fun TypeReferenceNode.toKotlinTypeIgnoringTypeAliases(typeMapper: ObjectTypeToKotlinTypeMapper): KtType { @@ -307,17 +309,17 @@ private fun TypeReferenceNode.toKotlinTypeIgnoringTypeAliases(typeMapper: Object return when (name) { // TODO: HACKS - KtQualifiedName("Function") -> KtType(name, listOf(starType())) + KtQualifiedName("Function") -> KtType(name, listOf(SingleKtType(starType()))) KtQualifiedName("Object") -> KtType(ANY) - else -> KtType(name, typeArguments?.arr?.map { typeMapper.mapType(it) } ?: emptyList()) + else -> KtType(name, typeArguments?.arr?.map { typeMapper.mapToEnhancedType(it) } ?: emptyList()) } } fun ExpressionWithTypeArguments.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): KtType { val name = expression.stringifyQualifiedName() - return KtType(name ?: KtQualifiedName("???"), typeArguments?.arr?.map { typeMapper.mapType(it) } ?: emptyList()) + return KtType(name ?: KtQualifiedName("???"), typeArguments?.arr?.map { typeMapper.mapToEnhancedType(it) } ?: emptyList()) } private fun PropertyAccessExpression.toKtQualifiedName(): KtQualifiedName { @@ -338,32 +340,21 @@ private fun Node.stringifyQualifiedName() = when (kind as Any) { else -> reportUnsupportedNode(this) } -fun UnionTypeNode.toKotlinTypeUnion(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeUnion { - val possibleTypes = types.arr.flatMap { typeMapper.mapTypeToUnion(it).possibleTypes }.distinct() - - // TODO unify KtTypeUnion and KtType and implement it better - if (possibleTypes.size == 2) { - val a = possibleTypes[0] - val b = possibleTypes[1] - - val t = if (a == NOTHING_TYPE) b else if (b == NOTHING_TYPE) a else null - - t?.let { - return KtTypeUnion(listOf(it.copy(isNullable = true))) - } - } +fun UnionTypeNode.toEnhancedType(typeMapper: ObjectTypeToKotlinTypeMapper): EnhancedKtType { + return mapUnionType(types.arr.map { typeMapper.mapToEnhancedType(it) }) +} - return KtTypeUnion(possibleTypes) +fun mapUnionType(possibleEnhancedTypes: List): EnhancedKtType { + val typesExceptNothing = possibleEnhancedTypes.filter { it.singleType != NOTHING_TYPE } + val isNullable = typesExceptNothing.size < possibleEnhancedTypes.size + return toKtTypeUnionOrSingleKtType(typesExceptNothing).forceNullable(isNullable) } -fun IntersectionTypeNode.toKotlinTypeUnion(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeUnion { - val kotlinTypeUnions = types.arr.map { typeMapper.mapTypeToUnion(it) } - val commentWithExpectedType = kotlinTypeUnions.join(" & ", stringify = KtTypeUnion::stringify) - // just take the first one for now since Kotlin doesn't support intersection types. - return kotlinTypeUnions[0].mapLast { it.copy(comment = commentWithExpectedType) } +fun IntersectionTypeNode.toEnhancedType(typeMapper: ObjectTypeToKotlinTypeMapper): KtTypeIntersection { + return KtTypeIntersection(types.arr.map { typeMapper.mapToEnhancedType(it) }) } -private fun KtTypeUnion.mapLast(function: (KtType) -> KtType): KtTypeUnion = KtTypeUnion(possibleTypes.dropLast(1) + function(possibleTypes.last())) +fun List.mapLast(function: (T) -> T): List = dropLast(1) + function(last()) fun ThisTypeNode.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): KtType { var parent = parent @@ -391,7 +382,7 @@ fun TypePredicateNode.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): Kt fun ClassOrInterfaceDeclaration.toKotlinType(typeMapper: ObjectTypeToKotlinTypeMapper): KtType { val name = identifierName!!.unescapedText - return KtType(KtQualifiedName(name), typeParameters?.arr?.map { KtType(KtQualifiedName(it.identifierName.unescapedText)) } ?: emptyList()) + return KtType(KtQualifiedName(name), typeParameters?.arr?.map { SingleKtType(KtType(KtQualifiedName(it.identifierName.unescapedText))) } ?: emptyList()) } fun forEachChild(visitor: Visitor, node: Node) { diff --git a/testData/misc/missedOverloads.d.kt b/testData/misc/missedOverloads.d.kt index 753c436..351b765 100644 --- a/testData/misc/missedOverloads.d.kt +++ b/testData/misc/missedOverloads.d.kt @@ -6,8 +6,8 @@ external interface MyEvent external interface MyOptions external interface JQueryStatic { fun get(url: String, success: (() -> Any)? = definedExternally /* null */, dataType: String? = definedExternally /* null */): MyXHR - fun get(url: String, data: String? = definedExternally /* null */, success: (() -> Any)? = definedExternally /* null */, dataType: String? = definedExternally /* null */): MyXHR fun get(url: String, data: Any? = definedExternally /* null */, success: (() -> Any)? = definedExternally /* null */, dataType: String? = definedExternally /* null */): MyXHR + fun get(url: String, data: String? = definedExternally /* null */, success: (() -> Any)? = definedExternally /* null */, dataType: String? = definedExternally /* null */): MyXHR fun get(settings: MyOptions): MyXHR @nativeInvoke operator fun invoke(selector: String, context: Element? = definedExternally /* null */): MyQuery @@ -33,29 +33,29 @@ external interface JQueryStatic { } external open class JJ { open fun foo(data: String, context: HTMLElement? = definedExternally /* null */, keepScripts: Boolean? = definedExternally /* null */): Array = definedExternally - open fun hide(duration: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally open fun hide(duration: Number? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally - open fun hide(duration: String? = definedExternally /* null */, easing: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally + open fun hide(duration: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally open fun hide(duration: Number? = definedExternally /* null */, easing: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally + open fun hide(duration: String? = definedExternally /* null */, easing: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally open fun hide(options: MyOptions): MyQuery = definedExternally - open fun trigger(eventType: String, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally open fun trigger(eventType: String, extraParameters: Array? = definedExternally /* null */): MyQuery = definedExternally - open fun trigger(event: MyEvent, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally + open fun trigger(eventType: String, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally open fun trigger(event: MyEvent, extraParameters: Array? = definedExternally /* null */): MyQuery = definedExternally + open fun trigger(event: MyEvent, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally open fun hide(): MyQuery = definedExternally open fun trigger(eventType: String): MyQuery = definedExternally open fun trigger(event: MyEvent): MyQuery = definedExternally } external fun foo(data: String, context: HTMLElement? = definedExternally /* null */, keepScripts: Boolean? = definedExternally /* null */): Array = definedExternally -external fun hide(duration: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally external fun hide(duration: Number? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally -external fun hide(duration: String? = definedExternally /* null */, easing: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally +external fun hide(duration: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally external fun hide(duration: Number? = definedExternally /* null */, easing: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally +external fun hide(duration: String? = definedExternally /* null */, easing: String? = definedExternally /* null */, complete: Function<*>? = definedExternally /* null */): MyQuery = definedExternally external fun hide(options: MyOptions): MyQuery = definedExternally -external fun trigger(eventType: String, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally external fun trigger(eventType: String, extraParameters: Array? = definedExternally /* null */): MyQuery = definedExternally -external fun trigger(event: MyEvent, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally +external fun trigger(eventType: String, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally external fun trigger(event: MyEvent, extraParameters: Array? = definedExternally /* null */): MyQuery = definedExternally +external fun trigger(event: MyEvent, extraParameters: Any? = definedExternally /* null */): MyQuery = definedExternally external fun hide(): MyQuery = definedExternally external fun trigger(eventType: String): MyQuery = definedExternally external fun trigger(event: MyEvent): MyQuery = definedExternally diff --git a/testData/objectType/functionTypedIntersectionParameter.d.kt b/testData/objectType/functionTypedIntersectionParameter.d.kt index 812116b..dc7132d 100644 --- a/testData/objectType/functionTypedIntersectionParameter.d.kt +++ b/testData/objectType/functionTypedIntersectionParameter.d.kt @@ -12,4 +12,5 @@ external interface `T$1` { external open class FooTypedUnion { open fun baz(p: `T$0`): Unit = definedExternally open fun bar(p: `T$1` /* `T$1` & FooPart */): Unit = definedExternally + open fun foo(p: `T$1`? /* (`T$1` & FooPart)? */): Unit = definedExternally } diff --git a/testData/objectType/functionTypedIntersectionParameter.d.ts b/testData/objectType/functionTypedIntersectionParameter.d.ts index f507170..f46cdda 100644 --- a/testData/objectType/functionTypedIntersectionParameter.d.ts +++ b/testData/objectType/functionTypedIntersectionParameter.d.ts @@ -5,4 +5,5 @@ type FooPartAndLiteral = {foo: T; bar;} & FooPart declare class FooTypedUnion { baz(p: {foo: T; sup;}); bar(p: FooPartAndLiteral); + foo(p: FooPartAndLiteral | null); } diff --git a/testData/objectType/generics.d.kt b/testData/objectType/generics.d.kt index 604961f..8b7605d 100644 --- a/testData/objectType/generics.d.kt +++ b/testData/objectType/generics.d.kt @@ -18,8 +18,8 @@ external interface `T$2` { } external interface `T$3` { fun bar(a: Any): T - fun foo(t: String) fun foo(t: `T$2`) + fun foo(t: String) var baz: Any? get() = definedExternally; set(value) = definedExternally var boo: S? get() = definedExternally; set(value) = definedExternally var show: (overrideChecks: Boolean) -> Unit diff --git a/testData/typeAlias/recursiveType.d.kt b/testData/typeAlias/recursiveType.d.kt index bcbba81..490236f 100644 --- a/testData/typeAlias/recursiveType.d.kt +++ b/testData/typeAlias/recursiveType.d.kt @@ -6,9 +6,11 @@ external interface Rec { } external interface `T$0` { @nativeGetter - operator fun get(key: String): dynamic + operator fun get(key: String): dynamic /* dynamic | AnimatedValue */ @nativeSetter operator fun set(key: String, value: dynamic) + @nativeSetter + operator fun set(key: String, value: AnimatedValue) } external fun foo(): dynamic /* `T$0` | AnimatedValue */ = definedExternally external fun bar(d: `T$0`): Unit = definedExternally diff --git a/testData/typeAlias/typeParams.d.kt b/testData/typeAlias/typeParams.d.kt index 2d59395..855e2f2 100644 --- a/testData/typeAlias/typeParams.d.kt +++ b/testData/typeAlias/typeParams.d.kt @@ -1,5 +1,7 @@ package typeParams +external interface Printable +external interface Readable external interface Map external interface List external var fooMap: Map> = definedExternally @@ -7,8 +9,15 @@ external fun mapKey(a: Map>): Unit = definedExternally external var fooStringOrMap: dynamic /* String | Map> */ = definedExternally external fun stringOrMapKey(a: String): Unit = definedExternally external fun stringOrMapKey(a: Map>): Unit = definedExternally -external var listOfStringOrNumber: dynamic /* String | List */ = definedExternally +external var listOfStringOrNumber: dynamic /* String | List */ = definedExternally +external var listOfPrintableAndReadable: dynamic /* String | List */ = definedExternally +external var listOfNullablePrintableAndReadable: List = definedExternally +external var listOfNullableString: List = definedExternally +external var listOfNullableStringOrNumber: List = definedExternally +external fun mapNullableStringOrNumber(item: T): T = definedExternally external fun listOfNumberOrString(a: List): Unit = definedExternally +external fun normalizeListOfNullableNumberOrString(a: List): List = definedExternally +external fun normalizeListOfNullableNumberAndString(a: List): List = definedExternally external var headers: Map> = definedExternally external fun getHeaders(): Map> = definedExternally external fun addHeaders(headers: Map>): Unit = definedExternally diff --git a/testData/typeAlias/typeParams.d.ts b/testData/typeAlias/typeParams.d.ts index 9e598ce..7914dcd 100644 --- a/testData/typeAlias/typeParams.d.ts +++ b/testData/typeAlias/typeParams.d.ts @@ -1,3 +1,5 @@ +interface Printable +interface Readable interface Map { } interface List { @@ -18,8 +20,22 @@ declare function stringOrMapKey(a: string | MultiMap); declare var listOfStringOrNumber: string | List; +declare var listOfPrintableAndReadable: string | List; + +declare var listOfNullablePrintableAndReadable: List; + +declare var listOfNullableString: List; + +declare var listOfNullableStringOrNumber: List; + +declare function mapNullableStringOrNumber(item: T): T; + declare function listOfNumberOrString(a: List); +declare function normalizeListOfNullableNumberOrString(a: List): List; + +declare function normalizeListOfNullableNumberAndString(a: List): List; + declare var headers: MyHeaders; declare function getHeaders(): MyHeaders; diff --git a/testData/unionType/stringValues.d.kt b/testData/unionType/stringValues.d.kt new file mode 100644 index 0000000..b2759e8 --- /dev/null +++ b/testData/unionType/stringValues.d.kt @@ -0,0 +1,5 @@ +package stringValues + +external interface MapGenerator { + fun generate(scope: String /* "city" | "state" | "country" | "world" */) +} diff --git a/testData/unionType/stringValues.d.ts b/testData/unionType/stringValues.d.ts new file mode 100644 index 0000000..c034f5e --- /dev/null +++ b/testData/unionType/stringValues.d.ts @@ -0,0 +1,5 @@ +export type GeographicScope = 'city' | 'state' | 'country' | 'world'; + +export interface MapGenerator { + generate(scope: GeographicScope) +} diff --git a/testData/unionType/withNullOrUndefined.d.kt b/testData/unionType/withNullOrUndefined.d.kt index 96c3e8b..fc759d8 100644 --- a/testData/unionType/withNullOrUndefined.d.kt +++ b/testData/unionType/withNullOrUndefined.d.kt @@ -3,6 +3,8 @@ package withNullOrUndefined external var foo: String? = definedExternally external var bar: String? = definedExternally external fun bar(a: String?): Foo? = definedExternally +external fun nullOverload(a: String?): dynamic /* Foo? | Number? */ = definedExternally +external fun nullOverload(a: Number?): dynamic /* Foo? | Number? */ = definedExternally external fun baz(a: Foo?, b: Number? = definedExternally /* null */): Any? = definedExternally external interface `T$0` { @nativeGetter @@ -11,7 +13,7 @@ external interface `T$0` { operator fun set(key: String?, value: String?) } external open class Foo(a: String?) { - open fun someMethod(): dynamic /* String | Number | Nothing? */ = definedExternally + open fun someMethod(): dynamic /* String? | Number? */ = definedExternally open var foo: Foo? = definedExternally open var optionalFoo: String? = definedExternally open var optionalFoo2: String? = definedExternally @@ -19,9 +21,10 @@ external open class Foo(a: String?) { open var refs: `T$0` = definedExternally } external interface IBar { - fun someMethod(): dynamic /* String | Number | Nothing? */ + fun someMethod(): dynamic /* String? | Number? */ var foo: Foo? var optionalFoo: String? get() = definedExternally; set(value) = definedExternally var optionalFoo2: String? get() = definedExternally; set(value) = definedExternally var optionalFoo3: String? get() = definedExternally; set(value) = definedExternally + var optionalFoo4: dynamic /* String? | Number? */ get() = definedExternally; set(value) = definedExternally } diff --git a/testData/unionType/withNullOrUndefined.d.ts b/testData/unionType/withNullOrUndefined.d.ts index 33cd7b7..cdcc49f 100644 --- a/testData/unionType/withNullOrUndefined.d.ts +++ b/testData/unionType/withNullOrUndefined.d.ts @@ -3,6 +3,8 @@ declare var bar: string | null; declare function bar(a: string | undefined): Foo | null; +declare function nullOverload(a: string | number | null): Foo | number | null; + declare function baz(a: Foo | null, b?: number | undefined): any | undefined; declare class Foo { @@ -26,4 +28,5 @@ interface IBar { optionalFoo?: string | null; optionalFoo2?: string | undefined; optionalFoo3?: string | undefined | null; + optionalFoo4?: string | number | null; } \ No newline at end of file