[FE1.0] Optimize search of expect class member in annotation checker

In the following scenario, when we search corresponding expect member
for actual `A.B`, we can skip checking compatibility of `B` scope.
```
class A {
  class B {
    fun foo() {}
  }
}
actual typealias AImpl = A
```

This is because:

1. Annotation checker runs no matter if found expect class is compatible
or not.
2. Class always has at most one corresponding `expect` class (unlike for
functions, which may have several overrides), so we are sure that we
found the right member.

^KT-60668
^KT-60936
This commit is contained in:
Roman Efremov
2023-08-17 16:16:27 +02:00
committed by Space Team
parent f1ea6545eb
commit b57940a59b
13 changed files with 102 additions and 11 deletions
@@ -687,6 +687,12 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/annotationMatching")
@TestDataPath("$PROJECT_ROOT")
public class AnnotationMatching {
@Test
@TestMetadata("actualInnerClassMissingMember.kt")
public void testActualInnerClassMissingMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/actualInnerClassMissingMember.kt");
}
@Test
public void testAllFilesPresentInAnnotationMatching() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/annotationMatching"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true);
@@ -687,6 +687,12 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/annotationMatching")
@TestDataPath("$PROJECT_ROOT")
public class AnnotationMatching {
@Test
@TestMetadata("actualInnerClassMissingMember.kt")
public void testActualInnerClassMissingMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/actualInnerClassMissingMember.kt");
}
@Test
public void testAllFilesPresentInAnnotationMatching() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/annotationMatching"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true);
@@ -469,6 +469,7 @@ class FirExpectActualMatchingContextImpl private constructor(
expectClass: RegularClassSymbolMarker,
actualClass: RegularClassSymbolMarker,
actualMember: DeclarationSymbolMarker,
checkClassScopesCompatibility: Boolean,
): Map<FirBasedSymbol<*>, ExpectActualCompatibility<*>> {
val mapping = actualClass.asSymbol().fir.memberExpectForActual
return mapping?.get(actualMember to expectClass) ?: emptyMap()
@@ -90,7 +90,12 @@ object FirExpectActualResolver {
is FirClassLikeSymbol<*> -> {
val expectClassSymbol = useSiteSession.dependenciesSymbolProvider
.getClassLikeSymbolByClassId(actualSymbol.classId) as? FirRegularClassSymbol ?: return null
val compatibility = AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(expectClassSymbol, actualSymbol, context)
val compatibility = AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
expectClassSymbol,
actualSymbol,
checkClassScopesCompatibility = true,
context
)
mapOf(compatibility to listOf(expectClassSymbol))
}
else -> null
@@ -517,5 +517,6 @@ internal abstract class IrExpectActualMatchingContext(
expectClass: RegularClassSymbolMarker,
actualClass: RegularClassSymbolMarker,
actualMember: DeclarationSymbolMarker,
checkClassScopesCompatibility: Boolean,
): Map<out DeclarationSymbolMarker, ExpectActualCompatibility<*>> = error("Should not be called")
}
@@ -154,7 +154,14 @@ object AbstractExpectActualAnnotationMatchChecker {
if (skipCheckingAnnotationsOfActualClassMember(actualMember)) {
continue
}
val expectToCompatibilityMap = findPotentialExpectClassMembersForActual(expectClass, actualClass, actualMember)
val expectToCompatibilityMap = findPotentialExpectClassMembersForActual(
expectClass, actualClass, actualMember,
// Optimization: don't check class scopes, because:
// 1. Annotation checker runs no matter if found expect class is compatible or not.
// 2. Class always has at most one corresponding `expect` class (unlike for functions, which may have several overrides),
// so we are sure that we found the right member.
checkClassScopesCompatibility = false,
)
val expectMember = expectToCompatibilityMap.filter { it.value == ExpectActualCompatibility.Compatible }.keys.singleOrNull()
// Check also incompatible members if only one is found
?: expectToCompatibilityMap.keys.singleOrNull()
@@ -26,10 +26,11 @@ object AbstractExpectActualCompatibilityChecker {
fun <T : DeclarationSymbolMarker> getClassifiersCompatibility(
expectClassSymbol: RegularClassSymbolMarker,
actualClassLikeSymbol: ClassLikeSymbolMarker,
checkClassScopesCompatibility: Boolean,
context: ExpectActualMatchingContext<T>,
): ExpectActualCompatibility<T> {
val result = with(context) {
getClassifiersCompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor = null)
getClassifiersCompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor = null, checkClassScopesCompatibility)
}
@Suppress("UNCHECKED_CAST")
return result as ExpectActualCompatibility<T>
@@ -62,7 +63,8 @@ object AbstractExpectActualCompatibilityChecker {
substitutor = null,
expectClassSymbol = null,
actualClassSymbol = null,
unfulfilled = null
unfulfilled = null,
checkClassScopesCompatibility = true,
)
}
}
@@ -73,7 +75,8 @@ object AbstractExpectActualCompatibilityChecker {
expectClassSymbol: RegularClassSymbolMarker,
actualClassLikeSymbol: ClassLikeSymbolMarker,
parentSubstitutor: TypeSubstitutorMarker?,
): ExpectActualCompatibility<*> = getClassifiersIncompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor)
checkClassScopes: Boolean,
): ExpectActualCompatibility<*> = getClassifiersIncompatibility(expectClassSymbol, actualClassLikeSymbol, parentSubstitutor, checkClassScopes)
?: ExpectActualCompatibility.Compatible
context(ExpectActualMatchingContext<*>)
@@ -81,7 +84,8 @@ object AbstractExpectActualCompatibilityChecker {
private fun getClassifiersIncompatibility(
expectClassSymbol: RegularClassSymbolMarker,
actualClassLikeSymbol: ClassLikeSymbolMarker,
parentSubstitutor: TypeSubstitutorMarker?
parentSubstitutor: TypeSubstitutorMarker?,
checkClassScopesCompatibility: Boolean,
): ExpectActualCompatibility.Incompatible.WeakIncompatible<*>? {
// Can't check FQ names here because nested expected class may be implemented via actual typealias's expansion with the other FQ name
require(expectClassSymbol.name == actualClassLikeSymbol.name) {
@@ -136,7 +140,9 @@ object AbstractExpectActualCompatibilityChecker {
return Incompatible.Supertypes
}
getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor)?.let { return it }
if (checkClassScopesCompatibility) {
getClassScopesIncompatibility(expectClassSymbol, actualClass, substitutor)?.let { return it }
}
return null
}
@@ -211,7 +217,8 @@ object AbstractExpectActualCompatibilityChecker {
substitutor,
expectClassSymbol,
actualClassSymbol,
unfulfilled
unfulfilled,
checkClassScopesCompatibility = true,
)
}
@@ -236,7 +243,8 @@ object AbstractExpectActualCompatibilityChecker {
substitutor: TypeSubstitutorMarker?,
expectClassSymbol: RegularClassSymbolMarker?,
actualClassSymbol: RegularClassSymbolMarker?,
unfulfilled: MutableList<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>?
unfulfilled: MutableList<Pair<DeclarationSymbolMarker, Map<Incompatible<*>, List<DeclarationSymbolMarker?>>>>?,
checkClassScopesCompatibility: Boolean,
) {
val mapping = actualMembers.keysToMap { actualMember ->
when (expectMember) {
@@ -253,7 +261,8 @@ object AbstractExpectActualCompatibilityChecker {
getClassifiersCompatibility(
expectMember,
actualMember as ClassLikeSymbolMarker,
parentSubstitutor
parentSubstitutor,
checkClassScopesCompatibility,
)
}
else -> error("Unsupported declaration: $expectMember ($actualMembers)")
@@ -210,5 +210,6 @@ interface ExpectActualMatchingContext<T : DeclarationSymbolMarker> : TypeSystemC
expectClass: RegularClassSymbolMarker,
actualClass: RegularClassSymbolMarker,
actualMember: DeclarationSymbolMarker,
checkClassScopesCompatibility: Boolean,
): Map<out DeclarationSymbolMarker, ExpectActualCompatibility<*>>
}
@@ -396,11 +396,13 @@ class ClassicExpectActualMatchingContext(
expectClass: RegularClassSymbolMarker,
actualClass: RegularClassSymbolMarker,
actualMember: DeclarationSymbolMarker,
checkClassScopesCompatibility: Boolean,
): Map<MemberDescriptor, ExpectActualCompatibility<*>> {
val compatibilityToExpects = ExpectedActualResolver.findExpectForActualClassMember(
actualMember as MemberDescriptor,
actualClass as ClassDescriptor,
expectClass as ClassDescriptor,
checkClassScopesCompatibility,
this,
)
return buildMap {
@@ -46,6 +46,7 @@ object ExpectedActualResolver {
AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
expected,
actual,
checkClassScopesCompatibility = true,
context
)
}
@@ -58,6 +59,7 @@ object ExpectedActualResolver {
actual: MemberDescriptor,
actualClass: ClassDescriptor,
expectClass: ClassDescriptor,
checkClassScopesCompatibility: Boolean,
context: ClassicExpectActualMatchingContext,
): Map<ExpectActualCompatibility<MemberDescriptor>, List<MemberDescriptor>> {
val candidates = with(context) {
@@ -76,6 +78,7 @@ object ExpectedActualResolver {
matchActualClassAgainstPotentialExpects(
actual,
candidates.filterIsInstance<ClassifierDescriptorWithTypeParameters>(),
checkClassScopesCompatibility,
context
)
}
@@ -110,7 +113,7 @@ object ExpectedActualResolver {
}
is ClassifierDescriptorWithTypeParameters -> {
val candidates = context.findClassifiersFromModule(actual.classId, actual.module, moduleFilter)
matchActualClassAgainstPotentialExpects(actual, candidates, context)
matchActualClassAgainstPotentialExpects(actual, candidates, checkClassScopesCompatibility = true, context)
}
else -> null
}
@@ -158,6 +161,7 @@ object ExpectedActualResolver {
private fun matchActualClassAgainstPotentialExpects(
actual: ClassifierDescriptorWithTypeParameters,
candidates: Collection<ClassifierDescriptorWithTypeParameters>,
checkClassScopesCompatibility: Boolean,
context: ClassicExpectActualMatchingContext,
): Map<ExpectActualCompatibility<MemberDescriptor>, List<ClassifierDescriptorWithTypeParameters>> {
return candidates.filter { declaration ->
@@ -166,6 +170,7 @@ object ExpectedActualResolver {
AbstractExpectActualCompatibilityChecker.getClassifiersCompatibility(
expected as ClassDescriptor,
actual,
checkClassScopesCompatibility,
context
)
}
@@ -0,0 +1,21 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
<!INCOMPATIBLE_MATCHING{JVM}!>expect class A {
<!INCOMPATIBLE_MATCHING{JVM}!>class B {
@Ann
fun foo()
<!NO_ACTUAL_FOR_EXPECT{JVM}!>fun missingOnActual()<!>
}<!>
}<!>
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
class AImpl {
class B {
fun foo() {}
}
}
<!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT!>actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS!>A<!> = AImpl<!>
@@ -0,0 +1,21 @@
// MODULE: m1-common
// FILE: common.kt
annotation class Ann
expect class A {
class B {
@Ann
fun foo()
fun missingOnActual()
}
}
// MODULE: m1-jvm()()(m1-common)
// FILE: jvm.kt
class AImpl {
class B {
fun foo() {}
}
}
actual typealias <!ACTUAL_ANNOTATIONS_NOT_MATCH_EXPECT, NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS!>A<!> = AImpl
@@ -23456,6 +23456,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
@TestMetadata("compiler/testData/diagnostics/tests/multiplatform/annotationMatching")
@TestDataPath("$PROJECT_ROOT")
public class AnnotationMatching {
@Test
@TestMetadata("actualInnerClassMissingMember.kt")
public void testActualInnerClassMissingMember() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/annotationMatching/actualInnerClassMissingMember.kt");
}
@Test
public void testAllFilesPresentInAnnotationMatching() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform/annotationMatching"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true);