From 84f8d4d315f30cec46b4f50bbeafe834215dd057 Mon Sep 17 00:00:00 2001 From: Tianyu Geng Date: Wed, 23 Jun 2021 18:29:39 -0700 Subject: [PATCH] FIR IDE: handle reference to package and outer classes For a qualified name like `foo.bar.Outer.Inner`, FIR represents it as one atomic FIR element. Hence, to properly resolve these names to the corresponding package and class, we need some additional work. --- .../fir/declarations/FirDeclarationUtil.kt | 6 + .../AbstractFirReferenceResolveTest.kt | 11 -- .../FirReferenceResolveTestGenerated.java | 48 ++++++ .../references/FirReferenceResolveHelper.kt | 156 ++++++++++++++---- .../kotlin/idea/search/searchUtil.kt | 6 +- .../kotlin/test/utils/IgnoreTests.kt | 1 + .../kotlin/companionObject/named.0.kt | 1 + .../kotlin/companionObject/simple.0.kt | 1 + .../kotlinLocalObjectUsages1.0.kt | 1 + .../kotlinLocalObjectUsages1.results.txt | 2 +- .../kotlinLocalObjectUsages2.0.kt | 1 + .../kotlinLocalObjectUsages2.results.txt | 2 +- .../TopLevelClassVsLocalClassQualifier.kt | 2 - .../TopLevelObjectVsLocalClassQualifier.kt | 1 + ...veFirstPackageOfFullyQualifiedReference.kt | 13 ++ .../ResolveFullyQualifiedCompanionObject.kt | 14 ++ ...olveOuterClassOfFullyQualifiedReference.kt | 13 ++ ...ResolvePackageOfFullyQualifiedReference.kt | 13 ++ .../qualifiedAccess/callableReference1.kt | 11 ++ .../qualifiedAccess/callableReference2.kt | 14 ++ .../qualifiedAccess/callableReference3.kt | 14 ++ .../resolve/AbstractReferenceResolveTest.kt | 11 +- .../ReferenceResolveTestGenerated.java | 48 ++++++ 23 files changed, 332 insertions(+), 58 deletions(-) create mode 100644 idea/testData/resolve/references/qualifiedAccess/ResolveFirstPackageOfFullyQualifiedReference.kt create mode 100644 idea/testData/resolve/references/qualifiedAccess/ResolveFullyQualifiedCompanionObject.kt create mode 100644 idea/testData/resolve/references/qualifiedAccess/ResolveOuterClassOfFullyQualifiedReference.kt create mode 100644 idea/testData/resolve/references/qualifiedAccess/ResolvePackageOfFullyQualifiedReference.kt create mode 100644 idea/testData/resolve/references/qualifiedAccess/callableReference1.kt create mode 100644 idea/testData/resolve/references/qualifiedAccess/callableReference2.kt create mode 100644 idea/testData/resolve/references/qualifiedAccess/callableReference3.kt diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt index c12516cfcc1..f5b514f2b21 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt @@ -116,6 +116,12 @@ fun FirRegularClassBuilder.addDeclarations(declarations: Collection.classId + get() = when (this) { + is FirClass<*> -> symbol.classId + is FirTypeAlias -> symbol.classId + } + val FirClass<*>.classId get() = symbol.classId val FirClassSymbol<*>.superConeTypes diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/AbstractFirReferenceResolveTest.kt b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/AbstractFirReferenceResolveTest.kt index 56b0d00720a..a72286ff085 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/AbstractFirReferenceResolveTest.kt +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/AbstractFirReferenceResolveTest.kt @@ -5,13 +5,11 @@ package org.jetbrains.kotlin.idea.fir.resolve -import org.jetbrains.kotlin.idea.completion.test.configureWithExtraFile import org.jetbrains.kotlin.idea.fir.invalidateCaches import org.jetbrains.kotlin.idea.resolve.AbstractReferenceResolveTest import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.test.utils.IgnoreTests abstract class AbstractFirReferenceResolveTest : AbstractReferenceResolveTest() { override fun isFirPlugin(): Boolean = true @@ -23,13 +21,4 @@ abstract class AbstractFirReferenceResolveTest : AbstractReferenceResolveTest() project.invalidateCaches(myFixture.file as? KtFile) super.tearDown() } - - override fun doTest(path: String) { - assert(path.endsWith(".kt")) { path } - myFixture.configureWithExtraFile(path, ".Data") - - IgnoreTests.runTestIfNotDisabledByFileDirective(testDataFile().toPath(), IgnoreTests.DIRECTIVES.IGNORE_FIR) { - performChecks() - } - } } \ No newline at end of file diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/FirReferenceResolveTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/FirReferenceResolveTestGenerated.java index 7afe75870ea..606076f982a 100644 --- a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/FirReferenceResolveTestGenerated.java +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/fir/resolve/FirReferenceResolveTestGenerated.java @@ -957,4 +957,52 @@ public class FirReferenceResolveTestGenerated extends AbstractFirReferenceResolv runTest("idea/testData/resolve/references/packageReference/kotlinPackageSecondQualifier.kt"); } } + + @TestMetadata("idea/testData/resolve/references/qualifiedAccess") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class QualifiedAccess extends AbstractFirReferenceResolveTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInQualifiedAccess() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/resolve/references/qualifiedAccess"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @TestMetadata("callableReference1.kt") + public void testCallableReference1() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/callableReference1.kt"); + } + + @TestMetadata("callableReference2.kt") + public void testCallableReference2() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/callableReference2.kt"); + } + + @TestMetadata("callableReference3.kt") + public void testCallableReference3() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/callableReference3.kt"); + } + + @TestMetadata("ResolveFirstPackageOfFullyQualifiedReference.kt") + public void testResolveFirstPackageOfFullyQualifiedReference() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolveFirstPackageOfFullyQualifiedReference.kt"); + } + + @TestMetadata("ResolveFullyQualifiedCompanionObject.kt") + public void testResolveFullyQualifiedCompanionObject() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolveFullyQualifiedCompanionObject.kt"); + } + + @TestMetadata("ResolveOuterClassOfFullyQualifiedReference.kt") + public void testResolveOuterClassOfFullyQualifiedReference() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolveOuterClassOfFullyQualifiedReference.kt"); + } + + @TestMetadata("ResolvePackageOfFullyQualifiedReference.kt") + public void testResolvePackageOfFullyQualifiedReference() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolvePackageOfFullyQualifiedReference.kt"); + } + } } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt index 71a036cc16d..a593da68de6 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/references/FirReferenceResolveHelper.kt @@ -69,7 +69,7 @@ internal object FirReferenceResolveHelper { private fun ClassId.toTargetPsi( session: FirSession, symbolBuilder: KtSymbolByFirBuilder, - calleeReference: FirReference? = null + calleeReference: FirReference? = null, ): KtSymbol? { val classLikeDeclaration = ConeClassLikeLookupTagImpl(this).toSymbol(session)?.fir if (classLikeDeclaration is FirRegularClass) { @@ -178,7 +178,7 @@ internal object FirReferenceResolveHelper { return when (fir) { is FirResolvedTypeRef -> getSymbolsForResolvedTypeRef(fir, expression, session, symbolBuilder) is FirResolvedQualifier -> - getSymbolsForResolvedQualifier(fir, expression, session, symbolBuilder, analysisSession) + getSymbolsForResolvedQualifier(fir, expression, session, symbolBuilder) is FirAnnotationCall -> getSymbolsForAnnotationCall(fir, session, symbolBuilder) is FirResolvedImport -> getSymbolsByResolvedImport(expression, symbolBuilder, fir, session) is FirFile -> getSymbolsByFirFile(expression, symbolBuilder, fir) @@ -374,42 +374,126 @@ internal object FirReferenceResolveHelper { fir: FirResolvedQualifier, expression: KtSimpleNameExpression, session: FirSession, - symbolBuilder: KtSymbolByFirBuilder, - analysisSession: KtFirAnalysisSession + symbolBuilder: KtSymbolByFirBuilder ): Collection { - // TODO refactor that block - val classId = fir.classId ?: return listOfNotNull(getPackageSymbolFor(expression, symbolBuilder, forQualifiedType = false)) - - var parent = expression.parent as? KtDotQualifiedExpression - // Distinguish A.foo() from A(.Companion).foo() - // Make expression.parent as? KtDotQualifiedExpression local function - while (parent != null) { - val selectorExpression = parent.selectorExpression ?: break - if (selectorExpression === expression) { - parent = parent.parent as? KtDotQualifiedExpression - continue - } - val receiverClassId = if (parent.receiverExpression == expression) { - /* - * A.Named.i -> class A - */ - val name = fir.relativeClassFqName?.pathSegments()?.firstOrNull() - name?.let { ClassId(fir.packageFqName, it) } - } else null - val parentFir = selectorExpression.getOrBuildFir(analysisSession.firResolveState) - when { - parentFir is FirQualifiedAccess -> { - return listOfNotNull( - (receiverClassId ?: classId).toTargetPsi(session, symbolBuilder, parentFir.calleeReference) - ) - } - receiverClassId != null -> { - return listOfNotNull(receiverClassId.toTargetPsi(session, symbolBuilder)) - } - else -> parent = parent.parent as? KtDotQualifiedExpression - } + val referencedSymbol = if (fir.resolvedToCompanionObject) { + (fir.symbol?.fir as? FirRegularClass)?.companionObject?.symbol + } else { + fir.symbol } - return listOfNotNull(classId.toTargetPsi(session, symbolBuilder)) + if (referencedSymbol == null) { + // If referencedSymbol is null, it means the reference goes to a package. + val parent = expression.parent as? KtDotQualifiedExpression ?: return emptyList() + val fqNameSegments = + when (expression) { + parent.selectorExpression -> parent.fqNameSegments() ?: return emptyList() + parent.receiverExpression -> listOf(expression.getReferencedName()) + else -> return emptyList() + } + return listOfNotNull(symbolBuilder.createPackageSymbolIfOneExists(FqName.fromSegments(fqNameSegments))) + } + val referencedClass = referencedSymbol.fir + val referencedSymbolsByFir = listOfNotNull(symbolBuilder.buildSymbol(referencedClass)) + val firSourcePsi = fir.source.psi ?: referencedSymbolsByFir + // The source of an `FirResolvedQualifier` is either a KtNamedReferenceExpression or a KtDotQualifiedExpression. In the former case, + // it implies the qualifier is an atomic reference and therefore, it should be identical with the `expression`. In the latter case, + // we need to manually break up the qualified access and resolve individual parts of it because in FIR, the entire qualified access + // is one element. + if (firSourcePsi === expression) return referencedSymbolsByFir + require(firSourcePsi is KtDotQualifiedExpression) + + if (referencedClass.isLocal) { + // TODO: handle local classes after KT-47135 is fixed + return referencedSymbolsByFir + } else { + var qualifiedAccess: KtDotQualifiedExpression = firSourcePsi + val referencedClassId = + if ((referencedClass as? FirRegularClass)?.isCompanion == true && + (qualifiedAccess.selectorExpression as? KtNameReferenceExpression)?.getReferencedName() != "Companion" + ) { + // Remove the last "Companion" part if the qualified access does not contain it. This is needed because the "Companion" + // part is optional. + referencedClass.classId.outerClassId ?: return referencedSymbolsByFir + } else { + referencedClass.classId + } + val qualifiedAccessSegments = qualifiedAccess.fqNameSegments() ?: return referencedSymbolsByFir + assert(referencedClassId.asSingleFqName().pathSegments().takeLast(qualifiedAccessSegments.size) + .map { it.identifierOrNullIfSpecial } == qualifiedAccessSegments) { + "Referenced classId $referencedClassId should end with qualifiedAccess expression ${qualifiedAccess.text} " + } + + // In the code below, we always maintain the contract that `classId` and `qualifiedAccess` should stay "in-sync", i.e. they + // refer to the same class and classId should be null if `qualifiedAccess` references to a package. + var classId: ClassId? = referencedClassId + + // Handle nested classes. + while (classId != null) { + if (expression === qualifiedAccess.selectorExpression) { + return listOfNotNull(classId.toTargetPsi(session, symbolBuilder)) + } + val outerClassId = classId.outerClassId + val receiverExpression = qualifiedAccess.receiverExpression + if (receiverExpression !is KtDotQualifiedExpression) { + // If the receiver is not a KtDotQualifiedExpression, it means we are hitting the end of nested receivers. In other + // words, this receiver expression should be pointing at an unqualified name of a class, whose class ID is + // `outerClassId`. + if (receiverExpression == expression) { + // If there is still an outer class, then return symbol of that class + outerClassId?.let { return listOfNotNull(it.toTargetPsi(session, symbolBuilder)) } + // Otherwise, it should be a package, so we return that + return listOfNotNull(symbolBuilder.createPackageSymbolIfOneExists(classId.packageFqName)) + } else { + // This is unexpected. The code probably contains some weird structures. In this case, we just fail the resolution + // with zero results. + return emptyList() + } + } + qualifiedAccess = receiverExpression + classId = outerClassId + } + + // Handle package names + var packageFqName = referencedClassId.packageFqName + + while (!packageFqName.isRoot) { + if (expression === qualifiedAccess.selectorExpression) { + return listOfNotNull(symbolBuilder.createPackageSymbolIfOneExists(packageFqName)) + } + val parentPackageFqName = packageFqName.parent() + val receiverExpression = qualifiedAccess.receiverExpression + if (receiverExpression !is KtDotQualifiedExpression) { + // If the receiver is not a KtDotQualifiedExpression, it means we are hitting the end of nested receivers. In other + // words, this receiver expression should be pointing at a top-level package now. + if (receiverExpression == expression) { + return listOfNotNull(symbolBuilder.createPackageSymbolIfOneExists(parentPackageFqName)) + } else { + // This is unexpected. The code probably contains some weird structures. In this case, we just fail the resolution + // with zero results. + return emptyList() + } + } + qualifiedAccess = receiverExpression + packageFqName = parentPackageFqName + } + return referencedSymbolsByFir + } + } + + /** + * Returns the segments of a qualified access PSI. For example, given `foo.bar.OuterClass.InnerClass`, this returns `["foo", "bar", + * "OuterClass", "InnerClass"]`. + */ + private fun KtDotQualifiedExpression.fqNameSegments(): List? { + val result: MutableList = mutableListOf() + var current: KtExpression = this + while (current is KtDotQualifiedExpression) { + result += (current.selectorExpression as? KtNameReferenceExpression)?.getReferencedName() ?: return null + current = current.receiverExpression + } + result += (current as? KtNameReferenceExpression)?.getReferencedName() ?: return null + result.reverse() + return result } private fun getSymbolsForAnnotationCall( diff --git a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt index 651e6ad7e61..5245235a084 100644 --- a/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt +++ b/idea/idea-frontend-independent/src/org/jetbrains/kotlin/idea/search/searchUtil.kt @@ -37,13 +37,9 @@ import org.jetbrains.kotlin.idea.search.KotlinSearchUsagesSupport.Companion.scri import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.KtFile -import org.jetbrains.kotlin.psi.KtImportDirective -import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.types.expressions.OperatorConventions -import java.util.* infix fun SearchScope.and(otherScope: SearchScope): SearchScope = intersectWith(otherScope) infix fun SearchScope.or(otherScope: SearchScope): SearchScope = union(otherScope) diff --git a/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt b/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt index 4994e734de1..5dbb09be563 100644 --- a/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt +++ b/idea/idea-frontend-independent/tests/org/jetbrains/kotlin/test/utils/IgnoreTests.kt @@ -218,6 +218,7 @@ object IgnoreTests { const val FIR_IDENTICAL = "// FIR_IDENTICAL" const val IGNORE_FE10_BINDING_BY_FIR = "// IGNORE_FE10_BINDING_BY_FIR" + const val IGNORE_FE10 = "// IGNORE_FE10" } enum class DirectivePosition { diff --git a/idea/testData/findUsages/kotlin/companionObject/named.0.kt b/idea/testData/findUsages/kotlin/companionObject/named.0.kt index f33d970177c..b6cff595d04 100644 --- a/idea/testData/findUsages/kotlin/companionObject/named.0.kt +++ b/idea/testData/findUsages/kotlin/companionObject/named.0.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON // PSI_ELEMENT: org.jetbrains.kotlin.psi.KtObjectDeclaration // OPTIONS: usages class Foo { diff --git a/idea/testData/findUsages/kotlin/companionObject/simple.0.kt b/idea/testData/findUsages/kotlin/companionObject/simple.0.kt index 782d1d15ddd..9c3f56e480a 100644 --- a/idea/testData/findUsages/kotlin/companionObject/simple.0.kt +++ b/idea/testData/findUsages/kotlin/companionObject/simple.0.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON // PSI_ELEMENT: org.jetbrains.kotlin.psi.KtObjectDeclaration // OPTIONS: usages class Foo { diff --git a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.0.kt b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.0.kt index 47dd973c842..c1199eb115a 100644 --- a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.0.kt +++ b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.0.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON // PSI_ELEMENT: org.jetbrains.kotlin.psi.KtObjectDeclaration // OPTIONS: usages fun foo(): Any { diff --git a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.results.txt b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.results.txt index 6cf46b74dc1..4c1da4283d0 100644 --- a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.results.txt +++ b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages1.results.txt @@ -1 +1 @@ -Value read 6 return Bar +Value read 7 return Bar diff --git a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.0.kt b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.0.kt index fdd8e151710..079e73d806a 100644 --- a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.0.kt +++ b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.0.kt @@ -1,3 +1,4 @@ +// FIR_COMPARISON // PSI_ELEMENT: org.jetbrains.kotlin.psi.KtObjectDeclaration // OPTIONS: usages fun foo(): Any { diff --git a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.results.txt b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.results.txt index 4c1da4283d0..a0bfcfe4645 100644 --- a/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.results.txt +++ b/idea/testData/findUsages/kotlin/findObjectUsages/kotlinLocalObjectUsages2.results.txt @@ -1 +1 @@ -Value read 7 return Bar +Value read 8 return Bar diff --git a/idea/testData/resolve/references/TopLevelClassVsLocalClassQualifier.kt b/idea/testData/resolve/references/TopLevelClassVsLocalClassQualifier.kt index 7c59f471bc4..bcfd753bfec 100644 --- a/idea/testData/resolve/references/TopLevelClassVsLocalClassQualifier.kt +++ b/idea/testData/resolve/references/TopLevelClassVsLocalClassQualifier.kt @@ -1,5 +1,3 @@ -// IGNORE_FIR - package test class Conflict diff --git a/idea/testData/resolve/references/TopLevelObjectVsLocalClassQualifier.kt b/idea/testData/resolve/references/TopLevelObjectVsLocalClassQualifier.kt index 8567aa99d58..33e2d97c45b 100644 --- a/idea/testData/resolve/references/TopLevelObjectVsLocalClassQualifier.kt +++ b/idea/testData/resolve/references/TopLevelObjectVsLocalClassQualifier.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR package test object Conflict diff --git a/idea/testData/resolve/references/qualifiedAccess/ResolveFirstPackageOfFullyQualifiedReference.kt b/idea/testData/resolve/references/qualifiedAccess/ResolveFirstPackageOfFullyQualifiedReference.kt new file mode 100644 index 00000000000..269a1dc9fe4 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/ResolveFirstPackageOfFullyQualifiedReference.kt @@ -0,0 +1,13 @@ +package foo.bar.baz + +class AA { + class BB { + companion object + } +} + +fun test() { + val b = foo.bar.baz.AA.BB +} + +// REF: foo diff --git a/idea/testData/resolve/references/qualifiedAccess/ResolveFullyQualifiedCompanionObject.kt b/idea/testData/resolve/references/qualifiedAccess/ResolveFullyQualifiedCompanionObject.kt new file mode 100644 index 00000000000..de4640d74d0 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/ResolveFullyQualifiedCompanionObject.kt @@ -0,0 +1,14 @@ +// IGNORE_FIR +package foo.bar.baz + +class AA { + class BB { + companion object + } +} + +fun test() { + val b = foo.bar.baz.AA.BB +} + +// REF: companion object of (in foo.bar.baz.AA).BB diff --git a/idea/testData/resolve/references/qualifiedAccess/ResolveOuterClassOfFullyQualifiedReference.kt b/idea/testData/resolve/references/qualifiedAccess/ResolveOuterClassOfFullyQualifiedReference.kt new file mode 100644 index 00000000000..177c8936686 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/ResolveOuterClassOfFullyQualifiedReference.kt @@ -0,0 +1,13 @@ +package foo.bar.baz + +class AA { + class BB { + companion object + } +} + +fun test() { + val b = foo.bar.baz.AA.BB +} + +// REF: (foo.bar.baz).AA diff --git a/idea/testData/resolve/references/qualifiedAccess/ResolvePackageOfFullyQualifiedReference.kt b/idea/testData/resolve/references/qualifiedAccess/ResolvePackageOfFullyQualifiedReference.kt new file mode 100644 index 00000000000..bad812bcfa5 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/ResolvePackageOfFullyQualifiedReference.kt @@ -0,0 +1,13 @@ +package foo.bar.baz + +class AA { + class BB { + companion object + } +} + +fun test() { + val b = foo.bar.baz.AA.BB +} + +// REF: baz diff --git a/idea/testData/resolve/references/qualifiedAccess/callableReference1.kt b/idea/testData/resolve/references/qualifiedAccess/callableReference1.kt new file mode 100644 index 00000000000..5179d7b36f8 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/callableReference1.kt @@ -0,0 +1,11 @@ +package foo.bar.baz + +class AA { + fun foo() {} +} + +fun test() { + AA::foo +} + +// REF: (foo.bar.baz).AA diff --git a/idea/testData/resolve/references/qualifiedAccess/callableReference2.kt b/idea/testData/resolve/references/qualifiedAccess/callableReference2.kt new file mode 100644 index 00000000000..61ec16f0640 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/callableReference2.kt @@ -0,0 +1,14 @@ +// IGNORE_FE10 +package foo.bar.baz + +class AA { + companion object { + fun foo() {} + } +} + +fun test() { + AA::foo // FE1.0 won't resolve this +} + +// REF: companion object of (foo.bar.baz).AA diff --git a/idea/testData/resolve/references/qualifiedAccess/callableReference3.kt b/idea/testData/resolve/references/qualifiedAccess/callableReference3.kt new file mode 100644 index 00000000000..500244af613 --- /dev/null +++ b/idea/testData/resolve/references/qualifiedAccess/callableReference3.kt @@ -0,0 +1,14 @@ +// IGNORE_FE10 +package foo.bar.baz + +class AA { + companion object +} + +fun AA.Companion.foo() {} + +fun test() { + AA::foo // FE1.0 won't resolve this +} + +// REF: companion object of (foo.bar.baz).AA diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractReferenceResolveTest.kt b/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractReferenceResolveTest.kt index 48aac9e40c7..5dc6907361b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractReferenceResolveTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/AbstractReferenceResolveTest.kt @@ -20,8 +20,8 @@ import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.util.renderAsGotoImplementation +import org.jetbrains.kotlin.test.utils.IgnoreTests import org.junit.Assert -import java.util.concurrent.Callable import kotlin.test.assertTrue abstract class AbstractReferenceResolveTest : KotlinLightPlatformCodeInsightFixtureTestCase() { @@ -35,7 +35,14 @@ abstract class AbstractReferenceResolveTest : KotlinLightPlatformCodeInsightFixt protected open fun doTest(path: String) { assert(path.endsWith(".kt")) { path } myFixture.configureWithExtraFile(path, ".Data") - performChecks() + val controlDirective = if (isFirPlugin()) { + IgnoreTests.DIRECTIVES.IGNORE_FIR + } else { + IgnoreTests.DIRECTIVES.IGNORE_FE10 + } + IgnoreTests.runTestIfNotDisabledByFileDirective(testDataFile().toPath(), controlDirective) { + performChecks() + } } protected fun performChecks() { diff --git a/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java index 14ab38e4bc9..f7d68f8f9b7 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/resolve/ReferenceResolveTestGenerated.java @@ -957,4 +957,52 @@ public class ReferenceResolveTestGenerated extends AbstractReferenceResolveTest runTest("idea/testData/resolve/references/packageReference/kotlinPackageSecondQualifier.kt"); } } + + @TestMetadata("idea/testData/resolve/references/qualifiedAccess") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class QualifiedAccess extends AbstractReferenceResolveTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInQualifiedAccess() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/resolve/references/qualifiedAccess"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @TestMetadata("callableReference1.kt") + public void testCallableReference1() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/callableReference1.kt"); + } + + @TestMetadata("callableReference2.kt") + public void testCallableReference2() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/callableReference2.kt"); + } + + @TestMetadata("callableReference3.kt") + public void testCallableReference3() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/callableReference3.kt"); + } + + @TestMetadata("ResolveFirstPackageOfFullyQualifiedReference.kt") + public void testResolveFirstPackageOfFullyQualifiedReference() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolveFirstPackageOfFullyQualifiedReference.kt"); + } + + @TestMetadata("ResolveFullyQualifiedCompanionObject.kt") + public void testResolveFullyQualifiedCompanionObject() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolveFullyQualifiedCompanionObject.kt"); + } + + @TestMetadata("ResolveOuterClassOfFullyQualifiedReference.kt") + public void testResolveOuterClassOfFullyQualifiedReference() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolveOuterClassOfFullyQualifiedReference.kt"); + } + + @TestMetadata("ResolvePackageOfFullyQualifiedReference.kt") + public void testResolvePackageOfFullyQualifiedReference() throws Exception { + runTest("idea/testData/resolve/references/qualifiedAccess/ResolvePackageOfFullyQualifiedReference.kt"); + } + } }