Skip to content

Commit

Permalink
IR: fix SAM conversion for types with contravariant intersection argu…
Browse files Browse the repository at this point in the history
…ment type

In the added test, the problem was that the SAM type as computed by
`SamTypeFactory.createByValueParameter` was `Consumer<{BaseClass &
BaseInterface}>`, which was latter approximated in psi2ir during the
KotlinType->IrType conversion to `Consumer<out Any?>` (here:
https://github.com/JetBrains/kotlin/blob/3034d9d791cf1f9033104e12448e0d262d3bc3ce/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/ArgumentsGenerationUtils.kt#L606),
because intersection type argument is approximated to `out Any?`.

To avoid this, replace intersection type in immediate arguments of a SAM
type with the common supertype of its components at the same place where
we're getting rid of projections.

 #KT-45945 Fixed
  • Loading branch information
udalov committed Apr 20, 2021
1 parent ac0af39 commit e6c089e
Show file tree
Hide file tree
Showing 16 changed files with 450 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.calls.NewCommonSuperTypeCalculator
import org.jetbrains.kotlin.resolve.calls.commonSuperType
import org.jetbrains.kotlin.resolve.sam.getAbstractMembers
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithNothing
Expand Down Expand Up @@ -80,11 +82,19 @@ open class SamTypeFactory {
}

private fun KotlinType.removeExternalProjections(): KotlinType {
val newArguments = arguments.map { TypeProjectionImpl(Variance.INVARIANT, it.type) }
val newArguments = arguments.map {
val type = it.type
TypeProjectionImpl(
Variance.INVARIANT,
if (type.constructor is IntersectionTypeConstructor)
NewCommonSuperTypeCalculator.commonSuperType(type.constructor.supertypes.map(KotlinType::unwrap))
else type
)
}
return replace(newArguments)
}

companion object {
val INSTANCE = SamTypeFactory()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,6 @@ fun CallableMemberDescriptor.createTypeParameterWithNewName(
return newDescriptor
}

fun KotlinType.removeExternalProjections(): KotlinType {
val newArguments = arguments.map { TypeProjectionImpl(Variance.INVARIANT, it.type) }
return replace(newArguments)
}

fun isInlineClassConstructorAccessor(descriptor: FunctionDescriptor): Boolean =
descriptor is AccessorForConstructorDescriptor &&
descriptor.calleeDescriptor.constructedClass.isInlineClass()

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: SAM_CONVERSIONS

// CHECK_BYTECODE_TEXT
// 0 java/lang/invoke/LambdaMetafactory

abstract class BaseClass
interface BaseInterface

class ConcreteType : BaseClass(), BaseInterface
class ConcreteType2 : BaseClass(), BaseInterface

fun box(): String {
example(0)
return "OK"
}

fun example(input: Int) {
val instance = when (input) {
0 -> GenericHolder<ConcreteType>()
else -> GenericHolder<ConcreteType2>()
}

instance.doOnSuccess {}
instance.doOnSuccess(::functionReference)
}

fun functionReference(x: Any) {}

class GenericHolder<T> {
fun doOnSuccess(onSuccess: Consumer<in T>) {
onSuccess.accept(object : BaseClass() {} as T)
}
}

fun interface Consumer<T> {
fun accept(t: T)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: SAM_CONVERSIONS

// CHECK_BYTECODE_TEXT
// 0 java/lang/invoke/LambdaMetafactory

interface Top

interface Common : Top

abstract class BaseClass : Common
interface BaseInterface : Common

class ConcreteType : BaseClass(), BaseInterface
class ConcreteType2 : BaseClass(), BaseInterface

fun box(): String {
example(0)
return "OK"
}

fun example(input: Int) {
val instance = when (input) {
0 -> GenericHolder<ConcreteType>()
else -> GenericHolder<ConcreteType2>()
}

instance.doOnSuccess {}
instance.doOnSuccess(::functionReference)
}

fun functionReference(x: Any) {}

class GenericHolder<T : Top> {
fun doOnSuccess(onSuccess: Consumer<in T>) {
onSuccess.accept(object : BaseClass() {} as T)
}
}

fun interface Consumer<T : Top> {
fun accept(t: T)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: SAM_CONVERSIONS

// CHECK_BYTECODE_TEXT
// 0 java/lang/invoke/LambdaMetafactory

interface Top
interface Unrelated

interface A : Top, Unrelated
interface B : Top, Unrelated

fun box(): String {
val g = when ("".length) {
0 -> G<A>()
else -> G<B>()
}

g.check {}
g.check(::functionReference)
return "OK"
}

fun functionReference(x: Any) {}

class G<T : Top> {
fun check(x: IFoo<in T>) {
x.accept(object : A {} as T)
}
}

fun interface IFoo<T : Top> {
fun accept(t: T)
}
42 changes: 42 additions & 0 deletions compiler/testData/codegen/box/sam/contravariantIntersectionType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// TARGET_BACKEND: JVM

// CHECK_BYTECODE_TEXT
// JVM_IR_TEMPLATES
// 2 java/lang/invoke/LambdaMetafactory

// FILE: test.kt

abstract class BaseClass
interface BaseInterface

class ConcreteType : BaseClass(), BaseInterface
class ConcreteType2 : BaseClass(), BaseInterface

fun box(): String {
example(0)
return "OK"
}

fun example(input: Int) {
val instance = when (input) {
0 -> GenericHolder<ConcreteType>()
else -> GenericHolder<ConcreteType2>()
}

instance.doOnSuccess {}
instance.doOnSuccess(::functionReference)
}

fun functionReference(x: Any) {}

class GenericHolder<T> {
fun doOnSuccess(onSuccess: Consumer<in T>) {
onSuccess.accept(object : BaseClass() {} as T)
}
}

// FILE: Consumer.java

public interface Consumer<T> {
void accept(T t);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// TARGET_BACKEND: JVM

// CHECK_BYTECODE_TEXT
// JVM_IR_TEMPLATES
// 2 java/lang/invoke/LambdaMetafactory

// FILE: test.kt

interface Top

interface Common : Top

abstract class BaseClass : Common
interface BaseInterface : Common

class ConcreteType : BaseClass(), BaseInterface
class ConcreteType2 : BaseClass(), BaseInterface

fun box(): String {
example(0)
return "OK"
}

fun example(input: Int) {
val instance = when (input) {
0 -> GenericHolder<ConcreteType>()
else -> GenericHolder<ConcreteType2>()
}

instance.doOnSuccess {}
instance.doOnSuccess(::functionReference)
}

fun functionReference(x: Any) {}

class GenericHolder<T : Top> {
fun doOnSuccess(onSuccess: Consumer<in T>) {
onSuccess.accept(object : BaseClass() {} as T)
}
}

// FILE: Consumer.java

public interface Consumer<T extends Top> {
void accept(T t);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// TARGET_BACKEND: JVM

// CHECK_BYTECODE_TEXT
// JVM_IR_TEMPLATES
// 2 java/lang/invoke/LambdaMetafactory

// FILE: test.kt

interface Top
interface Unrelated

interface A : Top, Unrelated
interface B : Top, Unrelated

fun box(): String {
val g = when ("".length) {
0 -> G<A>()
else -> G<B>()
}

g.check {}
g.check(::functionReference)
return "OK"
}

fun functionReference(x: Any) {}

class G<T : Top> {
fun check(x: IFoo<in T>) {
x.accept(object : A {} as T)
}
}

// FILE: IFoo.java

public interface IFoo<T extends Top> {
void accept(T t);
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e6c089e

Please sign in to comment.