From 9bb60b48b296ac93bc9ce08a4733090c174bedbd Mon Sep 17 00:00:00 2001 From: Alexey Tsvetkov Date: Thu, 25 Jun 2015 00:11:17 +0300 Subject: [PATCH] JS/RTTI. Fix run-time type checking against Comparable --- .../patterns/typePredicates/typePredicates.kt | 56 +++++++++++++++++++ .../js/test/semantics/RttiTestGenerated.java | 6 ++ .../kotlin/js/translate/context/Namer.java | 5 ++ .../expression/PatternTranslator.java | 23 ++++++-- js/js.translator/testData/kotlin_lib.js | 9 +++ .../testData/rtti/cases/isComparable.kt | 25 +++++++++ 6 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 js/js.frontend/src/org/jetbrains/kotlin/js/patterns/typePredicates/typePredicates.kt create mode 100644 js/js.translator/testData/rtti/cases/isComparable.kt diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/patterns/typePredicates/typePredicates.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/patterns/typePredicates/typePredicates.kt new file mode 100644 index 00000000000..f438d1ceab3 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/patterns/typePredicates/typePredicates.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.js.patterns.typePredicates + +import com.google.common.base.Predicate +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.types.KotlinType + +interface TypePredicate : Predicate { + override fun apply(type: KotlinType?): Boolean +} + +private val KOTLIN = TypePredicateImpl("kotlin") +val COMPARABLE: TypePredicate = KOTLIN.inner("Comparable") +val CHAR_SEQUENCE: TypePredicate = KOTLIN.inner("CharSequence") + +// TODO: replace all NamePredicate usages to TypePredicate +/** + * A more precise NamePredicate analog, that checks fully-qualified + * name rather than simple name. + * + * @see org.jetbrains.kotlin.js.patterns.NamePredicate + */ +private class TypePredicateImpl + private constructor (private val nameParts: List) +: TypePredicate { + constructor(name: String) : this(listOf(name)) + + override fun apply(type: KotlinType?): Boolean { + var descriptor: DeclarationDescriptor? = type?.constructor?.declarationDescriptor ?: return false + + for (i in nameParts.lastIndex downTo 0) { + if (nameParts[i] != descriptor?.name?.asString()) return false + + descriptor = descriptor?.containingDeclaration + } + + return true + } + + fun inner(name: String) = TypePredicateImpl(nameParts + listOf(name)) +} diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RttiTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RttiTestGenerated.java index fc8528dbaa5..9f12af40c56 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RttiTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/RttiTestGenerated.java @@ -41,6 +41,12 @@ public class RttiTestGenerated extends AbstractRttiTest { doTest(fileName); } + @TestMetadata("isComparable.kt") + public void testIsComparable() throws Exception { + String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/rtti/cases/isComparable.kt"); + doTest(fileName); + } + @TestMetadata("isJsPrimitiveType.kt") public void testIsJsPrimitiveType() throws Exception { String fileName = JetTestUtils.navigationMetadata("js/js.translator/testData/rtti/cases/isJsPrimitiveType.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index c4285201771..6ed3a4a8f33 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -407,6 +407,11 @@ public final class Namer { return invokeFunctionAndSetTypeCheckMetadata("isAny", null, TypeCheck.IS_ANY); } + @NotNull + public JsExpression isComparable() { + return kotlin("isComparable"); + } + @NotNull private JsExpression invokeFunctionAndSetTypeCheckMetadata( @NotNull String functionName, diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java index a8119e5c6af..ccd9bd27ebf 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/PatternTranslator.java @@ -27,6 +27,7 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor; import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor; import org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt; import org.jetbrains.kotlin.js.patterns.NamePredicate; +import org.jetbrains.kotlin.js.patterns.typePredicates.TypePredicatesPackage; import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TemporaryVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; @@ -130,15 +131,12 @@ public final class PatternTranslator extends AbstractTranslator { @NotNull private JsExpression doGetIsTypeCheckCallable(@NotNull KotlinType type) { - if (isAnyOrNullableAny(type)) return namer().isAny(); - - if (isFunctionTypeOrSubtype(type)) return namer().isTypeOf(program().getStringLiteral("function")); - - if (isArray(type)) return Namer.IS_ARRAY_FUN_REF; - JsExpression builtinCheck = getIsTypeCheckCallableForBuiltin(type); if (builtinCheck != null) return builtinCheck; + builtinCheck = getIsTypeCheckCallableForPrimitiveBuiltin(type); + if (builtinCheck != null) return builtinCheck; + TypeParameterDescriptor typeParameterDescriptor = getTypeParameterDescriptorOrNull(type); if (typeParameterDescriptor != null) { if (typeParameterDescriptor.isReified()) { @@ -152,6 +150,19 @@ public final class PatternTranslator extends AbstractTranslator { return namer().isInstanceOf(typeName); } + @Nullable + private JsExpression getIsTypeCheckCallableForBuiltin(@NotNull KotlinType type) { + if (isAnyOrNullableAny(type)) return namer().isAny(); + + if (isFunctionOrExtensionFunctionType(type)) return namer().isTypeOf(program().getStringLiteral("function")); + + if (isArray(type)) return Namer.IS_ARRAY_FUN_REF; + + if (TypePredicatesPackage.getCOMPARABLE().apply(type)) return namer().isComparable(); + + return null; + } + @Nullable private JsExpression getIsTypeCheckCallableForBuiltin(@NotNull KotlinType type) { Name typeName = DescriptorUtilsKt.getNameIfStandardType(type); diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index c8db5a02cef..f673c0b4578 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -126,6 +126,15 @@ return (typeof value) == "string" && value.length == 1; }; + Kotlin.isComparable = function (value) { + var type = typeof value; + + return type === "string" || + type === "boolean" || + Kotlin.isNumber(value) || + Kotlin.isType(value, Kotlin.Comparable); + }; + Kotlin.charInc = function (value) { return String.fromCharCode(value.charCodeAt(0)+1); }; diff --git a/js/js.translator/testData/rtti/cases/isComparable.kt b/js/js.translator/testData/rtti/cases/isComparable.kt new file mode 100644 index 00000000000..c08a210a961 --- /dev/null +++ b/js/js.translator/testData/rtti/cases/isComparable.kt @@ -0,0 +1,25 @@ +package foo + +class A : Comparable { + override fun compareTo(other: A): Int = 0 +} + +class B + +fun test(x: Any?): Boolean = x is Comparable<*> + +fun box(): String { + assertEquals(true, test(A()), "A()") + assertEquals(true, test("abc"), "\"abc\"") + assertEquals(true, test('a'), "\'a\'") + assertEquals(true, test(0), "0") + assertEquals(true, test(0.toChar()), "0.toChar()") + assertEquals(true, test(0.toByte()), "0.toByte()") + assertEquals(true, test(0.toShort()), "0.toShort()") + assertEquals(true, test(0.toLong()), "0.toLong()") + assertEquals(true, test(0.toDouble()), "0.toDouble()") + assertEquals(true, test(0.toFloat()), "0.toFloat()") + assertEquals(false, test(B()), "B()") + + return "OK" +} \ No newline at end of file