FIR: Refine SAM support for methods with names from j.l.Object

This commit is contained in:
Denis Zharkov
2019-12-09 16:14:39 +03:00
parent e89c777fc6
commit f0298aad77
4 changed files with 95 additions and 6 deletions
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl
import org.jetbrains.kotlin.fir.types.impl.FirResolvedTypeRefImpl
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.Variance
@@ -263,10 +264,13 @@ private fun FirRegularClass.findSingleAbstractMethodByNames(
classUseSiteMemberScope.processFunctionsByName(candidateName) { functionSymbol ->
val firFunction = functionSymbol.fir
require(firFunction is FirSimpleFunction) {
"${functionSymbol.callableId.callableName} is expected to be _root_ide_package_.org.jetbrains.kotlin.fir.declarations.FirSimpleFunction, but ${functionSymbol::class} was found"
"${functionSymbol.callableId
.callableName} is expected to be _root_ide_package_.org.jetbrains.kotlin.fir.declarations.FirSimpleFunction, but ${functionSymbol::class} was found"
}
if (firFunction.modality != Modality.ABSTRACT || firFunction.isPublicInObject()) return@processFunctionsByName ProcessorAction.NEXT
if (firFunction.modality != Modality.ABSTRACT || firFunction
.isPublicInObject(checkOnlyName = false)
) return@processFunctionsByName ProcessorAction.NEXT
if (resultMethod != null) {
metIncorrectMember = true
@@ -287,7 +291,9 @@ private fun FirRegularClass.hasMoreThenOneAbstractFunctionOrHasAbstractProperty(
var wasAbstractFunction = false
for (declaration in declarations) {
if (declaration is FirProperty && declaration.modality == Modality.ABSTRACT) return true
if (declaration is FirSimpleFunction && declaration.modality == Modality.ABSTRACT && !declaration.isPublicInObject()) {
if (declaration is FirSimpleFunction && declaration.modality == Modality.ABSTRACT &&
!declaration.isPublicInObject(checkOnlyName = true)
) {
if (wasAbstractFunction) return true
wasAbstractFunction = true
}
@@ -300,9 +306,36 @@ private fun FirRegularClass.hasMoreThenOneAbstractFunctionOrHasAbstractProperty(
// "methods that are members of I that do not have the same signature as any public instance method of the class Object"
// It means that if an interface declares `int hashCode()` then the method won't be taken into account when
// checking if the interface is SAM.
private fun FirSimpleFunction.isPublicInObject(): Boolean {
// TODO: We make here a conservative check just filtering out methods by name, check the signature as well
return name.asString() in PUBLIC_METHOD_NAMES_IN_OBJECT
private fun FirSimpleFunction.isPublicInObject(checkOnlyName: Boolean): Boolean {
if (name.asString() !in PUBLIC_METHOD_NAMES_IN_OBJECT) return false
if (checkOnlyName) return true
return when (name.asString()) {
"hashCode", "getClass", "notify", "notifyAll", "toString" -> valueParameters.isEmpty()
"equals" -> valueParameters.singleOrNull()?.hasTypeOf(StandardClassIds.Any, allowNullable = true) == true
"wait" -> when (valueParameters.size) {
0 -> true
1 -> valueParameters[0].hasTypeOf(StandardClassIds.Long, allowNullable = false)
2 -> valueParameters[0].hasTypeOf(StandardClassIds.Long, allowNullable = false) &&
valueParameters[1].hasTypeOf(StandardClassIds.Int, allowNullable = false)
else -> false
}
else -> error("Unexpected method name: $name")
}
}
private fun FirValueParameter.hasTypeOf(classId: ClassId, allowNullable: Boolean): Boolean {
val type = returnTypeRef.coneTypeSafe<ConeKotlinType>() ?: return false
val classLike = when (type) {
is ConeClassLikeType -> type
is ConeFlexibleType -> type.upperBound as? ConeClassLikeType ?: return false
else -> return false
}
if (classLike.isMarkedNullable && !allowNullable) return false
return classLike.lookupTag.classId == classId
}
private val PUBLIC_METHOD_NAMES_IN_OBJECT = setOf("equals", "hashCode", "getClass", "wait", "notify", "notifyAll", "toString")
@@ -0,0 +1,33 @@
// FILE: KotlinTypeChecker.java
public interface KotlinTypeChecker {
interface TypeConstructorEquality {
boolean equals(@NotNull TypeConstructor a, @NotNull TypeConstructor b);
}
KotlinTypeChecker DEFAULT = NewKotlinTypeChecker.Companion.getDefault();
boolean isSubtypeOf(@NotNull KotlinType subtype, @NotNull KotlinType supertype);
boolean equalTypes(@NotNull KotlinType a, @NotNull KotlinType b);
}
// FILE: OverridingUtil.java
public class OverridingUtil {
public static OverridingUtil createWithEqualityAxioms(@NotNull KotlinTypeChecker.TypeConstructorEquality equalityAxioms) {
return null;
}
}
// FILE: main.kt
interface TypeConstructor {
val x: String
}
fun main() {
OverridingUtil.createWithEqualityAxioms l1@{ c1, c2 ->
if (c1.x == c2.x) return@l1 true
false
}
}
@@ -0,0 +1,18 @@
FILE: main.kt
public abstract interface TypeConstructor : R|kotlin/Any| {
public abstract val x: R|kotlin/String|
public get(): R|kotlin/String|
}
public final fun main(): R|kotlin/Unit| {
Q|OverridingUtil|.R|/OverridingUtil.createWithEqualityAxioms|(<L> = l1@fun <anonymous>(c1: R|ft<TypeConstructor, TypeConstructor?>!|, c2: R|ft<TypeConstructor, TypeConstructor?>!|): R|kotlin/Boolean| {
when () {
==(R|<local>/c1|.R|/TypeConstructor.x|, R|<local>/c2|.R|/TypeConstructor.x|) -> {
^@l1 Boolean(true)
}
}
Boolean(false)
}
)
}
@@ -1115,6 +1115,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
runTest("compiler/fir/resolve/testData/resolve/samConversions/samSupertypeWithOverride.kt");
}
@TestMetadata("samWithEquals.kt")
public void testSamWithEquals() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.kt");
}
@TestMetadata("simple.kt")
public void testSimple() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/samConversions/simple.kt");