diff --git a/compiler/test/data/typescript/import/importAssignment/importAssignment.d.kt b/compiler/test/data/typescript/import/importAssignment/importAssignment.d.kt index 77488ab37..8b0b3d998 100644 --- a/compiler/test/data/typescript/import/importAssignment/importAssignment.d.kt +++ b/compiler/test/data/typescript/import/importAssignment/importAssignment.d.kt @@ -62,4 +62,10 @@ import org.w3c.performance.* import org.w3c.workers.* import org.w3c.xhr.* -external interface InterfaceA \ No newline at end of file +external interface InterfaceA + +external interface InterfaceB { + fun ping(): InterfaceC +} + +external interface InterfaceC \ No newline at end of file diff --git a/compiler/test/data/typescript/importAs/importAs.d.kt b/compiler/test/data/typescript/importAs/importAs.d.kt index 967072261..d02414255 100644 --- a/compiler/test/data/typescript/importAs/importAs.d.kt +++ b/compiler/test/data/typescript/importAs/importAs.d.kt @@ -32,11 +32,11 @@ external fun createPipable(): Pipable external fun createYetAnotherPipable(): Pipable // ------------------------------------------------------------------------------------------ -// [test] index._computable.module__computable.kt -@file:JsModule("_computable") +// [test] index._transformable.internalApi.module__transformable.kt +@file:JsModule("_transformable") @file:JsNonModule @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") -package _computable +package _transformable.internalApi import kotlin.js.* import kotlin.js.Json @@ -53,18 +53,14 @@ import org.w3c.performance.* import org.w3c.workers.* import org.w3c.xhr.* -external open class Computable { - open fun compute() -} - -external interface Pipable +external interface TransformOptions // ------------------------------------------------------------------------------------------ -// [test] index._transformable.internalApi.module__transformable.kt -@file:JsModule("_transformable") +// [test] index._computable.module__computable.kt +@file:JsModule("_computable") @file:JsNonModule @file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") -package _transformable.internalApi +package _computable import kotlin.js.* import kotlin.js.Json @@ -81,4 +77,8 @@ import org.w3c.performance.* import org.w3c.workers.* import org.w3c.xhr.* -external interface TransformOptions \ No newline at end of file +external open class Computable { + open fun compute() +} + +external interface Pipable \ No newline at end of file diff --git a/compiler/test/data/typescriptBodies/typePredicate/generatedVariable.kt b/compiler/test/data/typescriptBodies/typePredicate/generatedVariable.kt new file mode 100644 index 000000000..c33cc873f --- /dev/null +++ b/compiler/test/data/typescriptBodies/typePredicate/generatedVariable.kt @@ -0,0 +1,35 @@ +// [test] generatedVariable.kt +@file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") + +import kotlin.js.* +import kotlin.js.Json +import org.khronos.webgl.* +import org.w3c.dom.* +import org.w3c.dom.events.* +import org.w3c.dom.parsing.* +import org.w3c.dom.svg.* +import org.w3c.dom.url.* +import org.w3c.fetch.* +import org.w3c.files.* +import org.w3c.notifications.* +import org.w3c.performance.* +import org.w3c.workers.* +import org.w3c.xhr.* + +open class A { + open var x: Any = B() +} + +open class B { + open var y: Number = 6 +} + +external fun isB(x: Any): Boolean + +fun f() { + var s = A() + if (isB(s.x)) { + var _v1 = s.x as B + console.log(_v1.y) + } +} \ No newline at end of file diff --git a/compiler/test/data/typescriptBodies/typePredicate/generatedVariable.ts b/compiler/test/data/typescriptBodies/typePredicate/generatedVariable.ts new file mode 100644 index 000000000..e20e75963 --- /dev/null +++ b/compiler/test/data/typescriptBodies/typePredicate/generatedVariable.ts @@ -0,0 +1,16 @@ +class A { + x: any = new B(); +} + +class B { + y: number = 6; +} + +declare function isB(x: any) : x is B + +function f() { + let s = new A(); + if (isB(s.x)) { + console.log(s.x.y) + } +} \ No newline at end of file diff --git a/compiler/test/data/typescriptBodies/typePredicate/simple.kt b/compiler/test/data/typescriptBodies/typePredicate/simple.kt new file mode 100644 index 000000000..913b9124f --- /dev/null +++ b/compiler/test/data/typescriptBodies/typePredicate/simple.kt @@ -0,0 +1,41 @@ +// [test] simple.kt +@file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS") + +import kotlin.js.* +import kotlin.js.Json +import org.khronos.webgl.* +import org.w3c.dom.* +import org.w3c.dom.events.* +import org.w3c.dom.parsing.* +import org.w3c.dom.svg.* +import org.w3c.dom.url.* +import org.w3c.fetch.* +import org.w3c.files.* +import org.w3c.notifications.* +import org.w3c.performance.* +import org.w3c.workers.* +import org.w3c.xhr.* + +open class Something { + open var x: Number = 5 + open fun isChild(): Boolean = false +} + +open class Child : Something { + open var y: Number = 6 + override fun isChild(): Boolean = true +} + +external fun isSomething(x: Any): Boolean + +fun f() { + var s: Any = Something() + if (isSomething(s)) { + s as Something + console.log(s.x) + if (s.isChild()) { + s as Child + console.log(s.y) + } + } +} \ No newline at end of file diff --git a/compiler/test/data/typescriptBodies/typePredicate/simple.ts b/compiler/test/data/typescriptBodies/typePredicate/simple.ts new file mode 100644 index 000000000..cc7719c97 --- /dev/null +++ b/compiler/test/data/typescriptBodies/typePredicate/simple.ts @@ -0,0 +1,25 @@ +class Something { + x: number = 5; + isChild(): this is Child { + return false + } +} + +class Child extends Something { + y: number = 6; + isChild(): this is Child { + return true + } +} + +declare function isSomething(x: any) : x is Something; + +function f() { + let s: any = new Something(); + if (isSomething(s)) { + console.log(s.x) + if (s.isChild()) { + console.log(s.y) + } + } +} \ No newline at end of file diff --git a/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/ModelExpressionLowering.kt b/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/ModelExpressionLowering.kt index 90c0f84bc..cd2868513 100644 --- a/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/ModelExpressionLowering.kt +++ b/model-lowerings-common/src/org/jetbrains/dukat/model/commonLowerings/ModelExpressionLowering.kt @@ -9,6 +9,7 @@ import org.jetbrains.dukat.astModel.expressions.ConditionalExpressionModel import org.jetbrains.dukat.astModel.expressions.ExpressionModel import org.jetbrains.dukat.astModel.expressions.IdentifierExpressionModel import org.jetbrains.dukat.astModel.expressions.IndexExpressionModel +import org.jetbrains.dukat.astModel.expressions.IsExpressionModel import org.jetbrains.dukat.astModel.expressions.LambdaExpressionModel import org.jetbrains.dukat.astModel.expressions.NonNullExpressionModel import org.jetbrains.dukat.astModel.expressions.ParenthesizedExpressionModel @@ -67,6 +68,10 @@ interface ModelExpressionLowering { expression = lower(expression.expression), type = lowerTypeModel(NodeOwner(expression.type, null)) ) + is IsExpressionModel -> expression.copy( + expression = lower(expression.expression), + type = lowerTypeModel(NodeOwner(expression.type, null)) + ) is NonNullExpressionModel -> expression.copy( expression = lower(expression.expression) ) diff --git a/typescript/ts-converter/src/AstConverter.ts b/typescript/ts-converter/src/AstConverter.ts index c1d36b253..fc6ef413b 100644 --- a/typescript/ts-converter/src/AstConverter.ts +++ b/typescript/ts-converter/src/AstConverter.ts @@ -521,7 +521,7 @@ export class AstConverter { } else if (ts.isTupleTypeNode(type)) { return this.astFactory.createTupleDeclaration(type.elementTypes.map(elementType => this.convertType(elementType))) } else if (ts.isTypePredicateNode(type)) { - return this.createTypeDeclaration("boolean"); + return this.astFactory.createTypePredicate(type.parameterName.getText(), this.convertType(type.type)); } else if (type.kind == ts.SyntaxKind.ObjectKeyword) { return this.createTypeDeclaration("object"); } else { diff --git a/typescript/ts-converter/src/DependencyBuilder.ts b/typescript/ts-converter/src/DependencyBuilder.ts index 1ae1cc8f3..7cb337703 100644 --- a/typescript/ts-converter/src/DependencyBuilder.ts +++ b/typescript/ts-converter/src/DependencyBuilder.ts @@ -107,8 +107,8 @@ export class DependencyBuilder { this.registerDependency(new TranslateAllSymbolsDependency(resolvedModuleName)); } } - } else if (ts.isCallExpression(node)) { - this.checkReferences(node.expression) + } else if (ts.isIdentifier(node)) { + this.checkReferences(node) } ts.forEachChild(node, node => this.visit(node)); } diff --git a/typescript/ts-converter/src/ast/AstFactory.ts b/typescript/ts-converter/src/ast/AstFactory.ts index 09749ffa5..1bf2a109a 100644 --- a/typescript/ts-converter/src/ast/AstFactory.ts +++ b/typescript/ts-converter/src/ast/AstFactory.ts @@ -77,7 +77,7 @@ import { TupleDeclarationProto, TypeAliasDeclarationProto, TypeParameterDeclarationProto, - TypeParamReferenceDeclarationProto, + TypeParamReferenceDeclarationProto, TypePredicateDeclarationProto, TypeReferenceDeclarationProto, UnionTypeDeclarationProto, VariableDeclarationProto, @@ -675,6 +675,16 @@ export class AstFactory { return paramValueDeclaration; } + createTypePredicate(parameter: string, type: ParameterValueDeclarationProto): TypeDeclaration { + let typePredicateDeclaration = new TypePredicateDeclarationProto(); + typePredicateDeclaration.setParameter(parameter); + typePredicateDeclaration.setType(type); + + let paramValueDeclaration = new ParameterValueDeclarationProto(); + paramValueDeclaration.setTypepredicate(typePredicateDeclaration); + return paramValueDeclaration; + } + declareProperty(name: string, initializer: Expression | null, type: TypeDeclaration, typeParams: Array, optional: boolean, modifiers: Array, explicitlyDeclaredType: boolean): MemberDeclaration { let propertyDeclaration = new PropertyDeclarationProto(); propertyDeclaration.setName(name); diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationLowering.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationLowering.kt index 54daf23ee..b5d731760 100644 --- a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationLowering.kt +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationLowering.kt @@ -52,7 +52,7 @@ interface DeclarationLowering : TopLevelDeclarationLowering, DeclarationStatemen typeParameters = declaration.typeParameters.map { typeParameter -> typeParameter.copy(constraints = typeParameter.constraints.map { constraint -> lowerParameterValue(constraint, owner.wrap(declaration)) }) }, - body = declaration.body?.let { lowerBlockStatement(it) } + body = declaration.body?.let { lowerBlock(it) } ) } @@ -107,7 +107,7 @@ interface DeclarationLowering : TopLevelDeclarationLowering, DeclarationStatemen typeParameter.copy(constraints = typeParameter.constraints.map { constraint -> lowerParameterValue(constraint, owner.wrap(declaration)) }) }, type = lowerParameterValue(declaration.type, owner.wrap(declaration)), - body = declaration.body?.let { lowerBlockStatement(it) } + body = declaration.body?.let { lowerBlock(it) } ) } diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationStatementLowering.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationStatementLowering.kt index cb0d21440..0142ab434 100644 --- a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationStatementLowering.kt +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/DeclarationStatementLowering.kt @@ -148,8 +148,4 @@ interface DeclarationStatementLowering : ExpressionLowering { } } - fun lowerBlockStatement(block: BlockDeclaration): BlockDeclaration { - return BlockDeclaration(block.statements.map { lower(it) }) - } - } \ No newline at end of file diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ExpressionLowering.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ExpressionLowering.kt index 601bb041f..0677ddced 100644 --- a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ExpressionLowering.kt +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ExpressionLowering.kt @@ -1,5 +1,8 @@ package org.jetbrains.dukat.tsLowerings +import org.jetbrains.dukat.astCommon.IdentifierEntity +import org.jetbrains.dukat.astCommon.NameEntity +import org.jetbrains.dukat.astCommon.QualifierEntity import org.jetbrains.dukat.logger.Logging import org.jetbrains.dukat.ownerContext.NodeOwner import org.jetbrains.dukat.tsmodel.Declaration @@ -118,8 +121,8 @@ interface ExpressionLowering { is NumericLiteralExpressionDeclaration -> declaration is RegExLiteralExpressionDeclaration -> declaration is StringLiteralExpressionDeclaration -> lowerStringLiteralDeclaration(declaration) - is IdentifierExpressionDeclaration -> declaration - is QualifierExpressionDeclaration -> declaration + is IdentifierExpressionDeclaration -> lowerIdentifierExpression(declaration) + is QualifierExpressionDeclaration -> lowerQualifierExpression(declaration) else -> { logger.debug("[${this}] skipping $declaration") declaration @@ -131,6 +134,35 @@ interface ExpressionLowering { return literal } + fun lowerIdentifier(identifier: IdentifierEntity): IdentifierEntity { + return identifier + } + + fun lowerQualifier(qualifier: QualifierEntity): NameEntity { + return qualifier.copy( + left = lowerNameEntity(qualifier.left), + right = lowerIdentifier(qualifier.right) + ) + } + + fun lowerNameEntity(nameEntity: NameEntity): NameEntity { + return when (nameEntity) { + is IdentifierEntity -> lowerIdentifier(nameEntity) + is QualifierEntity -> lowerQualifier(nameEntity) + } + } + + fun lowerIdentifierExpression(expression: IdentifierExpressionDeclaration): ExpressionDeclaration { + return expression.copy(identifier = lowerIdentifier(expression.identifier)) + } + + fun lowerQualifierExpression(expression: QualifierExpressionDeclaration): ExpressionDeclaration { + return when (val newName = lowerQualifier(expression.qualifier)) { + is QualifierEntity -> expression.copy(qualifier = newName) + is IdentifierEntity -> IdentifierExpressionDeclaration(newName) + } + } + fun lowerTemplateToken(token: TemplateTokenDeclaration): TemplateTokenDeclaration { return when (token) { is ExpressionTemplateTokenDeclaration -> token.copy( diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ReplaceExpressionsLowering.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ReplaceExpressionsLowering.kt new file mode 100644 index 000000000..8620ede91 --- /dev/null +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/ReplaceExpressionsLowering.kt @@ -0,0 +1,33 @@ +package org.jetbrains.dukat.tsLowerings + +import org.jetbrains.dukat.astCommon.IdentifierEntity +import org.jetbrains.dukat.astCommon.NameEntity +import org.jetbrains.dukat.astCommon.QualifierEntity +import org.jetbrains.dukat.tsmodel.expression.ExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.PropertyAccessExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.name.IdentifierExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.name.QualifierExpressionDeclaration + +internal class ReplaceExpressionsLowering(private val replacements: Map): + DeclarationLowering { + override fun lowerIdentifier(identifier: IdentifierEntity): IdentifierEntity { + return replacements[IdentifierExpressionDeclaration( + identifier + )]?.identifier ?: identifier + } + + override fun lowerQualifier(qualifier: QualifierEntity): NameEntity { + return replacements[QualifierExpressionDeclaration( + qualifier + )]?.identifier ?: super.lowerQualifier(qualifier) + } + + override fun lower(declaration: ExpressionDeclaration): ExpressionDeclaration { + return when (declaration) { + is PropertyAccessExpressionDeclaration -> { + return replacements[declaration] ?: super.lower(declaration) + } + else -> super.lower(declaration) + } + } +} \ No newline at end of file diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/StatementTypeContext.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/StatementTypeContext.kt index d82d6692f..41e349ba0 100644 --- a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/StatementTypeContext.kt +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/StatementTypeContext.kt @@ -19,7 +19,8 @@ import org.jetbrains.dukat.tsmodel.types.ParameterValueDeclaration import org.jetbrains.dukat.tsmodel.types.TypeDeclaration private data class Scope( - val variables: MutableMap = mutableMapOf() + val variables: MutableMap = mutableMapOf(), + val expressionsToReplace: MutableSet = mutableSetOf() ) internal class StatementTypeContext : DeclarationLowering { @@ -78,6 +79,16 @@ internal class StatementTypeContext : DeclarationLowering { scopes.removeLast() } + fun addExpressionToReplace(expression: ExpressionDeclaration) { + scopes.last().expressionsToReplace += expression + } + + fun getRelevantExpressionsToReplace(): Set { + val expressions = scopes.last().expressionsToReplace.toSet() + scopes.last().expressionsToReplace.clear() + return expressions + } + override fun lowerClassLikeDeclaration( declaration: ClassLikeDeclaration, owner: NodeOwner? diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processNullabilityChecks.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processNullabilityChecks.kt index 331235f23..e8e54b2d1 100644 --- a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processNullabilityChecks.kt +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processNullabilityChecks.kt @@ -7,6 +7,7 @@ import org.jetbrains.dukat.tsmodel.FunctionDeclaration import org.jetbrains.dukat.tsmodel.FunctionOwnerDeclaration import org.jetbrains.dukat.tsmodel.IfStatementDeclaration import org.jetbrains.dukat.tsmodel.SourceSetDeclaration +import org.jetbrains.dukat.tsmodel.StatementDeclaration import org.jetbrains.dukat.tsmodel.VariableDeclaration import org.jetbrains.dukat.tsmodel.WhileStatementDeclaration import org.jetbrains.dukat.tsmodel.expression.BinaryExpressionDeclaration @@ -15,12 +16,33 @@ import org.jetbrains.dukat.tsmodel.expression.ExpressionDeclaration import org.jetbrains.dukat.tsmodel.expression.ParenthesizedExpressionDeclaration import org.jetbrains.dukat.tsmodel.expression.UnaryExpressionDeclaration import org.jetbrains.dukat.tsmodel.expression.UnknownExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.name.IdentifierExpressionDeclaration +import org.jetbrains.dukat.tsmodel.types.TypeDeclaration +import java.util.* private class ProcessNullabilityChecksLowering(private val typeContext: StatementTypeContext) : DeclarationLowering { private val booleanUnaryOperators = listOf("!") private val booleanBinaryOperators = listOf("&&", "||") + private var counter = 1; + + private fun generateVariableName(): String { + return "_vn${counter++}" + } + + private fun createReplacementVariable(expression: ExpressionDeclaration): VariableDeclaration { + return VariableDeclaration( + name = generateVariableName(), + type = TypeDeclaration(IdentifierEntity("Any"), emptyList()), + modifiers = setOf(), + initializer = expression, + definitionsInfo = listOf(), + uid = "", + explicitlyDeclaredType = false + ) + } + private fun ExpressionDeclaration.convertToNonNullCheck(): ExpressionDeclaration { return BinaryExpressionDeclaration( left = this, @@ -39,6 +61,9 @@ private class ProcessNullabilityChecksLowering(private val typeContext: Statemen private fun processConditionNonNull(condition: ExpressionDeclaration): ExpressionDeclaration { if (!typeContext.hasBooleanType(condition)) { + if (condition !is IdentifierExpressionDeclaration) { + typeContext.addExpressionToReplace(condition) + } return ParenthesizedExpressionDeclaration(condition.convertToNonNullCheck()) } return condition @@ -46,6 +71,9 @@ private class ProcessNullabilityChecksLowering(private val typeContext: Statemen private fun processEqualsNull(condition: UnaryExpressionDeclaration): ExpressionDeclaration { if (!typeContext.hasBooleanType(condition.operand)) { + if (condition.operand !is IdentifierExpressionDeclaration) { + typeContext.addExpressionToReplace(condition.operand) + } return ParenthesizedExpressionDeclaration(condition.operand.convertToEqualsNullCheck()) } return condition @@ -91,11 +119,31 @@ private class ProcessNullabilityChecksLowering(private val typeContext: Statemen ) } - override fun lowerBlockStatement(block: BlockDeclaration): BlockDeclaration { + override fun lowerBlock(statement: BlockDeclaration): BlockDeclaration { typeContext.startScope() - val newBlock = super.lowerBlockStatement(block) + val newStatements = mutableListOf() + var statementsToProcess = ArrayDeque(statement.statements) + while (statementsToProcess.isNotEmpty()) { + val nextStatement = super.lower(statementsToProcess.removeFirst()) + val expressionsToReplace = typeContext.getRelevantExpressionsToReplace() + if (expressionsToReplace.isNotEmpty()) { + val replacementVariables = expressionsToReplace.map { createReplacementVariable(it) } + val replacementLowering = ReplaceExpressionsLowering( + replacementVariables.map { variable -> + variable.initializer!! to IdentifierExpressionDeclaration(IdentifierEntity(variable.name)) + }.toMap() + ) + + statementsToProcess = ArrayDeque(statementsToProcess.map { + replacementLowering.lower(it) + }) + newStatements += replacementVariables + replacementLowering.lower(nextStatement) + } else { + newStatements += nextStatement + } + } typeContext.endScope() - return newBlock + return BlockDeclaration(newStatements) } override fun lowerVariableDeclaration(declaration: VariableDeclaration): VariableDeclaration { diff --git a/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processTypePredicates.kt b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processTypePredicates.kt new file mode 100644 index 000000000..8f0958a52 --- /dev/null +++ b/typescript/ts-lowerings/src/org/jetbrains/dukat/tsLowerings/processTypePredicates.kt @@ -0,0 +1,187 @@ +package org.jetbrains.dukat.tsLowerings + +import org.jetbrains.dukat.astCommon.IdentifierEntity +import org.jetbrains.dukat.astCommon.QualifierEntity +import org.jetbrains.dukat.ownerContext.NodeOwner +import org.jetbrains.dukat.tsmodel.BlockDeclaration +import org.jetbrains.dukat.tsmodel.ExpressionStatementDeclaration +import org.jetbrains.dukat.tsmodel.FunctionDeclaration +import org.jetbrains.dukat.tsmodel.FunctionOwnerDeclaration +import org.jetbrains.dukat.tsmodel.IfStatementDeclaration +import org.jetbrains.dukat.tsmodel.SourceSetDeclaration +import org.jetbrains.dukat.tsmodel.StatementDeclaration +import org.jetbrains.dukat.tsmodel.TypePredicateDeclaration +import org.jetbrains.dukat.tsmodel.VariableDeclaration +import org.jetbrains.dukat.tsmodel.expression.AsExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.CallExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.ExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.PropertyAccessExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.name.IdentifierExpressionDeclaration +import org.jetbrains.dukat.tsmodel.expression.name.QualifierExpressionDeclaration +import org.jetbrains.dukat.tsmodel.types.ParameterValueDeclaration +import org.jetbrains.dukat.tsmodel.types.TypeDeclaration + +private data class TypePredicateRecord( + val type: ParameterValueDeclaration, + val parameterIndex: Int, + val parameterIsThis: Boolean +) + +private data class Replacement( + val from: ExpressionDeclaration, + val to: IdentifierExpressionDeclaration +) + +private class TypePredicateCollector : DeclarationLowering { + + private val typePredicateFunctions = mutableMapOf() + + fun findTypePredicate(name: String): TypePredicateRecord? { + return typePredicateFunctions[name] + } + + override fun lowerFunctionDeclaration( + declaration: FunctionDeclaration, + owner: NodeOwner? + ): FunctionDeclaration { + val type = declaration.type + if (type is TypePredicateDeclaration) { + if (type.parameter == "this") { + typePredicateFunctions[declaration.name] = TypePredicateRecord( + type.type, + 0, + true + ) + } else { + val index = declaration.parameters.indexOfFirst { it.name == type.parameter } + if (index != -1) { + typePredicateFunctions[declaration.name] = TypePredicateRecord( + type.type, + index, + false + ) + } + } + } + return super.lowerFunctionDeclaration(declaration, owner) + } +} + +private class TypePredicateExpressionLowering(private val context: TypePredicateCollector) : DeclarationLowering { + + val typePredicateClauses = mutableListOf>() + + override fun lower(declaration: ExpressionDeclaration): ExpressionDeclaration { + when (declaration) { + is CallExpressionDeclaration -> { + val functionName = when (val function = declaration.expression) { + is IdentifierExpressionDeclaration -> function.identifier.value + is QualifierExpressionDeclaration -> function.qualifier.right.value + is PropertyAccessExpressionDeclaration -> function.name.value + else -> null + } + val typePredicate = functionName?.let { context.findTypePredicate(it) } + typePredicate?.let { + typePredicateClauses += declaration to it + } + } + } + return super.lower(declaration) + } +} + +private class TypePredicateLowering(private val context: TypePredicateCollector) : DeclarationLowering { + + private var counter = 1 + + private fun createAsExpression( + checkExpression: CallExpressionDeclaration, + parameterIndex: Int, + type: ParameterValueDeclaration, + parameterIsThis: Boolean + ): AsExpressionDeclaration? { + if (parameterIsThis) { + val callerName = when (val function = checkExpression.expression) { + is QualifierExpressionDeclaration -> when (val left = function.qualifier.left) { + is IdentifierEntity -> IdentifierExpressionDeclaration(left) + is QualifierEntity -> QualifierExpressionDeclaration(left) + } + is PropertyAccessExpressionDeclaration -> function.expression + else -> null + } + return callerName?.let { caller -> + AsExpressionDeclaration( + expression = caller, + type = type + ) + } + } + if (checkExpression.arguments.size <= parameterIndex) { + return null + } + return AsExpressionDeclaration( + expression = checkExpression.arguments[parameterIndex], + type = type + ) + } + + private fun generateVariableName(): String { + return "_v${counter++}" + } + + private fun createStatementsAndReplacements( + checkExpression: CallExpressionDeclaration, + record: TypePredicateRecord + ): Pair { + val asExpression = createAsExpression(checkExpression, record.parameterIndex, record.type, record.parameterIsThis) + ?: return null to null + return when (asExpression.expression) { + is IdentifierExpressionDeclaration -> ExpressionStatementDeclaration(asExpression) to null + else -> { + val name = generateVariableName() + VariableDeclaration( + name = name, + type = TypeDeclaration(IdentifierEntity("Any"), emptyList()), + modifiers = setOf(), + initializer = asExpression, + definitionsInfo = listOf(), + uid = "", + explicitlyDeclaredType = false + ) to Replacement(asExpression.expression, IdentifierExpressionDeclaration(IdentifierEntity(name))) + } + } + } + + override fun lowerIfStatement(statement: IfStatementDeclaration): IfStatementDeclaration { + val expressionLowering = TypePredicateExpressionLowering(context) + expressionLowering.lower(statement.condition) + val (newStatements, replacements) = expressionLowering.typePredicateClauses.map { (expression, record) -> + createStatementsAndReplacements(expression, record) + }.unzip() + return super.lowerIfStatement( + statement.copy( + thenStatement = BlockDeclaration( + newStatements.filterNotNull() + + ReplaceExpressionsLowering( + replacements.filterNotNull().map { (it.from to it.to) }.toMap() + ).lowerBlock(statement.thenStatement).statements + ) + ) + ) + } +} + +class ProcessTypePredicates : TsLowering { + override fun lower(source: SourceSetDeclaration): SourceSetDeclaration { + val typePredicateCollector = TypePredicateCollector() + source.sources.map { + typePredicateCollector.lowerSourceDeclaration(it.root) + } + + return source.copy(sources = source.sources.map { sourceFileDeclaration -> + sourceFileDeclaration.copy(root = TypePredicateLowering( + typePredicateCollector + ).lowerSourceDeclaration(sourceFileDeclaration.root)) + }) + } +} \ No newline at end of file diff --git a/typescript/ts-model-proto/src/Declarations.proto b/typescript/ts-model-proto/src/Declarations.proto index 7c5115116..77f7b5eca 100644 --- a/typescript/ts-model-proto/src/Declarations.proto +++ b/typescript/ts-model-proto/src/Declarations.proto @@ -41,6 +41,7 @@ message ParameterValueDeclarationProto { FunctionTypeDeclarationProto functionTypeDeclaration = 8; TypeParamReferenceDeclarationProto typeParamReferenceDeclaration = 9; NumericLiteralDeclarationProto numericLiteral = 10; + TypePredicateDeclarationProto typePredicate = 11; } } @@ -84,6 +85,11 @@ message UnionTypeDeclarationProto { repeated ParameterValueDeclarationProto params = 1; } +message TypePredicateDeclarationProto { + string parameter = 1; + ParameterValueDeclarationProto type = 2; +} + message TypeReferenceDeclarationProto { NameDeclarationProto value = 1; repeated ParameterValueDeclarationProto params = 2; diff --git a/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/TypePredicateDeclaration.kt b/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/TypePredicateDeclaration.kt new file mode 100644 index 000000000..854363cb7 --- /dev/null +++ b/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/TypePredicateDeclaration.kt @@ -0,0 +1,9 @@ +package org.jetbrains.dukat.tsmodel + +import org.jetbrains.dukat.astCommon.MetaData +import org.jetbrains.dukat.tsmodel.types.ParameterValueDeclaration + +data class TypePredicateDeclaration(val parameter: String, val type: ParameterValueDeclaration) : ParameterValueDeclaration { + override val nullable = false + override var meta: MetaData? = null +} \ No newline at end of file diff --git a/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/factory/convertProtobuf.kt b/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/factory/convertProtobuf.kt index a4e810f3a..69ba028c2 100644 --- a/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/factory/convertProtobuf.kt +++ b/typescript/ts-model/src/org/jetbrains/dukat/tsmodel/factory/convertProtobuf.kt @@ -47,6 +47,7 @@ import org.jetbrains.dukat.tsmodel.ThrowStatementDeclaration import org.jetbrains.dukat.tsmodel.TopLevelDeclaration import org.jetbrains.dukat.tsmodel.TypeAliasDeclaration import org.jetbrains.dukat.tsmodel.TypeParameterDeclaration +import org.jetbrains.dukat.tsmodel.TypePredicateDeclaration import org.jetbrains.dukat.tsmodel.VariableDeclaration import org.jetbrains.dukat.tsmodel.VariableLikeDeclaration import org.jetbrains.dukat.tsmodel.WhileStatementDeclaration @@ -161,6 +162,7 @@ import org.jetbrains.dukat.tsmodelproto.TopLevelDeclarationProto import org.jetbrains.dukat.tsmodelproto.TypeAliasDeclarationProto import org.jetbrains.dukat.tsmodelproto.TypeOfExpressionDeclarationProto import org.jetbrains.dukat.tsmodelproto.TypeParameterDeclarationProto +import org.jetbrains.dukat.tsmodelproto.TypePredicateDeclarationProto import org.jetbrains.dukat.tsmodelproto.TypeReferenceDeclarationProto import org.jetbrains.dukat.tsmodelproto.UnaryExpressionDeclarationProto import org.jetbrains.dukat.tsmodelproto.UnknownExpressionDeclarationProto @@ -594,6 +596,12 @@ private fun ParameterValueDeclarationProto.convert(): ParameterValueDeclaration type.convert() ) } + hasTypePredicate() -> { + TypePredicateDeclaration( + typePredicate.parameter, + typePredicate.type.convert() + ) + } else -> throw Exception("unknown ParameterValueDeclarationProto ${this}") } } diff --git a/typescript/ts-node-lowering/src/org/jetrbains/dukat/nodeLowering/lowerings/convertStatements.kt b/typescript/ts-node-lowering/src/org/jetrbains/dukat/nodeLowering/lowerings/convertStatements.kt index e92f22f98..f4886673c 100644 --- a/typescript/ts-node-lowering/src/org/jetrbains/dukat/nodeLowering/lowerings/convertStatements.kt +++ b/typescript/ts-node-lowering/src/org/jetrbains/dukat/nodeLowering/lowerings/convertStatements.kt @@ -4,7 +4,6 @@ import org.jetbrains.dukat.ast.model.duplicate import org.jetbrains.dukat.ast.model.nodes.TypeNode import org.jetbrains.dukat.ast.model.nodes.convertToNode import org.jetbrains.dukat.astCommon.IdentifierEntity -import org.jetbrains.dukat.astCommon.QualifierEntity import org.jetbrains.dukat.astModel.LambdaParameterModel import org.jetbrains.dukat.astModel.TypeModel import org.jetbrains.dukat.astModel.TypeParameterModel @@ -81,8 +80,6 @@ import org.jetbrains.dukat.astModel.statements.ThrowStatementModel import org.jetbrains.dukat.astModel.statements.WhenStatementModel import org.jetbrains.dukat.astModel.statements.WhileStatementModel import org.jetbrains.dukat.panic.raiseConcern -import org.jetbrains.dukat.tsmodel.ArrayDestructuringDeclaration -import org.jetbrains.dukat.tsmodel.BindingVariableDeclaration import org.jetbrains.dukat.tsmodel.BlockDeclaration import org.jetbrains.dukat.tsmodel.BreakStatementDeclaration import org.jetbrains.dukat.tsmodel.CaseDeclaration @@ -123,7 +120,6 @@ import org.jetbrains.dukat.tsmodel.expression.templates.StringTemplateTokenDecla import org.jetbrains.dukat.tsmodel.expression.templates.TemplateExpressionDeclaration import org.jetbrains.dukat.tsmodel.expression.templates.TemplateTokenDeclaration import org.jetbrains.dukat.tsmodel.types.ParameterValueDeclaration -import org.jetbrains.dukat.tsmodel.types.TypeDeclaration class ExpressionConverter(private val typeConverter: (TypeNode) -> TypeModel) { private fun LiteralExpressionDeclaration.convert(): ExpressionModel { diff --git a/typescript/ts-nodes/src/org/jetbrains/dukat/ast/model/nodes/convertTypeDeclarations.kt b/typescript/ts-nodes/src/org/jetbrains/dukat/ast/model/nodes/convertTypeDeclarations.kt index 5abd4b1ef..6d90151d7 100644 --- a/typescript/ts-nodes/src/org/jetbrains/dukat/ast/model/nodes/convertTypeDeclarations.kt +++ b/typescript/ts-nodes/src/org/jetbrains/dukat/ast/model/nodes/convertTypeDeclarations.kt @@ -5,6 +5,7 @@ import org.jetbrains.dukat.astCommon.IdentifierEntity import org.jetbrains.dukat.astCommon.MetaData import org.jetbrains.dukat.tsmodel.GeneratedInterfaceReferenceDeclaration import org.jetbrains.dukat.tsmodel.ParameterDeclaration +import org.jetbrains.dukat.tsmodel.TypePredicateDeclaration import org.jetbrains.dukat.tsmodel.types.FunctionTypeDeclaration import org.jetbrains.dukat.tsmodel.types.IntersectionTypeDeclaration import org.jetbrains.dukat.tsmodel.types.NumericLiteralDeclaration @@ -151,11 +152,13 @@ fun ParameterValueDeclaration.convertToNodeNullable(metaData: MetaData? = null): meta = metaData ?: meta ) is TypeNode -> this + is TypePredicateDeclaration -> TYPE_BOOLEAN else -> null } } private val TYPE_ANY = TypeValueNode(IdentifierEntity("Any"), emptyList(), null, false) +private val TYPE_BOOLEAN = TypeValueNode(IdentifierEntity("Boolean"), emptyList(), null, false) fun ParameterValueDeclaration.convertToNode(meta: MetaData? = null): TypeNode { return convertToNodeNullable(meta) ?: TYPE_ANY diff --git a/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt b/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt index b90610715..b3f8cf79b 100644 --- a/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt +++ b/typescript/ts-translator/src/org/jetbrains/dukat/ts/translator/TypescriptLowerer.kt @@ -42,6 +42,7 @@ import org.jetbrains.dukat.tsLowerings.MoveAliasesFromMergeableModules import org.jetbrains.dukat.tsLowerings.PreprocessUnionTypes import org.jetbrains.dukat.tsLowerings.ProcessForOfStatements import org.jetbrains.dukat.tsLowerings.ProcessNullabilityChecks +import org.jetbrains.dukat.tsLowerings.ProcessTypePredicates import org.jetbrains.dukat.tsLowerings.RemoveThisParameters import org.jetbrains.dukat.tsLowerings.RemoveUnusedGeneratedEntities import org.jetbrains.dukat.tsLowerings.RenameImpossibleDeclarations @@ -88,6 +89,7 @@ open class TypescriptLowerer( RemoveUnusedGeneratedEntities(), ProcessForOfStatements(), ProcessNullabilityChecks(), + ProcessTypePredicates(), EscapeLiterals(), MoveAliasesFromMergeableModules() )