JS: don't optimize RTTI when possible, i.e. don't remove is and as when object is known at compile time to have type in RHS of expression. Substitute kotlin.Any with Object. Fix #KT-7665, #KT-7664

This commit is contained in:
Alexey Andreev
2016-09-09 16:43:31 +03:00
parent c581ec6dd6
commit 66c78be10f
14 changed files with 71 additions and 74 deletions
@@ -1,3 +1,6 @@
// IGNORE_BACKEND: JS
// JS backend does not support Unit well. See KT-13932
val foo: () -> Unit = {} val foo: () -> Unit = {}
fun box(): String { fun box(): String {
@@ -111,7 +111,6 @@ enum class TypeCheck {
TYPEOF, TYPEOF,
INSTANCEOF, INSTANCEOF,
OR_NULL, OR_NULL,
IS_ANY,
AND_PREDICATE AND_PREDICATE
} }
@@ -137,7 +137,7 @@ public final class RhinoUtils {
} }
try { try {
ScriptableObject scope = getScope(ecmaVersion, context, jsLibraries); Scriptable scope = getScope(ecmaVersion, context, jsLibraries);
putGlobalVariablesIntoScope(scope, variables); putGlobalVariablesIntoScope(scope, variables);
context.evaluateString(scope, "kotlin.out = new kotlin.BufferedOutput();", "setup Kotlin.out", 0, null); context.evaluateString(scope, "kotlin.out = new kotlin.BufferedOutput();", "setup Kotlin.out", 0, null);
@@ -158,10 +158,8 @@ public final class RhinoUtils {
} }
@NotNull @NotNull
private static ScriptableObject getScope(@NotNull EcmaVersion version, @NotNull Context context, @NotNull List<String> jsLibraries) { private static Scriptable getScope(@NotNull EcmaVersion version, @NotNull Context context, @NotNull List<String> jsLibraries) {
ScriptableObject scope = context.initStandardObjects(null, false); return context.newObject(getParentScope(version, context, jsLibraries));
scope.setParentScope(getParentScope(version, context, jsLibraries));
return scope;
} }
@NotNull @NotNull
@@ -1765,12 +1765,6 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("noRuntimeTypeCheck.kt")
public void testNoRuntimeTypeCheck() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/cast/noRuntimeTypeCheck.kt");
doTest(fileName);
}
@TestMetadata("primitiveToClass.kt") @TestMetadata("primitiveToClass.kt")
public void testPrimitiveToClass() throws Exception { public void testPrimitiveToClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/cast/primitiveToClass.kt"); String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/expression/cast/primitiveToClass.kt");
@@ -6974,6 +6968,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/standardClasses"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.ANY, true); KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/box/standardClasses"), Pattern.compile("^([^_](.+))\\.kt$"), TargetBackend.ANY, true);
} }
@TestMetadata("any.kt")
public void testAny() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/standardClasses/any.kt");
doTest(fileName);
}
@TestMetadata("array.kt") @TestMetadata("array.kt")
public void testArray() throws Exception { public void testArray() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/standardClasses/array.kt"); String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/standardClasses/array.kt");
@@ -2691,7 +2691,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
@TestMetadata("lambdaToUnitCast.kt") @TestMetadata("lambdaToUnitCast.kt")
public void testLambdaToUnitCast() throws Exception { public void testLambdaToUnitCast() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/casts/lambdaToUnitCast.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/casts/lambdaToUnitCast.kt");
doTest(fileName); try {
doTest(fileName);
}
catch (Throwable ignore) {
return;
}
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
} }
@TestMetadata("notIs.kt") @TestMetadata("notIs.kt")
@@ -318,11 +318,6 @@ public final class Namer {
return invokeFunctionAndSetTypeCheckMetadata("andPredicate", Arrays.asList(a, b), TypeCheck.AND_PREDICATE); return invokeFunctionAndSetTypeCheckMetadata("andPredicate", Arrays.asList(a, b), TypeCheck.AND_PREDICATE);
} }
@NotNull
public JsExpression isAny() {
return invokeFunctionAndSetTypeCheckMetadata("isAny", Collections.<JsExpression>emptyList(), TypeCheck.IS_ANY);
}
@NotNull @NotNull
public JsExpression isComparable() { public JsExpression isComparable() {
return kotlin("isComparable"); return kotlin("isComparable");
@@ -279,6 +279,9 @@ public final class StaticContext {
@NotNull @NotNull
public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) { public JsName getNameForDescriptor(@NotNull DeclarationDescriptor descriptor) {
if (descriptor instanceof ClassDescriptor && KotlinBuiltIns.isAny((ClassDescriptor) descriptor)) {
return rootScope.declareName("Object");
}
SuggestedName suggested = nameSuggestion.suggest(descriptor); SuggestedName suggested = nameSuggestion.suggest(descriptor);
if (suggested == null) { if (suggested == null) {
throw new IllegalArgumentException("Can't generate name for root declarations: " + descriptor); throw new IllegalArgumentException("Can't generate name for root declarations: " + descriptor);
@@ -109,7 +109,7 @@ class CatchTranslator(
// translateIsCheck won't ever return `null` if its second argument is `null` // translateIsCheck won't ever return `null` if its second argument is `null`
val typeCheck = with (patternTranslator(nextContext)) { val typeCheck = with (patternTranslator(nextContext)) {
translateIsCheck(parameterRef, null, paramType) translateIsCheck(parameterRef, paramType)
}!! }!!
val elseBlock = translateCatches(nextContext, parameterDescriptor, parameterRef, catches) val elseBlock = translateCatches(nextContext, parameterDescriptor, parameterRef, catches)
@@ -44,18 +44,14 @@ import org.jetbrains.kotlin.psi.KtIsExpression;
import org.jetbrains.kotlin.psi.KtTypeReference; import org.jetbrains.kotlin.psi.KtTypeReference;
import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.types.DynamicTypesKt;
import org.jetbrains.kotlin.types.KotlinType; import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
import java.util.Collections; import java.util.Collections;
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionTypeOrSubtype; import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionTypeOrSubtype;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isAnyOrNullableAny;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isArray; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isArray;
import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt.getNameIfStandardType; import static org.jetbrains.kotlin.js.descriptorUtils.DescriptorUtilsKt.getNameIfStandardType;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeByReference; import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeByReference;
import static org.jetbrains.kotlin.js.translate.utils.BindingUtils.getTypeForExpression;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.equality; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.equality;
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.not; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.not;
import static org.jetbrains.kotlin.psi.KtPsiUtil.findChildByType; import static org.jetbrains.kotlin.psi.KtPsiUtil.findChildByType;
@@ -86,8 +82,7 @@ public final class PatternTranslator extends AbstractTranslator {
KtTypeReference typeReference = expression.getRight(); KtTypeReference typeReference = expression.getRight();
assert typeReference != null: "Cast expression must have type reference"; assert typeReference != null: "Cast expression must have type reference";
KotlinType sourceType = getTypeForExpression(bindingContext(), left); JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference);
JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), sourceType, typeReference);
if (isCheck == null) return expressionToCast; if (isCheck == null) return expressionToCast;
JsExpression onFail; JsExpression onFail;
@@ -108,8 +103,7 @@ public final class PatternTranslator extends AbstractTranslator {
JsExpression left = Translation.translateAsExpression(expression.getLeftHandSide(), context()); JsExpression left = Translation.translateAsExpression(expression.getLeftHandSide(), context());
KtTypeReference typeReference = expression.getTypeReference(); KtTypeReference typeReference = expression.getTypeReference();
assert typeReference != null; assert typeReference != null;
KotlinType sourceType = getTypeForExpression(bindingContext(), expression.getLeftHandSide()); JsExpression result = translateIsCheck(left, typeReference);
JsExpression result = translateIsCheck(left, sourceType, typeReference);
if (result == null) return JsLiteral.getBoolean(!expression.isNegated()); if (result == null) return JsLiteral.getBoolean(!expression.isNegated());
if (expression.isNegated()) { if (expression.isNegated()) {
@@ -119,10 +113,8 @@ public final class PatternTranslator extends AbstractTranslator {
} }
@Nullable @Nullable
public JsExpression translateIsCheck(@NotNull JsExpression subject, @Nullable KotlinType sourceType, public JsExpression translateIsCheck(@NotNull JsExpression subject, @NotNull KtTypeReference targetTypeReference) {
@NotNull KtTypeReference targetTypeReference) {
KotlinType targetType = getTypeByReference(bindingContext(), targetTypeReference); KotlinType targetType = getTypeByReference(bindingContext(), targetTypeReference);
if (sourceType != null && !DynamicTypesKt.isDynamic(sourceType) && TypeUtilsKt.isSubtypeOf(sourceType, targetType)) return null;
JsExpression checkFunReference = doGetIsTypeCheckCallable(targetType); JsExpression checkFunReference = doGetIsTypeCheckCallable(targetType);
if (checkFunReference == null) { if (checkFunReference == null) {
@@ -191,8 +183,6 @@ public final class PatternTranslator extends AbstractTranslator {
@Nullable @Nullable
private JsExpression getIsTypeCheckCallableForBuiltin(@NotNull KotlinType type) { private JsExpression getIsTypeCheckCallableForBuiltin(@NotNull KotlinType type) {
if (isAnyOrNullableAny(type)) return namer().isAny();
if (isFunctionTypeOrSubtype(type) && !ReflectionTypes.isNumberedKPropertyOrKMutablePropertyType(type)) { if (isFunctionTypeOrSubtype(type) && !ReflectionTypes.isNumberedKPropertyOrKMutablePropertyType(type)) {
return namer().isTypeOf(program().getStringLiteral("function")); return namer().isTypeOf(program().getStringLiteral("function"));
} }
@@ -174,9 +174,7 @@ public final class WhenTranslator extends AbstractTranslator {
KtExpression expressionToMatchNonTranslated = whenExpression.getSubjectExpression(); KtExpression expressionToMatchNonTranslated = whenExpression.getSubjectExpression();
assert expressionToMatchNonTranslated != null : "expressionToMatch != null => expressionToMatchNonTranslated != null: " + assert expressionToMatchNonTranslated != null : "expressionToMatch != null => expressionToMatchNonTranslated != null: " +
PsiUtilsKt.getTextWithLocation(conditionIsPattern); PsiUtilsKt.getTextWithLocation(conditionIsPattern);
KotlinType expressionToMatchType = BindingUtils.getTypeForExpression(bindingContext(), expressionToMatchNonTranslated); JsExpression result = Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference);
JsExpression result = Translation.patternTranslator(context).translateIsCheck(expressionToMatch, expressionToMatchType,
typeReference);
return result != null ? result : JsLiteral.TRUE; return result != null ? result : JsLiteral.TRUE;
} }
@@ -71,11 +71,6 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
private fun getReplacement(callee: JsInvocation, calleeArguments: List<JsExpression>, argument: JsExpression): JsExpression? { private fun getReplacement(callee: JsInvocation, calleeArguments: List<JsExpression>, argument: JsExpression): JsExpression? {
val typeCheck = callee.typeCheck val typeCheck = callee.typeCheck
return when (typeCheck) { return when (typeCheck) {
TypeCheck.IS_ANY -> {
// `Kotlin.isAny()(argument)` -> `argument != null`
if (calleeArguments.isEmpty()) TranslationUtils.isNotNullCheck(argument) else null
}
TypeCheck.TYPEOF -> { TypeCheck.TYPEOF -> {
// `Kotlin.isTypeOf(calleeArgument)(argument)` -> `typeOf argument === calleeArgument` // `Kotlin.isTypeOf(calleeArgument)(argument)` -> `typeOf argument === calleeArgument`
if (calleeArguments.size == 1) typeOfIs(argument, calleeArguments[0] as JsStringLiteral) else null if (calleeArguments.size == 1) typeOfIs(argument, calleeArguments[0] as JsStringLiteral) else null
@@ -1,28 +0,0 @@
package foo
// CHECK_NOT_CALLED_IN_SCOPE: scope=box function=isType
open class A()
class B() : A()
fun box(): String {
assertTrue(B() is A)
assertTrue(B() is A?)
assertTrue(B() is B)
assertTrue(B() is B?)
assertTrue((B() as B?) is A?)
assertTrue((null as A?) is A?)
assertNotEquals(null, B() as? A)
assertNotEquals(null, B() as? A?)
assertNotEquals(null, B() as? B)
assertNotEquals(null, B() as? B?)
assertNotEquals(null, B() as A)
assertNotEquals(null, B() as A?)
assertNotEquals(null, B() as B)
assertNotEquals(null, B() as B?)
return "OK"
}
+32
View File
@@ -0,0 +1,32 @@
// CHECK_CALLED_IN_SCOPE: function=isType scope=box
package foo
class A : Any()
fun Any?.asAny() = this
fun box(): String {
val x = Any().asAny()
if (x !is Any) return "fail1"
if (x.asDynamic().constructor !== js("Object")) return "fail1a"
if (A().asAny() !is Any) return "fail2"
if (arrayOf(1, 2, 3).asAny() !is Any) return "fail3"
if (createNakedObject() is Any) return "fail4"
if (({ }).asAny() !is Any) return "fail5"
if ((23).asAny() !is Any) return "fail6"
if ((3.14).asAny() !is Any) return "fail7"
if (false.asAny() !is Any) return "fail8"
if ("bar".asAny() !is Any) return "fail9"
return "OK"
}
fun createNakedObject(): Any? = js("Object.create(null)")
+12 -6
View File
@@ -92,6 +92,18 @@
* @returns {Boolean} * @returns {Boolean}
*/ */
Kotlin.isType = function (object, klass) { Kotlin.isType = function (object, klass) {
if (klass === Object) {
switch (typeof object) {
case "string":
case "number":
case "boolean":
case "function":
return true;
default:
return object instanceof Object;
}
}
if (object == null || klass == null || (typeof object !== 'object' && typeof object !== 'function')) { if (object == null || klass == null || (typeof object !== 'object' && typeof object !== 'function')) {
return false; return false;
} }
@@ -258,12 +270,6 @@
return object == null || fn(object); return object == null || fn(object);
} }
}; };
Kotlin.isAny = function () {
return function (object) {
return object != null;
}
};
Kotlin.andPredicate = function (a, b) { Kotlin.andPredicate = function (a, b) {
return function (object) { return function (object) {