diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt index c592061a45c..d1db6ed1f01 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderInfo.kt @@ -81,7 +81,8 @@ internal class ProgressionHeaderInfo( lowerBound: IrExpression, upperBound: IrExpression, step: IrExpression, - closed: Boolean = true + closed: Boolean = true, + val additionalNotEmptyCondition: IrExpression? = null ) : HeaderInfo(progressionType, lowerBound, upperBound, step, closed) internal class ArrayHeaderInfo( diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt index 7c15174d7eb..512df830c16 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/HeaderProcessor.kt @@ -80,8 +80,7 @@ internal class ProgressionLoopHeader( val progressionKotlinType = progressionType.elementType(builtIns).toKotlinType() val lessOrEqualFun = builtIns.lessOrEqualFunByOperandType[progressionKotlinType]!! - // TODO: Additional condition for `for (i in a until MIN_VALUE)` corner case - when (headerInfo.direction) { + val notEmptyCondition = when (headerInfo.direction) { ProgressionDirection.DECREASING -> // last <= inductionVariable irCall(lessOrEqualFun).apply { @@ -121,6 +120,11 @@ internal class ProgressionLoopHeader( ) } } + + if (headerInfo.additionalNotEmptyCondition != null) context.andand( + headerInfo.additionalNotEmptyCondition, + notEmptyCondition + ) else notEmptyCondition } } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt index 003312b55f5..51f8e71af47 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/lower/loops/ProgressionHandlers.kt @@ -6,15 +6,15 @@ package org.jetbrains.kotlin.backend.common.lower.loops import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder import org.jetbrains.kotlin.backend.common.lower.createIrBuilder import org.jetbrains.kotlin.backend.common.lower.matchers.SimpleCalleeMatcher import org.jetbrains.kotlin.backend.common.lower.matchers.createIrCallMatcher import org.jetbrains.kotlin.backend.common.lower.matchers.singleArgumentExtension import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.ir.builders.irCall -import org.jetbrains.kotlin.ir.builders.irGet -import org.jetbrains.kotlin.ir.builders.irInt +import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.types.classifierOrNull @@ -74,14 +74,54 @@ internal class UntilHandler(private val context: CommonBackendContext, private v override fun build(call: IrCall, data: ProgressionType): HeaderInfo? = with(context.createIrBuilder(call.symbol, call.startOffset, call.endOffset)) { + // + // An additional emptiness condition is required for the corner case: + // + // ``` + // for (i in a until MIN_VALUE) {} + // ``` + // + // ...which should always be considered an empty range. When the given bound is MIN_VALUE, and because `last = bound - 1`, + // "last" will underflow to MAX_VALUE, therefore the default emptiness check: + // + // ``` + // if (first <= last) { /* loop */ } + // ``` + // + // ...will always be true and won't consider the range as empty. Therefore, we need to add an additional condition to the + // emptiness check so that it becomes: + // + // ``` + // if (first <= last && bound > MIN_VALUE) { /* loop */ } + // ``` + // + // TODO: Do not add additionalEmptinessCondition if "bound" is const and > MIN_VALUE ProgressionHeaderInfo( data, lowerBound = call.extensionReceiver!!, upperBound = call.getValueArgument(0)!!, step = irInt(1), - closed = false + closed = false, + additionalNotEmptyCondition = buildMinValueConditionIfNecessary(data, call.getValueArgument(0)!!) ) } + + private fun DeclarationIrBuilder.buildMinValueConditionIfNecessary( + progressionType: ProgressionType, + bound: IrExpression + ): IrExpression? { + val irBuiltIns = context.irBuiltIns + val minConst = when (progressionType) { + ProgressionType.INT_PROGRESSION -> irInt(Int.MIN_VALUE) + ProgressionType.CHAR_PROGRESSION -> irChar(Char.MIN_VALUE) + ProgressionType.LONG_PROGRESSION -> irLong(Long.MIN_VALUE) + } + val progressionKotlinType = progressionType.elementType(irBuiltIns).toKotlinType() + return irCall(irBuiltIns.greaterFunByOperandType[progressionKotlinType]!!).apply { + putValueArgument(0, bound) + putValueArgument(1, minConst) + } + } } internal class IndicesHandler(val context: CommonBackendContext) : ProgressionHandler { @@ -111,6 +151,22 @@ internal class IndicesHandler(val context: CommonBackendContext) : ProgressionHa ) } + private fun DeclarationIrBuilder.buildMinValueConditionIfNecessary( + progressionType: ProgressionType, + bound: IrExpression + ): IrExpression? { + val irBuiltIns = context.irBuiltIns + val minConst = when (progressionType) { + ProgressionType.INT_PROGRESSION -> irInt(Int.MIN_VALUE) + ProgressionType.CHAR_PROGRESSION -> irChar(Char.MIN_VALUE) + ProgressionType.LONG_PROGRESSION -> irLong(Long.MIN_VALUE) + } + val progressionKotlinType = progressionType.elementType(irBuiltIns).toKotlinType() + return irCall(irBuiltIns.greaterFunByOperandType[progressionKotlinType]!!).apply { + putValueArgument(0, bound) + putValueArgument(1, minConst) + } + } } internal class ArrayIterationHandler(val context: CommonBackendContext) : HeaderInfoHandler { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt index 6718631b742..b22bbc0af73 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt @@ -282,6 +282,9 @@ fun IrBuilderWithScope.irInt(value: Int) = fun IrBuilderWithScope.irLong(value: Long) = IrConstImpl.long(startOffset, endOffset, context.irBuiltIns.longType, value) +fun IrBuilderWithScope.irChar(value: Char) = + IrConstImpl.char(startOffset, endOffset, context.irBuiltIns.charType, value) + fun IrBuilderWithScope.irString(value: String) = IrConstImpl.string(startOffset, endOffset, context.irBuiltIns.stringType, value) diff --git a/compiler/testData/codegen/box/ranges/forInUntil/forInUntilChar0.kt b/compiler/testData/codegen/box/ranges/forInUntil/forInUntilChar0.kt index b339d8029e4..5b7357ba109 100644 --- a/compiler/testData/codegen/box/ranges/forInUntil/forInUntilChar0.kt +++ b/compiler/testData/codegen/box/ranges/forInUntil/forInUntilChar0.kt @@ -1,5 +1,3 @@ -// TODO: Enable after corner case is addressed -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME import kotlin.test.assertEquals diff --git a/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinint.kt b/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinint.kt index a75708250ba..31ae7acede2 100644 --- a/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinint.kt +++ b/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinint.kt @@ -1,5 +1,3 @@ -// TODO: Enable after corner case is addressed -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME import kotlin.test.assertEquals diff --git a/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinlong.kt b/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinlong.kt index c8e0f4fa3e7..b44b7f9f440 100644 --- a/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinlong.kt +++ b/compiler/testData/codegen/box/ranges/forInUntil/forInUntilMinlong.kt @@ -1,5 +1,3 @@ -// TODO: Enable after corner case is addressed -// IGNORE_BACKEND: JVM_IR // WITH_RUNTIME import kotlin.test.assertEquals