diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt index e56220e6a33..c24979b39c7 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/Fir2IrImplicitCastInserter.kt @@ -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() ?: 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() ?: return false + return flexibility.lowerBound.isMarkedNullable != flexibility.upperBound.isMarkedNullable + } } } diff --git a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt index 3a0db2f0f72..0546b9934cb 100644 --- a/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt +++ b/compiler/fir/fir2ir/src/org/jetbrains/kotlin/fir/backend/generators/DelegatedMemberGenerator.kt @@ -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 diff --git a/compiler/testData/codegen/box/notNullAssertions/callAssertions.kt b/compiler/testData/codegen/box/notNullAssertions/callAssertions.kt index b67724274f0..5d059a95d15 100644 --- a/compiler/testData/codegen/box/notNullAssertions/callAssertions.kt +++ b/compiler/testData/codegen/box/notNullAssertions/callAssertions.kt @@ -1,6 +1,4 @@ // TARGET_BACKEND: JVM -// IGNORE_BACKEND_FIR: JVM_IR -// FIR status: NPE expected on calling foo // DISABLE_PARAM_ASSERTIONS // MODULE: lib diff --git a/compiler/testData/codegen/box/notNullAssertions/delegation.kt b/compiler/testData/codegen/box/notNullAssertions/delegation.kt index baa4da791b3..6a44a6a9323 100644 --- a/compiler/testData/codegen/box/notNullAssertions/delegation.kt +++ b/compiler/testData/codegen/box/notNullAssertions/delegation.kt @@ -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 diff --git a/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.ir.txt b/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.ir.txt index 217a25eed69..6207a88aaf3 100644 --- a/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.ir.txt +++ b/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.ir.txt @@ -44,9 +44,10 @@ FILE fqName: fileName:/delegatedImplementationOfJavaInterface.kt $this: VALUE_PARAMETER name: type:.Test BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun returnNotNull (): @[EnhancedNullability] kotlin.String declared in .Test' - CALL 'public abstract fun returnNotNull (): @[EnhancedNullability] kotlin.String declared in .J' type=@[EnhancedNullability] kotlin.String origin=null - $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.J visibility:local [final]' type=.J origin=null - receiver: GET_VAR ': .Test declared in .Test.returnNotNull' type=.Test origin=null + TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String + CALL 'public abstract fun returnNotNull (): @[EnhancedNullability] kotlin.String declared in .J' type=@[EnhancedNullability] kotlin.String origin=null + $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.J visibility:local [final]' type=.J origin=null + receiver: GET_VAR ': .Test declared in .Test.returnNotNull' type=.Test origin=null FUN DELEGATED_MEMBER name:returnNullable visibility:public modality:OPEN <> ($this:.Test) returnType:kotlin.String? annotations: Nullable(value = ) diff --git a/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.kt.txt b/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.kt.txt index 0af46a7c1ba..6c591cbe73e 100644 --- a/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.kt.txt +++ b/compiler/testData/ir/irText/classes/delegatedImplementationOfJavaInterface.fir.kt.txt @@ -19,7 +19,7 @@ class Test : J { @NotNull override fun returnNotNull(): @EnhancedNullability String { - return .#<$$delegate_0>.returnNotNull() + return .#<$$delegate_0>.returnNotNull() /*!! String */ } @Nullable diff --git a/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.ir.txt b/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.ir.txt index 019d6c07a13..a7c12f0dd1a 100644 --- a/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.ir.txt +++ b/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.ir.txt @@ -137,9 +137,10 @@ FILE fqName: fileName:/implicitNotNullOnDelegatedImplementation.kt $this: VALUE_PARAMETER name: type:.TestJFoo BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in .TestJFoo' - CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String declared in .JFoo' type=kotlin.String origin=null - $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.JFoo visibility:local [final]' type=.JFoo origin=null - receiver: GET_VAR ': .TestJFoo declared in .TestJFoo.foo' type=.TestJFoo origin=null + TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String + CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String declared in .JFoo' type=kotlin.String origin=null + $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.JFoo visibility:local [final]' type=.JFoo origin=null + receiver: GET_VAR ': .TestJFoo declared in .TestJFoo.foo' type=.TestJFoo origin=null FIELD DELEGATE name:<$$delegate_0> type:.JFoo visibility:local [final] EXPRESSION_BODY CONSTRUCTOR_CALL 'public constructor () [primary] declared in .JFoo' type=.JFoo origin=null @@ -168,9 +169,10 @@ FILE fqName: fileName:/implicitNotNullOnDelegatedImplementation.kt $this: VALUE_PARAMETER name: type:.TestK1 BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in .TestK1' - CALL 'public open fun foo (): @[EnhancedNullability] kotlin.String [fake_override] declared in .K1' type=kotlin.String origin=null - $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.K1 visibility:local [final]' type=.K1 origin=null - receiver: GET_VAR ': .TestK1 declared in .TestK1.foo' type=.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 .K1' type=kotlin.String origin=null + $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.K1 visibility:local [final]' type=.K1 origin=null + receiver: GET_VAR ': .TestK1 declared in .TestK1.foo' type=.TestK1 origin=null FIELD DELEGATE name:<$$delegate_0> type:.K1 visibility:local [final] EXPRESSION_BODY CONSTRUCTOR_CALL 'public constructor () [primary] declared in .K1' type=.K1 origin=null @@ -261,9 +263,10 @@ FILE fqName: fileName:/implicitNotNullOnDelegatedImplementation.kt $this: VALUE_PARAMETER name: type:.TestK4 BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in .TestK4' - CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in .K4' type=kotlin.String origin=null - $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.K4 visibility:local [final]' type=.K4 origin=null - receiver: GET_VAR ': .TestK4 declared in .TestK4.foo' type=.TestK4 origin=null + TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String + CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in .K4' type=kotlin.String origin=null + $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.K4 visibility:local [final]' type=.K4 origin=null + receiver: GET_VAR ': .TestK4 declared in .TestK4.foo' type=.TestK4 origin=null FIELD DELEGATE name:<$$delegate_0> type:.K4 visibility:local [final] EXPRESSION_BODY CONSTRUCTOR_CALL 'public constructor () [primary] declared in .K4' type=.K4 origin=null diff --git a/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.kt.txt b/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.kt.txt index 84fcff411ec..edfde1ed59c 100644 --- a/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.kt.txt +++ b/compiler/testData/ir/irText/classes/implicitNotNullOnDelegatedImplementation.fir.kt.txt @@ -55,7 +55,7 @@ class TestJFoo : IFoo { } override fun foo(): String { - return .#<$$delegate_0>.foo() + return .#<$$delegate_0>.foo() /*!! String */ } local /* final field */ val <$$delegate_0>: JFoo = JFoo() @@ -70,7 +70,7 @@ class TestK1 : IFoo { } override fun foo(): String { - return .#<$$delegate_0>.foo() + return .#<$$delegate_0>.foo() /*!! String */ } local /* final field */ val <$$delegate_0>: K1 = K1() @@ -115,7 +115,7 @@ class TestK4 : IFoo { } override fun foo(): String { - return .#<$$delegate_0>.foo() + return .#<$$delegate_0>.foo() /*!! String */ } local /* final field */ val <$$delegate_0>: K4 = K4() diff --git a/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.ir.txt b/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.ir.txt index 8e9b196bfcc..1be4bbd72b9 100644 --- a/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.ir.txt +++ b/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.ir.txt @@ -61,9 +61,10 @@ FILE fqName: fileName:/nullCheckOnInterfaceDelegation.kt $this: VALUE_PARAMETER name: type:.Delegated BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun foo (): kotlin.String declared in .Delegated' - CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in .Derived' type=kotlin.String origin=null - $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.Derived visibility:local [final]' type=.Derived origin=null - receiver: GET_VAR ': .Delegated declared in .Delegated.foo' type=.Delegated origin=null + TYPE_OP type=kotlin.String origin=IMPLICIT_NOTNULL typeOperand=kotlin.String + CALL 'public final fun foo (): @[FlexibleNullability] kotlin.String? declared in .Derived' type=kotlin.String origin=null + $this: GET_FIELD 'FIELD DELEGATE name:<$$delegate_0> type:.Derived visibility:local [final]' type=.Derived origin=null + receiver: GET_VAR ': .Delegated declared in .Delegated.foo' type=.Delegated origin=null FIELD DELEGATE name:<$$delegate_0> type:.Derived visibility:local [final] EXPRESSION_BODY CONSTRUCTOR_CALL 'public constructor () [primary] declared in .Derived' type=.Derived origin=null diff --git a/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.kt.txt b/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.kt.txt index ad3287f2477..d2376824ffb 100644 --- a/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.kt.txt +++ b/compiler/testData/ir/irText/types/nullChecks/nullCheckOnInterfaceDelegation.fir.kt.txt @@ -28,7 +28,7 @@ class Delegated : IFoo { } override fun foo(): String { - return .#<$$delegate_0>.foo() + return .#<$$delegate_0>.foo() /*!! String */ } local /* final field */ val <$$delegate_0>: Derived = Derived()