diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java index c002eed85bc..be58b03b2df 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/CastTestGenerated.java @@ -77,6 +77,12 @@ public class CastTestGenerated extends AbstractCastTest { doTest(fileName); } + @TestMetadata("noRuntimeTypeCheck.kt") + public void testNoRuntimeTypeCheck() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/noRuntimeTypeCheck.kt"); + doTest(fileName); + } + @TestMetadata("reifiedToNotNull.kt") public void testReifiedToNotNull() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/reifiedToNotNull.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/CallCounter.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/CallCounter.java index c48443c73b4..f23ce9a782e 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/CallCounter.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/CallCounter.java @@ -41,7 +41,7 @@ public class CallCounter extends RecursiveJsVisitor { return visitor; } - CallCounter(@NotNull Set exceptFunctionNames) { + private CallCounter(@NotNull Set exceptFunctionNames) { this.exceptFunctionNames = exceptFunctionNames; } @@ -63,6 +63,19 @@ public class CallCounter extends RecursiveJsVisitor { return count; } + public int getUnqualifiedCallsCount(String expectedName) { + int count = 0; + + for (JsNameRef callNameRef : callsNameRefs) { + JsName name = callNameRef.getName(); + if (name != null && name.getIdent().equals(expectedName)) { + count++; + } + } + + return count; + } + @Override public void visitInvocation(@NotNull JsInvocation invocation) { super.visitInvocation(invocation); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java index 352e0296433..bef3f02cbc1 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/utils/DirectiveTestUtils.java @@ -67,6 +67,13 @@ public class DirectiveTestUtils { } }; + private static final DirectiveHandler METHOD_NOT_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_METHOD_NOT_CALLED_IN_SCOPE") { + @Override + void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception { + checkMethodNotCalledInScope(ast, arguments.getNamedArgument("function"), arguments.getNamedArgument("scope")); + } + }; + private static final DirectiveHandler FUNCTIONS_HAVE_SAME_LINES = new DirectiveHandler("CHECK_FUNCTIONS_HAVE_SAME_LINES") { @Override void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception { @@ -189,6 +196,7 @@ public class DirectiveTestUtils { FUNCTION_NOT_CALLED, FUNCTION_CALLED_IN_SCOPE, FUNCTION_NOT_CALLED_IN_SCOPE, + METHOD_NOT_CALLED_IN_SCOPE, FUNCTIONS_HAVE_SAME_LINES, COUNT_LABELS, COUNT_VARS, @@ -241,6 +249,15 @@ public class DirectiveTestUtils { assertTrue(errorMessage, isCalledInScope(node, functionName, scopeFunctionName)); } + private static void checkMethodNotCalledInScope( + @NotNull JsNode node, + @NotNull String functionName, + @NotNull String scopeFunctionName + ) throws Exception { + String errorMessage = functionName + " is called inside " + scopeFunctionName; + assertTrue(errorMessage, isMethodCalledInScope(node, functionName, scopeFunctionName)); + } + private static boolean isCalledInScope( @NotNull JsNode node, @NotNull String functionName, @@ -252,6 +269,17 @@ public class DirectiveTestUtils { return counter.getQualifiedCallsCount(functionName) == 0; } + private static boolean isMethodCalledInScope( + @NotNull JsNode node, + @NotNull String functionName, + @NotNull String scopeFunctionName + ) throws Exception { + JsNode scope = AstSearchUtil.getFunction(node, scopeFunctionName); + + CallCounter counter = CallCounter.countCalls(scope); + return counter.getUnqualifiedCallsCount(functionName) == 0; + } + private abstract static class DirectiveHandler { @NotNull private final String directive; diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/CatchTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/CatchTranslator.kt index e9ff624a8e0..1d14808c7dd 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/CatchTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/CatchTranslator.kt @@ -90,9 +90,10 @@ class CatchTranslator( if (paramType.isThrowable) return thenBlock + // translateIsCheck won't ever return `null` if its second argument is `null` val typeCheck = with (patternTranslator(context())) { - translateIsCheck(parameterRef, paramType) - } + translateIsCheck(parameterRef, null, paramType) + }!! val elseBlock = translateCatches(parameterRef, catches) return JsIf(typeCheck, thenBlock, elseBlock) 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 032b16af7fb..d8ef5692998 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 @@ -31,7 +31,6 @@ import org.jetbrains.kotlin.js.translate.context.TemporaryVariable; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; import org.jetbrains.kotlin.js.translate.general.Translation; -import org.jetbrains.kotlin.js.translate.utils.BindingUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.name.Name; @@ -40,19 +39,21 @@ import org.jetbrains.kotlin.psi.KtExpression; import org.jetbrains.kotlin.psi.KtIsExpression; 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; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isAnyOrNullableAny; import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isArray; 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.getTypeForExpression; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.equality; import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.negated; import static org.jetbrains.kotlin.psi.KtPsiUtil.findChildByType; -import static org.jetbrains.kotlin.types.TypeUtils.getTypeParameterDescriptorOrNull; -import static org.jetbrains.kotlin.types.TypeUtils.isNullableType; -import static org.jetbrains.kotlin.types.TypeUtils.isReifiedTypeParameter; +import static org.jetbrains.kotlin.types.TypeUtils.*; public final class PatternTranslator extends AbstractTranslator { @@ -80,7 +81,10 @@ public final class PatternTranslator extends AbstractTranslator { KtTypeReference typeReference = expression.getRight(); assert typeReference != null: "Cast expression must have type reference"; - JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), typeReference); + KotlinType sourceType = getTypeForExpression(bindingContext(), left); + JsExpression isCheck = translateIsCheck(temporary.assignmentExpression(), sourceType, typeReference); + if (isCheck == null) return expressionToCast; + JsExpression onFail; if (isSafeCast(expression)) { @@ -99,23 +103,28 @@ public final class PatternTranslator extends AbstractTranslator { JsExpression left = Translation.translateAsExpression(expression.getLeftHandSide(), context()); KtTypeReference typeReference = expression.getTypeReference(); assert typeReference != null; - JsExpression result = translateIsCheck(left, typeReference); + KotlinType sourceType = getTypeForExpression(bindingContext(), expression.getLeftHandSide()); + JsExpression result = translateIsCheck(left, sourceType, typeReference); + if (result == null) return JsLiteral.getBoolean(!expression.isNegated()); + if (expression.isNegated()) { return negated(result); } return result; } - @NotNull - public JsExpression translateIsCheck(@NotNull JsExpression subject, @NotNull KtTypeReference typeReference) { + @Nullable + public JsExpression translateIsCheck(@NotNull JsExpression subject, @Nullable KotlinType sourceType, + @NotNull KtTypeReference targetTypeReference) { if (JsAstUtils.isEmptyExpression(subject)) return subject; - KotlinType type = BindingUtils.getTypeByReference(bindingContext(), typeReference); - JsExpression checkFunReference = doGetIsTypeCheckCallable(type); - boolean isReifiedType = isReifiedTypeParameter(type); + KotlinType targetType = getTypeByReference(bindingContext(), targetTypeReference); + if (sourceType != null && !DynamicTypesKt.isDynamic(sourceType) && TypeUtilsKt.isSubtypeOf(sourceType, targetType)) return null; - if (!isReifiedType && isNullableType(type) || - isReifiedType && findChildByType(typeReference, KtNodeTypes.NULLABLE_TYPE) != null + JsExpression checkFunReference = doGetIsTypeCheckCallable(targetType); + boolean isReifiedType = isReifiedTypeParameter(targetType); + if (!isReifiedType && isNullableType(targetType) || + isReifiedType && findChildByType(targetTypeReference, KtNodeTypes.NULLABLE_TYPE) != null ) { checkFunReference = namer().orNull(checkFunReference); } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java index 11d1a52aefe..4805272060b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/expression/WhenTranslator.java @@ -24,11 +24,13 @@ import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.general.AbstractTranslator; import org.jetbrains.kotlin.js.translate.general.Translation; import org.jetbrains.kotlin.js.translate.operation.InOperationTranslator; +import org.jetbrains.kotlin.js.translate.utils.BindingUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.*; import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt; +import org.jetbrains.kotlin.types.KotlinType; import java.util.HashMap; import java.util.Map; @@ -179,7 +181,13 @@ public final class WhenTranslator extends AbstractTranslator { KtTypeReference typeReference = conditionIsPattern.getTypeReference(); assert typeReference != null : "An is-check must have a type reference."; - return Translation.patternTranslator(context).translateIsCheck(expressionToMatch, typeReference); + KtExpression expressionToMatchNonTranslated = whenExpression.getSubjectExpression(); + assert expressionToMatchNonTranslated != null : "expressionToMatch != null => expressionToMatchNonTranslated != null: " + + PsiUtilsKt.getTextWithLocation(conditionIsPattern); + KotlinType expressionToMatchType = BindingUtils.getTypeForExpression(bindingContext(), expressionToMatchNonTranslated); + JsExpression result = Translation.patternTranslator(context).translateIsCheck(expressionToMatch, expressionToMatchType, + typeReference); + return result != null ? result : JsLiteral.TRUE; } @NotNull diff --git a/js/js.translator/testData/expression/cast/cases/noRuntimeTypeCheck.kt b/js/js.translator/testData/expression/cast/cases/noRuntimeTypeCheck.kt new file mode 100644 index 00000000000..35d9a7160e2 --- /dev/null +++ b/js/js.translator/testData/expression/cast/cases/noRuntimeTypeCheck.kt @@ -0,0 +1,28 @@ +package foo + +// CHECK_METHOD_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" +} diff --git a/js/js.translator/testData/kotlin_lib.js b/js/js.translator/testData/kotlin_lib.js index d2a332043b0..1b2dc121f86 100644 --- a/js/js.translator/testData/kotlin_lib.js +++ b/js/js.translator/testData/kotlin_lib.js @@ -17,7 +17,7 @@ (function (Kotlin) { "use strict"; - Kotlin.CharSequence = Kotlin.createTraitNow(null); + var CharSequence = Kotlin.createTraitNow(null); // Shims for String if (typeof String.prototype.startsWith === "undefined") { @@ -138,7 +138,7 @@ }; Kotlin.isCharSequence = function (value) { - return typeof value === "string" || Kotlin.isType(value, Kotlin.CharSequence); + return typeof value === "string" || Kotlin.isType(value, CharSequence); }; Kotlin.charInc = function (value) { @@ -1146,7 +1146,7 @@ }; - Kotlin.StringBuilder = Kotlin.createClassNow([Kotlin.CharSequence], + Kotlin.StringBuilder = Kotlin.createClassNow([CharSequence], function (content) { this.string = typeof(content) == "string" ? content : ""; }, {