[FE 1.0] Issue a warning if expect and actual are declared in the same module

The commit is based on b09561c3c3
Co-authored-by: Dmitriy Novozhilov <dmitriy.novozhilov@jetbrains.com>

^KT-40904 Fixed
^KT-55177 Fixed
Review: https://jetbrains.team/p/kt/reviews/8731

True negative test already exist:
`compiler/testData/diagnostics/tests/multiplatform/hmpp/multiplatformCompositeAnalysis/intermediateWithActualAndExpect.kt`
This commit is contained in:
Nikita Bobko
2023-02-15 17:45:04 +01:00
parent 456605542c
commit 9a865e4c55
19 changed files with 368 additions and 6 deletions
@@ -804,6 +804,8 @@ public interface Errors {
DiagnosticFactory2.create(ERROR, ACTUAL_DECLARATION_NAME);
DiagnosticFactory0<KtNamedDeclaration> ACTUAL_MISSING = DiagnosticFactory0.create(ERROR, ACTUAL_DECLARATION_NAME);
DiagnosticFactory1<KtNamedDeclaration, MemberDescriptor> EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE = DiagnosticFactory1.create(WARNING, DECLARATION_NAME);
DiagnosticFactory0<PsiElement> OPTIONAL_EXPECTATION_NOT_ON_EXPECTED = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> OPTIONAL_DECLARATION_OUTSIDE_OF_ANNOTATION_ENTRY = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> OPTIONAL_DECLARATION_USAGE_IN_NON_COMMON_SOURCE = DiagnosticFactory0.create(ERROR);
@@ -339,6 +339,9 @@ public class DefaultErrorMessages {
MAP.put(NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, "Actual class ''{0}'' has no corresponding members for expected class members:{1}",
NAME, adaptGenerics2(IncompatibleExpectedActualClassScopesRenderer.TEXT));
MAP.put(ACTUAL_MISSING, "Declaration must be marked with 'actual'");
MAP.put(EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE,
"{0}: expect and corresponding actual are declared in the same module, which will be prohibited in Kotlin 2.0. See https://youtrack.jetbrains.com/issue/KT-55177",
CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM);
MAP.put(OPTIONAL_EXPECTATION_NOT_ON_EXPECTED, "'@OptionalExpectation' can only be used on an expected annotation class");
MAP.put(OPTIONAL_DECLARATION_OUTSIDE_OF_ANNOTATION_ENTRY, "Declaration annotated with '@OptionalExpectation' can only be used inside an annotation entry");
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.resolve.lazy.DelegationFilter
import org.jetbrains.kotlin.types.DynamicTypesSettings
private val DEFAULT_DECLARATION_CHECKERS = listOf(
ExpectActualInTheSameModuleChecker,
DataClassDeclarationChecker(),
ConstModifierChecker,
UnderscoreChecker,
@@ -0,0 +1,66 @@
/*
* 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.resolve.checkers
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.MemberDescriptor
import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.platform.isCommon
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver
import org.jetbrains.kotlin.resolve.multiplatform.isCommonSource
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
object ExpectActualInTheSameModuleChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (!context.languageVersionSettings.supportsFeature(LanguageFeature.MultiPlatformProjects)) return
if (descriptor !is MemberDescriptor) return
if (declaration !is KtNamedDeclaration) return
if (!descriptor.isExpect) return
// Only look for top level actual members; class members will be handled as a part of that expected class
if (descriptor.containingDeclaration !is PackageFragmentDescriptor) return
val module = descriptor.module
val actuals = ExpectedActualResolver.findActualForExpected(descriptor, module)?.flatMap { it.value }
?.takeIf(List<MemberDescriptor>::isNotEmpty) ?: return
// There are 4 cases:
// 1. `expect` in common module, `actual` in platform module. It's a legal situation. In K2MetadataCompiler compiler, the
// declarations have different modules. In backend specific compiler, `expect` declaration is in common module so warning won't
// be issued
// 2. `expect` in common module X, `actual` in common module Y (Y depends on X). It's a legal situation. In K2MetadataCompiler
// compiler, the declarations have different modules. In backend specific compiler, `expect` declaration is in common so warning
// won't be issued
// 3. `expect` in common module X, `actual` in the very same common module X. It's an illegal situation. In K2MetadataCompiler
// compiler, the declarations have the same module, so the warning will be issue. In backend specific compiler, `expect`
// declaration is in common so warning won't be issued
// 4. `expect` in platform module X, `actual` in the very same platform module X. It's an illegal situation. K2MetadataCompiler is
// invoked only for common modules, so it won't issue a warning. In backend specific compiler, `expect` declaration is in
// platform module, the warning will be issued
//
// The checker doesn't consider cases when platform module depend on another platform module.
if (module.platform.isCommon()) { /* If true then we are in K2MetadataCompiler. `isCommon` never returns `true` in backend specific
compilers (even when the source is truly common) */
// modules can be distinguished only in K2MetadataCompiler because in platform specific compiler all sources are put in
// a single module
if (actuals.all { it.module != module }) return
} else { // backend specific compiler
if (declaration.containingKtFile.isCommonSource == true) return
}
context.trace.report(Errors.EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE.on(declaration, descriptor))
for (actual in actuals) {
val actualSource = actual.declarationSource ?: continue
context.trace.report(Errors.EXPECT_AND_ACTUAL_IN_THE_SAME_MODULE.on(actualSource, descriptor))
}
}
private val MemberDescriptor.declarationSource: KtNamedDeclaration?
get() = (this.source as? KotlinSourceElement)?.psi as? KtNamedDeclaration
}