diff options
Diffstat (limited to 'dokka-subprojects/analysis-kotlin-descriptors-ide')
26 files changed, 1675 insertions, 0 deletions
diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/README.md b/dokka-subprojects/analysis-kotlin-descriptors-ide/README.md new file mode 100644 index 00000000..14ed5baa --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/README.md @@ -0,0 +1,11 @@ +# Descriptors: IDE + +An internal module that encapsulates external IDE (`org.jetbrains.kotlin:idea`) dependencies. + +IDE artifacts are reused for things that are not possible to do with the Kotlin compiler API, such +as KDoc or KLib parsing/processing, because Dokka is very similar to an IDE when it comes to analyzing +source code and docs. + +Exists primarily to make sure that unreliable and coupled external dependencies are somewhat abstracted away, +otherwise everything gets tangled together and breaking changes in such dependencies become very +difficult to resolve. diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/api/analysis-kotlin-descriptors-ide.api b/dokka-subprojects/analysis-kotlin-descriptors-ide/api/analysis-kotlin-descriptors-ide.api new file mode 100644 index 00000000..a59658a3 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/api/analysis-kotlin-descriptors-ide.api @@ -0,0 +1,4 @@ +public final class org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin : org/jetbrains/dokka/plugability/DokkaPlugin { + public fun <init> ()V +} + diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/build.gradle.kts b/dokka-subprojects/analysis-kotlin-descriptors-ide/build.gradle.kts new file mode 100644 index 00000000..76a88353 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/build.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +plugins { + id("dokkabuild.kotlin-jvm") +} + +dependencies { + compileOnly(projects.dokkaSubprojects.dokkaCore) + compileOnly(projects.dokkaSubprojects.analysisKotlinApi) + + implementation(projects.dokkaSubprojects.analysisKotlinDescriptorsCompiler) + + // TODO [beresnev] get rid of it + compileOnly(libs.kotlinx.coroutines.core) +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt new file mode 100644 index 00000000..2a299009 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/CoreKotlinCacheService.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.caches.resolve.PlatformAnalysisSettings +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache + + +internal class CoreKotlinCacheService(private val resolutionFacade: DokkaResolutionFacade) : KotlinCacheService { + override fun getResolutionFacade(elements: List<KtElement>): ResolutionFacade { + return resolutionFacade + } + + override fun getResolutionFacade(element: KtElement): ResolutionFacade { + return resolutionFacade + } + + override fun getResolutionFacadeByFile( + file: PsiFile, + platform: TargetPlatform + ): ResolutionFacade { + return resolutionFacade + } + + override fun getResolutionFacadeByModuleInfo( + moduleInfo: ModuleInfo, + settings: PlatformAnalysisSettings + ): ResolutionFacade { + return resolutionFacade + } + + override fun getResolutionFacadeByModuleInfo( + moduleInfo: ModuleInfo, + platform: TargetPlatform + ): ResolutionFacade { + return resolutionFacade + } + + override fun getResolutionFacadeWithForcedPlatform( + elements: List<KtElement>, + platform: TargetPlatform + ): ResolutionFacade { + return resolutionFacade + } + + override fun getSuppressionCache(): KotlinSuppressCache { + throw UnsupportedOperationException() + } + +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt new file mode 100644 index 00000000..1ded0495 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/DokkaResolutionFacade.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:OptIn(FrontendInternals::class) + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import com.google.common.collect.ImmutableMap +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.analyzer.ResolverForModule +import org.jetbrains.kotlin.analyzer.ResolverForProject +import org.jetbrains.kotlin.container.getService +import org.jetbrains.kotlin.container.tryGetService +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.idea.FrontendInternals +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtExpression +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.lazy.ResolveSession +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice +import org.jetbrains.kotlin.util.slicedMap.WritableSlice + +internal class DokkaResolutionFacade( + override val project: Project, + override val moduleDescriptor: ModuleDescriptor, + val resolverForModule: ResolverForModule +) : ResolutionFacade { + override fun analyzeWithAllCompilerChecks( + elements: Collection<KtElement>, + callback: DiagnosticSink.DiagnosticsCallback? + ): AnalysisResult { + throw UnsupportedOperationException() + } + + @OptIn(FrontendInternals::class) + override fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? { + return resolverForModule.componentProvider.tryGetService(serviceClass) + } + + override fun resolveToDescriptor( + declaration: KtDeclaration, + bodyResolveMode: BodyResolveMode + ): DeclarationDescriptor { + return resolveSession.resolveToDescriptor(declaration) + } + + override fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext { + throw UnsupportedOperationException() + } + + val resolveSession: ResolveSession get() = getFrontendService(ResolveSession::class.java) + + override fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode): BindingContext { + if (element is KtDeclaration) { + val descriptor = resolveToDescriptor(element) + return object : BindingContext { + override fun <K : Any?, V : Any?> getKeys(p0: WritableSlice<K, V>?): Collection<K> { + throw UnsupportedOperationException() + } + + override fun getType(p0: KtExpression): KotlinType? { + throw UnsupportedOperationException() + } + + override fun <K : Any?, V : Any?> get(slice: ReadOnlySlice<K, V>?, key: K): V? { + if (key != element) { + throw UnsupportedOperationException() + } + @Suppress("UNCHECKED_CAST") + return when { + slice == BindingContext.DECLARATION_TO_DESCRIPTOR -> descriptor as V + slice == BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER && (element as KtParameter).hasValOrVar() -> descriptor as V + else -> null + } + } + + override fun getDiagnostics(): Diagnostics { + throw UnsupportedOperationException() + } + + override fun addOwnDataTo(p0: BindingTrace, p1: Boolean) { + throw UnsupportedOperationException() + } + + override fun <K : Any?, V : Any?> getSliceContents(p0: ReadOnlySlice<K, V>): ImmutableMap<K, V> { + throw UnsupportedOperationException() + } + + } + } + throw UnsupportedOperationException() + } + + override fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T { + throw UnsupportedOperationException() + } + + override fun <T : Any> getFrontendService(serviceClass: Class<T>): T { + return resolverForModule.componentProvider.getService(serviceClass) + } + + @Deprecated("DO NOT USE IT AS IT IS A ROOT CAUSE OF KTIJ-17649") + override fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T { + return resolverForModule.componentProvider.getService(serviceClass) + } + + override fun <T : Any> getIdeService(serviceClass: Class<T>): T { + throw UnsupportedOperationException() + } + + override fun getResolverForProject(): ResolverForProject<out ModuleInfo> { + throw UnsupportedOperationException() + } + +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt new file mode 100644 index 00000000..252fbd55 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeAnalysisContextCreator.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import com.intellij.mock.MockComponentManager +import com.intellij.mock.MockProject +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.AnalysisContextCreator +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisContext +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisEnvironment +import org.jetbrains.kotlin.analyzer.ResolverForModule +import org.jetbrains.kotlin.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.ModuleDescriptor + +internal class IdeAnalysisContextCreator : AnalysisContextCreator { + override fun create( + project: MockProject, + moduleDescriptor: ModuleDescriptor, + moduleResolver: ResolverForModule, + kotlinEnvironment: KotlinCoreEnvironment, + analysisEnvironment: AnalysisEnvironment, + ): AnalysisContext { + val facade = DokkaResolutionFacade(project, moduleDescriptor, moduleResolver) + val projectComponentManager = project as MockComponentManager + projectComponentManager.registerService( + KotlinCacheService::class.java, + CoreKotlinCacheService(facade) + ) + return ResolutionFacadeAnalysisContext(facade, kotlinEnvironment, analysisEnvironment) + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt new file mode 100644 index 00000000..e170b740 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorAnalysisPlugin.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import org.jetbrains.dokka.InternalDokkaApi +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.plugability.DokkaPluginApiPreview +import org.jetbrains.dokka.plugability.PluginApiPreviewAcknowledgement + +@InternalDokkaApi +public class IdeDescriptorAnalysisPlugin : DokkaPlugin() { + + internal val ideKdocFinder by extending { + plugin<CompilerDescriptorAnalysisPlugin>().kdocFinder providing ::IdePluginKDocFinder + } + + internal val ideDescriptorFinder by extending { + plugin<CompilerDescriptorAnalysisPlugin>().descriptorFinder providing { IdeDescriptorFinder() } + } + + internal val ideKlibService by extending { + plugin<CompilerDescriptorAnalysisPlugin>().klibService providing { IdeKLibService() } + } + + internal val ideApplicationHack by extending { + plugin<CompilerDescriptorAnalysisPlugin>().mockApplicationHack providing { IdeMockApplicationHack() } + } + + internal val ideAnalysisContextCreator by extending { + plugin<CompilerDescriptorAnalysisPlugin>().analysisContextCreator providing { IdeAnalysisContextCreator() } + } + + @OptIn(DokkaPluginApiPreview::class) + override fun pluginApiPreviewAcknowledgement(): PluginApiPreviewAcknowledgement = PluginApiPreviewAcknowledgement +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt new file mode 100644 index 00000000..7a1e04e4 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeDescriptorFinder.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.DescriptorFinder +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.idea.caches.resolve.resolveToParameterDescriptorIfAny +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +internal class IdeDescriptorFinder : DescriptorFinder { + override fun KtDeclaration.findDescriptor(): DeclarationDescriptor? { + return if (this is KtParameter) this.resolveToParameterDescriptorIfAny(BodyResolveMode.FULL) else this.resolveToDescriptorIfAny( + BodyResolveMode.FULL + ) + } + +} + + diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt new file mode 100644 index 00000000..e1c0eb31 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeKLibService.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KLibService +import org.jetbrains.kotlin.library.metadata.KlibMetadataModuleDescriptorFactory +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageFragmentProvider +import org.jetbrains.kotlin.idea.klib.CachingIdeKlibMetadataLoader +import org.jetbrains.kotlin.idea.klib.compatibilityInfo +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.library.KotlinLibrary +import org.jetbrains.kotlin.resolve.CompilerDeserializationConfiguration +import org.jetbrains.kotlin.storage.StorageManager + +internal class IdeKLibService : KLibService { + override fun KotlinLibrary.createPackageFragmentProvider( + storageManager: StorageManager, + metadataModuleDescriptorFactory: KlibMetadataModuleDescriptorFactory, + languageVersionSettings: LanguageVersionSettings, + moduleDescriptor: ModuleDescriptor, + lookupTracker: LookupTracker, + ): PackageFragmentProvider? { + return this.createKlibPackageFragmentProvider( + storageManager, metadataModuleDescriptorFactory, languageVersionSettings, moduleDescriptor, lookupTracker + ) + } + + override fun isAnalysisCompatible(kotlinLibrary: KotlinLibrary): Boolean { + return kotlinLibrary.compatibilityInfo.isCompatible + } +} + +internal fun KotlinLibrary.createKlibPackageFragmentProvider( + storageManager: StorageManager, + metadataModuleDescriptorFactory: KlibMetadataModuleDescriptorFactory, + languageVersionSettings: LanguageVersionSettings, + moduleDescriptor: ModuleDescriptor, + lookupTracker: LookupTracker +): PackageFragmentProvider? { + if (!compatibilityInfo.isCompatible) return null + + val packageFragmentNames = CachingIdeKlibMetadataLoader.loadModuleHeader(this).packageFragmentNameList + + return metadataModuleDescriptorFactory.createPackageFragmentProvider( + library = this, + packageAccessHandler = CachingIdeKlibMetadataLoader, + packageFragmentNames = packageFragmentNames, + storageManager = storageManager, + moduleDescriptor = moduleDescriptor, + configuration = CompilerDeserializationConfiguration(languageVersionSettings), + compositePackageFragmentAddend = null, + lookupTracker = lookupTracker + ) +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt new file mode 100644 index 00000000..2bc83504 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdeMockApplicationHack.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import com.intellij.mock.MockApplication +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.MockApplicationHack +import org.jetbrains.kotlin.idea.klib.KlibLoadingMetadataCache + +internal class IdeMockApplicationHack : MockApplicationHack { + override fun hack(mockApplication: MockApplication) { + if (mockApplication.getService(KlibLoadingMetadataCache::class.java) == null) + mockApplication.registerService(KlibLoadingMetadataCache::class.java, KlibLoadingMetadataCache()) + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt new file mode 100644 index 00000000..13119602 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/IdePluginKDocFinder.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import com.intellij.psi.PsiElement +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.CompilerDescriptorAnalysisPlugin +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.KDocFinder +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.plugin +import org.jetbrains.dokka.plugability.querySingle +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.idea.kdoc.findKDoc +import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils + +internal class IdePluginKDocFinder( + private val context: DokkaContext +) : KDocFinder { + + override fun KtElement.findKDoc(): KDocTag? { + return this.findKDoc { DescriptorToSourceUtils.descriptorToDeclaration(it) }?.contentTag + } + + override fun DeclarationDescriptor.find(descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement?): KDocTag? { + return this.findKDoc(descriptorToPsi)?.contentTag + } + + override fun resolveKDocLink( + fromDescriptor: DeclarationDescriptor, + qualifiedName: String, + sourceSet: DokkaConfiguration.DokkaSourceSet, + emptyBindingContext: Boolean + ): Collection<DeclarationDescriptor> { + val facadeAnalysisContext = context + .plugin<CompilerDescriptorAnalysisPlugin>() + .querySingle { kotlinAnalysis }[sourceSet] as ResolutionFacadeAnalysisContext + + return org.jetbrains.kotlin.idea.kdoc.resolveKDocLink( + context = if (emptyBindingContext) BindingContext.EMPTY else facadeAnalysisContext.resolveSession.bindingContext, + resolutionFacade = facadeAnalysisContext.facade, + fromDescriptor = fromDescriptor, + fromSubjectOfTag = null, + qualifiedName = qualifiedName.split('.'), + contextElement = fromDescriptor.findPsi() + ) + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt new file mode 100644 index 00000000..d70aeb99 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/dokka/analysis/kotlin/descriptors/ide/ResolutionFacadeAnalysisContext.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package org.jetbrains.dokka.analysis.kotlin.descriptors.ide + +import com.intellij.openapi.project.Project +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisContext +import org.jetbrains.dokka.analysis.kotlin.descriptors.compiler.configuration.AnalysisEnvironment +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.resolve.lazy.ResolveSession + +internal class ResolutionFacadeAnalysisContext( + val facade: DokkaResolutionFacade, + + private val kotlinEnvironment: KotlinCoreEnvironment, + private val analysisEnvironment: AnalysisEnvironment +) : AnalysisContext { + private var isClosed: Boolean = false + + override val environment: KotlinCoreEnvironment + get() = kotlinEnvironment.takeUnless { isClosed } + ?: throw IllegalStateException("AnalysisEnvironment is already closed") + + override val resolveSession: ResolveSession = facade.resolveSession + override val moduleDescriptor: ModuleDescriptor = facade.moduleDescriptor + override val project: Project = facade.project + + override fun close() { + isClosed = true + analysisEnvironment.dispose() + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/caches/resolve/KotlinCacheService.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/caches/resolve/KotlinCacheService.kt new file mode 100644 index 00000000..0f1b3ccf --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/caches/resolve/KotlinCacheService.kt @@ -0,0 +1,45 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.caches.resolve + +import com.intellij.openapi.components.ComponentManager +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.resolve.diagnostics.KotlinSuppressCache + +internal interface KotlinCacheService { + companion object { + inline fun <reified T : Any> ComponentManager.service(): T { + val serviceClass = T::class.java + return getService(serviceClass) + ?: error("Cannot find service ${serviceClass.name} in $this (classloader=${serviceClass.classLoader}") + } + + fun getInstance(project: Project): KotlinCacheService = project.service() + } + + /** + * Provides resolution facade for [elements], guaranteeing that the resolution will be seen from the [platform]-perspective. + * + * This allows to get resolution for common sources in MPP from the perspective of given platform (with expects substituted to actuals, + * declarations resolved from platform-specific artifacts, ModuleDescriptors will contain only platform dependencies, etc.) + * + * It is equivalent to usual [getResolutionFacade]-overloads if platform(s) of module(s) containing [elements] are equal to [platform] + * + * Doesn't support scripts or any other 'special' files. + */ + fun getResolutionFacadeWithForcedPlatform(elements: List<KtElement>, platform: TargetPlatform): ResolutionFacade + + fun getResolutionFacade(element: KtElement): ResolutionFacade + fun getResolutionFacade(elements: List<KtElement>): ResolutionFacade + fun getResolutionFacadeByFile(file: PsiFile, platform: TargetPlatform): ResolutionFacade? + + fun getSuppressionCache(): KotlinSuppressCache + fun getResolutionFacadeByModuleInfo(moduleInfo: ModuleInfo, platform: TargetPlatform): ResolutionFacade? + + fun getResolutionFacadeByModuleInfo(moduleInfo: ModuleInfo, settings: PlatformAnalysisSettings): ResolutionFacade? +}
\ No newline at end of file diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/caches/resolve/PlatformAnalysisSettings.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/caches/resolve/PlatformAnalysisSettings.kt new file mode 100644 index 00000000..f828580c --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/caches/resolve/PlatformAnalysisSettings.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2021 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.caches.resolve + +/** + * Regulates which sources should be analyzed together. + * + * There are exactly two descendants, which are in strong one-to-one correspondence with [ResolutionModeComponent.Mode] (meaning + * that after checking value of ResolutionMode, it's safe to downcast settings instance to the respective type): + * - [PlatformAnalysisSettingsImpl] should be used iff we're working under [Mode.SEPARATE], and will create separate + * facade for each platforms, sdk, builtIns settings and other stuff. + * This is the old and stable mode, which should be used by default. + * + * - [CompositeAnalysisSettings] should be used iff we're working under [Mode.COMPOSITE], and will analyze all sources + * together, in one facade. + * This mode is new and experimental, and works only together with TypeRefinement facilities in the compiler's frontend. + * This mode is currently enabled only for HMPP projects + */ +internal interface PlatformAnalysisSettings
\ No newline at end of file diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/caches/resolve/ExtendedResolutionApi.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/caches/resolve/ExtendedResolutionApi.kt new file mode 100644 index 00000000..3d93093f --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/caches/resolve/ExtendedResolutionApi.kt @@ -0,0 +1,78 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.caches.resolve + +import org.jetbrains.kotlin.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import org.jetbrains.kotlin.resolve.lazy.NoDescriptorForDeclarationException + + +internal fun KtElement.getResolutionFacade(): ResolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(this) + +/** + * This function first uses declaration resolvers to resolve this declaration and/or additional declarations (e.g. its parent), + * and then takes the relevant descriptor from binding context. + * The exact set of declarations to resolve depends on bodyResolveMode + */ +internal fun KtDeclaration.resolveToDescriptorIfAny( + resolutionFacade: ResolutionFacade, + bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL +): DeclarationDescriptor? { + //TODO: BodyResolveMode.PARTIAL is not quite safe! + val context = safeAnalyze(resolutionFacade, bodyResolveMode) + return if (this is KtParameter && hasValOrVar()) { + context.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, this) + // It is incorrect to have `val/var` parameters outside the primary constructor (e.g., `fun foo(val x: Int)`) + // but we still want to try to resolve in such cases. + ?: context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, this) + } else { + context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, this) + } +} + +internal fun KtParameter.resolveToParameterDescriptorIfAny(bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL) = + resolveToParameterDescriptorIfAny(getResolutionFacade(), bodyResolveMode) + +internal fun KtParameter.resolveToParameterDescriptorIfAny( + resolutionFacade: ResolutionFacade, + bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL +): ValueParameterDescriptor? { + val context = safeAnalyze(resolutionFacade, bodyResolveMode) + return context.get(BindingContext.VALUE_PARAMETER, this) as? ValueParameterDescriptor +} + +internal fun KtDeclaration.resolveToDescriptorIfAny( + bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL +): DeclarationDescriptor? = + resolveToDescriptorIfAny(getResolutionFacade(), bodyResolveMode) + +internal fun KtElement.analyze( + resolutionFacade: ResolutionFacade, + bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL +): BindingContext = resolutionFacade.analyze(this, bodyResolveMode) + +internal fun KtElement.safeAnalyze( + resolutionFacade: ResolutionFacade, + bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL +): BindingContext = try { + analyze(resolutionFacade, bodyResolveMode) +} catch (e: Exception) { + e.returnIfNoDescriptorForDeclarationException { BindingContext.EMPTY } +} + +internal inline fun <T> Exception.returnIfNoDescriptorForDeclarationException( + crossinline condition: (Boolean) -> Boolean = { v -> v }, + crossinline computable: () -> T +): T = + if (condition(this.isItNoDescriptorForDeclarationException)) { + computable() + } else { + throw this + } + +internal val Exception.isItNoDescriptorForDeclarationException: Boolean + get() = this is NoDescriptorForDeclarationException || (cause as? Exception)?.isItNoDescriptorForDeclarationException == true diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt new file mode 100644 index 00000000..1accf430 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/kdoc/findKDoc.kt @@ -0,0 +1,129 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.kdoc + +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource +import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.api.KDoc +import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildOfType +import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType +import org.jetbrains.kotlin.psi.psiUtil.isPropertyParameter +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly + +internal data class KDocContent( + val contentTag: KDocTag, + val sections: List<KDocSection> +) + +internal fun DeclarationDescriptor.findKDoc( + descriptorToPsi: (DeclarationDescriptorWithSource) -> PsiElement? = { DescriptorToSourceUtils.descriptorToDeclaration(it) } +): KDocContent? { + if (this is DeclarationDescriptorWithSource) { + val psiDeclaration = descriptorToPsi(this)?.navigationElement + return (psiDeclaration as? KtElement)?.findKDoc(descriptorToPsi) + } + return null +} + +private typealias DescriptorToPsi = (DeclarationDescriptorWithSource) -> PsiElement? + +internal fun KtElement.findKDoc(descriptorToPsi: DescriptorToPsi): KDocContent? { + return findKDoc() + ?: this.lookupInheritedKDoc(descriptorToPsi) +} + +internal fun KtElement.findKDoc(): KDocContent? { + return this.lookupOwnedKDoc() + ?: this.lookupKDocInContainer() +} + +private fun KtElement.lookupOwnedKDoc(): KDocContent? { + // KDoc for primary constructor is located inside of its class KDoc + val psiDeclaration = when (this) { + is KtPrimaryConstructor -> getContainingClassOrObject() + else -> this + } + + if (psiDeclaration is KtDeclaration) { + val kdoc = psiDeclaration.docComment + if (kdoc != null) { + if (this is KtConstructor<*>) { + // ConstructorDescriptor resolves to the same JetDeclaration + val constructorSection = kdoc.findSectionByTag(KDocKnownTag.CONSTRUCTOR) + if (constructorSection != null) { + // if annotated with @constructor tag and the caret is on constructor definition, + // then show @constructor description as the main content, and additional sections + // that contain @param tags (if any), as the most relatable ones + // practical example: val foo = Fo<caret>o("argument") -- show @constructor and @param content + val paramSections = kdoc.findSectionsContainingTag(KDocKnownTag.PARAM) + return KDocContent(constructorSection, paramSections) + } + } + return KDocContent(kdoc.getDefaultSection(), kdoc.getAllSections()) + } + } + return null +} + +/** + * Looks for sections that have a deeply nested [tag], + * as opposed to [KDoc.findSectionByTag], which only looks among the top level + */ +private fun KDoc.findSectionsContainingTag(tag: KDocKnownTag): List<KDocSection> { + return getChildrenOfType<KDocSection>() + .filter { it.findTagByName(tag.name.toLowerCaseAsciiOnly()) != null } +} + +private fun KtElement.lookupKDocInContainer(): KDocContent? { + val subjectName = name + val containingDeclaration = + PsiTreeUtil.findFirstParent(this, true) { + it is KtDeclarationWithBody && it !is KtPrimaryConstructor + || it is KtClassOrObject + } + + val containerKDoc = containingDeclaration?.getChildOfType<KDoc>() + if (containerKDoc == null || subjectName == null) return null + val propertySection = containerKDoc.findSectionByTag(KDocKnownTag.PROPERTY, subjectName) + val paramTag = containerKDoc.findDescendantOfType<KDocTag> { it.knownTag == KDocKnownTag.PARAM && it.getSubjectName() == subjectName } + + val primaryContent = when { + // class Foo(val <caret>s: String) + this is KtParameter && this.isPropertyParameter() -> propertySection ?: paramTag + // fun some(<caret>f: String) || class Some<<caret>T: Base> || Foo(<caret>s = "argument") + this is KtParameter || this is KtTypeParameter -> paramTag + // if this property is declared separately (outside primary constructor), but it's for some reason + // annotated as @property in class's description, instead of having its own KDoc + this is KtProperty && containingDeclaration is KtClassOrObject -> propertySection + else -> null + } + return primaryContent?.let { + // makes little sense to include any other sections, since we found + // documentation for a very specific element, like a property/param + KDocContent(it, sections = emptyList()) + } +} + +private fun KtElement.lookupInheritedKDoc(descriptorToPsi: DescriptorToPsi): KDocContent? { + if (this is KtCallableDeclaration) { + val descriptor = this.resolveToDescriptorIfAny() as? CallableDescriptor ?: return null + + for (baseDescriptor in descriptor.overriddenDescriptors) { + val baseKDoc = baseDescriptor.original.findKDoc(descriptorToPsi) + if (baseKDoc != null) { + return baseKDoc + } + } + } + return null +}
\ No newline at end of file diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt new file mode 100644 index 00000000..7e4e0bb5 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt @@ -0,0 +1,311 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +package org.jetbrains.kotlin.idea.kdoc + +import com.intellij.openapi.components.ComponentManager +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.caches.resolve.KotlinCacheService +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.FrontendInternals +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.idea.resolve.frontendService +import org.jetbrains.kotlin.idea.util.CallType +import org.jetbrains.kotlin.idea.util.substituteExtensionIfCallable +import org.jetbrains.kotlin.incremental.components.LookupLocation +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag +import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtPsiFactory +import org.jetbrains.kotlin.psi.KtQualifiedExpression +import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil +import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver +import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension +import org.jetbrains.kotlin.resolve.lazy.FileScopeProvider +import org.jetbrains.kotlin.resolve.scopes.* +import org.jetbrains.kotlin.resolve.scopes.utils.* +import org.jetbrains.kotlin.resolve.source.PsiSourceElement +import org.jetbrains.kotlin.utils.Printer +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.kotlin.utils.addIfNotNull + +/** + * Returns `null` if the service cannot be found in [this] component manager, + * otherwise initializes a service if not yet initialized, and returns the service instance. + * @see ComponentManager.getService + */ +internal inline fun <reified T : Any> ComponentManager.serviceOrNull(): T? { + return getService(T::class.java) +} + +@OptIn(FrontendInternals::class) +internal fun ResolutionFacade.getFileResolutionScope(file: KtFile): LexicalScope { + return frontendService<FileScopeProvider>().getFileResolutionScope(file) +} + +internal fun resolveKDocLink( + context: BindingContext, + resolutionFacade: ResolutionFacade, + fromDescriptor: DeclarationDescriptor, + contextElement: PsiElement?, + fromSubjectOfTag: KDocTag?, + qualifiedName: List<String> +): Collection<DeclarationDescriptor> { + val tag = fromSubjectOfTag?.knownTag + if (tag == KDocKnownTag.PARAM) { + return resolveParamLink(fromDescriptor, qualifiedName) + } + val contextScope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor) + if (qualifiedName.size == 1) { + val localDescriptors = resolveLocal(qualifiedName.single(), contextScope, fromDescriptor) + if (localDescriptors.isNotEmpty()) { + return localDescriptors + } + } + + // [IdeKDocLinkResolutionService] is omitted + + return resolveDefaultKDocLink(context, resolutionFacade, contextElement, qualifiedName, contextScope) +} + +internal fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> { + // TODO resolve parameters of functions passed as parameters + when (fromDescriptor) { + is CallableDescriptor -> { + return fromDescriptor.valueParameters + fromDescriptor.typeParameters + } + + is ClassifierDescriptor -> { + val typeParams = fromDescriptor.typeConstructor.parameters + if (fromDescriptor is ClassDescriptor) { + val constructorDescriptor = fromDescriptor.unsubstitutedPrimaryConstructor + if (constructorDescriptor != null) { + return typeParams + constructorDescriptor.valueParameters + } + } + return typeParams + } + + else -> { + return emptyList() + } + } +} + +private fun resolveParamLink(fromDescriptor: DeclarationDescriptor, qualifiedName: List<String>): List<DeclarationDescriptor> { + val name = qualifiedName.singleOrNull() ?: return emptyList() + return getParamDescriptors(fromDescriptor).filter { it.name.asString() == name } +} + +private fun resolveLocal( + nameToResolve: String, + contextScope: LexicalScope, + fromDescriptor: DeclarationDescriptor +): List<DeclarationDescriptor> { + val shortName = Name.identifier(nameToResolve) + + val descriptorsByName = SmartList<DeclarationDescriptor>() + descriptorsByName.addIfNotNull(contextScope.findClassifier(shortName, NoLookupLocation.FROM_IDE)) + descriptorsByName.addIfNotNull(contextScope.findPackage(shortName)) + descriptorsByName.addAll(contextScope.collectFunctions(shortName, NoLookupLocation.FROM_IDE)) + descriptorsByName.addAll(contextScope.collectVariables(shortName, NoLookupLocation.FROM_IDE)) + + if (fromDescriptor is FunctionDescriptor && fromDescriptor.isExtension && shortName.asString() == "this") { + return listOfNotNull(fromDescriptor.extensionReceiverParameter) + } + + // Try to find a matching local descriptor (parameter or type parameter) first + val localDescriptors = descriptorsByName.filter { it.containingDeclaration == fromDescriptor } + if (localDescriptors.isNotEmpty()) return localDescriptors + + return descriptorsByName +} + +private fun resolveDefaultKDocLink( + context: BindingContext, + resolutionFacade: ResolutionFacade, + contextElement: PsiElement?, + qualifiedName: List<String>, + contextScope: LexicalScope +): Collection<DeclarationDescriptor> { + @OptIn(FrontendInternals::class) + val qualifiedExpressionResolver = resolutionFacade.getFrontendService(QualifiedExpressionResolver::class.java) + + val factory = KtPsiFactory(resolutionFacade.project) + // TODO escape identifiers + val codeFragment = factory.createExpressionCodeFragment(qualifiedName.joinToString("."), contextElement) + val qualifiedExpression = + codeFragment.findElementAt(codeFragment.textLength - 1)?.getStrictParentOfType<KtQualifiedExpression>() ?: return emptyList() + val (descriptor, memberName) = qualifiedExpressionResolver.resolveClassOrPackageInQualifiedExpression( + qualifiedExpression, + contextScope, + context + ) + if (descriptor == null) return emptyList() + if (memberName != null) { + val memberScope = getKDocLinkMemberScope(descriptor, contextScope) + return memberScope.getContributedFunctions(memberName, NoLookupLocation.FROM_IDE) + + memberScope.getContributedVariables(memberName, NoLookupLocation.FROM_IDE) + + listOfNotNull(memberScope.getContributedClassifier(memberName, NoLookupLocation.FROM_IDE)) + } + return listOf(descriptor) +} + +private fun getPackageInnerScope(descriptor: PackageFragmentDescriptor): MemberScope { + return descriptor.containingDeclaration.getPackage(descriptor.fqName).memberScope +} + +private fun getClassInnerScope(outerScope: LexicalScope, descriptor: ClassDescriptor): LexicalScope { + + val headerScope = LexicalScopeImpl( + outerScope, descriptor, false, descriptor.thisAsReceiverParameter, + descriptor.contextReceivers, LexicalScopeKind.SYNTHETIC + ) { + descriptor.declaredTypeParameters.forEach { addClassifierDescriptor(it) } + descriptor.constructors.forEach { addFunctionDescriptor(it) } + } + + return LexicalChainedScope.create( + headerScope, descriptor, false, null, emptyList(), LexicalScopeKind.SYNTHETIC, + descriptor.defaultType.memberScope, + descriptor.staticScope, + descriptor.companionObjectDescriptor?.defaultType?.memberScope + ) +} + +internal fun getKDocLinkResolutionScope(resolutionFacade: ResolutionFacade, contextDescriptor: DeclarationDescriptor): LexicalScope { + return when (contextDescriptor) { + is PackageFragmentDescriptor -> + LexicalScope.Base(getPackageInnerScope(contextDescriptor).memberScopeAsImportingScope(), contextDescriptor) + + is PackageViewDescriptor -> + LexicalScope.Base(contextDescriptor.memberScope.memberScopeAsImportingScope(), contextDescriptor) + + is ClassDescriptor -> + getClassInnerScope(getOuterScope(contextDescriptor, resolutionFacade), contextDescriptor) + + is FunctionDescriptor -> FunctionDescriptorUtil.getFunctionInnerScope( + getOuterScope(contextDescriptor, resolutionFacade), + contextDescriptor, LocalRedeclarationChecker.DO_NOTHING + ) + + is PropertyDescriptor -> + ScopeUtils.makeScopeForPropertyHeader(getOuterScope(contextDescriptor, resolutionFacade), contextDescriptor) + + is DeclarationDescriptorNonRoot -> + getOuterScope(contextDescriptor, resolutionFacade) + + else -> throw IllegalArgumentException("Cannot find resolution scope for root $contextDescriptor") + } +} + +private fun getOuterScope(descriptor: DeclarationDescriptorWithSource, resolutionFacade: ResolutionFacade): LexicalScope { + val parent = descriptor.containingDeclaration!! + if (parent is PackageFragmentDescriptor) { + val containingFile = (descriptor.source as? PsiSourceElement)?.psi?.containingFile as? KtFile ?: return LexicalScope.Base( + ImportingScope.Empty, + parent + ) + val kotlinCacheService = containingFile.project.serviceOrNull<KotlinCacheService>() + val facadeToUse = kotlinCacheService?.getResolutionFacade(containingFile) ?: resolutionFacade + return facadeToUse.getFileResolutionScope(containingFile) + } else { + return getKDocLinkResolutionScope(resolutionFacade, parent) + } +} + +internal fun getKDocLinkMemberScope(descriptor: DeclarationDescriptor, contextScope: LexicalScope): MemberScope { + return when (descriptor) { + is PackageFragmentDescriptor -> getPackageInnerScope(descriptor) + + is PackageViewDescriptor -> descriptor.memberScope + + is ClassDescriptor -> { + ChainedMemberScope.create( + "Member scope for KDoc resolve", listOfNotNull( + descriptor.unsubstitutedMemberScope, + descriptor.staticScope, + descriptor.companionObjectDescriptor?.unsubstitutedMemberScope, + ExtensionsScope(descriptor, contextScope) + ) + ) + } + + else -> MemberScope.Empty + } +} + +private class ExtensionsScope( + private val receiverClass: ClassDescriptor, + private val contextScope: LexicalScope +) : MemberScope { + private val receiverTypes = listOf(receiverClass.defaultType) + + override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> { + return contextScope.collectFunctions(name, location).flatMap { + if (it is SimpleFunctionDescriptor && it.isExtension) { + it.substituteExtensionIfCallable( + receiverTypes = receiverTypes, + callType = CallType.DOT, + ignoreTypeParameters = true, + ) + } else { + emptyList() + } + } + } + + override fun getContributedVariables(name: Name, location: LookupLocation): Collection<PropertyDescriptor> { + return contextScope.collectVariables(name, location).flatMap { + if (it is PropertyDescriptor && it.isExtension) { + it.substituteExtensionIfCallable( + receiverTypes = receiverTypes, + callType = CallType.DOT, + ignoreTypeParameters = true, + ) + } else { + emptyList() + } + } + } + + override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? = null + + override fun getContributedDescriptors( + kindFilter: DescriptorKindFilter, + nameFilter: (Name) -> Boolean + ): Collection<DeclarationDescriptor> { + if (DescriptorKindExclude.Extensions in kindFilter.excludes) return emptyList() + return contextScope.collectDescriptorsFiltered( + kindFilter exclude DescriptorKindExclude.NonExtensions, + nameFilter, + changeNamesForAliased = true + ).flatMap { + if (it is CallableDescriptor && it.isExtension) { + it.substituteExtensionIfCallable( + receiverTypes = receiverTypes, + callType = CallType.DOT, + ignoreTypeParameters = true, + ) + } else { + emptyList() + } + } + } + + override fun getFunctionNames(): Set<Name> = + getContributedDescriptors(kindFilter = DescriptorKindFilter.FUNCTIONS).map { it.name }.toSet() + + override fun getVariableNames(): Set<Name> = + getContributedDescriptors(kindFilter = DescriptorKindFilter.VARIABLES).map { it.name }.toSet() + + override fun getClassifierNames() = null + + override fun printScopeStructure(p: Printer) { + p.println("Extensions for ${receiverClass.name} in:") + contextScope.printStructure(p) + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/CachingIdeKlibMetadataLoader.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/CachingIdeKlibMetadataLoader.kt new file mode 100644 index 00000000..5c7ea8fb --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/CachingIdeKlibMetadataLoader.kt @@ -0,0 +1,56 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.kotlin.idea.klib + +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.annotations.Contract +import org.jetbrains.kotlin.library.KotlinLibrary +import org.jetbrains.kotlin.library.impl.KotlinLibraryImpl +import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf +import org.jetbrains.kotlin.library.metadata.PackageAccessHandler +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.konan.file.File as KFile + + +@Contract("null -> null; !null -> !null") +internal fun toSystemIndependentName(path: String?): String? { + return if (path == null) null else FileUtilRt.toSystemIndependentName(path) +} + +internal object CachingIdeKlibMetadataLoader : PackageAccessHandler { + override fun loadModuleHeader(library: KotlinLibrary): KlibMetadataProtoBuf.Header { + val virtualFile = getVirtualFile(library, library.moduleHeaderFile) + return virtualFile?.let { cache.getCachedModuleHeader(virtualFile) } ?: KlibMetadataProtoBuf.Header.getDefaultInstance() + } + + override fun loadPackageFragment(library: KotlinLibrary, packageFqName: String, partName: String): ProtoBuf.PackageFragment { + val virtualFile = getVirtualFile(library, library.packageFragmentFile(packageFqName, partName)) + return virtualFile?.let { cache.getCachedPackageFragment(virtualFile) } ?: ProtoBuf.PackageFragment.getDefaultInstance() + } + + private fun getVirtualFile(library: KotlinLibrary, file: KFile): VirtualFile? = + if (library.isZipped) asJarFileSystemFile(library.libraryFile, file) else asLocalFile(file) + + private fun asJarFileSystemFile(jarFile: KFile, localFile: KFile): VirtualFile? { + val fullPath = jarFile.absolutePath + "!" + toSystemIndependentName(localFile.path) + return StandardFileSystems.jar().findFileByPath(fullPath) + } + + private fun asLocalFile(localFile: KFile): VirtualFile? { + val fullPath = localFile.absolutePath + return StandardFileSystems.local().findFileByPath(fullPath) + } + + private val cache + get() = KlibLoadingMetadataCache.getInstance() + + private val KotlinLibrary.moduleHeaderFile + get() = (this as KotlinLibraryImpl).metadata.access.layout.moduleHeaderFile + + private fun KotlinLibrary.packageFragmentFile(packageFqName: String, partName: String) = + (this as KotlinLibraryImpl).metadata.access.layout.packageFragmentFile(packageFqName, partName) + + private val KotlinLibrary.isZipped + get() = (this as KotlinLibraryImpl).base.access.layout.isZipped +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/KlibCompatibilityInfo.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/KlibCompatibilityInfo.kt new file mode 100644 index 00000000..4923534d --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/KlibCompatibilityInfo.kt @@ -0,0 +1,48 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:JvmName("KlibCompatibilityInfoUtils") + +package org.jetbrains.kotlin.idea.klib + + +import org.jetbrains.kotlin.library.* +import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion +import org.jetbrains.kotlin.library.metadata.metadataVersion +import java.io.IOException + +/** + * Whether a certain KLIB is compatible for the purposes of IDE: indexation, resolve, etc. + */ +internal sealed class KlibCompatibilityInfo(val isCompatible: Boolean) { + object Compatible : KlibCompatibilityInfo(true) + object Pre14Layout : KlibCompatibilityInfo(false) + class IncompatibleMetadata(val isOlder: Boolean) : KlibCompatibilityInfo(false) +} + + +internal fun <T> KotlinLibrary.safeRead(defaultValue: T, action: KotlinLibrary.() -> T) = try { + action() +} catch (_: IOException) { + defaultValue +} +internal val KotlinLibrary.compatibilityInfo: KlibCompatibilityInfo + get() { + val hasPre14Manifest = safeRead(false) { has_pre_1_4_manifest } + if (hasPre14Manifest) + return KlibCompatibilityInfo.Pre14Layout + + val metadataVersion = safeRead(null) { this.metadataVersion } + @Suppress("DEPRECATION") + return when { + metadataVersion == null -> { + // Too old KLIB format, even doesn't have metadata version + KlibCompatibilityInfo.IncompatibleMetadata(true) + } + + !metadataVersion.isCompatible() -> { + val isOlder = metadataVersion.isAtLeast(KlibMetadataVersion.INSTANCE) + KlibCompatibilityInfo.IncompatibleMetadata(!isOlder) + } + + else -> KlibCompatibilityInfo.Compatible + } + } diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/KlibLoadingMetadataCache.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/KlibLoadingMetadataCache.kt new file mode 100644 index 00000000..55c615f6 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/klib/KlibLoadingMetadataCache.kt @@ -0,0 +1,114 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.klib + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.util.containers.ContainerUtil +import org.jetbrains.kotlin.library.KLIB_MANIFEST_FILE_NAME +import org.jetbrains.kotlin.library.KLIB_METADATA_FILE_EXTENSION +import org.jetbrains.kotlin.library.KLIB_MODULE_METADATA_FILE_NAME +import org.jetbrains.kotlin.library.metadata.KlibMetadataProtoBuf +import org.jetbrains.kotlin.library.metadata.KlibMetadataVersion +import org.jetbrains.kotlin.library.metadata.parseModuleHeader +import org.jetbrains.kotlin.library.metadata.parsePackageFragment +import org.jetbrains.kotlin.library.readKonanLibraryVersioning +import org.jetbrains.kotlin.metadata.ProtoBuf +import org.jetbrains.kotlin.metadata.deserialization.BinaryVersion +import java.io.IOException +import java.util.* + +internal class KlibLoadingMetadataCache { + // Use special CacheKey class instead of VirtualFile for cache keys. Certain types of VirtualFiles (for example, obtained from JarFileSystem) + // do not compare path (url) and modification stamp in equals() method. + private data class CacheKey( + val url: String, + val modificationStamp: Long + ) { + constructor(virtualFile: VirtualFile) : this(virtualFile.url, virtualFile.modificationStamp) + } + + // ConcurrentWeakValueHashMap does not allow null values. + private class CacheValue<T : Any>(val value: T?) + + private val packageFragmentCache = ContainerUtil.createConcurrentWeakValueMap<CacheKey, CacheValue<ProtoBuf.PackageFragment>>() + private val moduleHeaderCache = ContainerUtil.createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataProtoBuf.Header>>() + private val libraryMetadataVersionCache = ContainerUtil.createConcurrentWeakValueMap<CacheKey, CacheValue<KlibMetadataVersion>>() + + fun getCachedPackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? { + check(packageFragmentFile.extension == KLIB_METADATA_FILE_EXTENSION) { + "Not a package metadata file: $packageFragmentFile" + } + + return packageFragmentCache.computeIfAbsent( + CacheKey(packageFragmentFile) + ) { + CacheValue(computePackageFragment(packageFragmentFile)) + }.value + } + + fun getCachedModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? { + check(moduleHeaderFile.name == KLIB_MODULE_METADATA_FILE_NAME) { + "Not a module header file: $moduleHeaderFile" + } + + return moduleHeaderCache.computeIfAbsent( + CacheKey(moduleHeaderFile) + ) { + CacheValue(computeModuleHeader(moduleHeaderFile)) + }.value + } + + private fun isMetadataCompatible(libraryRoot: VirtualFile): Boolean { + val manifestFile = libraryRoot.findChild(KLIB_MANIFEST_FILE_NAME) ?: return false + + val metadataVersion = libraryMetadataVersionCache.computeIfAbsent( + CacheKey(manifestFile) + ) { + CacheValue(computeLibraryMetadataVersion(manifestFile)) + }.value ?: return false + + @Suppress("DEPRECATION") + return metadataVersion.isCompatible() + } + + private fun computePackageFragment(packageFragmentFile: VirtualFile): ProtoBuf.PackageFragment? { + if (!isMetadataCompatible(packageFragmentFile.parent.parent.parent)) + return null + + return try { + parsePackageFragment(packageFragmentFile.contentsToByteArray(false)) + } catch (_: IOException) { + null + } + } + + private fun computeModuleHeader(moduleHeaderFile: VirtualFile): KlibMetadataProtoBuf.Header? { + if (!isMetadataCompatible(moduleHeaderFile.parent.parent)) + return null + + return try { + parseModuleHeader(moduleHeaderFile.contentsToByteArray(false)) + } catch (_: IOException) { + null + } + } + + private fun computeLibraryMetadataVersion(manifestFile: VirtualFile): KlibMetadataVersion? = try { + val versioning = Properties().apply { manifestFile.inputStream.use { load(it) } }.readKonanLibraryVersioning() + versioning.metadataVersion?.let(BinaryVersion.Companion::parseVersionArray)?.let(::KlibMetadataVersion) + } catch (_: IOException) { + // ignore and cache null value + null + } catch (_: IllegalArgumentException) { + // ignore and cache null value + null + } + + companion object { + @JvmStatic + fun getInstance(): KlibLoadingMetadataCache = + ApplicationManager.getApplication().getService(KlibLoadingMetadataCache::class.java) + } + +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/resolve/ResolutionFacade.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/resolve/ResolutionFacade.kt new file mode 100644 index 00000000..c4adb2e3 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/resolve/ResolutionFacade.kt @@ -0,0 +1,61 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.idea.resolve + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.analyzer.ModuleInfo +import org.jetbrains.kotlin.analyzer.ResolverForProject +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.idea.FrontendInternals +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode + +internal interface ResolutionFacade { + val project: Project + + fun analyze(element: KtElement, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): BindingContext + fun analyze(elements: Collection<KtElement>, bodyResolveMode: BodyResolveMode): BindingContext + + fun analyzeWithAllCompilerChecks(element: KtElement, callback: DiagnosticSink.DiagnosticsCallback? = null): AnalysisResult + = analyzeWithAllCompilerChecks(listOf(element), callback) + + fun analyzeWithAllCompilerChecks(elements: Collection<KtElement>, callback: DiagnosticSink.DiagnosticsCallback? = null): AnalysisResult + + fun fetchWithAllCompilerChecks(element: KtElement): AnalysisResult? = null + + fun resolveToDescriptor(declaration: KtDeclaration, bodyResolveMode: BodyResolveMode = BodyResolveMode.FULL): DeclarationDescriptor + + val moduleDescriptor: ModuleDescriptor + + // get service for the module this resolution was created for + @FrontendInternals + fun <T : Any> getFrontendService(serviceClass: Class<T>): T + + fun <T : Any> getIdeService(serviceClass: Class<T>): T + + // get service for the module defined by PsiElement/ModuleDescriptor passed as parameter + @FrontendInternals + fun <T : Any> getFrontendService(element: PsiElement, serviceClass: Class<T>): T + + @FrontendInternals + fun <T : Any> tryGetFrontendService(element: PsiElement, serviceClass: Class<T>): T? + + @Deprecated("DO NOT USE IT AS IT IS A ROOT CAUSE OF KTIJ-17649") + @FrontendInternals + fun <T : Any> getFrontendService(moduleDescriptor: ModuleDescriptor, serviceClass: Class<T>): T + + fun getResolverForProject(): ResolverForProject<out ModuleInfo> +} + +@FrontendInternals +internal inline fun <reified T : Any> ResolutionFacade.frontendService(): T = this.getFrontendService(T::class.java) + +internal inline fun <reified T : Any> ResolutionFacade.ideService(): T = this.getIdeService(T::class.java) diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/CallType.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/CallType.kt new file mode 100644 index 00000000..6d3077e2 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/CallType.kt @@ -0,0 +1,96 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.kotlin.idea.util + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindExclude +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter + + +@Suppress("ClassName") +internal sealed class CallType<TReceiver : KtElement?>(val descriptorKindFilter: DescriptorKindFilter) { + object UNKNOWN : CallType<Nothing?>(DescriptorKindFilter.ALL) + + object DEFAULT : CallType<Nothing?>(DescriptorKindFilter.ALL) + + object DOT : CallType<KtExpression>(DescriptorKindFilter.ALL) + + object SAFE : CallType<KtExpression>(DescriptorKindFilter.ALL) + + object SUPER_MEMBERS : CallType<KtSuperExpression>( + DescriptorKindFilter.CALLABLES exclude DescriptorKindExclude.Extensions exclude AbstractMembersExclude + ) + + object INFIX : CallType<KtExpression>(DescriptorKindFilter.FUNCTIONS exclude NonInfixExclude) + + object OPERATOR : CallType<KtExpression>(DescriptorKindFilter.FUNCTIONS exclude NonOperatorExclude) + + class CallableReference(settings: LanguageVersionSettings) : + CallType<KtExpression?>(DescriptorKindFilter.CALLABLES exclude LocalsAndSyntheticExclude(settings)) { + override fun equals(other: Any?): Boolean = other is CallableReference + override fun hashCode(): Int = javaClass.hashCode() + } + + object IMPORT_DIRECTIVE : CallType<KtExpression?>(DescriptorKindFilter.ALL) + + object PACKAGE_DIRECTIVE : CallType<KtExpression?>(DescriptorKindFilter.PACKAGES) + + object TYPE : CallType<KtExpression?>( + DescriptorKindFilter(DescriptorKindFilter.CLASSIFIERS_MASK or DescriptorKindFilter.PACKAGES_MASK) + exclude DescriptorKindExclude.EnumEntry + ) + + object DELEGATE : CallType<KtExpression?>(DescriptorKindFilter.FUNCTIONS exclude NonOperatorExclude) + + object ANNOTATION : CallType<KtExpression?>( + DescriptorKindFilter(DescriptorKindFilter.CLASSIFIERS_MASK or DescriptorKindFilter.PACKAGES_MASK) + exclude NonAnnotationClassifierExclude + ) + + private object NonInfixExclude : DescriptorKindExclude() { + override fun excludes(descriptor: DeclarationDescriptor) = + !(descriptor is SimpleFunctionDescriptor && descriptor.isInfix) + + override val fullyExcludedDescriptorKinds: Int + get() = 0 + } + + private object NonOperatorExclude : DescriptorKindExclude() { + override fun excludes(descriptor: DeclarationDescriptor) = + !(descriptor is SimpleFunctionDescriptor && descriptor.isOperator) + + override val fullyExcludedDescriptorKinds: Int + get() = 0 + } + + private class LocalsAndSyntheticExclude(private val settings: LanguageVersionSettings) : DescriptorKindExclude() { + // Currently, Kotlin doesn't support references to local variables + // References to Java synthetic properties are supported only since Kotlin 1.9 + override fun excludes(descriptor: DeclarationDescriptor): Boolean = + descriptor !is CallableMemberDescriptor || descriptor.kind == CallableMemberDescriptor.Kind.SYNTHESIZED && + !settings.supportsFeature(LanguageFeature.ReferencesToSyntheticJavaProperties) + + override val fullyExcludedDescriptorKinds: Int + get() = 0 + } + + private object NonAnnotationClassifierExclude : DescriptorKindExclude() { + override fun excludes(descriptor: DeclarationDescriptor): Boolean { + if (descriptor !is ClassifierDescriptor) return false + return descriptor !is ClassDescriptor || descriptor.kind != ClassKind.ANNOTATION_CLASS + } + + override val fullyExcludedDescriptorKinds: Int get() = 0 + } + + private object AbstractMembersExclude : DescriptorKindExclude() { + override fun excludes(descriptor: DeclarationDescriptor) = + descriptor is CallableMemberDescriptor && descriptor.modality == Modality.ABSTRACT + + override val fullyExcludedDescriptorKinds: Int + get() = 0 + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/ExtensionsUtils.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/ExtensionsUtils.kt new file mode 100644 index 00000000..b8a1ae56 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/ExtensionsUtils.kt @@ -0,0 +1,51 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +@file:JvmName("ExtensionUtils") + +package org.jetbrains.kotlin.idea.util + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeSubstitutor +import org.jetbrains.kotlin.types.typeUtil.TypeNullability +import org.jetbrains.kotlin.types.typeUtil.makeNotNullable +import org.jetbrains.kotlin.types.typeUtil.nullability + + +internal fun <TCallable : CallableDescriptor> TCallable.substituteExtensionIfCallable( + receiverTypes: Collection<KotlinType>, + callType: CallType<*>, + ignoreTypeParameters: Boolean = false, +): Collection<TCallable> { + if (!callType.descriptorKindFilter.accepts(this)) return listOf() + + var types = receiverTypes.asSequence() + if (callType == CallType.SAFE) { + types = types.map { it.makeNotNullable() } + } + + val extensionReceiverType = fuzzyExtensionReceiverType()!! + val substitutors = types.mapNotNull { + // NOTE: this creates a fuzzy type for `it` without type parameters + var substitutor = extensionReceiverType.checkIsSuperTypeOf(it) + + // If enabled, we can ignore type parameters in the receiver type, and only check whether the constructors match + if (ignoreTypeParameters && substitutor == null && it.constructor == extensionReceiverType.type.constructor) { + substitutor = TypeSubstitutor.EMPTY + } + + // check if we may fail due to receiver expression being nullable + if (substitutor == null && it.nullability() == TypeNullability.NULLABLE && extensionReceiverType.nullability() == TypeNullability.NOT_NULL) { + substitutor = extensionReceiverType.checkIsSuperTypeOf(it.makeNotNullable()) + } + substitutor + } + + return if (typeParameters.isEmpty()) { // optimization for non-generic callables + if (substitutors.any()) listOf(this) else listOf() + } else { + substitutors + .mapNotNull { @Suppress("UNCHECKED_CAST") (substitute(it) as TCallable?) } + .toList() + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/FuzzyTypeUtils.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/FuzzyTypeUtils.kt new file mode 100644 index 00000000..de0895e6 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/idea/util/FuzzyTypeUtils.kt @@ -0,0 +1,154 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +@file:JvmName("FuzzyTypeUtils") +package org.jetbrains.kotlin.idea.util + +import org.jetbrains.kotlin.descriptors.CallableDescriptor +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor +import org.jetbrains.kotlin.resolve.calls.inference.CallHandle +import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl +import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind +import org.jetbrains.kotlin.types.* +import org.jetbrains.kotlin.types.error.ErrorUtils +import org.jetbrains.kotlin.types.checker.StrictEqualityTypeChecker +import org.jetbrains.kotlin.types.typeUtil.* +import java.util.* + +internal fun CallableDescriptor.fuzzyExtensionReceiverType() = extensionReceiverParameter?.type?.toFuzzyType(typeParameters) + +internal fun FuzzyType.nullability() = type.nullability() + +internal fun KotlinType.toFuzzyType(freeParameters: Collection<TypeParameterDescriptor>) = FuzzyType(this, freeParameters) + +internal class FuzzyType(val type: KotlinType, freeParameters: Collection<TypeParameterDescriptor>) { + val freeParameters: Set<TypeParameterDescriptor> + + init { + if (freeParameters.isNotEmpty()) { + // we allow to pass type parameters from another function with the same original in freeParameters + val usedTypeParameters = HashSet<TypeParameterDescriptor>().apply { addUsedTypeParameters(type) } + if (usedTypeParameters.isNotEmpty()) { + val originalFreeParameters = freeParameters.map { it.toOriginal() }.toSet() + this.freeParameters = usedTypeParameters.filter { it.toOriginal() in originalFreeParameters }.toSet() + } else { + this.freeParameters = emptySet() + } + } else { + this.freeParameters = emptySet() + } + } + + // Diagnostic for EA-109046 + @Suppress("USELESS_ELVIS") + private fun TypeParameterDescriptor.toOriginal(): TypeParameterDescriptor { + val callableDescriptor = containingDeclaration as? CallableMemberDescriptor ?: return this + val original = callableDescriptor.original ?: error("original = null for $callableDescriptor") + val typeParameters = original.typeParameters ?: error("typeParameters = null for $original") + return typeParameters[index] + } + + override fun equals(other: Any?) = other is FuzzyType && other.type == type && other.freeParameters == freeParameters + + override fun hashCode() = type.hashCode() + + private fun MutableSet<TypeParameterDescriptor>.addUsedTypeParameters(type: KotlinType) { + val typeParameter = type.constructor.declarationDescriptor as? TypeParameterDescriptor + if (typeParameter != null && add(typeParameter)) { + typeParameter.upperBounds.forEach { addUsedTypeParameters(it) } + } + + for (argument in type.arguments) { + if (!argument.isStarProjection) { // otherwise we can fall into infinite recursion + addUsedTypeParameters(argument.type) + } + } + } + + fun checkIsSubtypeOf(otherType: FuzzyType): TypeSubstitutor? = matchedSubstitutor(otherType, MatchKind.IS_SUBTYPE) + + fun checkIsSuperTypeOf(otherType: FuzzyType): TypeSubstitutor? = matchedSubstitutor(otherType, + MatchKind.IS_SUPERTYPE + ) + + fun checkIsSubtypeOf(otherType: KotlinType): TypeSubstitutor? = checkIsSubtypeOf(otherType.toFuzzyType(emptyList())) + + fun checkIsSuperTypeOf(otherType: KotlinType): TypeSubstitutor? = checkIsSuperTypeOf(otherType.toFuzzyType(emptyList())) + + private enum class MatchKind { + IS_SUBTYPE, + IS_SUPERTYPE + } + + private fun matchedSubstitutor(otherType: FuzzyType, matchKind: MatchKind): TypeSubstitutor? { + if (type.isError) return null + if (otherType.type.isError) return null + if (otherType.type.isUnit() && matchKind == MatchKind.IS_SUBTYPE) return TypeSubstitutor.EMPTY + + fun KotlinType.checkInheritance(otherType: KotlinType): Boolean { + return when (matchKind) { + MatchKind.IS_SUBTYPE -> this.isSubtypeOf(otherType) + MatchKind.IS_SUPERTYPE -> otherType.isSubtypeOf(this) + } + } + + if (freeParameters.isEmpty() && otherType.freeParameters.isEmpty()) { + return if (type.checkInheritance(otherType.type)) TypeSubstitutor.EMPTY else null + } + + val builder = ConstraintSystemBuilderImpl() + val typeVariableSubstitutor = builder.registerTypeVariables(CallHandle.NONE, freeParameters + otherType.freeParameters) + + val typeInSystem = typeVariableSubstitutor.substitute(type, Variance.INVARIANT) + val otherTypeInSystem = typeVariableSubstitutor.substitute(otherType.type, Variance.INVARIANT) + + when (matchKind) { + MatchKind.IS_SUBTYPE -> + builder.addSubtypeConstraint(typeInSystem, otherTypeInSystem, ConstraintPositionKind.RECEIVER_POSITION.position()) + MatchKind.IS_SUPERTYPE -> + builder.addSubtypeConstraint(otherTypeInSystem, typeInSystem, ConstraintPositionKind.RECEIVER_POSITION.position()) + } + + builder.fixVariables() + + val constraintSystem = builder.build() + + if (constraintSystem.status.hasContradiction()) return null + + // currently ConstraintSystem return successful status in case there are problems with nullability + // that's why we have to check subtyping manually + val substitutor = constraintSystem.resultingSubstitutor + val substitutedType = substitutor.substitute(type, Variance.INVARIANT) ?: return null + if (substitutedType.isError) return TypeSubstitutor.EMPTY + val otherSubstitutedType = substitutor.substitute(otherType.type, Variance.INVARIANT) ?: return null + if (otherSubstitutedType.isError) return TypeSubstitutor.EMPTY + if (!substitutedType.checkInheritance(otherSubstitutedType)) return null + + val substitutorToKeepCapturedTypes = object : DelegatedTypeSubstitution(substitutor.substitution) { + override fun approximateCapturedTypes() = false + }.buildSubstitutor() + + val substitutionMap: Map<TypeConstructor, TypeProjection> = constraintSystem.typeVariables + .map { it.originalTypeParameter } + .associateBy( + keySelector = { it.typeConstructor }, + valueTransform = { parameterDescriptor -> + val typeProjection = TypeProjectionImpl(Variance.INVARIANT, parameterDescriptor.defaultType) + val substitutedProjection = substitutorToKeepCapturedTypes.substitute(typeProjection) + substitutedProjection?.takeUnless { ErrorUtils.containsUninferredTypeVariable(it.type) } ?: typeProjection + }) + return TypeConstructorSubstitution.createByConstructorsMap(substitutionMap, approximateCapturedTypes = true).buildSubstitutor() + } +} + + +internal fun TypeSubstitution.hasConflictWith(other: TypeSubstitution, freeParameters: Collection<TypeParameterDescriptor>): Boolean { + return freeParameters.any { parameter -> + val type = parameter.defaultType + val substituted1 = this[type] ?: return@any false + val substituted2 = other[type] ?: return@any false + !StrictEqualityTypeChecker.strictEqualTypes( + substituted1.type.unwrap(), + substituted2.type.unwrap() + ) || substituted1.projectionKind != substituted2.projectionKind + } +}
\ No newline at end of file diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/resolve/lazy/BodyResolveMode.kt b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/resolve/lazy/BodyResolveMode.kt new file mode 100644 index 00000000..d5c4b745 --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/kotlin/org/jetbrains/kotlin/resolve/lazy/BodyResolveMode.kt @@ -0,0 +1,30 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + +package org.jetbrains.kotlin.resolve.lazy + +import org.jetbrains.kotlin.resolve.BindingTraceFilter + +internal enum class BodyResolveMode(val bindingTraceFilter: BindingTraceFilter, val doControlFlowAnalysis: Boolean, val resolveAdditionals: Boolean = true) { + // All body statements are analyzed, diagnostics included + FULL(BindingTraceFilter.ACCEPT_ALL, doControlFlowAnalysis = true), + + // Analyzes only dependent statements, including all declaration statements (difference from PARTIAL_WITH_CFA) + PARTIAL_FOR_COMPLETION(BindingTraceFilter.NO_DIAGNOSTICS, doControlFlowAnalysis = true), + + // Analyzes only dependent statements, diagnostics included + PARTIAL_WITH_DIAGNOSTICS(BindingTraceFilter.ACCEPT_ALL, doControlFlowAnalysis = true), + + // Analyzes only dependent statements, performs control flow analysis (mostly needed for isUsedAsExpression / AsStatement) + PARTIAL_WITH_CFA(BindingTraceFilter.NO_DIAGNOSTICS, doControlFlowAnalysis = true), + + // Analyzes only dependent statements, including only used declaration statements, does not perform control flow analysis + PARTIAL(BindingTraceFilter.NO_DIAGNOSTICS, doControlFlowAnalysis = false), + + // Resolve mode to resolve only the element itself without the additional elements (annotation resolve would not lead to function resolve or default parameters) + PARTIAL_NO_ADDITIONAL(BindingTraceFilter.NO_DIAGNOSTICS, doControlFlowAnalysis = false, resolveAdditionals = false) + ; + + fun doesNotLessThan(other: BodyResolveMode): Boolean { + return this <= other && this.bindingTraceFilter.includesEverythingIn(other.bindingTraceFilter) + } +} diff --git a/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin new file mode 100644 index 00000000..ceff7e6c --- /dev/null +++ b/dokka-subprojects/analysis-kotlin-descriptors-ide/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin @@ -0,0 +1,5 @@ +# +# Copyright 2014-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. +# + +org.jetbrains.dokka.analysis.kotlin.descriptors.ide.IdeDescriptorAnalysisPlugin |