FIR2IR: insert implicit not-null casts in delegated calls

This commit is contained in:
Mikhail Glukhikh
2021-12-02 19:42:28 +03:00
parent 7cfec0d846
commit fa8441fb23
10 changed files with 100 additions and 80 deletions
@@ -99,7 +99,7 @@ class Fir2IrImplicitCastInserter(
override fun visitExpression(expression: FirExpression, data: IrElement): IrElement {
return when (expression) {
is FirBlock -> (data as IrContainerExpression).insertImplicitCasts()
is FirUnitExpression -> (data as IrExpression).let { it.coerceToUnitIfNeeded(it.type, irBuiltIns) }
is FirUnitExpression -> (data as IrExpression).let { coerceToUnitIfNeeded(it, irBuiltIns) }
else -> data
}
}
@@ -108,7 +108,7 @@ class Fir2IrImplicitCastInserter(
return when (statement) {
is FirTypeAlias -> data
FirStubStatement -> data
is FirUnitExpression -> (data as IrExpression).let { it.coerceToUnitIfNeeded(it.type, irBuiltIns) }
is FirUnitExpression -> (data as IrExpression).let { coerceToUnitIfNeeded(it, irBuiltIns) }
is FirBlock -> (data as IrContainerExpression).insertImplicitCasts()
else -> statement.accept(this, data)
}
@@ -203,12 +203,9 @@ class Fir2IrImplicitCastInserter(
insertImplicitCasts()
}
expectedType.isUnit -> {
coerceToUnitIfNeeded(type, irBuiltIns)
coerceToUnitIfNeeded(this, irBuiltIns)
}
valueType.isNullabilityFlexible() && valueType.canBeNull && !expectedType.acceptsNullValues() -> {
insertImplicitNotNullCastIfNeeded(expression)
}
valueType.hasEnhancedNullability() && !expectedType.acceptsNullValues() -> {
typeCanBeEnhancedOrFlexibleNullable(valueType) && !expectedType.acceptsNullValues() -> {
insertImplicitNotNullCastIfNeeded(expression)
}
// TODO: coerceIntToAnotherIntegerType
@@ -217,11 +214,6 @@ class Fir2IrImplicitCastInserter(
}
}
private fun FirTypeRef.isNullabilityFlexible(): Boolean {
val flexibility = coneTypeSafe<ConeFlexibleType>() ?: return false
return flexibility.lowerBound.isMarkedNullable != flexibility.upperBound.isMarkedNullable
}
private fun FirTypeRef.acceptsNullValues(): Boolean =
canBeNull || hasEnhancedNullability()
@@ -230,21 +222,7 @@ class Fir2IrImplicitCastInserter(
// [TypeOperatorLowering] will retrieve the source (from start offset to end offset) as an assertion message.
// Avoid type casting if we can't determine the source for some reasons, e.g., implicit `this` receiver.
if (expression.source == null) return this
// Cast type massage 1. Remove @EnhancedNullability
// Cast type massage 2. Convert it to a non-null variant (in case of @FlexibleNullability)
val castType = type.removeAnnotations {
val classId = it.symbol.owner.parentAsClass.classId
classId == StandardClassIds.Annotations.EnhancedNullability ||
classId == StandardClassIds.Annotations.FlexibleNullability
}.withHasQuestionMark(false)
return IrTypeOperatorCallImpl(
this.startOffset,
this.endOffset,
castType,
IrTypeOperator.IMPLICIT_NOTNULL,
castType,
this
)
return implicitNotNullCast(this)
}
private fun IrContainerExpression.insertImplicitCasts(): IrContainerExpression {
@@ -254,7 +232,7 @@ class Fir2IrImplicitCastInserter(
statements.forEachIndexed { i, irStatement ->
if (irStatement !is IrErrorCallExpression && irStatement is IrExpression) {
if (i != lastIndex) {
statements[i] = irStatement.coerceToUnitIfNeeded(irStatement.type, irBuiltIns)
statements[i] = coerceToUnitIfNeeded(irStatement, irBuiltIns)
}
// TODO: for the last statement, need to cast to the return type if mismatched
}
@@ -268,7 +246,7 @@ class Fir2IrImplicitCastInserter(
statements.forEachIndexed { i, irStatement ->
if (irStatement !is IrErrorCallExpression && irStatement is IrExpression) {
statements[i] = irStatement.coerceToUnitIfNeeded(irStatement.type, irBuiltIns)
statements[i] = coerceToUnitIfNeeded(irStatement, irBuiltIns)
}
}
return this
@@ -347,27 +325,61 @@ class Fir2IrImplicitCastInserter(
return implicitCast(original, castType)
}
private fun implicitCast(original: IrExpression, castType: IrType): IrExpression {
return IrTypeOperatorCallImpl(
original.startOffset,
original.endOffset,
castType,
IrTypeOperator.IMPLICIT_CAST,
castType,
original
)
}
private fun IrExpression.coerceToUnitIfNeeded(valueType: IrType, irBuiltIns: IrBuiltIns): IrExpression {
return if (valueType.isUnit() || valueType.isNothing())
this
else
IrTypeOperatorCallImpl(
startOffset, endOffset,
irBuiltIns.unitType,
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT,
irBuiltIns.unitType,
this
companion object {
private fun implicitCast(original: IrExpression, castType: IrType): IrExpression {
return IrTypeOperatorCallImpl(
original.startOffset,
original.endOffset,
castType,
IrTypeOperator.IMPLICIT_CAST,
castType,
original
)
}
private fun coerceToUnitIfNeeded(original: IrExpression, irBuiltIns: IrBuiltIns): IrExpression {
val valueType = original.type
return if (valueType.isUnit() || valueType.isNothing())
original
else
IrTypeOperatorCallImpl(
original.startOffset, original.endOffset,
irBuiltIns.unitType,
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT,
irBuiltIns.unitType,
original
)
}
internal fun implicitNotNullCast(original: IrExpression): IrTypeOperatorCall {
// Cast type massage 1. Remove @EnhancedNullability
// Cast type massage 2. Convert it to a non-null variant (in case of @FlexibleNullability)
val castType = original.type.removeAnnotations {
val classId = it.symbol.owner.parentAsClass.classId
classId == StandardClassIds.Annotations.EnhancedNullability ||
classId == StandardClassIds.Annotations.FlexibleNullability
}.withHasQuestionMark(false)
return IrTypeOperatorCallImpl(
original.startOffset,
original.endOffset,
castType,
IrTypeOperator.IMPLICIT_NOTNULL,
castType,
original
)
}
internal fun typeCanBeEnhancedOrFlexibleNullable(typeRef: FirTypeRef): Boolean {
return when {
typeRef.hasEnhancedNullability() -> true
typeRef.isNullabilityFlexible() && typeRef.canBeNull -> true
else -> false
}
}
private fun FirTypeRef.isNullabilityFlexible(): Boolean {
val flexibility = coneTypeSafe<ConeFlexibleType>() ?: return false
return flexibility.lowerBound.isMarkedNullable != flexibility.upperBound.isMarkedNullable
}
}
}
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrReturnImpl
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.types.isNothing
import org.jetbrains.kotlin.ir.types.isNullable
import org.jetbrains.kotlin.ir.types.isUnit
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
@@ -61,12 +62,13 @@ class DelegatedMemberGenerator(
fun generateBodies() {
for ((declaration, irField, delegateToSymbol, delegateToLookupTag) in bodiesInfo) {
val callTypeCanBeNullable = Fir2IrImplicitCastInserter.typeCanBeEnhancedOrFlexibleNullable(delegateToSymbol.fir.returnTypeRef)
when (declaration) {
is IrSimpleFunction -> {
val member = declarationStorage.getIrFunctionSymbol(
delegateToSymbol as FirNamedFunctionSymbol, delegateToLookupTag
).owner as? IrSimpleFunction ?: continue
val body = createDelegateBody(irField, declaration, member)
val body = createDelegateBody(irField, declaration, member, callTypeCanBeNullable)
declaration.body = body
}
is IrProperty -> {
@@ -74,10 +76,10 @@ class DelegatedMemberGenerator(
delegateToSymbol as FirPropertySymbol, delegateToLookupTag
).owner as? IrProperty ?: continue
val getter = declaration.getter!!
getter.body = createDelegateBody(irField, getter, member.getter!!)
getter.body = createDelegateBody(irField, getter, member.getter!!, callTypeCanBeNullable)
if (declaration.isVar) {
val setter = declaration.setter!!
setter.body = createDelegateBody(irField, setter, member.setter!!)
setter.body = createDelegateBody(irField, setter, member.setter!!, false)
}
}
}
@@ -226,7 +228,8 @@ class DelegatedMemberGenerator(
private fun createDelegateBody(
irField: IrField,
delegateFunction: IrSimpleFunction,
superFunction: IrSimpleFunction
superFunction: IrSimpleFunction,
callTypeCanBeNullable: Boolean
): IrBlockBody {
val startOffset = irField.startOffset
val endOffset = irField.endOffset
@@ -268,10 +271,14 @@ class DelegatedMemberGenerator(
)
}
}
val resultType = delegateFunction.returnType
val irCastOrCall =
if (callTypeCanBeNullable && !resultType.isNullable()) Fir2IrImplicitCastInserter.implicitNotNullCast(irCall)
else irCall
if (superFunction.returnType.isUnit() || superFunction.returnType.isNothing()) {
body.statements.add(irCall)
body.statements.add(irCastOrCall)
} else {
val irReturn = IrReturnImpl(startOffset, endOffset, irBuiltIns.nothingType, delegateFunction.symbol, irCall)
val irReturn = IrReturnImpl(startOffset, endOffset, irBuiltIns.nothingType, delegateFunction.symbol, irCastOrCall)
body.statements.add(irReturn)
}
return body
@@ -1,6 +1,4 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND_FIR: JVM_IR
// FIR status: NPE expected on calling foo
// DISABLE_PARAM_ASSERTIONS
// MODULE: lib
@@ -1,6 +1,4 @@
// TARGET_BACKEND: JVM
// IGNORE_BACKEND_FIR: JVM_IR
// FIR status: Fail: should have been an exception
// MODULE: lib
// FILE: Delegation.java
@@ -44,9 +44,10 @@ FILE fqName:<root> fileName:/delegatedImplementationOfJavaInterface.kt
$this: VALUE_PARAMETER name:<this> type:<root>.Test
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun returnNotNull (): @[EnhancedNullability] kotlin.String declared in <root>.Test'
CALL 'public abstract fun returnNotNull (): @[EnhancedNullability] kotlin.String declared in <root>.J' type=@[EnhancedNullability] kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.J visibility:local [final]' type=<root>.J origin=null
receiver: GET_VAR '<this>: <root>.Test declared in <root>.Test.returnNotNull' type=<root>.Test origin=null
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public abstract fun returnNotNull (): @[EnhancedNullability] kotlin.String declared in <root>.J' type=@[EnhancedNullability] kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.J visibility:local [final]' type=<root>.J origin=null
receiver: GET_VAR '<this>: <root>.Test declared in <root>.Test.returnNotNull' type=<root>.Test origin=null
FUN DELEGATED_MEMBER name:returnNullable visibility:public modality:OPEN <> ($this:<root>.Test) returnType:kotlin.String?
annotations:
Nullable(value = <null>)
@@ -19,7 +19,7 @@ class Test : J {
@NotNull
override fun returnNotNull(): @EnhancedNullability String {
return <this>.#<$$delegate_0>.returnNotNull()
return <this>.#<$$delegate_0>.returnNotNull() /*!! String */
}
@Nullable
@@ -137,9 +137,10 @@ FILE fqName:<root> fileName:/implicitNotNullOnDelegatedImplementation.kt
$this: VALUE_PARAMETER name:<this> type:<root>.TestJFoo
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in <root>.TestJFoo'
CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String declared in <root>.JFoo' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.JFoo visibility:local [final]' type=<root>.JFoo origin=null
receiver: GET_VAR '<this>: <root>.TestJFoo declared in <root>.TestJFoo.foo' type=<root>.TestJFoo origin=null
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String declared in <root>.JFoo' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.JFoo visibility:local [final]' type=<root>.JFoo origin=null
receiver: GET_VAR '<this>: <root>.TestJFoo declared in <root>.TestJFoo.foo' type=<root>.TestJFoo origin=null
FIELD DELEGATE name:<$$delegate_0> type:<root>.JFoo visibility:local [final]
EXPRESSION_BODY
CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.JFoo' type=<root>.JFoo origin=null
@@ -168,9 +169,10 @@ FILE fqName:<root> fileName:/implicitNotNullOnDelegatedImplementation.kt
$this: VALUE_PARAMETER name:<this> type:<root>.TestK1
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in <root>.TestK1'
CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String [fake_override] declared in <root>.K1' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.K1 visibility:local [final]' type=<root>.K1 origin=null
receiver: GET_VAR '<this>: <root>.TestK1 declared in <root>.TestK1.foo' type=<root>.TestK1 origin=null
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String [fake_override] declared in <root>.K1' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.K1 visibility:local [final]' type=<root>.K1 origin=null
receiver: GET_VAR '<this>: <root>.TestK1 declared in <root>.TestK1.foo' type=<root>.TestK1 origin=null
FIELD DELEGATE name:<$$delegate_0> type:<root>.K1 visibility:local [final]
EXPRESSION_BODY
CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.K1' type=<root>.K1 origin=null
@@ -261,9 +263,10 @@ FILE fqName:<root> fileName:/implicitNotNullOnDelegatedImplementation.kt
$this: VALUE_PARAMETER name:<this> type:<root>.TestK4
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in <root>.TestK4'
CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in <root>.K4' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.K4 visibility:local [final]' type=<root>.K4 origin=null
receiver: GET_VAR '<this>: <root>.TestK4 declared in <root>.TestK4.foo' type=<root>.TestK4 origin=null
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in <root>.K4' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.K4 visibility:local [final]' type=<root>.K4 origin=null
receiver: GET_VAR '<this>: <root>.TestK4 declared in <root>.TestK4.foo' type=<root>.TestK4 origin=null
FIELD DELEGATE name:<$$delegate_0> type:<root>.K4 visibility:local [final]
EXPRESSION_BODY
CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.K4' type=<root>.K4 origin=null
@@ -55,7 +55,7 @@ class TestJFoo : IFoo {
}
override fun foo(): String {
return <this>.#<$$delegate_0>.foo()
return <this>.#<$$delegate_0>.foo() /*!! String */
}
local /* final field */ val <$$delegate_0>: JFoo = JFoo()
@@ -70,7 +70,7 @@ class TestK1 : IFoo {
}
override fun foo(): String {
return <this>.#<$$delegate_0>.foo()
return <this>.#<$$delegate_0>.foo() /*!! String */
}
local /* final field */ val <$$delegate_0>: K1 = K1()
@@ -115,7 +115,7 @@ class TestK4 : IFoo {
}
override fun foo(): String {
return <this>.#<$$delegate_0>.foo()
return <this>.#<$$delegate_0>.foo() /*!! String */
}
local /* final field */ val <$$delegate_0>: K4 = K4()
@@ -61,9 +61,10 @@ FILE fqName:<root> fileName:/nullCheckOnInterfaceDelegation.kt
$this: VALUE_PARAMETER name:<this> type:<root>.Delegated
BLOCK_BODY
RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in <root>.Delegated'
CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in <root>.Derived' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.Derived visibility:local [final]' type=<root>.Derived origin=null
receiver: GET_VAR '<this>: <root>.Delegated declared in <root>.Delegated.foo' type=<root>.Delegated origin=null
TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String
CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in <root>.Derived' type=kotlin.String origin=null
$this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:<root>.Derived visibility:local [final]' type=<root>.Derived origin=null
receiver: GET_VAR '<this>: <root>.Delegated declared in <root>.Delegated.foo' type=<root>.Delegated origin=null
FIELD DELEGATE name:<$$delegate_0> type:<root>.Derived visibility:local [final]
EXPRESSION_BODY
CONSTRUCTOR_CALL 'public constructor <init> () [primary] declared in <root>.Derived' type=<root>.Derived origin=null
@@ -28,7 +28,7 @@ class Delegated : IFoo {
}
override fun foo(): String {
return <this>.#<$$delegate_0>.foo()
return <this>.#<$$delegate_0>.foo() /*!! String */
}
local /* final field */ val <$$delegate_0>: Derived = Derived()