KT-16436 Incorrect primitive constants handling
Expression '1.unaryMinus()' is resolved as Int::unaryMinus call with Int receiver. However, this expression is implicitly coerced to a different integral type (Byte, Short, Long) based on results of constant evaluation. Introduce IMPLICIT_INTEGER_COERCION type operator to handle such cases. TODO: should we use it for simple constant expressions like '1' and '-1'?
This commit is contained in:
+19
-17
@@ -93,7 +93,7 @@ class InsertImplicitCasts(val builtIns: KotlinBuiltIns): IrElementTransformerVoi
|
||||
override fun visitReturn(expression: IrReturn): IrExpression {
|
||||
expression.transformChildrenVoid(this)
|
||||
|
||||
expression.value = expression.value?.cast(expression.returnTarget.returnType)
|
||||
expression.value = expression.value.cast(expression.returnTarget.returnType)
|
||||
|
||||
return expression
|
||||
}
|
||||
@@ -186,27 +186,29 @@ class InsertImplicitCasts(val builtIns: KotlinBuiltIns): IrElementTransformerVoi
|
||||
|
||||
private fun IrExpression.cast(expectedType: KotlinType?): IrExpression {
|
||||
if (expectedType == null) return this
|
||||
|
||||
if (expectedType.isError) return this
|
||||
|
||||
if (KotlinBuiltIns.isUnit(expectedType)) return coerceToUnit()
|
||||
|
||||
val valueType = this.type
|
||||
|
||||
if (valueType.isNullabilityFlexible() && valueType.containsNull() && !expectedType.containsNull()) {
|
||||
val nonNullValueType = valueType.upperIfFlexible().makeNotNullable()
|
||||
return IrTypeOperatorCallImpl(
|
||||
this.startOffset, this.endOffset, nonNullValueType,
|
||||
IrTypeOperator.IMPLICIT_NOTNULL, nonNullValueType, this
|
||||
).cast(expectedType)
|
||||
return when {
|
||||
KotlinBuiltIns.isUnit(expectedType) ->
|
||||
coerceToUnit()
|
||||
valueType.isNullabilityFlexible() && valueType.containsNull() && !expectedType.containsNull() -> {
|
||||
val nonNullValueType = valueType.upperIfFlexible().makeNotNullable()
|
||||
IrTypeOperatorCallImpl(
|
||||
this.startOffset, this.endOffset, nonNullValueType,
|
||||
IrTypeOperator.IMPLICIT_NOTNULL, nonNullValueType, this
|
||||
).cast(expectedType)
|
||||
}
|
||||
KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType.makeNotNullable(), expectedType) ->
|
||||
this
|
||||
KotlinBuiltIns.isInt(valueType) ->
|
||||
IrTypeOperatorCallImpl(this.startOffset, this.endOffset, expectedType,
|
||||
IrTypeOperator.IMPLICIT_INTEGER_COERCION, expectedType.makeNotNullable(), this)
|
||||
else ->
|
||||
IrTypeOperatorCallImpl(this.startOffset, this.endOffset, expectedType,
|
||||
IrTypeOperator.IMPLICIT_CAST, expectedType, this)
|
||||
}
|
||||
|
||||
if (!KotlinTypeChecker.DEFAULT.isSubtypeOf(valueType.makeNotNullable(), expectedType)) {
|
||||
return IrTypeOperatorCallImpl(this.startOffset, this.endOffset, expectedType,
|
||||
IrTypeOperator.IMPLICIT_CAST, expectedType, this)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
private fun IrExpression.coerceToUnit(): IrExpression {
|
||||
|
||||
@@ -23,6 +23,7 @@ enum class IrTypeOperator {
|
||||
IMPLICIT_CAST,
|
||||
IMPLICIT_NOTNULL,
|
||||
IMPLICIT_COERCION_TO_UNIT,
|
||||
IMPLICIT_INTEGER_COERCION,
|
||||
SAFE_CAST,
|
||||
INSTANCEOF,
|
||||
NOT_INSTANCEOF;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
fun test() {
|
||||
val test1: Int? = 42
|
||||
val test2: Long = 42
|
||||
val test3: Long? = 42
|
||||
val test4: Long? = -1
|
||||
val test5: Long? = 1.unaryMinus()
|
||||
val test6: Short? = 1.unaryMinus()
|
||||
val test7: Byte? = 1.unaryMinus()
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
FILE /primitivesImplicitConversions.kt
|
||||
FUN public fun test(): kotlin.Unit
|
||||
BLOCK_BODY
|
||||
VAR val test1: kotlin.Int? = 42
|
||||
CONST Int type=kotlin.Int value='42'
|
||||
VAR val test2: kotlin.Long = 42.toLong()
|
||||
CONST Long type=kotlin.Long value='42'
|
||||
VAR val test3: kotlin.Long? = 42.toLong()
|
||||
CONST Long type=kotlin.Long value='42'
|
||||
VAR val test4: kotlin.Long? = -1.toLong()
|
||||
CONST Long type=kotlin.Long value='-1'
|
||||
VAR val test5: kotlin.Long? = -1.toLong()
|
||||
TYPE_OP origin=IMPLICIT_INTEGER_COERCION typeOperand=kotlin.Long
|
||||
CALL 'unaryMinus(): Int' type=kotlin.Int origin=null
|
||||
$this: CONST Int type=kotlin.Int value='1'
|
||||
VAR val test6: kotlin.Short? = -1.toShort()
|
||||
TYPE_OP origin=IMPLICIT_INTEGER_COERCION typeOperand=kotlin.Short
|
||||
CALL 'unaryMinus(): Int' type=kotlin.Int origin=null
|
||||
$this: CONST Int type=kotlin.Int value='1'
|
||||
VAR val test7: kotlin.Byte? = -1.toByte()
|
||||
TYPE_OP origin=IMPLICIT_INTEGER_COERCION typeOperand=kotlin.Byte
|
||||
CALL 'unaryMinus(): Int' type=kotlin.Int origin=null
|
||||
$this: CONST Int type=kotlin.Int value='1'
|
||||
@@ -545,6 +545,12 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("primitivesImplicitConversions.kt")
|
||||
public void testPrimitivesImplicitConversions() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/expressions/primitivesImplicitConversions.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("references.kt")
|
||||
public void testReferences() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/ir/irText/expressions/references.kt");
|
||||
|
||||
Reference in New Issue
Block a user