From 05cad83c7933c1217114806a4d6b8a504f8029fa Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Fri, 30 Jun 2017 17:25:12 +0300 Subject: [PATCH] Fix tests for in/in! with primitive range literals - ClosedFloatingPointRange - IntRange, etc - extensions in kotlin.ranges for ranges of Byte/Int/Short --- .../kotlin/codegen/RangeCodegenUtil.kt | 81 +++++++++---------- ...PrimitiveNumberRangeExpressionGenerator.kt | 25 +++++- .../InPrimitiveNumberRangeLiteralGenerator.kt | 2 +- .../kotlin/codegen/range/RangeValue.kt | 12 +-- .../bytecodeText/ranges/inOptimizableRange.kt | 28 +++---- 5 files changed, 80 insertions(+), 68 deletions(-) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.kt index 04a258c226d..21cfeef555c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/RangeCodegenUtil.kt @@ -21,19 +21,10 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns.RANGES_PACKAGE_FQ_NAME import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitiveNumberClassDescriptor import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtBinaryExpression -import org.jetbrains.kotlin.psi.KtExpression -import org.jetbrains.kotlin.psi.KtSimpleNameExpression -import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.DescriptorUtils -import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall -import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert -import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.org.objectweb.asm.Type private val RANGE_TO_ELEMENT_TYPE: Map = supportedRangeTypes().associateBy { @@ -77,8 +68,8 @@ fun isRangeOrProgression(className: FqName) = getPrimitiveRangeOrProgressionElementType(className) != null fun isPrimitiveNumberRangeTo(rangeTo: CallableDescriptor) = - "rangeTo" == rangeTo.name.asString() && - isPrimitiveNumberClassDescriptor(rangeTo.containingDeclaration) + "rangeTo" == rangeTo.name.asString() && isPrimitiveNumberClassDescriptor(rangeTo.containingDeclaration) || + isPrimitiveRangeToExtension(rangeTo) private fun isPrimitiveRangeToExtension(descriptor: CallableDescriptor): Boolean { if (!isTopLevelInPackage(descriptor, "rangeTo", "kotlin.ranges")) return false @@ -139,7 +130,6 @@ fun isComparableRangeTo(descriptor: CallableDescriptor): Boolean { return true } - fun isClosedRangeContains(descriptor: CallableDescriptor): Boolean { if (descriptor.name.asString() != "contains") return false val containingClassDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false @@ -148,40 +138,47 @@ fun isClosedRangeContains(descriptor: CallableDescriptor): Boolean { return true } -fun isPrimitiveRangeToExtension(operationReference: KtSimpleNameExpression, bindingContext: BindingContext): Boolean { - val resolvedCall = operationReference.getResolvedCallWithAssert(bindingContext) - val receiver = resolvedCall.dispatchReceiver as? ExpressionReceiver ?: return false +fun isPrimitiveRangeContains(descriptor: CallableDescriptor): Boolean { + if (descriptor.name.asString() != "contains") return false + val dispatchReceiverType = descriptor.dispatchReceiverParameter?.type ?: return false + if (!isPrimitiveRange(dispatchReceiverType)) return false - /* - * Range is optimizable if - * receiver is a call for 'rangeTo' from stdlib package - * and its argument has same primitive type as generic range parameter. - * For non-matching primitive types (e.g. int in double range) - * dispatch receiver will be null, because extension method will be called. - */ - - val resolvedReceiver = receiver.expression.getResolvedCall(bindingContext) ?: return false - return isPrimitiveRangeToExtension(resolvedReceiver.resultingDescriptor) + return true } -/* - * Checks whether for expression 'x in a..b' a..b is primitive integral range - * with same type as x. - */ -fun isPrimitiveRangeSpecializationOfType( - argumentType: Type, - rangeExpression: KtExpression, - bindingContext: BindingContext -): Boolean { - if (rangeExpression is KtBinaryExpression && rangeExpression.operationReference.getReferencedNameElementType() === KtTokens.RANGE) { - val kotlinType = bindingContext.getType(rangeExpression)!! - val descriptor = kotlinType.constructor.declarationDescriptor ?: return false - val fqName = DescriptorUtils.getFqName(descriptor) - return (fqName == KotlinBuiltIns.FQ_NAMES.longRange && argumentType === Type.LONG_TYPE) || - (fqName == KotlinBuiltIns.FQ_NAMES.charRange || fqName == KotlinBuiltIns.FQ_NAMES.intRange) && AsmUtil.isIntPrimitive(argumentType) - } +fun isIntPrimitiveRangeExtensionForInt(descriptor: CallableDescriptor): Boolean { + if (descriptor.name.asString() != "contains") return false - return false + val extensionReceiverType = descriptor.extensionReceiverParameter?.type ?: return false + val extensionReceiverClassDescriptor = extensionReceiverType.constructor.declarationDescriptor as? ClassDescriptor ?: return false + if (!isTopLevelInPackage(extensionReceiverClassDescriptor, "ClosedRange", "kotlin.ranges")) return false + + val rangeElementType = extensionReceiverType.arguments.singleOrNull()?.type ?: return false + if (!isIntPrimitiveType(rangeElementType)) return false + + val argumentType = descriptor.valueParameters.singleOrNull()?.type ?: return false + if (!isIntPrimitiveType(argumentType)) return false + + return true +} + +private fun isIntPrimitiveType(type: KotlinType) = + KotlinBuiltIns.isByte(type) || + KotlinBuiltIns.isShort(type) || + KotlinBuiltIns.isInt(type) + +fun isClosedFloatingPointRangeContains(descriptor: CallableDescriptor): Boolean { + if (descriptor.name.asString() != "contains") return false + val containingClassDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false + if (!isTopLevelInPackage(containingClassDescriptor, "ClosedFloatingPointRange", "kotlin.ranges")) return false + + return true +} + +fun getClosedFloatingPointRangeElementType(rangeType: KotlinType): KotlinType? { + val classDescriptor = rangeType.constructor.declarationDescriptor as? ClassDescriptor ?: return null + if (!isTopLevelInPackage(classDescriptor, "ClosedFloatingPointRange", "kotlin.ranges")) return null + return rangeType.arguments.singleOrNull()?.type } private fun isTopLevelInPackage(descriptor: DeclarationDescriptor, name: String, packageName: String): Boolean { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/AbstractInPrimitiveNumberRangeExpressionGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/AbstractInPrimitiveNumberRangeExpressionGenerator.kt index 6a346075b25..e9f8408c795 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/AbstractInPrimitiveNumberRangeExpressionGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/AbstractInPrimitiveNumberRangeExpressionGenerator.kt @@ -16,8 +16,11 @@ package org.jetbrains.kotlin.codegen.range +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.codegen.ExpressionCodegen +import org.jetbrains.kotlin.codegen.getClosedFloatingPointRangeElementType import org.jetbrains.kotlin.codegen.getPrimitiveRangeElementType +import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.jvm.AsmTypes @@ -31,7 +34,8 @@ abstract class AbstractInPrimitiveNumberRangeExpressionGenerator( rangeCall: ResolvedCall<*>, isInclusiveHighBound: Boolean ) : AbstractInRangeWithKnownBoundsExpressionGenerator( - codegen, operatorReference, isInclusiveHighBound, AsmTypes.valueTypeForPrimitive(getPrimitiveRangeElementType(rangeCall.resultingDescriptor.returnType!!)) + codegen, operatorReference, isInclusiveHighBound, + getAsmRangeElementTypeForPrimitiveRange(rangeCall.resultingDescriptor) ) { override val comparisonGenerator: ComparisonGenerator = when (asmElementType) { @@ -92,3 +96,22 @@ abstract class AbstractInPrimitiveNumberRangeExpressionGenerator( } } } + + +internal fun getAsmRangeElementTypeForPrimitiveRange(rangeCallee: CallableDescriptor): Type { + val rangeType = rangeCallee.returnType!! + + getPrimitiveRangeElementType(rangeType)?.let { + return AsmTypes.valueTypeForPrimitive(it) + } + + val floatingPointElementType = getClosedFloatingPointRangeElementType(rangeType) ?: + throw AssertionError("Unexpected range type: $rangeType") + + if (KotlinBuiltIns.isDouble(floatingPointElementType)) + return Type.DOUBLE_TYPE + else if (KotlinBuiltIns.isFloat(floatingPointElementType)) + return Type.FLOAT_TYPE + else + throw AssertionError("Unexpected ClosedFloatingPointRange element type: $floatingPointElementType") +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/InPrimitiveNumberRangeLiteralGenerator.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/InPrimitiveNumberRangeLiteralGenerator.kt index b8f3eb8cf0f..10f86a8f70f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/InPrimitiveNumberRangeLiteralGenerator.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/InPrimitiveNumberRangeLiteralGenerator.kt @@ -28,7 +28,7 @@ class InPrimitiveNumberRangeLiteralGenerator( operatorReference: KtSimpleNameExpression, rangeCall: ResolvedCall<*> ) : AbstractInPrimitiveNumberRangeExpressionGenerator(codegen, operatorReference, rangeCall, isInclusiveHighBound = true) { - private val from: ReceiverValue = rangeCall.dispatchReceiver!! + private val from: ReceiverValue = rangeCall.dispatchReceiver ?: rangeCall.extensionReceiver!! private val to: KtExpression = ExpressionCodegen.getSingleArgumentExpression(rangeCall)!! override fun genLowBound(): StackValue = codegen.generateReceiverValue(from, false) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/RangeValue.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/RangeValue.kt index 107d74fea5e..4207a542fa2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/range/RangeValue.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/range/RangeValue.kt @@ -16,10 +16,8 @@ package org.jetbrains.kotlin.codegen.range -import org.jetbrains.kotlin.codegen.ExpressionCodegen +import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.forLoop.* -import org.jetbrains.kotlin.codegen.isClosedRangeContains -import org.jetbrains.kotlin.codegen.isPrimitiveRange import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.psi.KtForExpression import org.jetbrains.kotlin.psi.KtSimpleNameExpression @@ -88,9 +86,11 @@ abstract class CallIntrinsicRangeValue(protected val rangeCall: ResolvedCall): CallIntrinsicRangeValue(rangeCall) { override fun isIntrinsicInCall(resolvedCallForIn: ResolvedCall) = - resolvedCallForIn.resultingDescriptor.dispatchReceiverParameter?.let { - isPrimitiveRange(it.type) - } ?: false + resolvedCallForIn.resultingDescriptor.let { + isPrimitiveRangeContains(it) || + isClosedFloatingPointRangeContains(it) || + isIntPrimitiveRangeExtensionForInt(it) + } } class PrimitiveNumberRangeLiteralRangeValue(rangeCall: ResolvedCall): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) { diff --git a/compiler/testData/codegen/bytecodeText/ranges/inOptimizableRange.kt b/compiler/testData/codegen/bytecodeText/ranges/inOptimizableRange.kt index 5b8fa2c81d3..bffa522af77 100644 --- a/compiler/testData/codegen/bytecodeText/ranges/inOptimizableRange.kt +++ b/compiler/testData/codegen/bytecodeText/ranges/inOptimizableRange.kt @@ -1,28 +1,20 @@ // WITH_RUNTIME -fun Byte.in2(left: Byte, right: Byte): Boolean { - return this in left..right -} +fun Byte.inByte(left: Byte, right: Byte) = this in left..right -fun inInt(x: Int, left: Int, right: Int): Boolean { - return x in left..right -} +fun Short.inInt(left: Int, right: Int) = this in left .. right -fun inDouble(x: Double, left: Double, right: Double): Boolean { - return x in left..right -} +fun Short.inByte(left: Byte, right: Byte) = this in left..right -fun inFloat(x: Float, left: Float, right: Float): Boolean { - return x in left..right -} +fun inInt(x: Int, left: Int, right: Int) = x in left..right -fun inLong(x: Long, left: Long, right: Long): Boolean { - return x in left..right -} +fun inDouble(x: Double, left: Double, right: Double) = x in left..right -fun inCharWithNullableParameter(x: Char?, left: Char, right: Char): Boolean { - return x!! in left..right -} +fun inFloat(x: Float, left: Float, right: Float) = x in left..right + +fun inLong(x: Long, left: Long, right: Long) = x in left..right + +fun inCharWithNullableParameter(x: Char?, left: Char, right: Char) = x!! in left..right // 0 INVOKESPECIAL // 0 NEW