diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt index 8cb297ec7d7..0d4e8d9f433 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt @@ -463,13 +463,44 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable QualifiedExpressionResolveResult(result, null) + path.size - 1 -> QualifiedExpressionResolveResult(result, path[index].name) + else -> QualifiedExpressionResolveResult.UNRESOLVED + } + } + fun resolveQualifierInExpressionAndUnroll( expression: KtQualifiedExpression, context: ExpressionTypingContext, isValue: (KtSimpleNameExpression) -> Boolean ): List { val qualifiedExpressions = unrollToLeftMostQualifiedExpression(expression) - val maxPossibleQualifierPrefix = getMaxPossibleQualifierPrefix(qualifiedExpressions) + val maxPossibleQualifierPrefix = mapToQualifierParts(qualifiedExpressions, 1) val nextIndexAfterPrefix = resolveToPackageOrClassPrefix( path = maxPossibleQualifierPrefix, @@ -489,7 +520,8 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable) : List { + private fun mapToQualifierParts(qualifiedExpressions: List, + skipLast: Int): List { if (qualifiedExpressions.isEmpty()) return emptyList() val first = qualifiedExpressions.first() @@ -508,7 +540,7 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable() qualifierParts.add(QualifierPart(firstReceiver)) - for (qualifiedExpression in qualifiedExpressions.dropLast(1)) { + for (qualifiedExpression in qualifiedExpressions.dropLast(skipLast)) { if (qualifiedExpression !is KtDotQualifiedExpression) break val selector = qualifiedExpression.selectorExpression if (selector !is KtSimpleNameExpression) break diff --git a/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt b/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt index 8708f5005e3..348b99f7f82 100644 --- a/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt +++ b/idea/ide-common/src/org/jetbrains/kotlin/idea/kdoc/resolveKDocLink.kt @@ -22,44 +22,54 @@ import org.jetbrains.kotlin.idea.util.getFileResolutionScope import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag 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.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.FunctionDescriptorUtil +import org.jetbrains.kotlin.resolve.QualifiedExpressionResolver +import org.jetbrains.kotlin.resolve.descriptorUtil.module import org.jetbrains.kotlin.resolve.scopes.* import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope import org.jetbrains.kotlin.resolve.source.PsiSourceElement -fun resolveKDocLink(resolutionFacade: ResolutionFacade, +fun resolveKDocLink(context: BindingContext, + resolutionFacade: ResolutionFacade, fromDescriptor: DeclarationDescriptor, fromSubjectOfTag: KDocTag?, qualifiedName: List): Collection { + if (fromSubjectOfTag?.knownTag == KDocKnownTag.PARAM) { return resolveParamLink(fromDescriptor, qualifiedName) } - // Try to find a matching local descriptor (parameter or type parameter) first. - if (qualifiedName.size == 1) { - val localResult = resolveInLocalScope(fromDescriptor, qualifiedName.single(), resolutionFacade) - if (!localResult.isEmpty()) { - return localResult - } - } - - var result: Collection = listOf(fromDescriptor) - qualifiedName.forEach { nameComponent -> - val scope = getKDocLinkResolutionScope(resolutionFacade, result.singleOrNull() ?: return emptyList()) - result = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == nameComponent}) - } - - return result -} - -private fun resolveInLocalScope(fromDescriptor: DeclarationDescriptor, - name: String, - resolutionFacade: ResolutionFacade): List { val scope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor) - return scope.collectDescriptorsFiltered(nameFilter = { it.asString() == name }).filter { - it.containingDeclaration == fromDescriptor + + if (qualifiedName.size == 1) { + val descriptorsByName = scope.collectDescriptorsFiltered(nameFilter = { it.asString() == qualifiedName.single() }) + // 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 } + + val moduleDescriptor = fromDescriptor.module + val qualifiedExpressionResolver = resolutionFacade.getFrontendService(moduleDescriptor, QualifiedExpressionResolver::class.java) + + val contextElement = DescriptorToSourceUtils.descriptorToDeclaration(fromDescriptor) + val factory = KtPsiFactory(resolutionFacade.project) + // TODO escape identifiers + val codeFragment = factory.createExpressionCodeFragment(qualifiedName.joinToString("."), contextElement) + val qualifiedExpression = codeFragment.findElementAt(codeFragment.textLength - 1)?.getStrictParentOfType() ?: return emptyList() + val (descriptor, memberName) = qualifiedExpressionResolver.resolveClassOrPackageInQualifiedExpression(qualifiedExpression, scope, context) + if (descriptor == null) return emptyList() + if (memberName != null) { + val memberScope = getKDocLinkResolutionScope(resolutionFacade, descriptor) + return memberScope.collectDescriptorsFiltered(nameFilter = { it == memberName }) + } + return listOf(descriptor) } fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List { diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt index d04ddfb358c..0c2ea582824 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/kdoc/KDocReference.kt @@ -32,7 +32,8 @@ class KDocReference(element: KDocName): KtMultiReference(element) { val declarationDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] ?: return arrayListOf() val kdocLink = element.getStrictParentOfType()!! - return resolveKDocLink(element.getResolutionFacade(), + return resolveKDocLink(context, + element.getResolutionFacade(), declarationDescriptor, kdocLink.getTagIfSubject(), element.getQualifiedName()) diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt index 66b1e7c4fdf..2b57c059b44 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt @@ -59,11 +59,9 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() { } val bindingContext = context.analyze(BodyResolveMode.PARTIAL) - val contextDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, context] - if (contextDescriptor == null) { - return null - } - val descriptors = resolveKDocLink(context.getResolutionFacade(), contextDescriptor, null, StringUtil.split(link, ",")) + val contextDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, context] ?: return null + val descriptors = resolveKDocLink(bindingContext, context.getResolutionFacade(), + contextDescriptor, null, StringUtil.split(link, ",")) val target = descriptors.firstOrNull() if (target is DeclarationDescriptorWithSource) { val source = target.source diff --git a/idea/testData/kdoc/resolve/QualifiedNameFunctionReference.kt b/idea/testData/kdoc/resolve/QualifiedNameFunctionReference.kt new file mode 100644 index 00000000000..58caf519aec --- /dev/null +++ b/idea/testData/kdoc/resolve/QualifiedNameFunctionReference.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +/** + * Contains [kotlin.require] + */ + +class C { + +} + +// REF: (kotlin).require(kotlin.Boolean) diff --git a/idea/testData/kdoc/resolve/QualifiedNameReference.kt b/idea/testData/kdoc/resolve/QualifiedNameReference.kt new file mode 100644 index 00000000000..bdfe4c71839 --- /dev/null +++ b/idea/testData/kdoc/resolve/QualifiedNameReference.kt @@ -0,0 +1,11 @@ +// WITH_RUNTIME + +/** + * Contains [kotlin.collections.Collection] + */ + +class C { + +} + +// REF: (kotlin.collections).Collection diff --git a/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java index d06033a2cad..596186d13c1 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/kdoc/KdocResolveTestGenerated.java @@ -102,6 +102,18 @@ public class KdocResolveTestGenerated extends AbstractReferenceResolveTest { doTest(fileName); } + @TestMetadata("QualifiedNameFunctionReference.kt") + public void testQualifiedNameFunctionReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/QualifiedNameFunctionReference.kt"); + doTest(fileName); + } + + @TestMetadata("QualifiedNameReference.kt") + public void testQualifiedNameReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/QualifiedNameReference.kt"); + doTest(fileName); + } + @TestMetadata("SeeReference.kt") public void testSeeReference() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/kdoc/resolve/SeeReference.kt");