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:
@@ -21,19 +21,10 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns.RANGES_PACKAGE_FQ_NAME
|
|||||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||||
import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitiveNumberClassDescriptor
|
import org.jetbrains.kotlin.codegen.AsmUtil.isPrimitiveNumberClassDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.*
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
|
||||||
import org.jetbrains.kotlin.name.FqName
|
import org.jetbrains.kotlin.name.FqName
|
||||||
import org.jetbrains.kotlin.name.Name
|
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.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.kotlin.types.KotlinType
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
|
||||||
|
|
||||||
private val RANGE_TO_ELEMENT_TYPE: Map<FqName, PrimitiveType> =
|
private val RANGE_TO_ELEMENT_TYPE: Map<FqName, PrimitiveType> =
|
||||||
supportedRangeTypes().associateBy {
|
supportedRangeTypes().associateBy {
|
||||||
@@ -77,8 +68,8 @@ fun isRangeOrProgression(className: FqName) =
|
|||||||
getPrimitiveRangeOrProgressionElementType(className) != null
|
getPrimitiveRangeOrProgressionElementType(className) != null
|
||||||
|
|
||||||
fun isPrimitiveNumberRangeTo(rangeTo: CallableDescriptor) =
|
fun isPrimitiveNumberRangeTo(rangeTo: CallableDescriptor) =
|
||||||
"rangeTo" == rangeTo.name.asString() &&
|
"rangeTo" == rangeTo.name.asString() && isPrimitiveNumberClassDescriptor(rangeTo.containingDeclaration) ||
|
||||||
isPrimitiveNumberClassDescriptor(rangeTo.containingDeclaration)
|
isPrimitiveRangeToExtension(rangeTo)
|
||||||
|
|
||||||
private fun isPrimitiveRangeToExtension(descriptor: CallableDescriptor): Boolean {
|
private fun isPrimitiveRangeToExtension(descriptor: CallableDescriptor): Boolean {
|
||||||
if (!isTopLevelInPackage(descriptor, "rangeTo", "kotlin.ranges")) return false
|
if (!isTopLevelInPackage(descriptor, "rangeTo", "kotlin.ranges")) return false
|
||||||
@@ -139,7 +130,6 @@ fun isComparableRangeTo(descriptor: CallableDescriptor): Boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun isClosedRangeContains(descriptor: CallableDescriptor): Boolean {
|
fun isClosedRangeContains(descriptor: CallableDescriptor): Boolean {
|
||||||
if (descriptor.name.asString() != "contains") return false
|
if (descriptor.name.asString() != "contains") return false
|
||||||
val containingClassDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false
|
val containingClassDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false
|
||||||
@@ -148,40 +138,47 @@ fun isClosedRangeContains(descriptor: CallableDescriptor): Boolean {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPrimitiveRangeToExtension(operationReference: KtSimpleNameExpression, bindingContext: BindingContext): Boolean {
|
fun isPrimitiveRangeContains(descriptor: CallableDescriptor): Boolean {
|
||||||
val resolvedCall = operationReference.getResolvedCallWithAssert(bindingContext)
|
if (descriptor.name.asString() != "contains") return false
|
||||||
val receiver = resolvedCall.dispatchReceiver as? ExpressionReceiver ?: return false
|
val dispatchReceiverType = descriptor.dispatchReceiverParameter?.type ?: return false
|
||||||
|
if (!isPrimitiveRange(dispatchReceiverType)) return false
|
||||||
|
|
||||||
/*
|
return true
|
||||||
* 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
fun isIntPrimitiveRangeExtensionForInt(descriptor: CallableDescriptor): Boolean {
|
||||||
* Checks whether for expression 'x in a..b' a..b is primitive integral range
|
if (descriptor.name.asString() != "contains") return false
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
private fun isTopLevelInPackage(descriptor: DeclarationDescriptor, name: String, packageName: String): Boolean {
|
||||||
|
|||||||
+24
-1
@@ -16,8 +16,11 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.range
|
package org.jetbrains.kotlin.codegen.range
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||||
|
import org.jetbrains.kotlin.codegen.getClosedFloatingPointRangeElementType
|
||||||
import org.jetbrains.kotlin.codegen.getPrimitiveRangeElementType
|
import org.jetbrains.kotlin.codegen.getPrimitiveRangeElementType
|
||||||
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
@@ -31,7 +34,8 @@ abstract class AbstractInPrimitiveNumberRangeExpressionGenerator(
|
|||||||
rangeCall: ResolvedCall<*>,
|
rangeCall: ResolvedCall<*>,
|
||||||
isInclusiveHighBound: Boolean
|
isInclusiveHighBound: Boolean
|
||||||
) : AbstractInRangeWithKnownBoundsExpressionGenerator(
|
) : AbstractInRangeWithKnownBoundsExpressionGenerator(
|
||||||
codegen, operatorReference, isInclusiveHighBound, AsmTypes.valueTypeForPrimitive(getPrimitiveRangeElementType(rangeCall.resultingDescriptor.returnType!!))
|
codegen, operatorReference, isInclusiveHighBound,
|
||||||
|
getAsmRangeElementTypeForPrimitiveRange(rangeCall.resultingDescriptor)
|
||||||
) {
|
) {
|
||||||
override val comparisonGenerator: ComparisonGenerator =
|
override val comparisonGenerator: ComparisonGenerator =
|
||||||
when (asmElementType) {
|
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")
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ class InPrimitiveNumberRangeLiteralGenerator(
|
|||||||
operatorReference: KtSimpleNameExpression,
|
operatorReference: KtSimpleNameExpression,
|
||||||
rangeCall: ResolvedCall<*>
|
rangeCall: ResolvedCall<*>
|
||||||
) : AbstractInPrimitiveNumberRangeExpressionGenerator(codegen, operatorReference, rangeCall, isInclusiveHighBound = true) {
|
) : 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)!!
|
private val to: KtExpression = ExpressionCodegen.getSingleArgumentExpression(rangeCall)!!
|
||||||
|
|
||||||
override fun genLowBound(): StackValue = codegen.generateReceiverValue(from, false)
|
override fun genLowBound(): StackValue = codegen.generateReceiverValue(from, false)
|
||||||
|
|||||||
@@ -16,10 +16,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.range
|
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.forLoop.*
|
||||||
import org.jetbrains.kotlin.codegen.isClosedRangeContains
|
|
||||||
import org.jetbrains.kotlin.codegen.isPrimitiveRange
|
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||||
import org.jetbrains.kotlin.psi.KtForExpression
|
import org.jetbrains.kotlin.psi.KtForExpression
|
||||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
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) {
|
abstract class PrimitiveNumberRangeIntrinsicRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): CallIntrinsicRangeValue(rangeCall) {
|
||||||
override fun isIntrinsicInCall(resolvedCallForIn: ResolvedCall<out CallableDescriptor>) =
|
override fun isIntrinsicInCall(resolvedCallForIn: ResolvedCall<out CallableDescriptor>) =
|
||||||
resolvedCallForIn.resultingDescriptor.dispatchReceiverParameter?.let {
|
resolvedCallForIn.resultingDescriptor.let {
|
||||||
isPrimitiveRange(it.type)
|
isPrimitiveRangeContains(it) ||
|
||||||
} ?: false
|
isClosedFloatingPointRangeContains(it) ||
|
||||||
|
isIntPrimitiveRangeExtensionForInt(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PrimitiveNumberRangeLiteralRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) {
|
class PrimitiveNumberRangeLiteralRangeValue(rangeCall: ResolvedCall<out CallableDescriptor>): PrimitiveNumberRangeIntrinsicRangeValue(rangeCall) {
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
|
|
||||||
fun Byte.in2(left: Byte, right: Byte): Boolean {
|
fun Byte.inByte(left: Byte, right: Byte) = this in left..right
|
||||||
return this in left..right
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inInt(x: Int, left: Int, right: Int): Boolean {
|
fun Short.inInt(left: Int, right: Int) = this in left .. right
|
||||||
return x in left..right
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inDouble(x: Double, left: Double, right: Double): Boolean {
|
fun Short.inByte(left: Byte, right: Byte) = this in left..right
|
||||||
return x in left..right
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inFloat(x: Float, left: Float, right: Float): Boolean {
|
fun inInt(x: Int, left: Int, right: Int) = x in left..right
|
||||||
return x in left..right
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inLong(x: Long, left: Long, right: Long): Boolean {
|
fun inDouble(x: Double, left: Double, right: Double) = x in left..right
|
||||||
return x in left..right
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inCharWithNullableParameter(x: Char?, left: Char, right: Char): Boolean {
|
fun inFloat(x: Float, left: Float, right: Float) = x in left..right
|
||||||
return 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 INVOKESPECIAL
|
||||||
// 0 NEW
|
// 0 NEW
|
||||||
|
|||||||
Reference in New Issue
Block a user