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:
Alexey Andreev
2016-04-28 19:29:27 +03:00
parent 0f6a6a3ba4
commit 6454613b51
8 changed files with 113 additions and 20 deletions
@@ -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;
@@ -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)
@@ -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);
}
@@ -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
View File
@@ -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 : "";
}, {