JS/RTTI: if it's known that T <: X & Y, where T is non-reified type variable, for each a is T check that a is X && a is Y
This commit is contained in:
+2
-1
@@ -61,5 +61,6 @@ enum class TypeCheck {
|
|||||||
TYPEOF,
|
TYPEOF,
|
||||||
INSTANCEOF,
|
INSTANCEOF,
|
||||||
OR_NULL,
|
OR_NULL,
|
||||||
IS_ANY
|
IS_ANY,
|
||||||
|
AND_PREDICATE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ public class CastTestGenerated extends AbstractCastTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("castToGenericTypeWithMultipleUpperBounds.kt")
|
||||||
|
public void testCastToGenericTypeWithMultipleUpperBounds() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToGenericTypeWithMultipleUpperBounds.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("castToGenericTypeWithUpperBound.kt")
|
@TestMetadata("castToGenericTypeWithUpperBound.kt")
|
||||||
public void testCastToGenericTypeWithUpperBound() throws Exception {
|
public void testCastToGenericTypeWithUpperBound() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToGenericTypeWithUpperBound.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/expression/cast/cases/castToGenericTypeWithUpperBound.kt");
|
||||||
|
|||||||
@@ -56,23 +56,25 @@ public class DirectiveTestUtils {
|
|||||||
private static final DirectiveHandler FUNCTION_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_CALLED_IN_SCOPE") {
|
private static final DirectiveHandler FUNCTION_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_CALLED_IN_SCOPE") {
|
||||||
@Override
|
@Override
|
||||||
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
||||||
checkCalledInScope(ast, arguments.getNamedArgument("function"), arguments.getNamedArgument("scope"));
|
// Be more restrictive, check qualified match by default
|
||||||
|
checkCalledInScope(ast, arguments.getNamedArgument("function"), arguments.getNamedArgument("scope"),
|
||||||
|
parseBooleanArgument(arguments, "qualified", true));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final DirectiveHandler FUNCTION_NOT_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_NOT_CALLED_IN_SCOPE") {
|
private static final DirectiveHandler FUNCTION_NOT_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_NOT_CALLED_IN_SCOPE") {
|
||||||
@Override
|
@Override
|
||||||
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
||||||
checkNotCalledInScope(ast, arguments.getNamedArgument("function"), arguments.getNamedArgument("scope"));
|
// Be more restrictive, check unqualified match by default
|
||||||
|
checkNotCalledInScope(ast, arguments.getNamedArgument("function"), arguments.getNamedArgument("scope"),
|
||||||
|
parseBooleanArgument(arguments, "qualified", false));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final DirectiveHandler METHOD_NOT_CALLED_IN_SCOPE = new DirectiveHandler("CHECK_METHOD_NOT_CALLED_IN_SCOPE") {
|
private static boolean parseBooleanArgument(@NotNull ArgumentsHelper arguments, @NotNull String name, boolean defaultValue) {
|
||||||
@Override
|
String value = arguments.findNamedArgument(name);
|
||||||
void processEntry(@NotNull JsNode ast, @NotNull ArgumentsHelper arguments) throws Exception {
|
return value != null ? Boolean.parseBoolean(value) : defaultValue;
|
||||||
checkMethodNotCalledInScope(ast, arguments.getNamedArgument("function"), arguments.getNamedArgument("scope"));
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final DirectiveHandler FUNCTIONS_HAVE_SAME_LINES = new DirectiveHandler("CHECK_FUNCTIONS_HAVE_SAME_LINES") {
|
private static final DirectiveHandler FUNCTIONS_HAVE_SAME_LINES = new DirectiveHandler("CHECK_FUNCTIONS_HAVE_SAME_LINES") {
|
||||||
@Override
|
@Override
|
||||||
@@ -196,7 +198,6 @@ public class DirectiveTestUtils {
|
|||||||
FUNCTION_NOT_CALLED,
|
FUNCTION_NOT_CALLED,
|
||||||
FUNCTION_CALLED_IN_SCOPE,
|
FUNCTION_CALLED_IN_SCOPE,
|
||||||
FUNCTION_NOT_CALLED_IN_SCOPE,
|
FUNCTION_NOT_CALLED_IN_SCOPE,
|
||||||
METHOD_NOT_CALLED_IN_SCOPE,
|
|
||||||
FUNCTIONS_HAVE_SAME_LINES,
|
FUNCTIONS_HAVE_SAME_LINES,
|
||||||
COUNT_LABELS,
|
COUNT_LABELS,
|
||||||
COUNT_VARS,
|
COUNT_VARS,
|
||||||
@@ -234,50 +235,38 @@ public class DirectiveTestUtils {
|
|||||||
public static void checkCalledInScope(
|
public static void checkCalledInScope(
|
||||||
@NotNull JsNode node,
|
@NotNull JsNode node,
|
||||||
@NotNull String functionName,
|
@NotNull String functionName,
|
||||||
@NotNull String scopeFunctionName
|
@NotNull String scopeFunctionName,
|
||||||
|
boolean checkQualifier
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
String errorMessage = functionName + " is not called inside " + scopeFunctionName;
|
String errorMessage = functionName + " is not called inside " + scopeFunctionName;
|
||||||
assertFalse(errorMessage, isCalledInScope(node, functionName, scopeFunctionName));
|
assertFalse(errorMessage, isCalledInScope(node, functionName, scopeFunctionName, checkQualifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void checkNotCalledInScope(
|
public static void checkNotCalledInScope(
|
||||||
@NotNull JsNode node,
|
@NotNull JsNode node,
|
||||||
@NotNull String functionName,
|
@NotNull String functionName,
|
||||||
@NotNull String scopeFunctionName
|
@NotNull String scopeFunctionName,
|
||||||
|
boolean checkQualifier
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
String errorMessage = functionName + " is called inside " + scopeFunctionName;
|
String errorMessage = functionName + " is called inside " + scopeFunctionName;
|
||||||
assertTrue(errorMessage, isCalledInScope(node, functionName, scopeFunctionName));
|
assertTrue(errorMessage, isCalledInScope(node, functionName, scopeFunctionName, checkQualifier));
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
private static boolean isCalledInScope(
|
||||||
@NotNull JsNode node,
|
@NotNull JsNode node,
|
||||||
@NotNull String functionName,
|
@NotNull String functionName,
|
||||||
@NotNull String scopeFunctionName
|
@NotNull String scopeFunctionName,
|
||||||
|
boolean checkQualifier
|
||||||
) throws Exception {
|
) throws Exception {
|
||||||
JsNode scope = AstSearchUtil.getFunction(node, scopeFunctionName);
|
JsNode scope = AstSearchUtil.getFunction(node, scopeFunctionName);
|
||||||
|
|
||||||
CallCounter counter = CallCounter.countCalls(scope);
|
CallCounter counter = CallCounter.countCalls(scope);
|
||||||
return counter.getQualifiedCallsCount(functionName) == 0;
|
if (checkQualifier) {
|
||||||
}
|
return counter.getQualifiedCallsCount(functionName) == 0;
|
||||||
|
}
|
||||||
private static boolean isMethodCalledInScope(
|
else {
|
||||||
@NotNull JsNode node,
|
return counter.getUnqualifiedCallsCount(functionName) == 0;
|
||||||
@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 {
|
private abstract static class DirectiveHandler {
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import org.jetbrains.kotlin.name.FqNameUnsafe;
|
|||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static com.google.dart.compiler.backend.js.ast.JsScopesKt.JsObjectScope;
|
import static com.google.dart.compiler.backend.js.ast.JsScopesKt.JsObjectScope;
|
||||||
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.fqnWithoutSideEffects;
|
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.fqnWithoutSideEffects;
|
||||||
@@ -403,9 +405,14 @@ public final class Namer {
|
|||||||
return invokeFunctionAndSetTypeCheckMetadata("orNull", callable, TypeCheck.OR_NULL);
|
return invokeFunctionAndSetTypeCheckMetadata("orNull", callable, TypeCheck.OR_NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public JsExpression andPredicate(@NotNull JsExpression a, @NotNull JsExpression b) {
|
||||||
|
return invokeFunctionAndSetTypeCheckMetadata("andPredicate", Arrays.asList(a, b), TypeCheck.AND_PREDICATE);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public JsExpression isAny() {
|
public JsExpression isAny() {
|
||||||
return invokeFunctionAndSetTypeCheckMetadata("isAny", null, TypeCheck.IS_ANY);
|
return invokeFunctionAndSetTypeCheckMetadata("isAny", Collections.<JsExpression>emptyList(), TypeCheck.IS_ANY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -423,13 +430,19 @@ public final class Namer {
|
|||||||
@NotNull String functionName,
|
@NotNull String functionName,
|
||||||
@Nullable JsExpression argument,
|
@Nullable JsExpression argument,
|
||||||
@NotNull TypeCheck metadata
|
@NotNull TypeCheck metadata
|
||||||
|
) {
|
||||||
|
List<JsExpression> arguments = argument != null ? Collections.singletonList(argument) : Collections.<JsExpression>emptyList();
|
||||||
|
return invokeFunctionAndSetTypeCheckMetadata(functionName, arguments, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private JsExpression invokeFunctionAndSetTypeCheckMetadata(
|
||||||
|
@NotNull String functionName,
|
||||||
|
@NotNull List<JsExpression> arguments,
|
||||||
|
@NotNull TypeCheck metadata
|
||||||
) {
|
) {
|
||||||
JsInvocation invocation = new JsInvocation(kotlin(functionName));
|
JsInvocation invocation = new JsInvocation(kotlin(functionName));
|
||||||
|
invocation.getArguments().addAll(arguments);
|
||||||
if (argument != null) {
|
|
||||||
invocation.getArguments().add(argument);
|
|
||||||
}
|
|
||||||
|
|
||||||
MetadataProperties.setTypeCheck(invocation, metadata);
|
MetadataProperties.setTypeCheck(invocation, metadata);
|
||||||
MetadataProperties.setSideEffects(invocation, false);
|
MetadataProperties.setSideEffects(invocation, false);
|
||||||
return invocation;
|
return invocation;
|
||||||
|
|||||||
+7
-2
@@ -41,7 +41,6 @@ import org.jetbrains.kotlin.psi.KtTypeReference;
|
|||||||
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
||||||
import org.jetbrains.kotlin.types.DynamicTypesKt;
|
import org.jetbrains.kotlin.types.DynamicTypesKt;
|
||||||
import org.jetbrains.kotlin.types.KotlinType;
|
import org.jetbrains.kotlin.types.KotlinType;
|
||||||
import org.jetbrains.kotlin.types.TypeIntersector;
|
|
||||||
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
|
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionTypeOrSubtype;
|
import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionTypeOrSubtype;
|
||||||
@@ -157,7 +156,13 @@ public final class PatternTranslator extends AbstractTranslator {
|
|||||||
return getIsTypeCheckCallableForReifiedType(typeParameterDescriptor);
|
return getIsTypeCheckCallableForReifiedType(typeParameterDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return doGetIsTypeCheckCallable(TypeIntersector.getUpperBoundsAsType(typeParameterDescriptor));
|
JsExpression result = null;
|
||||||
|
for (KotlinType upperBound : typeParameterDescriptor.getUpperBounds()) {
|
||||||
|
JsExpression next = doGetIsTypeCheckCallable(upperBound);
|
||||||
|
result = result != null ? namer().andPredicate(result, next) : next;
|
||||||
|
}
|
||||||
|
assert result != null : "KotlinType is expected to return at least one upper bound: " + type;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsNameRef typeName = getClassNameReference(type);
|
JsNameRef typeName = getClassNameReference(type);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
|
|||||||
|
|
||||||
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
|
override fun visit(x: JsFunction, ctx: JsContext<*>): Boolean {
|
||||||
scopes.push(x.scope)
|
scopes.push(x.scope)
|
||||||
localVars.push(IdentitySet())
|
localVars.push(IdentitySet<JsName>().apply { this += x.parameters.map { it.name } })
|
||||||
return super.visit(x, ctx)
|
return super.visit(x, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,19 +50,14 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
|
|||||||
super.endVisit(x, ctx)
|
super.endVisit(x, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun endVisit(x: JsConditional, ctx: JsContext<JsNode>) {
|
|
||||||
val test = x.testExpression
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsInvocation, ctx: JsContext<JsNode>): Boolean {
|
override fun visit(x: JsInvocation, ctx: JsContext<JsNode>): Boolean {
|
||||||
// callee(calleeArgument)(argument)
|
// callee(calleeArgument)(argument)
|
||||||
val callee = x.qualifier as? JsInvocation
|
val callee = x.qualifier as? JsInvocation
|
||||||
val calleeArgument = callee?.arguments?.firstOrNull()
|
val calleeArguments = callee?.arguments
|
||||||
val argument = x.arguments.firstOrNull()
|
val argument = x.arguments.firstOrNull()
|
||||||
|
|
||||||
if (callee != null && argument != null) {
|
if (callee != null && argument != null && calleeArguments != null) {
|
||||||
val replacement = getReplacement(callee, calleeArgument, argument)
|
val replacement = getReplacement(callee, calleeArguments, argument)
|
||||||
|
|
||||||
if (replacement != null) {
|
if (replacement != null) {
|
||||||
ctx.replaceMe(accept(replacement))
|
ctx.replaceMe(accept(replacement))
|
||||||
@@ -73,64 +68,100 @@ private class TypeCheckRewritingVisitor(private val context: TranslationContext)
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getReplacement(callee: JsInvocation, calleeArgument: JsExpression?, argument: JsExpression): JsExpression? {
|
private fun getReplacement(callee: JsInvocation, calleeArguments: List<JsExpression>, argument: JsExpression): JsExpression? {
|
||||||
if (calleeArgument == null) {
|
val typeCheck = callee.typeCheck
|
||||||
// `Kotlin.isAny()(argument)` -> `argument != null`
|
return when (typeCheck) {
|
||||||
if (callee.typeCheck == TypeCheck.IS_ANY) {
|
TypeCheck.IS_ANY -> {
|
||||||
return TranslationUtils.isNotNullCheck(argument)
|
// `Kotlin.isAny()(argument)` -> `argument != null`
|
||||||
|
if (calleeArguments.isEmpty()) TranslationUtils.isNotNullCheck(argument) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
TypeCheck.TYPEOF -> {
|
||||||
}
|
// `Kotlin.isTypeOf(calleeArgument)(argument)` -> `typeOf argument === calleeArgument`
|
||||||
|
if (calleeArguments.size == 1) typeOfIs(argument, calleeArguments[0] as JsStringLiteral) else null
|
||||||
// `Kotlin.isTypeOf(calleeArgument)(argument)` -> `typeOf argument === calleeArgument`
|
|
||||||
if (callee.typeCheck == TypeCheck.TYPEOF) {
|
|
||||||
return typeOfIs(argument, calleeArgument as JsStringLiteral)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `Kotlin.isInstanceOf(calleeArgument)(argument)` -> `argument instanceof calleeArgument`
|
|
||||||
if (callee.typeCheck == TypeCheck.INSTANCEOF) {
|
|
||||||
return context.namer().isInstanceOf(argument, calleeArgument)
|
|
||||||
}
|
|
||||||
|
|
||||||
// `Kotlin.orNull(calleeArgument)(argument)` -> `(tmp = argument) == null || calleeArgument(tmp)`
|
|
||||||
if (callee.typeCheck == TypeCheck.OR_NULL) {
|
|
||||||
if (calleeArgument is JsInvocation && calleeArgument.typeCheck == TypeCheck.OR_NULL) {
|
|
||||||
return JsInvocation(calleeArgument, argument)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var nullCheckTarget = argument
|
TypeCheck.INSTANCEOF -> {
|
||||||
var nextCheckTarget = argument
|
// `Kotlin.isInstanceOf(calleeArgument)(argument)` -> `argument instanceof calleeArgument`
|
||||||
|
if (calleeArguments.size == 1) context.namer().isInstanceOf(argument, calleeArguments[0]) else null
|
||||||
if (argument.isAssignmentToLocalVar) {
|
|
||||||
// `Kotlin.orNull(Kotlin.isInstance(SomeType))(localVar=someExpr)` -> `(localVar=someExpr) != null || Kotlin.isInstance(SomeType)(localVar)`
|
|
||||||
val localVar = (argument as JsBinaryOperation).getArg1()
|
|
||||||
nextCheckTarget = localVar
|
|
||||||
}
|
|
||||||
else if (!argument.isLocalVar) {
|
|
||||||
val currentScope = scopes.peek()
|
|
||||||
val tmp = currentScope.declareTemporary()
|
|
||||||
val statementContext = lastStatementLevelContext
|
|
||||||
statementContext.addPrevious(newVar(tmp, null))
|
|
||||||
nullCheckTarget = assignment(tmp.makeRef(), argument)
|
|
||||||
nextCheckTarget = tmp.makeRef()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val isNull = TranslationUtils.isNullCheck(nullCheckTarget)
|
TypeCheck.OR_NULL -> {
|
||||||
return or(isNull, JsInvocation(calleeArgument, nextCheckTarget))
|
// `Kotlin.orNull(calleeArgument)(argument)` -> `(tmp = argument) == null || calleeArgument(tmp)`
|
||||||
|
if (calleeArguments.size == 1) getReplacementForOrNull(argument, calleeArguments[0]) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
TypeCheck.AND_PREDICATE -> {
|
||||||
|
// `Kotlin.andPredicate(p1, p2)(argument)` -> `p1(tmp = argument) && p2(tmp)`
|
||||||
|
if (calleeArguments.size == 2) {
|
||||||
|
getReplacementForAndPredicate(argument, calleeArguments[0], calleeArguments[1])
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
null -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val JsExpression.isLocalVar: Boolean
|
private fun getReplacementForOrNull(argument: JsExpression, calleeArgument: JsExpression): JsExpression {
|
||||||
get() {
|
if (calleeArgument is JsInvocation && calleeArgument.typeCheck == TypeCheck.OR_NULL) {
|
||||||
if (localVars.empty() || this !is JsNameRef) return false
|
return JsInvocation(calleeArgument, argument)
|
||||||
|
|
||||||
val name = this.getName()
|
|
||||||
return name != null && localVars.peek().contains(name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nullCheckTarget = argument
|
||||||
|
var nextCheckTarget = argument
|
||||||
|
|
||||||
|
if (argument.isAssignmentToLocalVar) {
|
||||||
|
// `Kotlin.orNull(Kotlin.isInstance(SomeType))(localVar=someExpr)` ->
|
||||||
|
// `(localVar=someExpr) != null || Kotlin.isInstance(SomeType)(localVar)`
|
||||||
|
val localVar = (argument as JsBinaryOperation).arg1
|
||||||
|
nextCheckTarget = localVar
|
||||||
|
}
|
||||||
|
else if (argument.needsAlias) {
|
||||||
|
val currentScope = scopes.peek()
|
||||||
|
val tmp = currentScope.declareTemporary()
|
||||||
|
val statementContext = lastStatementLevelContext
|
||||||
|
statementContext.addPrevious(newVar(tmp, null))
|
||||||
|
nullCheckTarget = assignment(tmp.makeRef(), argument)
|
||||||
|
nextCheckTarget = tmp.makeRef()
|
||||||
|
}
|
||||||
|
|
||||||
|
val isNull = TranslationUtils.isNullCheck(nullCheckTarget)
|
||||||
|
return or(isNull, JsInvocation(calleeArgument, nextCheckTarget))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getReplacementForAndPredicate(argument: JsExpression, p1: JsExpression, p2: JsExpression): JsExpression {
|
||||||
|
val (arg1, arg2) = if (!argument.needsAlias) {
|
||||||
|
Pair(argument, argument)
|
||||||
|
}
|
||||||
|
else if (argument.isAssignmentToLocalVar) {
|
||||||
|
Pair(argument, JsAstUtils.decomposeAssignment(argument)!!.first)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val currentScope = scopes.peek()
|
||||||
|
val tmp = currentScope.declareTemporary()
|
||||||
|
val statementContext = lastStatementLevelContext
|
||||||
|
statementContext.addPrevious(newVar(tmp, null))
|
||||||
|
Pair(assignment(tmp.makeRef(), argument), tmp.makeRef())
|
||||||
|
}
|
||||||
|
|
||||||
|
val first = accept(JsInvocation(p1, arg1) as JsExpression)
|
||||||
|
val second = accept(JsInvocation(p2, arg2) as JsExpression)
|
||||||
|
return JsAstUtils.and(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val JsExpression.needsAlias: Boolean
|
||||||
|
get() = when (this) {
|
||||||
|
is JsStringLiteral -> false
|
||||||
|
else -> !isLocalVar
|
||||||
|
}
|
||||||
|
|
||||||
|
private val JsExpression.isLocalVar: Boolean
|
||||||
|
get() = localVars.isNotEmpty() && this is JsNameRef && name.let { it != null && it in localVars.peek() }
|
||||||
|
|
||||||
private val JsExpression.isAssignmentToLocalVar: Boolean
|
private val JsExpression.isAssignmentToLocalVar: Boolean
|
||||||
get() = this is JsBinaryOperation && getOperator() == JsBinaryOperator.ASG
|
get() = localVars.isNotEmpty() &&
|
||||||
|
JsAstUtils.decomposeAssignmentToVariable(this).let { it != null && it.first in localVars.peek() }
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package foo
|
||||||
|
|
||||||
|
open class A()
|
||||||
|
|
||||||
|
interface X
|
||||||
|
|
||||||
|
interface Y
|
||||||
|
|
||||||
|
class B() : A(), X, Y {
|
||||||
|
override fun toString() = "B"
|
||||||
|
}
|
||||||
|
|
||||||
|
class C() : A(), X, Y {
|
||||||
|
override fun toString() = "C"
|
||||||
|
}
|
||||||
|
|
||||||
|
class D() : A() {
|
||||||
|
override fun toString() = "D"
|
||||||
|
}
|
||||||
|
|
||||||
|
class E() : X {
|
||||||
|
override fun toString() = "E"
|
||||||
|
}
|
||||||
|
|
||||||
|
class F() : A(), Y {
|
||||||
|
override fun toString() = "E"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> test(a: Any): String where T : A, T : X, T : Y {
|
||||||
|
return (try {
|
||||||
|
a as T
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
"error"
|
||||||
|
}).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
val b = B()
|
||||||
|
val c = C()
|
||||||
|
val d = D()
|
||||||
|
val e = E()
|
||||||
|
val f = F()
|
||||||
|
|
||||||
|
assertEquals("B", test<B>(b))
|
||||||
|
assertEquals("B", test<C>(b))
|
||||||
|
|
||||||
|
assertEquals("C", test<B>(c))
|
||||||
|
assertEquals("C", test<C>(c))
|
||||||
|
|
||||||
|
assertEquals("error", test<B>(d))
|
||||||
|
assertEquals("error", test<C>(d))
|
||||||
|
assertEquals("error", test<B>(e))
|
||||||
|
assertEquals("error", test<C>(e))
|
||||||
|
assertEquals("error", test<B>(f))
|
||||||
|
assertEquals("error", test<C>(f))
|
||||||
|
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
// CHECK_METHOD_NOT_CALLED_IN_SCOPE: scope=box function=isType
|
// CHECK_NOT_CALLED_IN_SCOPE: scope=box function=isType
|
||||||
|
|
||||||
open class A()
|
open class A()
|
||||||
|
|
||||||
|
|||||||
@@ -560,6 +560,12 @@ var Kotlin = {};
|
|||||||
return object != null;
|
return object != null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Kotlin.andPredicate = function (a, b) {
|
||||||
|
return function (object) {
|
||||||
|
return a(object) && b(object);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) {
|
Kotlin.kotlinModuleMetadata = function (abiVersion, moduleName, data) {
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package foo
|
package foo
|
||||||
|
|
||||||
// CHECK_NOT_CALLED: isTypeOfOrNull
|
// CHECK_NOT_CALLED: isTypeOfOrNull
|
||||||
// CHECK_NULLS_COUNT: function=box count=8
|
// CHECK_NULLS_COUNT: function=box count=10
|
||||||
|
|
||||||
inline
|
inline
|
||||||
fun <reified T> Any?.isTypeOfOrNull() = this is T?
|
fun <reified T> Any?.isTypeOfOrNull() = this is T?
|
||||||
|
|||||||
Reference in New Issue
Block a user