From d39755b578c9ad60ec9922e79df169319d2620af Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Wed, 28 Jun 2023 16:42:26 +0200 Subject: [PATCH] [FE] Convert specific diagnostic for actual function with default arguments into a common incompatibility ^KT-59665 Fixed Review: https://jetbrains.team/p/kt/reviews/11039/timeline It's better to have this logic in common place (AbstractExpectActualCompatibilityChecker) to avoid missing compilation errors in the future This commit fixes: 1. Missing compilation error for actual function with default arguments for 'actual typealias' KT-59665 2. Missing compilation error for actual function with default arguments for actual fake-override KT-59665 Alternative solution for KT-59665 is to create a special checker. "incompatibility" vs "special checker": Arguments for common incompatibility: - What if we had a rule that expect and actual default params must match? If so then it certainly would be an incompatibility. - Technically, we do the matching of expect and actual params (because we allow default params in common ancestors of expect and actual declarations). - It's hard to check that the actual definition doesn't use default params because `ExpectedActualResolver.findActualForExpected` filters out fake-overrides and doesn't return them. It's not clear logic for me, that I'm afraid to touch. implicitActualFakeOverride_AbstractMap.kt test breaks if you drop this weird logic - WEAK incompatibilities can be considered as "checkers". So it doesn't matter how it's implemented, as a "incompatibility" or a "checker" Arguments against common incompatibility: - Although we match expect and actual declarations to allow default params in common ancestors of expect and actual declarations, it's still can be considered that we check that the actual declaration doesn't have default params. And it doesn't feel right that we check correctness of the actual declaration in expect-actual matcher. - ~~It may change the rules of expect actual matching~~ (It's not true, because ActualFunctionWithDefaultParameters is declared as WEAK incompatibility) --- .../api/fir/diagnostics/KtFirDiagnostics.kt | 2 +- .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 2 +- ...DiagnosticsWithLightTreeTestGenerated.java | 54 +++++++++++++++++++ ...endMPPDiagnosticsWithPsiTestGenerated.java | 54 +++++++++++++++++++ .../diagnostics/FirDiagnosticsList.kt | 2 +- .../model/RegularDiagnosticData.kt | 1 + .../fir/analysis/diagnostics/FirErrors.kt | 2 +- .../FirActualCallableDeclarationChecker.kt | 12 ----- .../FirExpectActualDeclarationChecker.kt | 6 ++- .../mpp/FirExpectActualMatchingContextImpl.kt | 26 +++++++-- .../LightTreePositioningStrategies.kt | 24 +++++++++ .../diagnostics/PositioningStrategies.kt | 9 ++++ .../SourceElementPositioningStrategies.kt | 5 ++ .../ClassicPositioningStrategies.kt | 1 + .../jetbrains/kotlin/diagnostics/Errors.java | 2 +- .../kotlin/resolve/DeclarationsChecker.kt | 23 -------- .../ExpectedActualDeclarationChecker.kt | 10 +++- .../IrExpectActualMatchingContext.kt | 9 ++++ ...bstractExpectActualCompatibilityChecker.kt | 16 ++++++ .../calls/mpp/ExpectActualMatchingContext.kt | 8 +++ .../ClassicExpectActualMatchingContext.kt | 20 +++++-- .../multiplatform/ExpectedActualResolver.kt | 11 ++-- .../bothInExpectAndActual2.kt | 1 + .../bothInExpectAndActual2.kt | 1 + ...keOverride_defaultParamsIncompatibility.kt | 16 ++++++ ...patibleViaSharedMethodWithDefaultParams.kt | 12 +++++ ...TransitiveSharedMethodWithDefaultParams.kt | 14 +++++ ...alFakeOverride_substitutionFakeOverride.kt | 20 +++++++ ...ride_transitiveFakeOverrides_compatible.kt | 18 +++++++ ...de_transitiveFakeOverrides_incompatible.kt | 18 +++++++ ...lTypealias_defaultParamsIncompatibility.kt | 15 ++++++ .../tests/multiplatform/arraySort.kt | 13 +++++ .../tests/multiplatform/arraySortFixed.kt | 13 +++++ ...onstructorDefaultArgsViaActualTypealias.kt | 2 +- ...methodDefaultArgsViaActualTypealias.fir.kt | 4 +- .../methodDefaultArgsViaActualTypealias.kt | 4 +- .../test/runners/DiagnosticTestGenerated.java | 54 +++++++++++++++++++ .../ExpectActualCompatibility.kt | 2 + libraries/stdlib/js/src/kotlin/text/regex.kt | 1 + .../stdlib/jvm/src/kotlin/text/regex/Regex.kt | 1 + .../native-wasm/src/kotlin/text/Regex.kt | 1 + 41 files changed, 450 insertions(+), 59 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/arraySort.kt create mode 100644 compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt index 423a7307a84..0c5de6e4fba 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt @@ -2459,7 +2459,7 @@ sealed interface KtFirDiagnostic : KtDiagnosticWithPsi { override val diagnosticClass get() = ActualTypeAliasToNothing::class } - interface ActualFunctionWithDefaultArguments : KtFirDiagnostic { + interface ActualFunctionWithDefaultArguments : KtFirDiagnostic { override val diagnosticClass get() = ActualFunctionWithDefaultArguments::class } diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index dd9e3218082..d3dc377d163 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -2968,7 +2968,7 @@ internal class ActualTypeAliasToNothingImpl( internal class ActualFunctionWithDefaultArgumentsImpl( firDiagnostic: KtPsiDiagnostic, token: KtLifetimeToken, -) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.ActualFunctionWithDefaultArguments +) : KtAbstractFirDiagnostic(firDiagnostic, token), KtFirDiagnostic.ActualFunctionWithDefaultArguments internal class DefaultArgumentsInExpectWithActualTypealiasImpl( override val expectClassSymbol: KtClassLikeSymbol, diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java index d0c180a7ddc..d701ec40417 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated.java @@ -32,17 +32,71 @@ public class FirOldFrontendMPPDiagnosticsWithLightTreeTestGenerated extends Abst runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverrideVirtual.kt"); } + @Test + @TestMetadata("actualFakeOverride_defaultParamsIncompatibility.kt") + public void testActualFakeOverride_defaultParamsIncompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt") + public void testActualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt") + public void testActualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_substitutionFakeOverride.kt") + public void testActualFakeOverride_substitutionFakeOverride() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_transitiveFakeOverrides_compatible.kt") + public void testActualFakeOverride_transitiveFakeOverrides_compatible() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_transitiveFakeOverrides_incompatible.kt") + public void testActualFakeOverride_transitiveFakeOverrides_incompatible() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt"); + } + @Test @TestMetadata("actualTypealiasToSpecialAnnotation.kt") public void testActualTypealiasToSpecialAnnotation() throws Exception { runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealiasToSpecialAnnotation.kt"); } + @Test + @TestMetadata("actualTypealias_defaultParamsIncompatibility.kt") + public void testActualTypealias_defaultParamsIncompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt"); + } + @Test public void testAllFilesPresentInMultiplatform() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true); } + @Test + @TestMetadata("arraySort.kt") + public void testArraySort() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/arraySort.kt"); + } + + @Test + @TestMetadata("arraySortFixed.kt") + public void testArraySortFixed() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt"); + } + @Test @TestMetadata("checkNoActualForExpectInLastModule.kt") public void testCheckNoActualForExpectInLastModule() throws Exception { diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java index 8302879d1c2..062083fa793 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/FirOldFrontendMPPDiagnosticsWithPsiTestGenerated.java @@ -32,17 +32,71 @@ public class FirOldFrontendMPPDiagnosticsWithPsiTestGenerated extends AbstractFi runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverrideVirtual.kt"); } + @Test + @TestMetadata("actualFakeOverride_defaultParamsIncompatibility.kt") + public void testActualFakeOverride_defaultParamsIncompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt") + public void testActualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt") + public void testActualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_substitutionFakeOverride.kt") + public void testActualFakeOverride_substitutionFakeOverride() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_transitiveFakeOverrides_compatible.kt") + public void testActualFakeOverride_transitiveFakeOverrides_compatible() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_transitiveFakeOverrides_incompatible.kt") + public void testActualFakeOverride_transitiveFakeOverrides_incompatible() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt"); + } + @Test @TestMetadata("actualTypealiasToSpecialAnnotation.kt") public void testActualTypealiasToSpecialAnnotation() throws Exception { runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealiasToSpecialAnnotation.kt"); } + @Test + @TestMetadata("actualTypealias_defaultParamsIncompatibility.kt") + public void testActualTypealias_defaultParamsIncompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt"); + } + @Test public void testAllFilesPresentInMultiplatform() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), TargetBackend.JVM_IR, true); } + @Test + @TestMetadata("arraySort.kt") + public void testArraySort() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/arraySort.kt"); + } + + @Test + @TestMetadata("arraySortFixed.kt") + public void testArraySortFixed() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt"); + } + @Test @TestMetadata("checkNoActualForExpectInLastModule.kt") public void testCheckNoActualForExpectInLastModule() throws Exception { diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 1b4fe5a3cbe..afe990fb787 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -1184,7 +1184,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") { val ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION by error(PositioningStrategy.DECLARATION_SIGNATURE) val ACTUAL_TYPE_ALIAS_TO_NULLABLE_TYPE by error(PositioningStrategy.DECLARATION_SIGNATURE) val ACTUAL_TYPE_ALIAS_TO_NOTHING by error(PositioningStrategy.DECLARATION_SIGNATURE) - val ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS by error() + val ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS by error(PositioningStrategy.PARAMETERS_WITH_DEFAULT_VALUE) val DEFAULT_ARGUMENTS_IN_EXPECT_WITH_ACTUAL_TYPEALIAS by error { parameter>("expectClassSymbol") parameter>>("members") diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt index e7713d947c6..f27155a6c6c 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/model/RegularDiagnosticData.kt @@ -52,6 +52,7 @@ enum class PositioningStrategy(private val strategy: String? = null) { MODALITY_MODIFIER, OPERATOR, PARAMETER_DEFAULT_VALUE, + PARAMETERS_WITH_DEFAULT_VALUE, PARAMETER_VARARG_MODIFIER, DECLARATION_RETURN_TYPE, OVERRIDE_MODIFIER, diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index fc2236ab663..04ac8d125b8 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -635,7 +635,7 @@ object FirErrors { val ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION by error0(SourceElementPositioningStrategies.DECLARATION_SIGNATURE) val ACTUAL_TYPE_ALIAS_TO_NULLABLE_TYPE by error0(SourceElementPositioningStrategies.DECLARATION_SIGNATURE) val ACTUAL_TYPE_ALIAS_TO_NOTHING by error0(SourceElementPositioningStrategies.DECLARATION_SIGNATURE) - val ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS by error0() + val ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS by error0(SourceElementPositioningStrategies.PARAMETERS_WITH_DEFAULT_VALUE) val DEFAULT_ARGUMENTS_IN_EXPECT_WITH_ACTUAL_TYPEALIAS by error2, Collection>>() val ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE by error1>() val EXPECTED_FUNCTION_SOURCE_WITH_DEFAULT_ARGUMENTS_NOT_FOUND by error0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualCallableDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualCallableDeclarationChecker.kt index c9f664ed417..32dc97090a1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualCallableDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirActualCallableDeclarationChecker.kt @@ -22,21 +22,9 @@ object FirActualCallableDeclarationChecker : FirCallableDeclarationChecker() { override fun check(declaration: FirCallableDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { if (!declaration.isActual) return - if (declaration is FirFunction) { - checkActualFunctionWithDefaultArguments(declaration, reporter, context) - } checkReturnTypes(declaration, context, reporter) } - private fun checkActualFunctionWithDefaultArguments(function: FirFunction, reporter: DiagnosticReporter, context: CheckerContext) { - if (function.symbol.isAnnotationConstructor(context.session)) return - for (valueParameter in function.valueParameters) { - if (valueParameter.defaultValue != null) { - reporter.reportOn(valueParameter.source, FirErrors.ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS, context) - } - } - } - private fun checkReturnTypes(callableDeclaration: FirCallableDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { val actualFunctionSymbol = callableDeclaration.symbol val expectFunctionSymbol = actualFunctionSymbol.getSingleExpectForActualOrNull() as? FirCallableSymbol ?: return diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt index 72c9fe9d9f9..209dcdaf03f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirExpectActualDeclarationChecker.kt @@ -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 @@ -168,7 +169,10 @@ object FirExpectActualDeclarationChecker : FirBasicDeclarationChecker() { } Compatible !in compatibilityToMembersMap -> { - if (requireActualModifier(declaration.symbol, context.session)) { + // A nicer diagnostic for functions with default params + if (declaration is FirFunction && compatibilityToMembersMap.keys.any { it is Incompatible.ActualFunctionWithDefaultParameters }) { + reporter.reportOn(declaration.source, FirErrors.ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS, context) + } else if (requireActualModifier(declaration.symbol, context.session)) { reporter.reportOn( source, FirErrors.ACTUAL_WITHOUT_EXPECT, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt index 0b7adc18ed3..485004ef7b1 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/mpp/FirExpectActualMatchingContextImpl.kt @@ -8,15 +8,13 @@ package org.jetbrains.kotlin.fir.resolve.transformers.mpp import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Visibility -import org.jetbrains.kotlin.fir.FirExpectActualMatchingContext -import org.jetbrains.kotlin.fir.FirExpectActualMatchingContextFactory -import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.getRetention import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirAnnotation import org.jetbrains.kotlin.fir.expressions.FirConstExpression import org.jetbrains.kotlin.fir.expressions.FirExpression -import org.jetbrains.kotlin.fir.isSubstitutionOrIntersectionOverride +import org.jetbrains.kotlin.fir.declarations.utils.isOverride import org.jetbrains.kotlin.fir.resolve.* import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor import org.jetbrains.kotlin.fir.scopes.* @@ -47,6 +45,9 @@ class FirExpectActualMatchingContextImpl private constructor( override val shouldCheckReturnTypesOfCallables: Boolean get() = false + override val shouldCheckAbsenceOfDefaultParamsInActual: Boolean + get() = true + override val enumConstructorsAreAlwaysCompatible: Boolean get() = true @@ -243,6 +244,23 @@ class FirExpectActualMatchingContextImpl private constructor( get() = asSymbol().resolvedReturnType.type override val CallableSymbolMarker.typeParameters: List get() = asSymbol().typeParameterSymbols + + override fun FunctionSymbolMarker.allOverriddenDeclarationsRecursive(): Sequence { + return when (val symbol = asSymbol()) { + is FirConstructorSymbol, is FirFunctionWithoutNameSymbol -> sequenceOf(this) + is FirNamedFunctionSymbol -> { + val session = symbol.moduleData.session + val containingClass = symbol.containingClassLookupTag()?.toFirRegularClassSymbol(session) + ?: return sequenceOf(symbol) + (sequenceOf(symbol) + symbol.overriddenFunctions(containingClass, session, scopeSession).asSequence()) + // Tests work even if you don't filter out fake-overrides. Filtering fake-overrides is needed because + // the returned descriptors are compared by `equals`. And `equals` for fake-overrides is weird. + // I didn't manage to invent a test that would check this condition + .filter { !it.isSubstitutionOrIntersectionOverride } + } + } + } + override val FunctionSymbolMarker.valueParameters: List get() = asSymbol().valueParameterSymbols diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt index 035b21709e9..cc51a5f2184 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/LightTreePositioningStrategies.kt @@ -533,6 +533,21 @@ object LightTreePositioningStrategies { } } + val PARAMETERS_WITH_DEFAULT_VALUE: LightTreePositioningStrategy = object : LightTreePositioningStrategy() { + override fun mark( + node: LighterASTNode, + startOffset: Int, + endOffset: Int, + tree: FlyweightCapableTreeStructure + ): List { + val newNodes = tree.valueParameters(node).filter { tree.defaultValue(it) != null }.takeIf(List<*>::isNotEmpty) + ?: tree.valueParameterList(node)?.let(::listOf) + ?: tree.nameIdentifier(node)?.let(::listOf) + ?: listOf(node) + return newNodes.flatMap { markElement(it, startOffset, endOffset, tree, node) } + } + } + val PARAMETER_VARARG_MODIFIER: LightTreePositioningStrategy = object : LightTreePositioningStrategy() { override fun mark( node: LighterASTNode, @@ -1401,6 +1416,9 @@ private fun FlyweightCapableTreeStructure.primaryConstructor(nod private fun FlyweightCapableTreeStructure.valueParameterList(node: LighterASTNode): LighterASTNode? = findChildByType(node, KtNodeTypes.VALUE_PARAMETER_LIST) +private fun FlyweightCapableTreeStructure.valueParameters(node: LighterASTNode): List = + valueParameterList(node)?.let { findChildrenByType(it, KtNodeTypes.VALUE_PARAMETER) }.orEmpty() + private fun FlyweightCapableTreeStructure.typeReference(node: LighterASTNode): LighterASTNode? { val childrenRef = Ref>() getChildren(node, childrenRef) @@ -1490,6 +1508,12 @@ fun FlyweightCapableTreeStructure.findChildByType(node: LighterA return childrenRef.get()?.firstOrNull { it?.tokenType == type } } +fun FlyweightCapableTreeStructure.findChildrenByType(node: LighterASTNode, type: IElementType): List { + val childrenRef = Ref>() + getChildren(node, childrenRef) + return childrenRef.get()?.filter { it?.tokenType == type }?.filterNotNull().orEmpty() +} + fun FlyweightCapableTreeStructure.findLastChildByType(node: LighterASTNode, type: IElementType): LighterASTNode? { val childrenRef = Ref>() getChildren(node, childrenRef) diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt index 991ec4fbc0c..bd891ebc020 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/PositioningStrategies.kt @@ -510,6 +510,15 @@ object PositioningStrategies { } } + @JvmField + val PARAMETERS_WITH_DEFAULT_VALUE: PositioningStrategy = object : PositioningStrategy() { + override fun mark(element: KtFunction): List = + element.valueParameters.filter(KtParameter::hasDefaultValue).takeIf(List<*>::isNotEmpty)?.flatMap { markNode(it.node) } + ?: element.valueParameterList?.let { markNode(it.node) } + ?: element.nameIdentifier?.let { markNode(it.node) } + ?: markNode(element.node) + } + @JvmField val PARAMETER_VARARG_MODIFIER: PositioningStrategy = object : PositioningStrategy() { override fun mark(element: KtParameter): List { diff --git a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt index a41fe84e286..79f0ac553e9 100644 --- a/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt +++ b/compiler/frontend.common-psi/src/org/jetbrains/kotlin/diagnostics/SourceElementPositioningStrategies.kt @@ -148,6 +148,11 @@ object SourceElementPositioningStrategies { PositioningStrategies.PARAMETER_DEFAULT_VALUE ) + val PARAMETERS_WITH_DEFAULT_VALUE = SourceElementPositioningStrategy( + LightTreePositioningStrategies.PARAMETERS_WITH_DEFAULT_VALUE, + PositioningStrategies.PARAMETERS_WITH_DEFAULT_VALUE + ) + val PARAMETER_VARARG_MODIFIER = SourceElementPositioningStrategy( LightTreePositioningStrategies.PARAMETER_VARARG_MODIFIER, PositioningStrategies.PARAMETER_VARARG_MODIFIER diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt index 54b00894f8f..0e82e3c391b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/ClassicPositioningStrategies.kt @@ -79,6 +79,7 @@ object ClassicPositioningStrategies { } ExpectActualCompatibility.Incompatible.ParameterCount, ExpectActualCompatibility.Incompatible.ParameterTypes, ExpectActualCompatibility.Incompatible.ParameterNames, ExpectActualCompatibility.Incompatible.ValueParameterVararg, + ExpectActualCompatibility.Incompatible.ActualFunctionWithDefaultParameters, ExpectActualCompatibility.Incompatible.ValueParameterNoinline, ExpectActualCompatibility.Incompatible.ValueParameterCrossinline -> { callableDeclaration?.valueParameterList diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index 7ba774eaedf..1bafa971057 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -811,7 +811,7 @@ public interface Errors { DiagnosticFactory0 ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); DiagnosticFactory0 ACTUAL_TYPE_ALIAS_TO_NULLABLE_TYPE = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); DiagnosticFactory0 ACTUAL_TYPE_ALIAS_TO_NOTHING = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE); - DiagnosticFactory0 ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS = DiagnosticFactory0.create(ERROR); + DiagnosticFactory0 ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS = DiagnosticFactory0.create(ERROR, PARAMETERS_WITH_DEFAULT_VALUE); DiagnosticFactory2> DEFAULT_ARGUMENTS_IN_EXPECT_WITH_ACTUAL_TYPEALIAS = DiagnosticFactory2.create(ERROR); DiagnosticFactory1 ACTUAL_ANNOTATION_CONFLICTING_DEFAULT_ARGUMENT_VALUE = DiagnosticFactory1.create(ERROR); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt index b92395f4177..5f5abdeecfa 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/DeclarationsChecker.kt @@ -261,10 +261,6 @@ class DeclarationsChecker( checkVarargParameters(trace, constructorDescriptor) checkConstructorVisibility(constructorDescriptor, declaration) checkExpectedClassConstructor(constructorDescriptor, declaration) - - if (constructorDescriptor.isActual) { - checkActualFunction(declaration, constructorDescriptor) - } } private fun checkExpectedClassConstructor(constructorDescriptor: ClassConstructorDescriptor, declaration: KtConstructor<*>) { @@ -938,9 +934,6 @@ class DeclarationsChecker( if (functionDescriptor.isExpect) { checkExpectedFunction(function, functionDescriptor) } - if (functionDescriptor.isActual) { - checkActualFunction(function, functionDescriptor) - } shadowedExtensionChecker.checkDeclaration(function, functionDescriptor) } @@ -953,22 +946,6 @@ class DeclarationsChecker( checkExpectDeclarationModifiers(function, functionDescriptor) } - private fun checkActualFunction(element: KtDeclaration, functionDescriptor: FunctionDescriptor) { - // Actual annotation constructors can have default argument values; their consistency with arguments in the expected annotation - // is checked in ExpectedActualDeclarationChecker.checkAnnotationConstructors - if (!functionDescriptor.isAnnotationConstructor()) { - for (valueParameter in functionDescriptor.valueParameters) { - if (valueParameter.declaresDefaultValue()) { - trace.report( - ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS.on( - DescriptorToSourceUtils.descriptorToDeclaration(valueParameter) ?: element - ) - ) - } - } - } - } - private fun checkImplicitCallableType(declaration: KtCallableDeclaration, descriptor: CallableDescriptor) { descriptor.returnType?.unwrap()?.let { val target = declaration.nameIdentifier ?: declaration diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt index 6fe90bc1846..2290d658d4e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -297,7 +297,8 @@ class ExpectedActualDeclarationChecker( trace: BindingTrace, moduleVisibilityFilter: ModuleFilter ) { - val compatibility = ExpectedActualResolver.findExpectedForActual(descriptor, moduleVisibilityFilter) + val compatibility = ExpectedActualResolver + .findExpectedForActual(descriptor, moduleVisibilityFilter, shouldCheckAbsenceOfDefaultParamsInActual = true) ?: return checkAmbiguousExpects(compatibility, trace, reportOn, descriptor) @@ -357,7 +358,12 @@ class ExpectedActualDeclarationChecker( assert(compatibility.keys.all { it is Incompatible }) @Suppress("UNCHECKED_CAST") val incompatibility = compatibility as Map, Collection> - trace.report(Errors.ACTUAL_WITHOUT_EXPECT.on(reportOn, descriptor, incompatibility)) + // A nicer diagnostic for functions with default params + if (reportOn is KtFunction && incompatibility.keys.any { it is Incompatible.ActualFunctionWithDefaultParameters }) { + trace.report(Errors.ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS.on(reportOn)) + } else { + trace.report(Errors.ACTUAL_WITHOUT_EXPECT.on(reportOn, descriptor, incompatibility)) + } } else { val expected = compatibility[Compatible]!!.first() if (expected is ClassDescriptor && expected.kind == ClassKind.ANNOTATION_CLASS) { diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt index 8ac7f101622..d14313fdeaa 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/actualizer/IrExpectActualMatchingContext.kt @@ -49,6 +49,12 @@ internal abstract class IrExpectActualMatchingContext( override val allowTransitiveSupertypesActualization: Boolean get() = true + // This incompatibility is often suppressed in the source code (e.g. in kotlin-stdlib). + // The backend must be able to do expect-actual matching to emit bytecode + // That's why we disable the checker here. Probably, this checker can be enabled once KT-60426 is fixed + override val shouldCheckAbsenceOfDefaultParamsInActual: Boolean + get() = false + private inline fun CallableSymbolMarker.processIr( onFunction: (IrFunction) -> R, onProperty: (IrProperty) -> R, @@ -285,6 +291,9 @@ internal abstract class IrExpectActualMatchingContext( onEnumEntry = { emptyList() } ) + override fun FunctionSymbolMarker.allOverriddenDeclarationsRecursive(): Sequence = + throw NotImplementedError("Not implemented because it's unused") + override val FunctionSymbolMarker.valueParameters: List get() = asIr().valueParameters.map { it.symbol } diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt index 1a00e87e4a1..f1a9e869e45 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/AbstractExpectActualCompatibilityChecker.kt @@ -376,6 +376,22 @@ object AbstractExpectActualCompatibilityChecker { } } + if (shouldCheckAbsenceOfDefaultParamsInActual) { + // "Default parameters in actual" check is required only for functions, because only functions can have parameters + if (actualDeclaration is FunctionSymbolMarker && expectDeclaration is FunctionSymbolMarker) { + // Actual annotation constructors can have default argument values; their consistency with arguments in the expected annotation + // is checked in ExpectedActualDeclarationChecker.checkAnnotationConstructors + if (!actualDeclaration.isAnnotationConstructor() && + // If default params came from common supertypes of actual class and expect class then it's a valid code. + // Here we filter out such default params. + (actualDeclaration.allOverriddenDeclarationsRecursive() - expectDeclaration.allOverriddenDeclarationsRecursive().toSet()) + .flatMap { it.valueParameters }.any { it.hasDefaultValue } + ) { + return Incompatible.ActualFunctionWithDefaultParameters + } + } + } + if (!equalsBy(expectedValueParameters, actualValueParameters) { it.isVararg }) { return Incompatible.ValueParameterVararg } diff --git a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt index 6511221d01c..9935d2e57eb 100644 --- a/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt +++ b/compiler/resolution.common/src/org/jetbrains/kotlin/resolve/calls/mpp/ExpectActualMatchingContext.kt @@ -39,6 +39,9 @@ interface ExpectActualMatchingContext : TypeSystemC val enumConstructorsAreAlwaysCompatible: Boolean get() = false + // Try to drop it once KT-61105 is fixed + val shouldCheckAbsenceOfDefaultParamsInActual: Boolean + /** * This flag determines, how visibilities for classes/typealiases will be matched * - `false` means that visibilities should be identical @@ -115,6 +118,11 @@ interface ExpectActualMatchingContext : TypeSystemC val CallableSymbolMarker.typeParameters: List val FunctionSymbolMarker.valueParameters: List + /** + * Returns all symbols that are overridden by [this] symbol + */ + fun FunctionSymbolMarker.allOverriddenDeclarationsRecursive(): Sequence + val CallableSymbolMarker.valueParameters: List get() = (this as? FunctionSymbolMarker)?.valueParameters ?: emptyList() diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ClassicExpectActualMatchingContext.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ClassicExpectActualMatchingContext.kt index f66fdfc15a5..815ae891b53 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ClassicExpectActualMatchingContext.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ClassicExpectActualMatchingContext.kt @@ -19,6 +19,7 @@ import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext import org.jetbrains.kotlin.resolve.calls.mpp.ExpectActualMatchingContext.AnnotationCallInfo import org.jetbrains.kotlin.resolve.checkers.OptInNames import org.jetbrains.kotlin.resolve.descriptorUtil.* +import org.jetbrains.kotlin.resolve.findTopMostOverriddenDescriptors import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered @@ -34,9 +35,16 @@ import org.jetbrains.kotlin.utils.addToStdlib.castAll import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled import org.jetbrains.kotlin.utils.keysToMap -class ClassicExpectActualMatchingContext(val platformModule: ModuleDescriptor) : ExpectActualMatchingContext, - TypeSystemContext by ClassicTypeSystemContextForCS(platformModule.builtIns, KotlinTypeRefiner.Default) -{ +class ClassicExpectActualMatchingContext( + val platformModule: ModuleDescriptor, + /** + * You want to enable this check only in expect-actual matcher checker. And disable everywhere else (especially on backends) + * + * Otherwise, it won't be possible to suppress the compilation error in user code + */ + override val shouldCheckAbsenceOfDefaultParamsInActual: Boolean = false +) : ExpectActualMatchingContext, + TypeSystemContext by ClassicTypeSystemContextForCS(platformModule.builtIns, KotlinTypeRefiner.Default) { override val shouldCheckReturnTypesOfCallables: Boolean get() = true @@ -194,6 +202,12 @@ class ClassicExpectActualMatchingContext(val platformModule: ModuleDescriptor) : get() = asDescriptor().isCrossinline override val ValueParameterSymbolMarker.hasDefaultValue: Boolean get() = asDescriptor().declaresDefaultValue() + override fun FunctionSymbolMarker.allOverriddenDeclarationsRecursive(): Sequence = + (sequenceOf(asDescriptor()) + asDescriptor().overriddenTreeAsSequence(useOriginal = true)) + // Tests work even if you don't filter out fake-overrides. Filtering fake-overrides is needed because + // the returned descriptors are compared by `equals`. And `equals` for fake-overrides is weird. + // I didn't manage to invent a test that would check this condition + .filter { it.kind.isReal } override fun CallableSymbolMarker.isAnnotationConstructor(): Boolean { val descriptor = safeAsDescriptor() ?: return false diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt index 6a6712f01ea..d72f1c5d716 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt @@ -56,9 +56,10 @@ object ExpectedActualResolver { fun findExpectedForActual( actual: MemberDescriptor, - moduleFilter: (ModuleDescriptor) -> Boolean = allModulesProvidingExpectsFor(actual.module) + moduleFilter: (ModuleDescriptor) -> Boolean = allModulesProvidingExpectsFor(actual.module), + shouldCheckAbsenceOfDefaultParamsInActual: Boolean = false, ): Map, List>? { - val context = ClassicExpectActualMatchingContext(actual.module) + val context = ClassicExpectActualMatchingContext(actual.module, shouldCheckAbsenceOfDefaultParamsInActual) return when (actual) { is CallableMemberDescriptor -> { val container = actual.containingDeclaration @@ -66,7 +67,8 @@ object ExpectedActualResolver { is ClassifierDescriptorWithTypeParameters -> { // TODO: replace with 'singleOrNull' as soon as multi-module diagnostic tests are refactored val expectedClass = - findExpectedForActual(container, moduleFilter)?.values?.firstOrNull()?.firstOrNull() as? ClassDescriptor + findExpectedForActual(container, moduleFilter, shouldCheckAbsenceOfDefaultParamsInActual)?.values + ?.firstOrNull()?.firstOrNull() as? ClassDescriptor with(context) { expectedClass?.getMembersForExpectClass(actual.name)?.filterIsInstance().orEmpty() } @@ -160,11 +162,12 @@ object ExpectedActualResolver { } } -// FIXME(dsavvinov): review clients, as they won't work properly in HMPP projects +// FIXME(dsavvinov): review clients, as they won't work properly in HMPP projects. KT-61105 @JvmOverloads fun MemberDescriptor.findCompatibleActualsForExpected( platformModule: ModuleDescriptor, moduleFilter: ModuleFilter = allModulesProvidingActualsFor(module, platformModule) ): List = + // ?.get(Compatible) is suspicious. Probably, we must check not only Compatible but Incompatible.WeakIncompatible as well ExpectedActualResolver.findActualForExpected(this, platformModule, moduleFilter)?.get(Compatible).orEmpty() @JvmOverloads diff --git a/compiler/testData/codegen/box/multiplatform/defaultArguments/bothInExpectAndActual2.kt b/compiler/testData/codegen/box/multiplatform/defaultArguments/bothInExpectAndActual2.kt index 3a7cab2e617..e47648ec2d3 100644 --- a/compiler/testData/codegen/box/multiplatform/defaultArguments/bothInExpectAndActual2.kt +++ b/compiler/testData/codegen/box/multiplatform/defaultArguments/bothInExpectAndActual2.kt @@ -13,6 +13,7 @@ expect interface J : I // FILE: platform.kt +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") actual interface I { // This test should be updated once KT-22818 is fixed; default values are not allowed in the actual function @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") diff --git a/compiler/testData/codegen/box/multiplatform/k2/defaultArguments/bothInExpectAndActual2.kt b/compiler/testData/codegen/box/multiplatform/k2/defaultArguments/bothInExpectAndActual2.kt index e8e549a1785..127a13480d6 100644 --- a/compiler/testData/codegen/box/multiplatform/k2/defaultArguments/bothInExpectAndActual2.kt +++ b/compiler/testData/codegen/box/multiplatform/k2/defaultArguments/bothInExpectAndActual2.kt @@ -13,6 +13,7 @@ expect interface J : I // MODULE: platform()()(common) // FILE: platform.kt +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") actual interface I { // This test should be updated once KT-22818 is fixed; default values are not allowed in the actual function @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt new file mode 100644 index 00000000000..a0dd9bb0f13 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt @@ -0,0 +1,16 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt + +expect class A { + fun foo(a: Int): Int +} + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt + +interface FooProvider { + fun foo(a: Int = 2): Int = 42 +} + +actual class A : FooProvider diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt new file mode 100644 index 00000000000..102586f57f6 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt @@ -0,0 +1,12 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt +interface Shared { + fun sharedMethod(withDefaultParam: Int = 2) {} +} + +expect class Foo : Shared + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +actual class Foo : Shared diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt new file mode 100644 index 00000000000..0fa48b571e2 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt @@ -0,0 +1,14 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt +interface Shared { + fun sharedMethod(withDefaultParam: Int = 2) {} +} + +interface Transitive + +expect class Foo : Transitive + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +actual class Foo : Transitive diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt new file mode 100644 index 00000000000..81ada2a64d7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt @@ -0,0 +1,20 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt +expect class Foo { + fun foo(param: Int) +} + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +actual class Foo : A + +interface A : B { + override fun getDefault(): Int = 3 +} + +interface B { + fun foo(param: T = getDefault()) {} + + fun getDefault(): T +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt new file mode 100644 index 00000000000..0b10759869e --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt @@ -0,0 +1,18 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt +expect class Foo { + fun foo(param: Int) +} + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +actual class Foo : A + +interface A : B { + override fun foo(param: Int) {} +} + +interface B { + fun foo(param: Int) {} +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt new file mode 100644 index 00000000000..58d893c6d5b --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt @@ -0,0 +1,18 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt +expect class Foo { + fun foo(param: Int) +} + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +actual class Foo : A + +interface A : B { + override fun foo(param: Int) {} +} + +interface B { + fun foo(param: Int = 3) {} +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt b/compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt new file mode 100644 index 00000000000..0f8ebb0ce9b --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt @@ -0,0 +1,15 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt +expect class Foo { + fun foo(a: Int) +} + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +actual typealias Foo = FooImpl + +class FooImpl { + fun foo(a: Int = 2) { + } +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/arraySort.kt b/compiler/testData/diagnostics/tests/multiplatform/arraySort.kt new file mode 100644 index 00000000000..4417d5ae5ca --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/arraySort.kt @@ -0,0 +1,13 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt + +public expect fun > Array.sort(fromIndex: Int = 0, toIndex: Int = size): Unit + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +public actual fun > Array.sort(fromIndex: Int = 0, toIndex: Int = size): Unit { +} + +public fun Array.sort(fromIndex: Int = 0, toIndex: Int = size): Unit { +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt b/compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt new file mode 100644 index 00000000000..0d4d43f65ed --- /dev/null +++ b/compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt @@ -0,0 +1,13 @@ +// FIR_IDENTICAL +// MODULE: m1-common +// FILE: common.kt + +public expect fun > Array.sort(fromIndex: Int = 0, toIndex: Int = size): Unit + +// MODULE: m2-jvm()()(m1-common) +// FILE: jvm.kt +public actual fun > Array.sort(fromIndex: Int, toIndex: Int): Unit { +} + +public fun Array.sort(fromIndex: Int = 0, toIndex: Int = size): Unit { +} diff --git a/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/constructorDefaultArgsViaActualTypealias.kt b/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/constructorDefaultArgsViaActualTypealias.kt index aa1578c468b..375475c3b1f 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/constructorDefaultArgsViaActualTypealias.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/constructorDefaultArgsViaActualTypealias.kt @@ -7,4 +7,4 @@ expect class DefaultArgsInConstructor(p1: String = "common", p2: String = "commo // FILE: jvm.kt class DefaultArgsInConstructorImpl(p1: String = "common", p2: String = "common", p3: String) -actual typealias DefaultArgsInConstructor = DefaultArgsInConstructorImpl +actual typealias DefaultArgsInConstructor = DefaultArgsInConstructorImpl diff --git a/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.fir.kt b/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.fir.kt index f0471169b53..a1d13252c63 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.fir.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.fir.kt @@ -28,13 +28,13 @@ class AImpl { fun foo(p1: String = "impl", p2: String = "impl", p3: String) {} } -actual typealias A = AImpl +actual typealias A = AImpl class BImpl { fun foo(s: String = "impl") {} } -actual typealias B = BImpl +actual typealias B = BImpl class WithDefaultArgFromSuperImpl : I { override fun methodWithDefaultArg(s: String) {} diff --git a/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.kt b/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.kt index 2aff9ad6a38..5e46b410fcf 100644 --- a/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.kt +++ b/compiler/testData/diagnostics/tests/multiplatform/defaultArguments/methodDefaultArgsViaActualTypealias.kt @@ -28,13 +28,13 @@ class AImpl { fun foo(p1: String = "impl", p2: String = "impl", p3: String) {} } -actual typealias A = AImpl +actual typealias A = AImpl class BImpl { fun foo(s: String = "impl") {} } -actual typealias B = BImpl +actual typealias B = BImpl class WithDefaultArgFromSuperImpl : I { override fun methodWithDefaultArg(s: String) {} diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java index 8151a2f6a8c..9e311f34d97 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/DiagnosticTestGenerated.java @@ -22711,17 +22711,71 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest { runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverrideVirtual.kt"); } + @Test + @TestMetadata("actualFakeOverride_defaultParamsIncompatibility.kt") + public void testActualFakeOverride_defaultParamsIncompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_defaultParamsIncompatibility.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt") + public void testActualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaSharedMethodWithDefaultParams.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt") + public void testActualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_paramsAreCompatibleViaTransitiveSharedMethodWithDefaultParams.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_substitutionFakeOverride.kt") + public void testActualFakeOverride_substitutionFakeOverride() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_substitutionFakeOverride.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_transitiveFakeOverrides_compatible.kt") + public void testActualFakeOverride_transitiveFakeOverrides_compatible() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_compatible.kt"); + } + + @Test + @TestMetadata("actualFakeOverride_transitiveFakeOverrides_incompatible.kt") + public void testActualFakeOverride_transitiveFakeOverrides_incompatible() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualFakeOverride_transitiveFakeOverrides_incompatible.kt"); + } + @Test @TestMetadata("actualTypealiasToSpecialAnnotation.kt") public void testActualTypealiasToSpecialAnnotation() throws Exception { runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealiasToSpecialAnnotation.kt"); } + @Test + @TestMetadata("actualTypealias_defaultParamsIncompatibility.kt") + public void testActualTypealias_defaultParamsIncompatibility() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/actualTypealias_defaultParamsIncompatibility.kt"); + } + @Test public void testAllFilesPresentInMultiplatform() throws Exception { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/diagnostics/tests/multiplatform"), Pattern.compile("^(.*)\\.kts?$"), Pattern.compile("^(.+)\\.(reversed|fir|ll)\\.kts?$"), true); } + @Test + @TestMetadata("arraySort.kt") + public void testArraySort() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/arraySort.kt"); + } + + @Test + @TestMetadata("arraySortFixed.kt") + public void testArraySortFixed() throws Exception { + runTest("compiler/testData/diagnostics/tests/multiplatform/arraySortFixed.kt"); + } + @Test @TestMetadata("checkNoActualForExpectInLastModule.kt") public void testCheckNoActualForExpectInLastModule() throws Exception { diff --git a/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt b/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt index 45aabd08d35..5a4ed1a72e4 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectActualCompatibility.kt @@ -55,6 +55,8 @@ sealed class ExpectActualCompatibility { object FunctionModifiersNotSubset : Incompatible( "some modifiers on expected declaration are missing on the actual one (infix, inline, operator)" ) + object ActualFunctionWithDefaultParameters : + Incompatible("actual function cannot have default argument values, they should be declared in the expected function") // Properties diff --git a/libraries/stdlib/js/src/kotlin/text/regex.kt b/libraries/stdlib/js/src/kotlin/text/regex.kt index 2a089bfff1b..1c1da983cd7 100644 --- a/libraries/stdlib/js/src/kotlin/text/regex.kt +++ b/libraries/stdlib/js/src/kotlin/text/regex.kt @@ -61,6 +61,7 @@ public actual operator fun MatchGroupCollection.get(name: String): MatchGroup? { * * @constructor Creates a regular expression from the specified [pattern] string and the specified set of [options]. */ +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual class Regex actual constructor(pattern: String, options: Set) { /** Creates a regular expression from the specified [pattern] string and the specified single [option]. */ diff --git a/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt b/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt index 087e338073b..bdc939036c6 100644 --- a/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt +++ b/libraries/stdlib/jvm/src/kotlin/text/regex/Regex.kt @@ -78,6 +78,7 @@ public actual data class MatchGroup(public actual val value: String, public val * * For pattern syntax reference see [Pattern]. */ +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual class Regex @PublishedApi internal constructor(private val nativePattern: Pattern) : Serializable { diff --git a/libraries/stdlib/native-wasm/src/kotlin/text/Regex.kt b/libraries/stdlib/native-wasm/src/kotlin/text/Regex.kt index d6509251700..bc651ae57b5 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/text/Regex.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/text/Regex.kt @@ -82,6 +82,7 @@ public actual operator fun MatchGroupCollection.get(name: String): MatchGroup? { * * Note that in the future, the behavior of regular expression matching and replacement functions can be altered to match JVM implementation behavior where differences exist. */ +@Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // Counterpart for @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") public actual class Regex internal constructor(internal val nativePattern: Pattern) { internal enum class Mode {