diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt index 8d72652ab56..fc3cd029df2 100644 --- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt +++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/references/FirReferenceResolveHelper.kt @@ -468,9 +468,31 @@ internal object FirReferenceResolveHelper { 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} " + + fun referencedClassIdAndQualifiedAccessMatch( + qualifiedAccessSegments: List + ): Boolean { + val referencedClassIdSegments = + referencedClassId.asSingleFqName().pathSegments() + .takeLast(qualifiedAccessSegments.size) + .map { it.identifierOrNullIfSpecial } + return referencedClassIdSegments == qualifiedAccessSegments + } + + if (!referencedClassIdAndQualifiedAccessMatch(qualifiedAccessSegments)) { + // Referenced ClassId and qualified access (from source PSI) could be not identical if an import alias is involved. + // E.g., test.pkg.R.string.hello v.s. coreR.string.hello where test.pkg.R is imported as coreR + // Since an import alias ends with a simple identifier (i.e., can't be non-trivial dotted qualifier), we can safely assume + // that the first segment of the qualified access could be the import alias if any. Then, we can still compare the + // remaining parts. + // E.g., coreR.string.hello + // -> string.hello (drop the first segment) + // test.pkg.R.string.hello + // -> string.hello (take last two segments, where the size is determined by the size of qualified access minus 1) + qualifiedAccessSegments.removeAt(0) + assert(referencedClassIdAndQualifiedAccessMatch(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 @@ -534,7 +556,7 @@ internal object FirReferenceResolveHelper { * 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? { + private fun KtDotQualifiedExpression.fqNameSegments(): MutableList? { val result: MutableList = mutableListOf() var current: KtExpression = this while (current is KtDotQualifiedExpression) { diff --git a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java index 4df383eb9c9..c33ce77135a 100644 --- a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirLibrarySourceReferenceResolveTestGenerated.java @@ -184,6 +184,12 @@ public class FirLibrarySourceReferenceResolveTestGenerated extends AbstractRefer runTest("analysis/analysis-api/testData/referenceResolve/implicitFunctionalInterfaceInvoke.kt"); } + @Test + @TestMetadata("importAlias.kt") + public void testImportAlias() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/importAlias.kt"); + } + @Test @TestMetadata("ImportFromRootScope.kt") public void testImportFromRootScope() throws Exception { diff --git a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirSourceReferenceResolveTestGenerated.java b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirSourceReferenceResolveTestGenerated.java index 31e2a47f3e1..55be26d7030 100644 --- a/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirSourceReferenceResolveTestGenerated.java +++ b/analysis/analysis-api-fir/tests/org/jetbrains/kotlin/analysis/api/fir/FirSourceReferenceResolveTestGenerated.java @@ -184,6 +184,12 @@ public class FirSourceReferenceResolveTestGenerated extends AbstractReferenceRes runTest("analysis/analysis-api/testData/referenceResolve/implicitFunctionalInterfaceInvoke.kt"); } + @Test + @TestMetadata("importAlias.kt") + public void testImportAlias() throws Exception { + runTest("analysis/analysis-api/testData/referenceResolve/importAlias.kt"); + } + @Test @TestMetadata("ImportFromRootScope.kt") public void testImportFromRootScope() throws Exception { diff --git a/analysis/analysis-api/testData/referenceResolve/importAlias.kt b/analysis/analysis-api/testData/referenceResolve/importAlias.kt new file mode 100644 index 00000000000..b2763daf62d --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/importAlias.kt @@ -0,0 +1,20 @@ +// FILE: R.kt +package test.pkg + +class R { + class string { + companion object { + val hello : Int = 42 + } + } +} + +// FILE: main.kt + +package test.pkg + +import test.pkg.R as coreR + +fun box() { + val s = coreR.string.hello +} diff --git a/analysis/analysis-api/testData/referenceResolve/importAlias.txt b/analysis/analysis-api/testData/referenceResolve/importAlias.txt new file mode 100644 index 00000000000..b72cb0e1590 --- /dev/null +++ b/analysis/analysis-api/testData/referenceResolve/importAlias.txt @@ -0,0 +1,2 @@ +Resolved to: +0: (in test.pkg) class R