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.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 {
@@ -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")
}
@@ -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