Skip to content

Support type predicates #332

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,10 @@ import org.w3c.performance.*
import org.w3c.workers.*
import org.w3c.xhr.*

external interface InterfaceA
external interface InterfaceA

external interface InterfaceB {
fun ping(): InterfaceC
}

external interface InterfaceC
24 changes: 12 additions & 12 deletions compiler/test/data/typescript/importAs/importAs.d.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -81,4 +77,8 @@ import org.w3c.performance.*
import org.w3c.workers.*
import org.w3c.xhr.*

external interface TransformOptions
external open class Computable {
open fun compute()
}

external interface Pipable
Original file line number Diff line number Diff line change
@@ -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)
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
41 changes: 41 additions & 0 deletions compiler/test/data/typescriptBodies/typePredicate/simple.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
25 changes: 25 additions & 0 deletions compiler/test/data/typescriptBodies/typePredicate/simple.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
)
Expand Down
2 changes: 1 addition & 1 deletion typescript/ts-converter/src/AstConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions typescript/ts-converter/src/DependencyBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
12 changes: 11 additions & 1 deletion typescript/ts-converter/src/ast/AstFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ import {
TupleDeclarationProto,
TypeAliasDeclarationProto,
TypeParameterDeclarationProto,
TypeParamReferenceDeclarationProto,
TypeParamReferenceDeclarationProto, TypePredicateDeclarationProto,
TypeReferenceDeclarationProto,
UnionTypeDeclarationProto,
VariableDeclarationProto,
Expand Down Expand Up @@ -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<TypeParameter>, optional: boolean, modifiers: Array<ModifierDeclaration>, explicitlyDeclaredType: boolean): MemberDeclaration {
let propertyDeclaration = new PropertyDeclarationProto();
propertyDeclaration.setName(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
)
}

Expand Down Expand Up @@ -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) }
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,4 @@ interface DeclarationStatementLowering : ExpressionLowering {
}
}

fun lowerBlockStatement(block: BlockDeclaration): BlockDeclaration {
return BlockDeclaration(block.statements.map { lower(it) })
}

}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ExpressionDeclaration, IdentifierExpressionDeclaration>):
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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<NameEntity, ParameterValueDeclaration> = mutableMapOf()
val variables: MutableMap<NameEntity, ParameterValueDeclaration> = mutableMapOf(),
val expressionsToReplace: MutableSet<ExpressionDeclaration> = mutableSetOf()
)

internal class StatementTypeContext : DeclarationLowering {
Expand Down Expand Up @@ -78,6 +79,16 @@ internal class StatementTypeContext : DeclarationLowering {
scopes.removeLast()
}

fun addExpressionToReplace(expression: ExpressionDeclaration) {
scopes.last().expressionsToReplace += expression
}

fun getRelevantExpressionsToReplace(): Set<ExpressionDeclaration> {
val expressions = scopes.last().expressionsToReplace.toSet()
scopes.last().expressionsToReplace.clear()
return expressions
}

override fun lowerClassLikeDeclaration(
declaration: ClassLikeDeclaration,
owner: NodeOwner<ModuleDeclaration>?
Expand Down
Loading