FIR2IR: insert implicit not-null casts in delegated calls
This commit is contained in:
+62
-50
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-6
@@ -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
|
||||
|
||||
|
||||
+4
-3
@@ -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>)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+12
-9
@@ -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
|
||||
|
||||
+3
-3
@@ -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()
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user