From a8f1e32dec0782eab3d52ce682572d6f7d6fd07b Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Wed, 10 May 2017 16:35:54 +0300 Subject: [PATCH] Refactoring: move utils related to casts to CastDiagnosticUtil --- .../kotlin/types/CastDiagnosticsUtil.kt | 86 +++++++++++++++++++ .../BasicExpressionTypingVisitor.java | 75 +--------------- 2 files changed, 87 insertions(+), 74 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt index add512eb03e..3fbf0e8fa7c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/CastDiagnosticsUtil.kt @@ -17,13 +17,19 @@ package org.jetbrains.kotlin.types import com.google.common.collect.Maps +import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.isExtensionFunctionType +import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.platform.PlatformToKotlinClassMap +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.types.checker.KotlinTypeChecker import org.jetbrains.kotlin.types.checker.TypeCheckingProcedure +import org.jetbrains.kotlin.types.expressions.DataFlowAnalyzer +import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext import org.jetbrains.kotlin.types.typeUtil.makeNotNullable object CastDiagnosticsUtil { @@ -193,4 +199,84 @@ object CastDiagnosticsUtil { } private fun allParametersReified(subtype: KotlinType) = subtype.constructor.parameters.all { it.isReified } + + fun castIsUseless( + expression: KtBinaryExpressionWithTypeRHS, + context: ExpressionTypingContext, + targetType: KotlinType, + actualType: KotlinType, + typeChecker: KotlinTypeChecker + ): Boolean { + // Here: x as? Type <=> x as Type? + val refinedTargetType = if (KtPsiUtil.isSafeCast(expression)) TypeUtils.makeNullable(targetType) else targetType + val possibleTypes = DataFlowAnalyzer.getAllPossibleTypes(expression.left, actualType, context) + return isRefinementUseless(possibleTypes, refinedTargetType, typeChecker, shouldCheckForExactType(expression, context.expectedType)) + } + + fun isRefinementUseless( + possibleTypes: Collection, + targetType: KotlinType, + typeChecker: KotlinTypeChecker, + shouldCheckForExactType: Boolean + ): Boolean { + val intersectedType = TypeIntersector.intersectTypes(typeChecker, possibleTypes) ?: return false + + return if (shouldCheckForExactType) + isExactTypeCast(intersectedType, targetType) + else + isUpcast(intersectedType, targetType, typeChecker) + } + + private fun shouldCheckForExactType(expression: KtBinaryExpressionWithTypeRHS, expectedType: KotlinType): Boolean { + if (TypeUtils.noExpectedType(expectedType)) { + return checkExactTypeForUselessCast(expression) + } + + // If expected type is parameterized, then cast has an effect on inference, therefore it isn't a useless cast + // Otherwise, we are interested in situation like: `a: Any? = 1 as Int?` + return TypeUtils.isDontCarePlaceholder(expectedType) + } + + private fun isExactTypeCast(candidateType: KotlinType, targetType: KotlinType): Boolean { + return candidateType == targetType && candidateType.isExtensionFunctionType == targetType.isExtensionFunctionType + } + + private fun isUpcast(candidateType: KotlinType, targetType: KotlinType, typeChecker: KotlinTypeChecker): Boolean { + if (!typeChecker.isSubtypeOf(candidateType, targetType)) return false + + if (candidateType.isFunctionType && targetType.isFunctionType) { + return candidateType.isExtensionFunctionType == targetType.isExtensionFunctionType + } + + return true + } + + // Casting an argument or a receiver to a supertype may be useful to select an exact overload of a method. + // Casting to a supertype in other contexts is unlikely to be useful. + private fun checkExactTypeForUselessCast(expression: KtBinaryExpressionWithTypeRHS): Boolean { + var parent = expression.parent + while (parent is KtParenthesizedExpression || + parent is KtLabeledExpression || + parent is KtAnnotatedExpression) { + parent = parent.parent + } + + return when (parent) { + is KtValueArgument -> true + + is KtQualifiedExpression -> { + val receiver = parent.receiverExpression + PsiTreeUtil.isAncestor(receiver, expression, false) + } + + // in binary expression, left argument can be a receiver and right an argument + // in unary expression, left argument can be a receiver + is KtBinaryExpression, is KtUnaryExpression -> true + + // Previously we've checked that there is no expected type, therefore cast in property has an effect on inference + is KtProperty, is KtPropertyAccessor -> true + + else -> false + } + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java index 822d11b36dc..5df71d9fee3 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/types/expressions/BasicExpressionTypingVisitor.java @@ -79,8 +79,6 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isExtensionFunctionType; -import static org.jetbrains.kotlin.builtins.FunctionTypesKt.isFunctionType; import static org.jetbrains.kotlin.diagnostics.Errors.*; import static org.jetbrains.kotlin.lexer.KtTokens.*; import static org.jetbrains.kotlin.resolve.BindingContext.*; @@ -377,7 +375,7 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { } KotlinTypeChecker typeChecker = KotlinTypeChecker.DEFAULT; - if (castIsUseless(expression, context, targetType, actualType, typeChecker)) { + if (CastDiagnosticsUtil.INSTANCE.castIsUseless(expression, context, targetType, actualType, typeChecker)) { context.trace.report(USELESS_CAST.on(expression)); return; } @@ -387,77 +385,6 @@ public class BasicExpressionTypingVisitor extends ExpressionTypingVisitor { } } - private static boolean castIsUseless( - @NotNull KtBinaryExpressionWithTypeRHS expression, - @NotNull ExpressionTypingContext context, - @NotNull KotlinType targetType, - @NotNull KotlinType actualType, - @NotNull KotlinTypeChecker typeChecker - ) { - // Here: x as? Type <=> x as Type? - KotlinType refinedTargetType = KtPsiUtil.isSafeCast(expression) ? TypeUtils.makeNullable(targetType) : targetType; - - Collection possibleTypes = DataFlowAnalyzer.getAllPossibleTypes(expression.getLeft(), actualType, context); - KotlinType intersectedType = TypeIntersector.intersectTypes(typeChecker, possibleTypes); - if (intersectedType == null) return false; - - return shouldCheckForExactType(expression, context.expectedType) - ? isExactTypeCast(intersectedType, refinedTargetType) - : isUpcast(intersectedType, refinedTargetType, typeChecker); - } - - private static boolean shouldCheckForExactType(KtBinaryExpressionWithTypeRHS expression, KotlinType expectedType) { - if (TypeUtils.noExpectedType(expectedType)) { - return checkExactTypeForUselessCast(expression); - } - - // If expected type is parameterized, then cast has an effect on inference, therefore it isn't a useless cast - // Otherwise, we are interested in situation like: `a: Any? = 1 as Int?` - return TypeUtils.isDontCarePlaceholder(expectedType); - } - - private static boolean isExactTypeCast(KotlinType candidateType, KotlinType targetType) { - return candidateType.equals(targetType) && isExtensionFunctionType(candidateType) == isExtensionFunctionType(targetType); - } - - private static boolean isUpcast(KotlinType candidateType, KotlinType targetType, KotlinTypeChecker typeChecker) { - if (!typeChecker.isSubtypeOf(candidateType, targetType)) return false; - - if (isFunctionType(candidateType) && isFunctionType(targetType)) { - return isExtensionFunctionType(candidateType) == isExtensionFunctionType(targetType); - } - - return true; - } - - // Casting an argument or a receiver to a supertype may be useful to select an exact overload of a method. - // Casting to a supertype in other contexts is unlikely to be useful. - private static boolean checkExactTypeForUselessCast(KtBinaryExpressionWithTypeRHS expression) { - PsiElement parent = expression.getParent(); - while (parent instanceof KtParenthesizedExpression || - parent instanceof KtLabeledExpression || - parent instanceof KtAnnotatedExpression) { - parent = parent.getParent(); - } - if (parent instanceof KtValueArgument) { - return true; - } - if (parent instanceof KtQualifiedExpression) { - KtExpression receiver = ((KtQualifiedExpression) parent).getReceiverExpression(); - return PsiTreeUtil.isAncestor(receiver, expression, false); - } - // in binary expression, left argument can be a receiver and right an argument - // in unary expression, left argument can be a receiver - if (parent instanceof KtBinaryExpression || parent instanceof KtUnaryExpression) { - return true; - } - // Previously we've checked that there is no expected type, therefore cast in property has an effect on inference - if (parent instanceof KtProperty || parent instanceof KtPropertyAccessor) { - return true; - } - return false; - } - @Override public KotlinTypeInfo visitThisExpression(@NotNull KtThisExpression expression, ExpressionTypingContext context) { KotlinType result = null;