AA: relax the assertion about referenced ClassId and qualified access

If an import alias is involved, they could be not identical.
Since an import alias ends with a simple identifer, we can simply drop
the first segment of qualified access and try the assertion again.
This commit is contained in:
Jinseong Jeon
2022-03-01 01:16:47 -08:00
committed by Ilya Kirillov
parent 7fbfb1bd95
commit a68fd25982
5 changed files with 60 additions and 4 deletions
@@ -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<String>
): 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<String>? {
private fun KtDotQualifiedExpression.fqNameSegments(): MutableList<String>? {
val result: MutableList<String> = mutableListOf()
var current: KtExpression = this
while (current is KtDotQualifiedExpression) {
@@ -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 {
@@ -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 {
@@ -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 = core<caret>R.string.hello
}
@@ -0,0 +1,2 @@
Resolved to:
0: (in test.pkg) class R