From f0298aad77507a45f0140fd1a9f27df9170b881f Mon Sep 17 00:00:00 2001 From: Denis Zharkov Date: Mon, 9 Dec 2019 16:14:39 +0300 Subject: [PATCH] FIR: Refine SAM support for methods with names from j.l.Object --- .../kotlin/fir/resolve/SamResolution.kt | 45 ++++++++++++++++--- .../resolve/samConversions/samWithEquals.kt | 33 ++++++++++++++ .../resolve/samConversions/samWithEquals.txt | 18 ++++++++ .../fir/FirDiagnosticsTestGenerated.java | 5 +++ 4 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.kt create mode 100644 compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.txt diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SamResolution.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SamResolution.kt index b7491df806c..4cb0fae05ee 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SamResolution.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/SamResolution.kt @@ -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() ?: 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") diff --git a/compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.kt b/compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.kt new file mode 100644 index 00000000000..700146897ee --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.kt @@ -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 + } +} diff --git a/compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.txt b/compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.txt new file mode 100644 index 00000000000..e217f3eb161 --- /dev/null +++ b/compiler/fir/resolve/testData/resolve/samConversions/samWithEquals.txt @@ -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|( = l1@fun (c1: R|ft!|, c2: R|ft!|): R|kotlin/Boolean| { + when () { + ==(R|/c1|.R|/TypeConstructor.x|, R|/c2|.R|/TypeConstructor.x|) -> { + ^@l1 Boolean(true) + } + } + + Boolean(false) + } + ) + } diff --git a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index acd416ad2d4..e32a7c60da7 100644 --- a/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/resolve/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -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");