JS/RTTI: in expressions like a as T when inferred type for a is S, where S <: T, don't generate type checking
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -41,7 +41,7 @@ public class CallCounter extends RecursiveJsVisitor {
|
||||
return visitor;
|
||||
}
|
||||
|
||||
CallCounter(@NotNull Set<String> exceptFunctionNames) {
|
||||
private CallCounter(@NotNull Set<String> 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
+3
-2
@@ -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)
|
||||
|
||||
+22
-13
@@ -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);
|
||||
}
|
||||
|
||||
+9
-1
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
+3
-3
@@ -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 : "";
|
||||
}, {
|
||||
|
||||
Reference in New Issue
Block a user