psi2ir: inferred type for 'when' is Nothing only if it's exhaustive
TODO: fix inference bug
This commit is contained in:
+15
-8
@@ -88,15 +88,15 @@ class BranchingExpressionGenerator(statementGenerator: StatementGenerator) : Sta
|
|||||||
scope.createTemporaryVariable(it.genExpr(), "subject")
|
scope.createTemporaryVariable(it.genExpr(), "subject")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val inferredType = getInferredTypeWithImplicitCastsOrFail(expression)
|
val inferredType = getInferredTypeWithImplicitCastsOrFail(expression)
|
||||||
|
|
||||||
// TODO relies on ControlFlowInformationProvider, get rid of it
|
// TODO relies on ControlFlowInformationProvider, get rid of it
|
||||||
val isUsedAsExpression = context.bindingContext[BindingContext.USED_AS_EXPRESSION, expression] ?: false
|
val isUsedAsExpression = get(BindingContext.USED_AS_EXPRESSION, expression) ?: false
|
||||||
|
val isExhaustive = expression.isExhaustiveWhen()
|
||||||
|
|
||||||
val resultType = when {
|
val resultType = when {
|
||||||
isUsedAsExpression -> inferredType
|
isUsedAsExpression -> inferredType
|
||||||
KotlinBuiltIns.isNothing(inferredType) -> inferredType
|
isExhaustive && KotlinBuiltIns.isNothing(inferredType) -> inferredType
|
||||||
else -> context.builtIns.unitType
|
else -> context.builtIns.unitType
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,10 +129,8 @@ class BranchingExpressionGenerator(statementGenerator: StatementGenerator) : Sta
|
|||||||
|
|
||||||
private fun addElseBranchForExhaustiveWhenIfNeeded(irWhen: IrWhen, whenExpression: KtWhenExpression) {
|
private fun addElseBranchForExhaustiveWhenIfNeeded(irWhen: IrWhen, whenExpression: KtWhenExpression) {
|
||||||
if (irWhen.branches.filterIsInstance<IrElseBranch>().isEmpty()) {
|
if (irWhen.branches.filterIsInstance<IrElseBranch>().isEmpty()) {
|
||||||
val bindingContext = context.bindingContext
|
|
||||||
//TODO: check condition: seems it's safe to always generate exception
|
//TODO: check condition: seems it's safe to always generate exception
|
||||||
val isExhaustive = java.lang.Boolean.TRUE == bindingContext.get(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, whenExpression) ||
|
val isExhaustive = whenExpression.isExhaustiveWhen()
|
||||||
java.lang.Boolean.TRUE == bindingContext.get(BindingContext.EXHAUSTIVE_WHEN, whenExpression)
|
|
||||||
|
|
||||||
if (isExhaustive) {
|
if (isExhaustive) {
|
||||||
val call = IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.noWhenBranchMatchedExceptionSymbol)
|
val call = IrCallImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.noWhenBranchMatchedExceptionSymbol)
|
||||||
@@ -141,6 +139,11 @@ class BranchingExpressionGenerator(statementGenerator: StatementGenerator) : Sta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KtWhenExpression.isExhaustiveWhen(): Boolean =
|
||||||
|
elseExpression != null // TODO front-end should provide correct exhaustiveness information
|
||||||
|
|| true == get(BindingContext.EXHAUSTIVE_WHEN, this)
|
||||||
|
|| true == get(BindingContext.IMPLICIT_EXHAUSTIVE_WHEN, this)
|
||||||
|
|
||||||
private fun generateWhenBody(expression: KtWhenExpression, irSubject: IrVariable?, irWhen: IrWhen): IrExpression {
|
private fun generateWhenBody(expression: KtWhenExpression, irSubject: IrVariable?, irWhen: IrWhen): IrExpression {
|
||||||
return if (irSubject == null) {
|
return if (irSubject == null) {
|
||||||
if (irWhen.branches.isEmpty())
|
if (irWhen.branches.isEmpty())
|
||||||
@@ -178,10 +181,14 @@ class BranchingExpressionGenerator(statementGenerator: StatementGenerator) : Sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateIsPatternCondition(irSubject: IrVariable, ktCondition: KtWhenConditionIsPattern): IrExpression {
|
private fun generateIsPatternCondition(irSubject: IrVariable, ktCondition: KtWhenConditionIsPattern): IrExpression {
|
||||||
val isType = getOrFail(BindingContext.TYPE, ktCondition.typeReference)
|
val typeOperand = getOrFail(BindingContext.TYPE, ktCondition.typeReference)
|
||||||
|
val typeOperandDescriptor = typeOperand.constructor.declarationDescriptor
|
||||||
|
?: throw AssertionError("No declaration descriptor for type $typeOperand")
|
||||||
|
val typeOperandSymbol = context.symbolTable.referenceClassifier(typeOperandDescriptor)
|
||||||
|
|
||||||
return IrTypeOperatorCallImpl(
|
return IrTypeOperatorCallImpl(
|
||||||
ktCondition.startOffset, ktCondition.endOffset, context.builtIns.booleanType,
|
ktCondition.startOffset, ktCondition.endOffset, context.builtIns.booleanType,
|
||||||
IrTypeOperator.INSTANCEOF, isType, irSubject.defaultLoad()
|
IrTypeOperator.INSTANCEOF, typeOperand, irSubject.defaultLoad(), typeOperandSymbol
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -76,7 +76,7 @@ FILE fqName:<root> fileName:/whenByFloatingPoint.kt
|
|||||||
WHEN type=kotlin.Int origin=WHEN
|
WHEN type=kotlin.Int origin=WHEN
|
||||||
BRANCH
|
BRANCH
|
||||||
if: TYPE_OP type=kotlin.Boolean origin=INSTANCEOF typeOperand=kotlin.Double
|
if: TYPE_OP type=kotlin.Boolean origin=INSTANCEOF typeOperand=kotlin.Double
|
||||||
typeOperand: UNBOUND: class Double : kotlin.Number, kotlin.Comparable<kotlin.Double>, java.io.Serializable
|
typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public flags:
|
||||||
GET_VAR 'tmp0_subject: Any' type=kotlin.Any origin=null
|
GET_VAR 'tmp0_subject: Any' type=kotlin.Any origin=null
|
||||||
then: CONST Int type=kotlin.Int value=-1
|
then: CONST Int type=kotlin.Int value=-1
|
||||||
BRANCH
|
BRANCH
|
||||||
|
|||||||
+1
-1
@@ -40,7 +40,7 @@ FILE fqName:<root> fileName:/when.kt
|
|||||||
then: CONST String type=kotlin.String value=A
|
then: CONST String type=kotlin.String value=A
|
||||||
BRANCH
|
BRANCH
|
||||||
if: TYPE_OP type=kotlin.Boolean origin=INSTANCEOF typeOperand=kotlin.String
|
if: TYPE_OP type=kotlin.Boolean origin=INSTANCEOF typeOperand=kotlin.String
|
||||||
typeOperand: UNBOUND: class String : kotlin.Comparable<kotlin.String>, kotlin.CharSequence, java.io.Serializable
|
typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public flags:
|
||||||
GET_VAR 'tmp0_subject: Any?' type=kotlin.Any? origin=null
|
GET_VAR 'tmp0_subject: Any?' type=kotlin.Any? origin=null
|
||||||
then: CONST String type=kotlin.String value=String
|
then: CONST String type=kotlin.String value=String
|
||||||
BRANCH
|
BRANCH
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
fun one() = 1
|
||||||
|
fun two() = 2
|
||||||
|
|
||||||
|
fun test1(): Int {
|
||||||
|
while (true) {
|
||||||
|
when (one()) {
|
||||||
|
1 -> {
|
||||||
|
when(two()) {
|
||||||
|
2 -> return 2
|
||||||
|
}
|
||||||
|
} // : Nothing
|
||||||
|
else -> return 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test2(): Int {
|
||||||
|
while (true) {
|
||||||
|
when (one()) {
|
||||||
|
1 ->
|
||||||
|
when (two()) {
|
||||||
|
2 -> return 2
|
||||||
|
} // : Unit -> Nothing
|
||||||
|
else -> return 3
|
||||||
|
} // : Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
FILE fqName:<root> fileName:/kt24114.kt
|
||||||
|
FUN name:one visibility:public modality:FINAL <> () returnType:Int flags:
|
||||||
|
BLOCK_BODY
|
||||||
|
RETURN type=kotlin.Nothing from='one(): Int'
|
||||||
|
CONST Int type=kotlin.Int value=1
|
||||||
|
FUN name:two visibility:public modality:FINAL <> () returnType:Int flags:
|
||||||
|
BLOCK_BODY
|
||||||
|
RETURN type=kotlin.Nothing from='two(): Int'
|
||||||
|
CONST Int type=kotlin.Int value=2
|
||||||
|
FUN name:test1 visibility:public modality:FINAL <> () returnType:Int flags:
|
||||||
|
BLOCK_BODY
|
||||||
|
WHILE label=null origin=WHILE_LOOP
|
||||||
|
condition: CONST Boolean type=kotlin.Boolean value=true
|
||||||
|
body: BLOCK type=kotlin.Unit origin=null
|
||||||
|
BLOCK type=kotlin.Nothing origin=WHEN
|
||||||
|
VAR IR_TEMPORARY_VARIABLE name:tmp0_subject type:kotlin.Int flags:val
|
||||||
|
CALL 'one(): Int' type=kotlin.Int origin=null
|
||||||
|
WHEN type=kotlin.Nothing origin=WHEN
|
||||||
|
BRANCH
|
||||||
|
if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ
|
||||||
|
arg0: GET_VAR 'tmp0_subject: Int' type=kotlin.Int origin=null
|
||||||
|
arg1: CONST Int type=kotlin.Int value=1
|
||||||
|
then: BLOCK type=kotlin.Nothing origin=null
|
||||||
|
TYPE_OP type=kotlin.Nothing origin=IMPLICIT_CAST typeOperand=kotlin.Nothing
|
||||||
|
typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Nothing modality:FINAL visibility:public flags:
|
||||||
|
BLOCK type=kotlin.Unit origin=WHEN
|
||||||
|
VAR IR_TEMPORARY_VARIABLE name:tmp1_subject type:kotlin.Int flags:val
|
||||||
|
CALL 'two(): Int' type=kotlin.Int origin=null
|
||||||
|
WHEN type=kotlin.Unit origin=WHEN
|
||||||
|
BRANCH
|
||||||
|
if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ
|
||||||
|
arg0: GET_VAR 'tmp1_subject: Int' type=kotlin.Int origin=null
|
||||||
|
arg1: CONST Int type=kotlin.Int value=2
|
||||||
|
then: RETURN type=kotlin.Nothing from='test1(): Int'
|
||||||
|
CONST Int type=kotlin.Int value=2
|
||||||
|
BRANCH
|
||||||
|
if: CONST Boolean type=kotlin.Boolean value=true
|
||||||
|
then: RETURN type=kotlin.Nothing from='test1(): Int'
|
||||||
|
CONST Int type=kotlin.Int value=3
|
||||||
|
FUN name:test2 visibility:public modality:FINAL <> () returnType:Int flags:
|
||||||
|
BLOCK_BODY
|
||||||
|
WHILE label=null origin=WHILE_LOOP
|
||||||
|
condition: CONST Boolean type=kotlin.Boolean value=true
|
||||||
|
body: BLOCK type=kotlin.Unit origin=null
|
||||||
|
BLOCK type=kotlin.Nothing origin=WHEN
|
||||||
|
VAR IR_TEMPORARY_VARIABLE name:tmp0_subject type:kotlin.Int flags:val
|
||||||
|
CALL 'one(): Int' type=kotlin.Int origin=null
|
||||||
|
WHEN type=kotlin.Nothing origin=WHEN
|
||||||
|
BRANCH
|
||||||
|
if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ
|
||||||
|
arg0: GET_VAR 'tmp0_subject: Int' type=kotlin.Int origin=null
|
||||||
|
arg1: CONST Int type=kotlin.Int value=1
|
||||||
|
then: TYPE_OP type=kotlin.Nothing origin=IMPLICIT_CAST typeOperand=kotlin.Nothing
|
||||||
|
typeOperand: CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Nothing modality:FINAL visibility:public flags:
|
||||||
|
BLOCK type=kotlin.Unit origin=WHEN
|
||||||
|
VAR IR_TEMPORARY_VARIABLE name:tmp1_subject type:kotlin.Int flags:val
|
||||||
|
CALL 'two(): Int' type=kotlin.Int origin=null
|
||||||
|
WHEN type=kotlin.Unit origin=WHEN
|
||||||
|
BRANCH
|
||||||
|
if: CALL 'EQEQ(Any?, Any?): Boolean' type=kotlin.Boolean origin=EQEQ
|
||||||
|
arg0: GET_VAR 'tmp1_subject: Int' type=kotlin.Int origin=null
|
||||||
|
arg1: CONST Int type=kotlin.Int value=2
|
||||||
|
then: RETURN type=kotlin.Nothing from='test2(): Int'
|
||||||
|
CONST Int type=kotlin.Int value=2
|
||||||
|
BRANCH
|
||||||
|
if: CONST Boolean type=kotlin.Boolean value=true
|
||||||
|
then: RETURN type=kotlin.Nothing from='test2(): Int'
|
||||||
|
CONST Int type=kotlin.Int value=3
|
||||||
@@ -1256,6 +1256,11 @@ public class IrTextTestCaseGenerated extends AbstractIrTextTestCase {
|
|||||||
runTest("compiler/testData/ir/irText/regressions/integerCoercionToT.kt");
|
runTest("compiler/testData/ir/irText/regressions/integerCoercionToT.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt24114.kt")
|
||||||
|
public void testKt24114() throws Exception {
|
||||||
|
runTest("compiler/testData/ir/irText/regressions/kt24114.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("typeAliasCtorForGenericClass.kt")
|
@TestMetadata("typeAliasCtorForGenericClass.kt")
|
||||||
public void testTypeAliasCtorForGenericClass() throws Exception {
|
public void testTypeAliasCtorForGenericClass() throws Exception {
|
||||||
runTest("compiler/testData/ir/irText/regressions/typeAliasCtorForGenericClass.kt");
|
runTest("compiler/testData/ir/irText/regressions/typeAliasCtorForGenericClass.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user