JS/RTTI: if it's known that T <: X & Y, where T is non-reified type variable, for each a is T check that a is X && a is Y

This commit is contained in:
Alexey Andreev
2016-04-29 11:31:11 +03:00
parent 6454613b51
commit 7ff658414f
10 changed files with 213 additions and 103 deletions
@@ -34,6 +34,8 @@ import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.google.dart.compiler.backend.js.ast.JsScopesKt.JsObjectScope;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.fqnWithoutSideEffects;
@@ -403,9 +405,14 @@ public final class Namer {
return invokeFunctionAndSetTypeCheckMetadata("orNull", callable, TypeCheck.OR_NULL);
}
@NotNull
public JsExpression andPredicate(@NotNull JsExpression a, @NotNull JsExpression b) {
return invokeFunctionAndSetTypeCheckMetadata("andPredicate", Arrays.asList(a, b), TypeCheck.AND_PREDICATE);
}
@NotNull
public JsExpression isAny() {
return invokeFunctionAndSetTypeCheckMetadata("isAny", null, TypeCheck.IS_ANY);
return invokeFunctionAndSetTypeCheckMetadata("isAny", Collections.<JsExpression>emptyList(), TypeCheck.IS_ANY);
}
@NotNull
@@ -423,13 +430,19 @@ public final class Namer {
@NotNull String functionName,
@Nullable JsExpression argument,
@NotNull TypeCheck metadata
) {
List<JsExpression> arguments = argument != null ? Collections.singletonList(argument) : Collections.<JsExpression>emptyList();
return invokeFunctionAndSetTypeCheckMetadata(functionName, arguments, metadata);
}
@NotNull
private JsExpression invokeFunctionAndSetTypeCheckMetadata(
@NotNull String functionName,
@NotNull List<JsExpression> arguments,
@NotNull TypeCheck metadata
) {
JsInvocation invocation = new JsInvocation(kotlin(functionName));
if (argument != null) {
invocation.getArguments().add(argument);
}
invocation.getArguments().addAll(arguments);
MetadataProperties.setTypeCheck(invocation, metadata);
MetadataProperties.setSideEffects(invocation, false);
return invocation;
@@ -41,7 +41,6 @@ import org.jetbrains.kotlin.psi.KtTypeReference;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.types.DynamicTypesKt;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeIntersector;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionTypeOrSubtype;
@@ -157,7 +156,13 @@ public final class PatternTranslator extends AbstractTranslator {
return getIsTypeCheckCallableForReifiedType(typeParameterDescriptor);
}
return doGetIsTypeCheckCallable(TypeIntersector.getUpperBoundsAsType(typeParameterDescriptor));
JsExpression result = null;
for (KotlinType upperBound : typeParameterDescriptor.getUpperBounds()) {
JsExpression next = doGetIsTypeCheckCallable(upperBound);
result = result != null ? namer().andPredicate(result, next) : next;
}
assert result != null : "KotlinType is expected to return at least one upper bound: " + type;
return result;
}
JsNameRef typeName = getClassNameReference(type);
@@ -35,7 +35,7 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
scopes.push(x.scope)
localVars.push(IdentitySet())
localVars.push(IdentitySet<JsName>().apply { this += x.parameters.map { it.name } })
return super.visit(x, ctx)
}
@@ -50,19 +50,14 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
super.endVisit(x, ctx)
}
override fun endVisit(x: JsConditional, ctx: JsContext<JsNode>) {
val test = x.testExpression
}
override fun visit(x: JsInvocation, ctx: JsContext<JsNode>): Boolean {
// callee(calleeArgument)(argument)
val callee = x.qualifier as? JsInvocation
val calleeArgument = callee?.arguments?.firstOrNull()
val calleeArguments = callee?.arguments
val argument = x.arguments.firstOrNull()
if (callee != null && argument != null) {
val replacement = getReplacement(callee, calleeArgument, argument)
if (callee != null && argument != null && calleeArguments != null) {
val replacement = getReplacement(callee, calleeArguments, argument)
if (replacement != null) {
ctx.replaceMe(accept(replacement))
@@ -73,64 +68,100 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
return true
}
private fun getReplacement(callee: JsInvocation, calleeArgument: JsExpression?, argument: JsExpression): JsExpression? {
if (calleeArgument == null) {
// `Kotlin.isAny()(argument)` -> `argument != null`
if (callee.typeCheck == TypeCheck.IS_ANY) {
return TranslationUtils.isNotNullCheck(argument)
private fun getReplacement(callee: JsInvocation, calleeArguments: List<JsExpression>, argument: JsExpression): JsExpression? {
val typeCheck = callee.typeCheck
return when (typeCheck) {
TypeCheck.IS_ANY -> {
// `Kotlin.isAny()(argument)` -> `argument != null`
if (calleeArguments.isEmpty()) TranslationUtils.isNotNullCheck(argument) else null
}
return null
}
// `Kotlin.isTypeOf(calleeArgument)(argument)` -> `typeOf argument === calleeArgument`
if (callee.typeCheck == TypeCheck.TYPEOF) {
return typeOfIs(argument, calleeArgument as JsStringLiteral)
}
// `Kotlin.isInstanceOf(calleeArgument)(argument)` -> `argument instanceof calleeArgument`
if (callee.typeCheck == TypeCheck.INSTANCEOF) {
return context.namer().isInstanceOf(argument, calleeArgument)
}
// `Kotlin.orNull(calleeArgument)(argument)` -> `(tmp = argument) == null || calleeArgument(tmp)`
if (callee.typeCheck == TypeCheck.OR_NULL) {
if (calleeArgument is JsInvocation && calleeArgument.typeCheck == TypeCheck.OR_NULL) {
return JsInvocation(calleeArgument, argument)
TypeCheck.TYPEOF -> {
// `Kotlin.isTypeOf(calleeArgument)(argument)` -> `typeOf argument === calleeArgument`
if (calleeArguments.size == 1) typeOfIs(argument, calleeArguments[0] as JsStringLiteral) else null
}
var nullCheckTarget = argument
var nextCheckTarget = argument
if (argument.isAssignmentToLocalVar) {
// `Kotlin.orNull(Kotlin.isInstance(SomeType))(localVar=someExpr)` -> `(localVar=someExpr) != null || Kotlin.isInstance(SomeType)(localVar)`
val localVar = (argument as JsBinaryOperation).getArg1()
nextCheckTarget = localVar
}
else if (!argument.isLocalVar) {
val currentScope = scopes.peek()
val tmp = currentScope.declareTemporary()
val statementContext = lastStatementLevelContext
statementContext.addPrevious(newVar(tmp, null))
nullCheckTarget = assignment(tmp.makeRef(), argument)
nextCheckTarget = tmp.makeRef()
TypeCheck.INSTANCEOF -> {
// `Kotlin.isInstanceOf(calleeArgument)(argument)` -> `argument instanceof calleeArgument`
if (calleeArguments.size == 1) context.namer().isInstanceOf(argument, calleeArguments[0]) else null
}
val isNull = TranslationUtils.isNullCheck(nullCheckTarget)
return or(isNull, JsInvocation(calleeArgument, nextCheckTarget))
TypeCheck.OR_NULL -> {
// `Kotlin.orNull(calleeArgument)(argument)` -> `(tmp = argument) == null || calleeArgument(tmp)`
if (calleeArguments.size == 1) getReplacementForOrNull(argument, calleeArguments[0]) else null
}
TypeCheck.AND_PREDICATE -> {
// `Kotlin.andPredicate(p1, p2)(argument)` -> `p1(tmp = argument) && p2(tmp)`
if (calleeArguments.size == 2) {
getReplacementForAndPredicate(argument, calleeArguments[0], calleeArguments[1])
}
else {
null
}
}
null -> null
}
return null
}
private val JsExpression.isLocalVar: Boolean
get() {
if (localVars.empty() || this !is JsNameRef) return false
val name = this.getName()
return name != null && localVars.peek().contains(name)
private fun getReplacementForOrNull(argument: JsExpression, calleeArgument: JsExpression): JsExpression {
if (calleeArgument is JsInvocation && calleeArgument.typeCheck == TypeCheck.OR_NULL) {
return JsInvocation(calleeArgument, argument)
}
var nullCheckTarget = argument
var nextCheckTarget = argument
if (argument.isAssignmentToLocalVar) {
// `Kotlin.orNull(Kotlin.isInstance(SomeType))(localVar=someExpr)` ->
// `(localVar=someExpr) != null || Kotlin.isInstance(SomeType)(localVar)`
val localVar = (argument as JsBinaryOperation).arg1
nextCheckTarget = localVar
}
else if (argument.needsAlias) {
val currentScope = scopes.peek()
val tmp = currentScope.declareTemporary()
val statementContext = lastStatementLevelContext
statementContext.addPrevious(newVar(tmp, null))
nullCheckTarget = assignment(tmp.makeRef(), argument)
nextCheckTarget = tmp.makeRef()
}
val isNull = TranslationUtils.isNullCheck(nullCheckTarget)
return or(isNull, JsInvocation(calleeArgument, nextCheckTarget))
}
private fun getReplacementForAndPredicate(argument: JsExpression, p1: JsExpression, p2: JsExpression): JsExpression {
val (arg1, arg2) = if (!argument.needsAlias) {
Pair(argument, argument)
}
else if (argument.isAssignmentToLocalVar) {
Pair(argument, JsAstUtils.decomposeAssignment(argument)!!.first)
}
else {
val currentScope = scopes.peek()
val tmp = currentScope.declareTemporary()
val statementContext = lastStatementLevelContext
statementContext.addPrevious(newVar(tmp, null))
Pair(assignment(tmp.makeRef(), argument), tmp.makeRef())
}
val first = accept(JsInvocation(p1, arg1) as JsExpression)
val second = accept(JsInvocation(p2, arg2) as JsExpression)
return JsAstUtils.and(first, second)
}
private val JsExpression.needsAlias: Boolean
get() = when (this) {
is JsStringLiteral -> false
else -> !isLocalVar
}
private val JsExpression.isLocalVar: Boolean
get() = localVars.isNotEmpty() && this is JsNameRef && name.let { it != null && it in localVars.peek() }
private val JsExpression.isAssignmentToLocalVar: Boolean
get() = this is JsBinaryOperation && getOperator() == JsBinaryOperator.ASG
get() = localVars.isNotEmpty() &&
JsAstUtils.decomposeAssignmentToVariable(this).let { it != null && it.first in localVars.peek() }
}
@@ -0,0 +1,59 @@
package foo
open class A()
interface X
interface Y
class B() : A(), X, Y {
override fun toString() = "B"
}
class C() : A(), X, Y {
override fun toString() = "C"
}
class D() : A() {
override fun toString() = "D"
}
class E() : X {
override fun toString() = "E"
}
class F() : A(), Y {
override fun toString() = "E"
}
fun <T> test(a: Any): String where T : A, T : X, T : Y {
return (try {
a as T
}
catch (e: Exception) {
"error"
}).toString()
}
fun box(): String {
val b = B()
val c = C()
val d = D()
val e = E()
val f = F()
assertEquals("B", test<B>(b))
assertEquals("B", test<C>(b))
assertEquals("C", test<B>(c))
assertEquals("C", test<C>(c))
assertEquals("error", test<B>(d))
assertEquals("error", test<C>(d))
assertEquals("error", test<B>(e))
assertEquals("error", test<C>(e))
assertEquals("error", test<B>(f))
assertEquals("error", test<C>(f))
return "OK"
}
@@ -1,6 +1,6 @@
package foo
// CHECK_METHOD_NOT_CALLED_IN_SCOPE: scope=box function=isType
// CHECK_NOT_CALLED_IN_SCOPE: scope=box function=isType
open class A()
+6
View File
@@ -560,6 +560,12 @@ var Kotlin = {};
return object != null;
}
};
Kotlin.andPredicate = function (a, b) {
return function (object) {
return a(object) && b(object);
}
};
Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) {
};
+1 -1
View File
@@ -1,7 +1,7 @@
package foo
// CHECK_NOT_CALLED: isTypeOfOrNull
// CHECK_NULLS_COUNT: function=box count=8
// CHECK_NULLS_COUNT: function=box count=10
inline
fun <reified T> Any?.isTypeOfOrNull() = this is T?