From 29beed9b2160ce4c1b2bfc4766e6a53f772c9a8f Mon Sep 17 00:00:00 2001 From: Dmitry Savvinov Date: Fri, 12 Apr 2019 13:23:58 +0300 Subject: [PATCH] [Expect/Actual] Support matching of expect/actual in hierarchical MPP --- .../jetbrains/kotlin/analyzer/ModuleInfo.kt | 3 + .../kotlin/analyzer/AnalyzerFacade.kt | 3 + .../jetbrains/kotlin/diagnostics/Errors.java | 5 + .../rendering/DefaultErrorMessages.java | 8 +- .../kotlin/diagnostics/rendering/Renderers.kt | 16 +- .../ExpectedActualDeclarationChecker.kt | 188 ++++++++++++++---- .../multiplatform/ExpectedActualResolver.kt | 61 ++++-- .../multiplatform/moduleFilterUtils.kt | 18 ++ .../idea/caches/project/IdeaModuleInfos.kt | 5 +- .../idea/highlighter/IdeErrorMessages.java | 4 +- .../idea/project/IdeaModuleStructureOracle.kt | 3 +- .../OptionalExpectationInspection.kt | 1 + 12 files changed, 247 insertions(+), 68 deletions(-) create mode 100644 compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/moduleFilterUtils.kt diff --git a/compiler/frontend.common/src/org/jetbrains/kotlin/analyzer/ModuleInfo.kt b/compiler/frontend.common/src/org/jetbrains/kotlin/analyzer/ModuleInfo.kt index da9566ff27d..94885db3a83 100644 --- a/compiler/frontend.common/src/org/jetbrains/kotlin/analyzer/ModuleInfo.kt +++ b/compiler/frontend.common/src/org/jetbrains/kotlin/analyzer/ModuleInfo.kt @@ -39,3 +39,6 @@ interface ModuleInfo { val Capability = ModuleDescriptor.Capability("ModuleInfo") } } + +val ModuleDescriptor.moduleInfo: ModuleInfo? + get() = getCapability(ModuleInfo.Capability) \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt index c6324c7dc37..8b34fa6ed06 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt @@ -280,6 +280,7 @@ interface PlatformAnalysisParameters { interface CombinedModuleInfo : ModuleInfo { val containedModules: List + val platformModule: ModuleInfo } fun ModuleInfo.flatten(): List = when (this) { @@ -287,6 +288,8 @@ fun ModuleInfo.flatten(): List = when (this) { else -> listOf(this) } +fun ModuleInfo.unwrapPlatform(): ModuleInfo = if (this is CombinedModuleInfo) platformModule else this + interface TrackableModuleInfo : ModuleInfo { fun createModificationTracker(): ModificationTracker } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java index e21c5e2e349..4e6ba234e75 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/Errors.java @@ -11,6 +11,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.LeafPsiElement; import kotlin.Pair; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.analyzer.ModuleInfo; import org.jetbrains.kotlin.cfg.WhenMissingCase; import org.jetbrains.kotlin.config.LanguageFeature; import org.jetbrains.kotlin.config.LanguageVersion; @@ -645,6 +646,10 @@ public interface Errors { DiagnosticFactory2>> ACTUAL_WITHOUT_EXPECT = DiagnosticFactory2.create(ERROR, INCOMPATIBLE_DECLARATION); + DiagnosticFactory2> AMBIGUOUS_ACTUALS = + DiagnosticFactory2.create(ERROR, INCOMPATIBLE_DECLARATION); + DiagnosticFactory2> AMBIGUOUS_EXPECTS = + DiagnosticFactory2.create(ERROR, INCOMPATIBLE_DECLARATION); DiagnosticFactory2>>>> NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS = diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java index f01dc47f122..c39b18f6a21 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/DefaultErrorMessages.java @@ -280,10 +280,14 @@ public class DefaultErrorMessages { "Expected function source is not found, therefore it's impossible to generate default argument values declared there. " + "Please add the corresponding file to compilation sources"); - MAP.put(NO_ACTUAL_FOR_EXPECT, "Expected {0} has no actual declaration in module{1}{2}", DECLARATION_NAME_WITH_KIND, - PLATFORM, PlatformIncompatibilityDiagnosticRenderer.TEXT); + MAP.put(NO_ACTUAL_FOR_EXPECT, "Expected {0} has no actual declaration in module {1}{2}", DECLARATION_NAME_WITH_KIND, + MODULE_WITH_PLATFORM, PlatformIncompatibilityDiagnosticRenderer.TEXT); MAP.put(ACTUAL_WITHOUT_EXPECT, "{0} has no corresponding expected declaration{1}", CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM, PlatformIncompatibilityDiagnosticRenderer.TEXT); + MAP.put(AMBIGUOUS_ACTUALS, "{0} has several compatible actual declarations in modules {1}", CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM, commaSeparated( + STRING)); + MAP.put(AMBIGUOUS_EXPECTS, "{0} has several compatible expect declarations in modules {1}", CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM, commaSeparated( + STRING)); MAP.put(NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS, "Actual class ''{0}'' has no corresponding members for expected class members:{1}", NAME, IncompatibleExpectedActualClassScopesRenderer.TEXT); diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt index 753c0fda351..ecce7d54318 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/rendering/Renderers.kt @@ -21,6 +21,8 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.text.StringUtil import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.analyzer.moduleInfo +import org.jetbrains.kotlin.analyzer.unwrapPlatform import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.cfg.WhenMissingCase import org.jetbrains.kotlin.cfg.hasUnknown @@ -85,14 +87,14 @@ object Renderers { val NAME = Renderer { it.name.asString() } @JvmField - val PLATFORM = Renderer { - val platform = it.platform - " ${it.getCapability(ModuleInfo.Capability)?.displayedName ?: ""}" + when { - platform == null || platform.isCommon() -> "" - else -> " for " + platform.single().platformName - } - } + val MODULE_WITH_PLATFORM = Renderer { module -> + val platform = module.platform + val moduleName = module.moduleInfo?.unwrapPlatform()?.displayedName ?: "" + val platformNameIfAny = if (platform == null || platform.isCommon()) "" else " for " + platform.single().platformName + moduleName + platformNameIfAny + } + @JvmField val VISIBILITY = Renderer { if (it == Visibilities.INVISIBLE_FAKE) 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 46113c5faa8..56b9751ba00 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.resolve.checkers import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.analyzer.CombinedModuleInfo +import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.config.AnalysisFlags import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.descriptors.* @@ -35,13 +37,17 @@ import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Compatible import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible +import org.jetbrains.kotlin.resolve.multiplatform.ModuleFilter import org.jetbrains.kotlin.resolve.source.PsiSourceFile import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.ifEmpty import java.io.File -class ExpectedActualDeclarationChecker(val argumentExtractors: Iterable) : DeclarationChecker { +class ExpectedActualDeclarationChecker( + val moduleStructureOracle: ModuleStructureOracle, + val argumentExtractors: Iterable +) : DeclarationChecker { interface ActualAnnotationArgumentExtractor { fun extractDefaultValue(parameter: ValueParameterDescriptor, expectedType: KotlinType): ConstantValue<*>? } @@ -56,44 +62,126 @@ class ExpectedActualDeclarationChecker(val argumentExtractors: Iterable>()) { resultMap, partialMap -> + resultMap.apply { putAll(partialMap) } + } + + // Several compatible actuals on one path: report AMBIGUIOUS_ACTUALS here + val atLeastWeaklyCompatibleActuals = compatibility.filterKeys { it.isCompatibleOrWeakCompatible() }.values.flatten() + if (atLeastWeaklyCompatibleActuals.size > 1) { + trace.report(Errors.AMBIGUOUS_ACTUALS.on( + reportOn, + descriptor, + atLeastWeaklyCompatibleActuals.map { DescriptorUtils.getContainingSourceFile(it).let { it.name ?: it.toString() } } + )) + } + } + + private fun checkExpectedDeclarationHasAtLeastOneActual( + reportOn: KtNamedDeclaration, + descriptor: MemberDescriptor, + trace: BindingTrace, + module: ModuleDescriptor, + expectActualTracker: ExpectActualTracker, + moduleVisibilityFilter: ModuleFilter ) { // 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 compatibility = ExpectedActualResolver.findActualForExpected(descriptor, platformModule) ?: return + val compatibility = ExpectedActualResolver.findActualForExpected(descriptor, module, moduleVisibilityFilter) ?: return + // Only strong incompatibilities, but this is an OptionalExpectation -- don't report it if (compatibility.allStrongIncompatibilities() && isOptionalAnnotationClass(descriptor)) return - val shouldReportError = - compatibility.allStrongIncompatibilities() || - Compatible !in compatibility && descriptor.hasNoActualWithDiagnostic(compatibility) - - if (shouldReportError) { + // Only strong incompatibilities, or error won't be reported on actual: report NO_ACTUAL_FOR_EXPECT here + if (compatibility.allStrongIncompatibilities() || + Compatible !in compatibility && descriptor.hasNoActualWithDiagnostic(compatibility) + ) { assert(compatibility.keys.all { it is Incompatible }) @Suppress("UNCHECKED_CAST") val incompatibility = compatibility as Map> - trace.report(Errors.NO_ACTUAL_FOR_EXPECT.on(reportOn, descriptor, platformModule, incompatibility)) - } else { - val actualMembers = compatibility.asSequence() - .filter { (compatibility, _) -> - compatibility is Compatible || (compatibility is Incompatible && compatibility.kind != Compatibility.IncompatibilityKind.STRONG) - }.flatMap { it.value.asSequence() } - - expectActualTracker.reportExpectActual(expected = descriptor, actualMembers = actualMembers) + trace.report(Errors.NO_ACTUAL_FOR_EXPECT.on(reportOn, descriptor, module, incompatibility)) + return } + + // Here we have exactly one compatible actual and/or some weakly incompatible. In either case, we don't report anything on expect + val actualMembers = compatibility.asSequence() + .filter { (compatibility, _) -> + compatibility is Compatible || (compatibility is Incompatible && compatibility.kind != Compatibility.IncompatibilityKind.STRONG) + }.flatMap { it.value.asSequence() } + + expectActualTracker.reportExpectActual(expected = descriptor, actualMembers = actualMembers) } private fun MemberDescriptor.hasNoActualWithDiagnostic( @@ -122,19 +210,16 @@ class ExpectedActualDeclarationChecker(val argumentExtractors: Iterable>()) { resultMap, partialMap -> - resultMap.apply { putAll(partialMap) } - } + val compatibility = ExpectedActualResolver.findExpectedForActual(descriptor, descriptor.module, moduleVisibilityFilter) + ?: return + + checkAmbiguousExpects(compatibility, trace, reportOn, descriptor) val hasActualModifier = descriptor.isActual && reportOn.hasActualModifier() if (!hasActualModifier) { @@ -180,7 +265,7 @@ class ExpectedActualDeclarationChecker(val argumentExtractors: Iterable>, + trace: BindingTrace, + reportOn: KtNamedDeclaration, + descriptor: MemberDescriptor + ) { + val filesWithAtLeastWeaklyCompatibleExpects = compatibility.asSequence() + .filter { (compatibility, _) -> + compatibility.isCompatibleOrWeakCompatible() + } + .map { (_, members) -> members } + .flatten() + .map { DescriptorUtils.getContainingSourceFile(it).let { it.name ?: it.toString() } } + .toList() + + if (filesWithAtLeastWeaklyCompatibleExpects.size > 1) { + trace.report(Errors.AMBIGUOUS_EXPECTS.on(reportOn, descriptor, filesWithAtLeastWeaklyCompatibleExpects)) + } + } + + private fun Compatibility.isCompatibleOrWeakCompatible() = + this is Compatible || + this is Incompatible && kind == ExpectedActualResolver.Compatibility.IncompatibilityKind.WEAK + // we don't require `actual` modifier on // - annotation constructors, because annotation classes can only have one constructor // - inline class primary constructors, because inline class must have primary constructor @@ -295,5 +404,16 @@ class ExpectedActualDeclarationChecker(val argumentExtractors: Iterable>.allStrongIncompatibilities(): Boolean = this.keys.all { it is Incompatible && it.kind == Compatibility.IncompatibilityKind.STRONG } + + private fun LinkedHashMap>.merge(other: Map>): LinkedHashMap> { + for ((key, newValue) in other) { + val oldValue = this[key] ?: emptyList() + this[key] = oldValue + newValue + } + + return this + } + + private fun ModuleInfo.unwrapModuleInfo(): List = if (this is CombinedModuleInfo) this.containedModules else listOf(this) } } 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 ce550c2b2d0..7cbe25e0bab 100644 --- a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/ExpectedActualResolver.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.utils.SmartList import org.jetbrains.kotlin.utils.keysToMap object ExpectedActualResolver { + // FIXME(dsavvinov): review clients, as they won't work properly in HMPP projects fun MemberDescriptor.findCompatibleActualForExpected(platformModule: ModuleDescriptor): List = findActualForExpected(this, platformModule)?.get(Compatible).orEmpty() @@ -42,10 +43,14 @@ object ExpectedActualResolver { fun MemberDescriptor.findCompatibleExpectedForActual(commonModule: ModuleDescriptor): List = findExpectedForActual(this, commonModule)?.get(Compatible).orEmpty() - fun findActualForExpected(expected: MemberDescriptor, platformModule: ModuleDescriptor): Map>? { + fun findActualForExpected( + expected: MemberDescriptor, + platformModule: ModuleDescriptor, + moduleVisibilityFilter: ModuleFilter = onlyFromThisModule(platformModule) + ): Map>? { return when (expected) { is CallableMemberDescriptor -> { - expected.findNamesakesFromModule(platformModule).filter { actual -> + expected.findNamesakesFromModule(platformModule, moduleVisibilityFilter).filter { actual -> expected != actual && !actual.isExpect && // TODO: support non-source definitions (e.g. from Java) actual.source.containingFile != SourceFile.NO_SOURCE_FILE @@ -54,7 +59,7 @@ object ExpectedActualResolver { } } is ClassDescriptor -> { - expected.findClassifiersFromModule(platformModule).filter { actual -> + expected.findClassifiersFromModule(platformModule, moduleVisibilityFilter).filter { actual -> expected != actual && !actual.isExpect && actual.source.containingFile != SourceFile.NO_SOURCE_FILE }.groupBy { actual -> @@ -65,7 +70,11 @@ object ExpectedActualResolver { } } - fun findExpectedForActual(actual: MemberDescriptor, commonModule: ModuleDescriptor): Map>? { + fun findExpectedForActual( + actual: MemberDescriptor, + commonModule: ModuleDescriptor, + moduleFilter: (ModuleDescriptor) -> Boolean = onlyFromThisModule(commonModule) + ): Map>? { return when (actual) { is CallableMemberDescriptor -> { val container = actual.containingDeclaration @@ -73,10 +82,10 @@ object ExpectedActualResolver { is ClassifierDescriptorWithTypeParameters -> { // TODO: replace with 'singleOrNull' as soon as multi-module diagnostic tests are refactored val expectedClass = - findExpectedForActual(container, commonModule)?.values?.firstOrNull()?.firstOrNull() as? ClassDescriptor + findExpectedForActual(container, commonModule, moduleFilter)?.values?.firstOrNull()?.firstOrNull() as? ClassDescriptor expectedClass?.getMembers(actual.name)?.filterIsInstance().orEmpty() } - is PackageFragmentDescriptor -> actual.findNamesakesFromModule(commonModule) + is PackageFragmentDescriptor -> actual.findNamesakesFromModule(commonModule, moduleFilter) else -> return null // do not report anything for incorrect code, e.g. 'actual' local function } @@ -95,7 +104,7 @@ object ExpectedActualResolver { } } is ClassifierDescriptorWithTypeParameters -> { - actual.findClassifiersFromModule(commonModule).filter { declaration -> + actual.findClassifiersFromModule(commonModule, moduleFilter).filter { declaration -> actual != declaration && declaration is ClassDescriptor && declaration.isExpect }.groupBy { expected -> @@ -106,14 +115,16 @@ object ExpectedActualResolver { } } - private fun CallableMemberDescriptor.findNamesakesFromModule(module: ModuleDescriptor): Collection { - val containingDeclaration = containingDeclaration - val scopes = when (containingDeclaration) { + private fun CallableMemberDescriptor.findNamesakesFromModule( + module: ModuleDescriptor, + moduleFilter: (ModuleDescriptor) -> Boolean + ): Collection { + val scopes = when (val containingDeclaration = containingDeclaration) { is PackageFragmentDescriptor -> { listOf(module.getPackage(containingDeclaration.fqName).memberScope) } is ClassDescriptor -> { - val classes = containingDeclaration.findClassifiersFromModule(module) + val classes = containingDeclaration.findClassifiersFromModule(module, moduleFilter) .mapNotNull { if (it is TypeAliasDescriptor) it.classDescriptor else it } .filterIsInstance() if (this is ConstructorDescriptor) return classes.flatMap { it.constructors } @@ -124,15 +135,23 @@ object ExpectedActualResolver { } return when (this) { - is FunctionDescriptor -> scopes.flatMap { it.getContributedFunctions(name, NoLookupLocation.FOR_ALREADY_TRACKED) } - is PropertyDescriptor -> scopes.flatMap { it.getContributedVariables(name, NoLookupLocation.FOR_ALREADY_TRACKED) } + is FunctionDescriptor -> scopes.flatMap { + it.getContributedDescriptors(DescriptorKindFilter.FUNCTIONS, { it == name }) + .filter { it.name == name } as Collection + } + + is PropertyDescriptor -> scopes.flatMap { + it.getContributedDescriptors(DescriptorKindFilter.VARIABLES, { it == name }) + .filter { it.name == name } as Collection + } + else -> throw AssertionError("Unsupported declaration: $this") - }.onlyFromThis(module) + }.applyFilter(moduleFilter) } private fun ClassifierDescriptorWithTypeParameters.findClassifiersFromModule( module: ModuleDescriptor, - includeDependencies: Boolean = false + moduleFilter: (ModuleDescriptor) -> Boolean ): Collection { val classId = classId ?: return emptyList() @@ -142,7 +161,7 @@ object ExpectedActualResolver { val segments = classId.relativeClassName.pathSegments() var classifiers = module.getPackage(classId.packageFqName).memberScope.getAllClassifiers(segments.first()) - if (!includeDependencies) classifiers = classifiers.onlyFromThis(module) + classifiers = classifiers.applyFilter(moduleFilter) for (name in segments.subList(1, segments.size)) { classifiers = classifiers.mapNotNull { classifier -> @@ -155,9 +174,6 @@ object ExpectedActualResolver { return classifiers } - private fun Iterable.onlyFromThis(module: ModuleDescriptor): List = - filter { it.module == module } - sealed class Compatibility { // For IncompatibilityKind.STRONG `actual` declaration is considered as overload and error reports on expected declaration enum class IncompatibilityKind { @@ -231,7 +247,9 @@ object ExpectedActualResolver { platformModule: ModuleDescriptor = b.module, parentSubstitutor: Substitutor? = null ): Compatibility { - assert(a.name == b.name) { "This function should be invoked only for declarations with the same name: $a, $b" } + assert(a.name == b.name) { + "This function should be invoked only for declarations with the same name: $a, $b" + } assert(a.containingDeclaration is ClassifierDescriptorWithTypeParameters == b.containingDeclaration is ClassifierDescriptorWithTypeParameters) { "This function should be invoked only for declarations in the same kind of container (both members or both top level): $a, $b" } @@ -329,7 +347,7 @@ object ExpectedActualResolver { return expected is ClassifierDescriptorWithTypeParameters && expected.isExpect && actual is ClassifierDescriptorWithTypeParameters && - expected.findClassifiersFromModule(platformModule, includeDependencies = true).any { classifier -> + expected.findClassifiersFromModule(platformModule, moduleFilter = ALL_MODULES).any { classifier -> // Note that it's fine to only check that this "actual typealias" expands to the expected class, without checking // whether the type arguments in the expansion are in the correct order or have the correct variance, because we only // allow simple cases like "actual typealias Foo = FooImpl", see DeclarationsChecker#checkActualTypeAlias @@ -537,3 +555,4 @@ object ExpectedActualResolver { (parent?.invoke(type) ?: type)?.asTypeProjection()?.let(typeSubstitutor::substitute)?.type } } + diff --git a/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/moduleFilterUtils.kt b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/moduleFilterUtils.kt new file mode 100644 index 00000000000..a7eb6e667fd --- /dev/null +++ b/compiler/resolution/src/org/jetbrains/kotlin/resolve/multiplatform/moduleFilterUtils.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. 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.multiplatform + +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.resolve.descriptorUtil.module + +typealias ModuleFilter = (ModuleDescriptor) -> Boolean + +fun onlyFromThisModule(module: ModuleDescriptor): ModuleFilter = { it == module } + +val ALL_MODULES: ModuleFilter = { true } + +fun Iterable.applyFilter(filter: ModuleFilter): List = filter { filter(it.module) } diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt index d91d8836211..d20daa00f3c 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt @@ -464,7 +464,7 @@ interface SourceForBinaryModuleInfo : IdeaModuleInfo { } data class PlatformModuleInfo( - val platformModule: ModuleSourceInfo, + override val platformModule: ModuleSourceInfo, private val commonModules: List // NOTE: usually contains a single element for current implementation ) : IdeaModuleInfo, CombinedModuleInfo, TrackableModuleInfo { override val capabilities: Map, Any?> @@ -485,6 +485,9 @@ data class PlatformModuleInfo( override fun dependencies() = platformModule.dependencies() + override val expectedBy: List + get() = platformModule.expectedBy + override fun modulesWhoseInternalsAreVisible() = containedModules.flatMap { it.modulesWhoseInternalsAreVisible() } override val name: Name diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/IdeErrorMessages.java b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/IdeErrorMessages.java index 312a9b4b330..aa1eede1bde 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/IdeErrorMessages.java +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/highlighter/IdeErrorMessages.java @@ -181,8 +181,8 @@ public class IdeErrorMessages { MAP.put(EXPERIMENTAL_FEATURE_WARNING, "{0}", new LanguageFeatureMessageRenderer(LanguageFeatureMessageRenderer.Type.WARNING, true)); MAP.put(EXPERIMENTAL_FEATURE_ERROR, "{0}", new LanguageFeatureMessageRenderer(LanguageFeatureMessageRenderer.Type.ERROR, true)); - MAP.put(NO_ACTUAL_FOR_EXPECT, "Expected {0} has no actual declaration in module{1}{2}", DECLARATION_NAME_WITH_KIND, - PLATFORM, new PlatformIncompatibilityDiagnosticRenderer(IdeMultiplatformDiagnosticRenderingMode.INSTANCE)); + MAP.put(NO_ACTUAL_FOR_EXPECT, "Expected {0} has no actual declaration in module {1}{2}", DECLARATION_NAME_WITH_KIND, + MODULE_WITH_PLATFORM, new PlatformIncompatibilityDiagnosticRenderer(IdeMultiplatformDiagnosticRenderingMode.INSTANCE)); MAP.put(ACTUAL_WITHOUT_EXPECT, "{0} has no corresponding expected declaration{1}", CAPITALIZED_DECLARATION_NAME_WITH_KIND_AND_PLATFORM, new PlatformIncompatibilityDiagnosticRenderer(IdeMultiplatformDiagnosticRenderingMode.INSTANCE)); diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaModuleStructureOracle.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaModuleStructureOracle.kt index 0f514524ec3..d1495657a70 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaModuleStructureOracle.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/project/IdeaModuleStructureOracle.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.idea.project import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors import org.jetbrains.kotlin.resolve.ModulePath import org.jetbrains.kotlin.resolve.ModuleStructureOracle @@ -28,7 +29,7 @@ class IdeaModuleStructureOracle : ModuleStructureOracle { val currentPath: Stack = Stack() return sequence { - yieldPathsFromSubgraph(module, currentPath, getChilds = { it.expectedByModules }) + yieldPathsFromSubgraph(module, currentPath, getChilds = { it.implementedDescriptors }) }.toList() } diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt index c1809b4c622..5437830a4d2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/OptionalExpectationInspection.kt @@ -35,6 +35,7 @@ class OptionalExpectationInspection : AbstractKotlinInspection() { val descriptor = classOrObject.resolveToDescriptorIfAny() ?: return if (!descriptor.annotations.hasAnnotation(ExpectedActualDeclarationChecker.OPTIONAL_EXPECTATION_FQ_NAME)) return + // FIXME(dsavvinov): this is wrong in HMPP model, use logic similar to ExpectedActualDeclarationChecker val implementingModules = classOrObject.findModuleDescriptor().implementingDescriptors if (implementingModules.isEmpty()) return