diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java index 47f29214141..2bccd237853 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/BindingContext.java @@ -267,6 +267,9 @@ public interface BindingContext { WritableSlice NEW_INFERENCE_LAMBDA_INFO = new BasicWritableSlice<>(DO_NOTHING); + WritableSlice PRIMITIVE_NUMERIC_COMPARISON_TYPE = Slices.createSimpleSlice(); + WritableSlice PRIMITIVE_NUMERIC_COMPARISON_OPERAND_TYPE = Slices.createSimpleSlice(); + @SuppressWarnings("UnusedDeclaration") @Deprecated // This field is needed only for the side effects of its initializer Void _static_initializer = BasicWritableSlice.initSliceDebugNames(BindingContext.class); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt index 53a6bcd4da7..514bc1dd583 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/TargetPlatform.kt @@ -90,7 +90,8 @@ private val DEFAULT_CALL_CHECKERS = listOf( ConstructorHeaderCallChecker, ProtectedConstructorCallChecker, ApiVersionCallChecker, CoroutineSuspendCallChecker, BuilderFunctionsCallChecker, DslScopeViolationCallChecker, MissingDependencyClassChecker, CallableReferenceCompatibilityChecker(), LateinitIntrinsicApplicabilityChecker, - UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker() + UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(), + PrimitiveNumericComparisonCallChecker ) private val DEFAULT_TYPE_CHECKERS = emptyList() private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf( diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PrimitiveNumericComparisonCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PrimitiveNumericComparisonCallChecker.kt new file mode 100644 index 00000000000..13c9fcd7c14 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/PrimitiveNumericComparisonCallChecker.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.resolve.checkers + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker +import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.* + +object PrimitiveNumericComparisonCallChecker : CallChecker { + + private val comparisonOperatorTokens = setOf(KtTokens.EQEQ, KtTokens.EXCLEQ, KtTokens.LT, KtTokens.LTEQ, KtTokens.GT, KtTokens.GTEQ) + + override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { + // Primitive number comparisons only take part in binary operator convention resolution + val binaryExpression = resolvedCall.call.callElement as? KtBinaryExpression ?: return + if (!comparisonOperatorTokens.contains(binaryExpression.operationReference.getReferencedNameElementType())) return + + val leftExpr = binaryExpression.left ?: return + val rightExpr = binaryExpression.right ?: return + + val leftType = context.getInferredPrimitiveNumericType(leftExpr) ?: return + val rightType = context.getInferredPrimitiveNumericType(rightExpr) ?: return + + context.trace.record(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_OPERAND_TYPE, leftExpr, leftType) + context.trace.record(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_OPERAND_TYPE, rightExpr, rightType) + + val leastCommonType = leastCommonPrimitiveNumericType(leftType, rightType) + + context.trace.record(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_TYPE, binaryExpression, leastCommonType) + } + + private fun leastCommonPrimitiveNumericType(t1: KotlinType, t2: KotlinType): KotlinType { + val pt1 = t1.promoteIntegerTypeToIntIfRequired() + val pt2 = t2.promoteIntegerTypeToIntIfRequired() + + return when { + pt1.isDouble() || pt2.isDouble() -> t1.builtIns.doubleType + pt1.isFloat() || pt2.isFloat() -> t1.builtIns.floatType + pt1.isLong() || pt2.isLong() -> t1.builtIns.longType + pt1.isInt() || pt2.isInt() -> t1.builtIns.intType + else -> throw AssertionError("Unexpected types: t1=$t1, t2=$t2") + } + } + + private fun KotlinType.promoteIntegerTypeToIntIfRequired() = + when { + !isPrimitiveNumberType() -> throw AssertionError("Primitive number type expected: $this") + isByte() || isChar() || isShort() -> builtIns.intType + else -> this + } + + private fun CallCheckerContext.getInferredPrimitiveNumericType(expression: KtExpression): KotlinType? { + val type = trace.bindingContext.getType(expression) ?: return null + val dataFlowValue = DataFlowValueFactory.createDataFlowValue( + expression, type, trace.bindingContext, resolutionContext.scope.ownerDescriptor + ) + val dataFlowInfo = trace.get(BindingContext.EXPRESSION_TYPE_INFO, expression)?.dataFlowInfo ?: return null + val stableTypes = dataFlowInfo.getStableTypes(dataFlowValue, languageVersionSettings) + return (listOf(type) + stableTypes).findPrimitiveType() + } + + private fun List.findPrimitiveType() = + find { it.isPrimitiveNumberOrNullableType() }?.makeNotNullable() +} \ No newline at end of file diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt index 21bb294f90d..7a858d60bb4 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt @@ -57,8 +57,20 @@ fun KotlinType.isAnyOrNullableAny(): Boolean = KotlinBuiltIns.isAnyOrNullableAny fun KotlinType.isNullableAny(): Boolean = KotlinBuiltIns.isNullableAny(this) fun KotlinType.isBoolean(): Boolean = KotlinBuiltIns.isBoolean(this) fun KotlinType.isPrimitiveNumberType(): Boolean = KotlinBuiltIns.isPrimitiveType(this) && !isBoolean() + fun KotlinType.isBooleanOrNullableBoolean(): Boolean = KotlinBuiltIns.isBooleanOrNullableBoolean(this) fun KotlinType.isNotNullThrowable(): Boolean = KotlinBuiltIns.isThrowableOrNullableThrowable(this) && !isMarkedNullable +fun KotlinType.isByte() = KotlinBuiltIns.isByte(this) +fun KotlinType.isChar() = KotlinBuiltIns.isChar(this) +fun KotlinType.isShort() = KotlinBuiltIns.isShort(this) +fun KotlinType.isInt() = KotlinBuiltIns.isInt(this) +fun KotlinType.isLong() = KotlinBuiltIns.isLong(this) +fun KotlinType.isFloat() = KotlinBuiltIns.isFloat(this) +fun KotlinType.isDouble() = KotlinBuiltIns.isDouble(this) + +fun KotlinType.isPrimitiveNumberOrNullableType(): Boolean = + KotlinBuiltIns.isPrimitiveTypeOrNullablePrimitiveType(this) && + !KotlinBuiltIns.isBooleanOrNullableBoolean(this) fun KotlinType.isTypeParameter(): Boolean = TypeUtils.isTypeParameter(this) @@ -75,10 +87,10 @@ fun KotlinType?.isArrayOfNothing(): Boolean { fun KotlinType.isSubtypeOf(superType: KotlinType): Boolean = KotlinTypeChecker.DEFAULT.isSubtypeOf(this, superType) fun isNullabilityMismatch(expected: KotlinType, actual: KotlinType) = - !expected.isMarkedNullable && actual.isMarkedNullable && actual.isSubtypeOf(TypeUtils.makeNullable(expected)) + !expected.isMarkedNullable && actual.isMarkedNullable && actual.isSubtypeOf(TypeUtils.makeNullable(expected)) fun KotlinType.cannotBeReified(): Boolean = - KotlinBuiltIns.isNothingOrNullableNothing(this) || this.isDynamic() || this.isCaptured() + KotlinBuiltIns.isNothingOrNullableNothing(this) || this.isDynamic() || this.isCaptured() fun TypeProjection.substitute(doSubstitute: (KotlinType) -> KotlinType): TypeProjection { return if (isStarProjection) @@ -104,7 +116,7 @@ fun List.defaultProjections(): List = map(::TypeProj fun KotlinType.isDefaultBound(): Boolean = KotlinBuiltIns.isDefaultBound(getSupertypeRepresentative()) fun createProjection(type: KotlinType, projectionKind: Variance, typeParameterDescriptor: TypeParameterDescriptor?): TypeProjection = - TypeProjectionImpl(if (typeParameterDescriptor?.variance == projectionKind) Variance.INVARIANT else projectionKind, type) + TypeProjectionImpl(if (typeParameterDescriptor?.variance == projectionKind) Variance.INVARIANT else projectionKind, type) fun Collection.closure(f: (KotlinType) -> Collection): Collection { if (size == 0) return this @@ -124,7 +136,7 @@ fun Collection.closure(f: (KotlinType) -> Collection): C } fun boundClosure(types: Collection): Collection = - types.closure { type -> TypeUtils.getTypeParameterDescriptorOrNull(type)?.upperBounds ?: emptySet() } + types.closure { type -> TypeUtils.getTypeParameterDescriptorOrNull(type)?.upperBounds ?: emptySet() } fun constituentTypes(types: Collection): Collection { val result = hashSetOf() @@ -133,15 +145,14 @@ fun constituentTypes(types: Collection): Collection { } fun KotlinType.constituentTypes(): Collection = - constituentTypes(listOf(this)) + constituentTypes(listOf(this)) private fun constituentTypes(result: MutableSet, types: Collection) { result.addAll(types) for (type in types) { if (type.isFlexible()) { - with (type.asFlexibleType()) { constituentTypes(result, setOf(lowerBound, upperBound)) } - } - else { + with(type.asFlexibleType()) { constituentTypes(result, setOf(lowerBound, upperBound)) } + } else { constituentTypes(result, type.arguments.mapNotNull { if (!it.isStarProjection) it.type else null }) } } @@ -163,8 +174,8 @@ fun KotlinType.replaceArgumentsWithStarProjections(): KotlinType { val unwrapped = unwrap() return when (unwrapped) { is FlexibleType -> KotlinTypeFactory.flexibleType( - unwrapped.lowerBound.replaceArgumentsWithStarProjections(), - unwrapped.upperBound.replaceArgumentsWithStarProjections() + unwrapped.lowerBound.replaceArgumentsWithStarProjections(), + unwrapped.upperBound.replaceArgumentsWithStarProjections() ) is SimpleType -> unwrapped.replaceArgumentsWithStarProjections() }.inheritEnhancement(unwrapped) @@ -179,24 +190,24 @@ private fun SimpleType.replaceArgumentsWithStarProjections(): SimpleType { } fun KotlinType.containsTypeAliasParameters(): Boolean = - contains { - it.constructor.declarationDescriptor?.isTypeAliasParameter() ?: false - } + contains { + it.constructor.declarationDescriptor?.isTypeAliasParameter() ?: false + } fun KotlinType.containsTypeAliases(): Boolean = - contains { - it.constructor.declarationDescriptor is TypeAliasDescriptor - } + contains { + it.constructor.declarationDescriptor is TypeAliasDescriptor + } fun ClassifierDescriptor.isTypeAliasParameter(): Boolean = - this is TypeParameterDescriptor && containingDeclaration is TypeAliasDescriptor + this is TypeParameterDescriptor && containingDeclaration is TypeAliasDescriptor fun KotlinType.requiresTypeAliasExpansion(): Boolean = - contains { - it.constructor.declarationDescriptor?.let { - it is TypeAliasDescriptor || it is TypeParameterDescriptor - } ?: false - } + contains { + it.constructor.declarationDescriptor?.let { + it is TypeAliasDescriptor || it is TypeParameterDescriptor + } ?: false + } fun KotlinType.containsTypeProjectionsInTopLevelArguments(): Boolean { if (isError) return false @@ -205,6 +216,6 @@ fun KotlinType.containsTypeProjectionsInTopLevelArguments(): Boolean { } fun UnwrappedType.canHaveUndefinedNullability(): Boolean = - constructor is NewTypeVariableConstructor || - constructor.declarationDescriptor is TypeParameterDescriptor || - this is NewCapturedType + constructor is NewTypeVariableConstructor || + constructor.declarationDescriptor is TypeParameterDescriptor || + this is NewCapturedType