[FIR, IR] Convert FirDefaultArgumentsInExpectActualizedByFakeOverrideChecker into ExpectActualCheckingCompatibility

FirDefaultArgumentsInExpectActualizedByFakeOverrideChecker is an adhoc
checker which can be converted to ExpectActualCheckingCompatibility to
reuse common expect-actual checking infrastructure.

^KT-62913 Fixed
Review: https://jetbrains.team/p/kt/reviews/13094/timeline

Tests that were broken by one of my previous commits are now fixed:
- actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt
- inheritedJavaMembers.kt

DEFAULT_ARGUMENTS_IN_EXPECT_ACTUALIZED_BY_FAKE_OVERRIDE diagnostic
disappeared in delegation.fir.kt because only one
AbstractExpectActualChecker incompatibility can be reported at a time
(DEFAULT_ARGUMENTS_IN_EXPECT_ACTUALIZED_BY_FAKE_OVERRIDE is now reported
not by adhoc checker but by common AbstractExpectActualChecker). It
would be nice to report both of them, but it's a separate issue KT-62631

delegation2 test makes sure that
DEFAULT_ARGUMENTS_IN_EXPECT_ACTUALIZED_BY_FAKE_OVERRIDE is reported when
NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS is fixed
This commit is contained in:
Nikita Bobko
2023-11-17 11:39:24 +01:00
committed by teamcity
parent c8a9928624
commit 7166adb179
20 changed files with 156 additions and 152 deletions
@@ -1243,6 +1243,12 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst
runTest("compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualFakeOverride/delegation.kt");
}
@Test
@TestMetadata("delegation2.kt")
public void testDelegation2() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualFakeOverride/delegation2.kt");
}
@Test
@TestMetadata("disabledFeature.kt")
public void testDisabledFeature() throws Exception {
@@ -1243,6 +1243,12 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi
runTest("compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualFakeOverride/delegation.kt");
}
@Test
@TestMetadata("delegation2.kt")
public void testDelegation2() throws Exception {
runTest("compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualFakeOverride/delegation2.kt");
}
@Test
@TestMetadata("disabledFeature.kt")
public void testDisabledFeature() throws Exception {
@@ -142,7 +142,6 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirMultipleDefaultsInheritedFromSupertypesChecker,
FirFiniteBoundRestrictionChecker,
FirNonExpansiveInheritanceRestrictionChecker,
FirDefaultArgumentsInExpectActualizedByFakeOverrideChecker,
)
override val constructorCheckers: Set<FirConstructorChecker>
@@ -1,89 +0,0 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirExpectActualMatchingContext
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.getSingleMatchedExpectForActualOrNull
import org.jetbrains.kotlin.fir.declarations.utils.isActual
import org.jetbrains.kotlin.fir.expectActualMatchingContextFactory
import org.jetbrains.kotlin.fir.isDelegated
import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.classId
import org.jetbrains.kotlin.mpp.DeclarationSymbolMarker
import org.jetbrains.kotlin.resolve.calls.mpp.AbstractExpectActualMatcher
import org.jetbrains.kotlin.resolve.multiplatform.ExpectActualMatchingCompatibility
// TODO KT-62913 create one more ExpectActualCheckingCompatibility incompatibility, and replace this checker with this incompatibility
internal object FirDefaultArgumentsInExpectActualizedByFakeOverrideChecker : FirRegularClassChecker() {
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
if (!context.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects) ||
!context.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitDefaultArgumentsInExpectActualizedByFakeOverride)) {
return
}
if (!declaration.isActual) {
return
}
val actualClassSymbol = declaration.symbol
// We want to report errors even if a candidate is incompatible, but it's single
val expectedSingleCandidate = actualClassSymbol.getSingleMatchedExpectForActualOrNull() ?: return
val expectClassSymbol = expectedSingleCandidate as FirRegularClassSymbol
val expectActualMatchingContext = context.session.expectActualMatchingContextFactory.create(
context.session, context.scopeSession,
allowedWritingMemberExpectForActualMapping = true,
)
AbstractExpectActualMatcher.recursivelyMatchClassScopes(expectClassSymbol, actualClassSymbol, expectActualMatchingContext)
val matchingContext = context.session.expectActualMatchingContextFactory.create(context.session, context.scopeSession)
val problematicExpectMembers = with(matchingContext) { findProblematicExpectMembers(expectClassSymbol, actualClassSymbol) }
if (problematicExpectMembers.isNotEmpty()) {
reporter.reportOn(
declaration.source, FirErrors.DEFAULT_ARGUMENTS_IN_EXPECT_ACTUALIZED_BY_FAKE_OVERRIDE,
expectClassSymbol, problematicExpectMembers, context
)
}
}
private fun FirExpectActualMatchingContext.findProblematicExpectMembers(
expectClassSymbol: FirRegularClassSymbol, actualClassSymbol: FirRegularClassSymbol,
): List<FirNamedFunctionSymbol> {
val actualFakeOverrideMembers = actualClassSymbol.collectAllMembers(isActualDeclaration = true)
.filterIsInstance<FirNamedFunctionSymbol>()
.filter { it.isFakeOverride(actualClassSymbol) || it.isDelegated }
return actualFakeOverrideMembers
.mapNotNull { getSingleMatchingExpect(it, expectClassSymbol, actualClassSymbol) }
.filter(::hasDefaultArgumentValues)
}
private fun FirExpectActualMatchingContext.getSingleMatchingExpect(
actualMember: FirNamedFunctionSymbol,
expectSymbol: FirRegularClassSymbol,
actualSymbol: FirRegularClassSymbol
): FirNamedFunctionSymbol? {
val potentialExpects = findPotentialExpectClassMembersForActual(
expectSymbol, actualSymbol, actualMember,
)
val expectMember: DeclarationSymbolMarker = potentialExpects.entries
.singleOrNull { it.value == ExpectActualMatchingCompatibility.MatchedSuccessfully }?.key
?: potentialExpects.keys.singleOrNull()
?: return null
return expectMember as FirNamedFunctionSymbol
}
private fun hasDefaultArgumentValues(function: FirFunctionSymbol<*>): Boolean {
return function.valueParameterSymbols.any { it.hasDefaultValue }
}
}
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
@@ -25,10 +26,7 @@ import org.jetbrains.kotlin.fir.expectActualMatchingContextFactory
import org.jetbrains.kotlin.fir.languageVersionSettings
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.mpp.RegularClassSymbolMarker
import org.jetbrains.kotlin.name.StandardClassIds
@@ -157,7 +155,7 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() {
when {
checkingCompatibility is ExpectActualCheckingCompatibility.ClassScopes -> {
require(symbol is FirRegularClassSymbol || symbol is FirTypeAliasSymbol) {
require((symbol is FirRegularClassSymbol || symbol is FirTypeAliasSymbol) && expectedSingleCandidate is FirRegularClassSymbol) {
"Incompatible.ClassScopes is only possible for a class or a typealias: $declaration"
}
@@ -178,10 +176,35 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() {
val nonTrivialIncompatibleMembers = checkingCompatibility.incompatibleMembers.filterNot(::hasSingleActualSuspect)
if (nonTrivialIncompatibleMembers.isNotEmpty()) {
reporter.reportOn(source, FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, symbol, nonTrivialIncompatibleMembers, context)
val (defaultArgsIncompatibleMembers, otherIncompatibleMembers) =
nonTrivialIncompatibleMembers.partition { it.second.contains(ExpectActualCheckingCompatibility.DefaultArgumentsInExpectActualizedByFakeOverride) }
if (defaultArgsIncompatibleMembers.isNotEmpty()) { // report a nicer diagnostic for DefaultArgumentsInExpectActualizedByFakeOverride
val problematicExpectMembers = defaultArgsIncompatibleMembers
.map {
it.first as? FirNamedFunctionSymbol
?: error("${ExpectActualCheckingCompatibility.DefaultArgumentsInExpectActualizedByFakeOverride} can be reported only for ${FirNamedFunctionSymbol::class}")
}
reporter.reportOn(
source,
FirErrors.DEFAULT_ARGUMENTS_IN_EXPECT_ACTUALIZED_BY_FAKE_OVERRIDE,
expectedSingleCandidate,
problematicExpectMembers,
context
)
}
if (otherIncompatibleMembers.isNotEmpty()) {
reporter.reportOn(source, FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, symbol, otherIncompatibleMembers, context)
}
}
if (checkingCompatibility.mismatchedMembers.isNotEmpty()) {
reporter.reportOn(source, FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, symbol, checkingCompatibility.mismatchedMembers, context)
reporter.reportOn(
source,
FirErrors.NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS,
symbol,
checkingCompatibility.mismatchedMembers,
context
)
}
}
@@ -43,7 +43,7 @@ class FirExpectActualMatchingContextImpl private constructor(
private val actualScopeSession: ScopeSession,
private val allowedWritingMemberExpectForActualMapping: Boolean,
) : FirExpectActualMatchingContext, TypeSystemContext by actualSession.typeContext {
override val shouldCheckAbsenceOfDefaultParamsInActual: Boolean
override val shouldCheckDefaultParams: Boolean
get() = true
override val allowClassActualizationWithWiderVisibility: Boolean
@@ -257,7 +257,7 @@ class FirExpectActualMatchingContextImpl private constructor(
override val CallableSymbolMarker.typeParameters: List<TypeParameterSymbolMarker>
get() = asSymbol().typeParameterSymbols
override fun FunctionSymbolMarker.allOverriddenDeclarationsRecursive(): Sequence<CallableSymbolMarker> {
override fun FunctionSymbolMarker.allRecursivelyOverriddenDeclarationsIncludingSelf(): Sequence<CallableSymbolMarker> {
return when (val symbol = asSymbol()) {
is FirConstructorSymbol, is FirFunctionWithoutNameSymbol -> sequenceOf(this)
is FirNamedFunctionSymbol -> {
@@ -285,6 +285,9 @@ class FirExpectActualMatchingContextImpl private constructor(
override val ValueParameterSymbolMarker.hasDefaultValue: Boolean
get() = asSymbol().hasDefaultValue
override val ValueParameterSymbolMarker.hasDefaultValueNonRecursive: Boolean
get() = asSymbol().hasDefaultValue
override fun CallableSymbolMarker.isAnnotationConstructor(): Boolean {
val symbol = asSymbol()
return symbol.isAnnotationConstructor(symbol.moduleData.session)
@@ -375,6 +378,9 @@ class FirExpectActualMatchingContextImpl private constructor(
return symbol.isSubstitutionOrIntersectionOverride
}
override val CallableSymbolMarker.isDelegatedMember: Boolean
get() = asSymbol().isDelegated
override val CallableSymbolMarker.hasStableParameterNames: Boolean
get() = asSymbol().rawStatus.hasStableParameterNames