Fix tests for in/in! with primitive range literals

- ClosedFloatingPointRange<T>
- IntRange, etc
- extensions in kotlin.ranges for ranges of Byte/Int/Short
This commit is contained in:
Dmitry Petrov
2017-06-30 17:25:12 +03:00
parent 5decf65d6a
commit 05cad83c79
5 changed files with 80 additions and 68 deletions
@@ -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<FqName, PrimitiveType> =
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 {
@@ -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")
}
@@ -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)
@@ -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<out
abstract class PrimitiveNumberRangeIntrinsicRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): CallIntrinsicRangeValue(rangeCall) {
override fun isIntrinsicInCall(resolvedCallForIn: ResolvedCall<out CallableDescriptor>) =
resolvedCallForIn.resultingDescriptor.dispatchReceiverParameter?.let {
isPrimitiveRange(it.type)
} ?: false
resolvedCallForIn.resultingDescriptor.let {
isPrimitiveRangeContains(it) ||
isClosedFloatingPointRangeContains(it) ||
isIntPrimitiveRangeExtensionForInt(it)
}
}
class PrimitiveNumberRangeLiteralRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) {
@@ -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