[LL FIR] get rid of on-air resolve of partial type qualifiers

It's very slow and leads to performance problems (see KT-58125)

Instead, we do the following:

- For a fully resolved type qualifier, when we want to resolve its part,
 we are looking for the corresponding symbol by traversing nested classes
 bottom up.
 - For an error qualifier, we are trying to resolve the maximum possible
  qualifier in the types transformer where all the type scopes are
  already available.

^KT-58125 fixed
This commit is contained in:
Ilya Kirillov
2023-05-27 15:11:20 +02:00
committed by Space Team
parent 51cf0ce3e5
commit 481962d5f6
42 changed files with 600 additions and 33 deletions
@@ -2158,5 +2158,75 @@ public class Fe10IdeNormalAnalysisSourceModuleReferenceResolveTestGenerated exte
public void testWrongNumberOfTypeArgumentsInSupertype() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/WrongNumberOfTypeArgumentsInSupertype.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier")
@TestDataPath("$PROJECT_ROOT")
public class PartiallyUnresolvedTypeQualifier {
@Test
public void testAllFilesPresentInPartiallyUnresolvedTypeQualifier() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClass.kt")
public void testClassNameBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClasses.kt")
public void testClassNameBeforeTwoUnsresolvedClasses() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClasses.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt")
public void testClassNameBeforeTwoUnsresolvedClassesTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesWithDot.kt")
public void testClassNameBeforeTwoUnsresolvedClassesWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
}
}
}
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.idea.references
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
@@ -15,9 +16,12 @@ import org.jetbrains.kotlin.analysis.api.fir.symbols.KtFirPackageSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFir
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFirSafe
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirElementError
import org.jetbrains.kotlin.analysis.utils.errors.unexpectedElementError
import org.jetbrains.kotlin.fir.FirPackageDirective
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.ROOT_PREFIX_FOR_IDE_RESOLUTION_MODE
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.builder.buildImport
import org.jetbrains.kotlin.fir.declarations.utils.classId
@@ -28,10 +32,9 @@ import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.*
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.expressions.unwrapSmartcastExpression
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnmatchedTypeArgumentsError
import org.jetbrains.kotlin.fir.resolve.scope
import org.jetbrains.kotlin.fir.resolve.providers.toSymbol
import org.jetbrains.kotlin.fir.resolve.scope
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.resolve.transformers.FirImportResolveTransformer
import org.jetbrains.kotlin.fir.scopes.FakeOverrideTypeCalculator
@@ -39,6 +42,7 @@ import org.jetbrains.kotlin.fir.scopes.impl.FirExplicitSimpleImportingScope
import org.jetbrains.kotlin.fir.scopes.processClassifiersByName
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.ClassId
@@ -48,6 +52,7 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
internal object FirReferenceResolveHelper {
fun FirResolvedTypeRef.toTargetSymbol(session: FirSession, symbolBuilder: KtSymbolByFirBuilder): KtSymbol? {
@@ -471,14 +476,62 @@ internal object FirReferenceResolveHelper {
session: FirSession,
symbolBuilder: KtSymbolByFirBuilder,
): Collection<KtSymbol> {
if (!expression.isPartOfUserTypeRefQualifier()) {
return listOfNotNull(fir.toTargetSymbol(session, symbolBuilder))
}
val isPossiblyPackage = fir is FirErrorTypeRef && expression.isPartOfUserTypeRefQualifier()
if (fir is FirErrorTypeRef) {
tryGettingSymbolFromPartiallyResolvedType(fir, expression, session, symbolBuilder).ifNotEmpty { return this }
return listOfNotNull(getPackageSymbolFor(expression, symbolBuilder, forQualifiedType = true))
}
val resultSymbol =
if (isPossiblyPackage) getPackageSymbolFor(expression, symbolBuilder, forQualifiedType = true)
else fir.toTargetSymbol(session, symbolBuilder)
fun unwrapType(type: PsiElement?): KtTypeElement = when (type) {
is KtTypeReference -> unwrapType(type.typeElement)
is KtNullableType -> type.innerType
is KtTypeElement -> type
is KtNameReferenceExpression -> unwrapType(type.parent)
else -> unexpectedElementError<PsiElement>(type)
} ?: unexpectedElementError<PsiElement>(type)
return listOfNotNull(resultSymbol)
val ktTypeElementFromFirType = unwrapType(fir.psi)
val classifiersToSkip = expression.parents.takeWhile { it != ktTypeElementFromFirType }.count()
var classifier: FirClassLikeSymbol<*>? = fir.type.toRegularClassSymbol(session)
repeat(classifiersToSkip) {
classifier = classifier?.getContainingClassSymbol(session)
}
val firClassSymbol = classifier
?: return listOfNotNull(getPackageSymbolFor(expression, symbolBuilder, forQualifiedType = true))
return listOf(symbolBuilder.classifierBuilder.buildClassifierSymbol(firClassSymbol))
}
private tailrec fun tryGettingSymbolFromPartiallyResolvedType(
typeRef: FirTypeRef?,
expression: KtSimpleNameExpression,
session: FirSession,
symbolBuilder: KtSymbolByFirBuilder,
): Collection<KtSymbol> {
return when (typeRef) {
null -> emptyList()
is FirErrorTypeRef -> {
tryGettingSymbolFromPartiallyResolvedType(
typeRef.partiallyResolvedTypeRef,
expression,
session,
symbolBuilder,
)
}
is FirResolvedTypeRef -> {
return getSymbolsForResolvedTypeRef(typeRef, expression, session, symbolBuilder)
}
else -> throwUnexpectedFirElementError(
typeRef,
ktElement = null,
FirErrorTypeRef::class,
FirResolvedTypeRef::class
)
}
}
private fun getSymbolsForResolvedQualifier(
@@ -2158,5 +2158,75 @@ public class FirIdeDependentAnalysisSourceModuleReferenceResolveTestGenerated ex
public void testWrongNumberOfTypeArgumentsInSupertype() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/WrongNumberOfTypeArgumentsInSupertype.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier")
@TestDataPath("$PROJECT_ROOT")
public class PartiallyUnresolvedTypeQualifier {
@Test
public void testAllFilesPresentInPartiallyUnresolvedTypeQualifier() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClass.kt")
public void testClassNameBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClasses.kt")
public void testClassNameBeforeTwoUnsresolvedClasses() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClasses.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt")
public void testClassNameBeforeTwoUnsresolvedClassesTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesWithDot.kt")
public void testClassNameBeforeTwoUnsresolvedClassesWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
}
}
}
@@ -2158,5 +2158,75 @@ public class FirIdeNormalAnalysisSourceModuleReferenceResolveTestGenerated exten
public void testWrongNumberOfTypeArgumentsInSupertype() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/WrongNumberOfTypeArgumentsInSupertype.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier")
@TestDataPath("$PROJECT_ROOT")
public class PartiallyUnresolvedTypeQualifier {
@Test
public void testAllFilesPresentInPartiallyUnresolvedTypeQualifier() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClass.kt")
public void testClassNameBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClasses.kt")
public void testClassNameBeforeTwoUnsresolvedClasses() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClasses.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt")
public void testClassNameBeforeTwoUnsresolvedClassesTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesWithDot.kt")
public void testClassNameBeforeTwoUnsresolvedClassesWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
}
}
}
@@ -2158,5 +2158,75 @@ public class FirStandaloneNormalAnalysisSourceModuleReferenceResolveTestGenerate
public void testWrongNumberOfTypeArgumentsInSupertype() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/WrongNumberOfTypeArgumentsInSupertype.kt");
}
@Nested
@TestMetadata("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier")
@TestDataPath("$PROJECT_ROOT")
public class PartiallyUnresolvedTypeQualifier {
@Test
public void testAllFilesPresentInPartiallyUnresolvedTypeQualifier() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClass.kt")
public void testClassNameBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClasses.kt")
public void testClassNameBeforeTwoUnsresolvedClasses() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClasses.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt")
public void testClassNameBeforeTwoUnsresolvedClassesTwoResolved() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesTwoResolved.kt");
}
@Test
@TestMetadata("ClassNameBeforeTwoUnsresolvedClassesWithDot.kt")
public void testClassNameBeforeTwoUnsresolvedClassesWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameBeforeTwoUnsresolvedClassesWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClass() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClass.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithDot.kt");
}
@Test
@TestMetadata("ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt")
public void testClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot() throws Exception {
runTest("analysis/analysis-api/testData/referenceResolve/withErrors/partiallyUnresolvedTypeQualifier/ClassNameSecondQualifierBeforeOneUnsresolvedClassWithTwoResolvedWithDot.kt");
}
}
}
}
@@ -0,0 +1,7 @@
class Foo
class C {
val prop: <caret>Foo.Bar
}
@@ -0,0 +1,2 @@
Resolved to:
0: (in ROOT) class Foo
@@ -0,0 +1,7 @@
class Foo
class C {
val prop: <caret>Foo.Bar.
}
@@ -0,0 +1,9 @@
class Foo {
class Bar
}
class C {
val prop: <caret>Foo.Bar.Baz
}
@@ -0,0 +1,8 @@
class Foo {
class Bar
}
class C {
val prop: <caret>Foo.Bar.Baz.
}
@@ -0,0 +1,7 @@
class Foo
class C {
val prop: <caret>Foo.Bar.Baz
}
@@ -0,0 +1,9 @@
class Foo {
class Bar
}
class C {
val prop: <caret>Foo.Bar.Baz.Bazzzz
}
@@ -0,0 +1,7 @@
class Foo
class C {
val prop: <caret>Foo.Bar.Baz.
}
@@ -0,0 +1,9 @@
class Foo {
class Bar
}
class C {
val prop: Foo.<caret>Bar.Baz
}
@@ -0,0 +1,9 @@
class Foo {
class Bar
}
class C {
val prop: Foo.<caret>Bar.Baz.
}
@@ -0,0 +1,8 @@
class Foo {
class Bar
}
class C {
val prop: Foo.<caret>Bar.Baz.
}
@@ -60,7 +60,7 @@ internal class LLFirResolveSessionDepended(
override fun getOrBuildFirFor(element: KtElement): FirElement? {
val psi = FirElementBuilder.getPsiAsFirElementSource(element) ?: return null
ktToFirMapping?.getFirOfClosestParent(psi, this)?.let { return it }
ktToFirMapping?.getFirOfClosestParent(psi)?.let { return it }
return originalFirResolveSession.getOrBuildFirFor(element = element)
}
@@ -84,7 +84,7 @@ internal class FirElementBuilder(
val structureElement = fileStructure.getStructureElementFor(element)
val psi = getPsiAsFirElementSource(element) ?: return null
val mappings = structureElement.mappings
return mappings.getFirOfClosestParent(psi, firResolveSession)
return mappings.getFirOfClosestParent(psi)
?: firResolveSession.getOrBuildFirFile(firFile)
}
@@ -8,13 +8,17 @@ package org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.analysis.low.level.api.fir.LLFirModuleResolveComponents
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.*
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignation
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.FirDesignationWithFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDesignation
import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.ClassDiagnosticRetriever
import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.FileDiagnosticRetriever
import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.FileStructureElementDiagnostics
import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostics.SingleNonLocalDeclarationDiagnosticRetriever
import org.jetbrains.kotlin.analysis.low.level.api.fir.lazy.resolve.RawFirNonLocalDeclarationBuilder
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.correspondingProperty
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.builder.FirBackingFieldBuilder
import org.jetbrains.kotlin.fir.declarations.builder.FirFunctionBuilder
@@ -26,7 +30,6 @@ import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.psi.*
import java.util.concurrent.ConcurrentHashMap
import org.jetbrains.kotlin.fir.correspondingProperty
internal sealed class FileStructureElement(val firFile: FirFile, protected val moduleComponents: LLFirModuleResolveComponents) {
abstract val psi: KtAnnotated
@@ -36,31 +39,16 @@ internal sealed class FileStructureElement(val firFile: FirFile, protected val m
internal class KtToFirMapping(firElement: FirElement, recorder: FirElementsRecorder) {
private val mapping = FirElementsRecorder.recordElementsFrom(firElement, recorder)
private val userTypeMapping = ConcurrentHashMap<KtUserType, FirElement>()
fun getElement(ktElement: KtElement, firResolveSession: LLFirResolveSession): FirElement? {
mapping[ktElement]?.let { return it }
val userType = when (ktElement) {
is KtUserType -> ktElement
is KtNameReferenceExpression -> ktElement.parent as? KtUserType
else -> null
} ?: return null
//This is for not inner KtUserType
if (userType.parent is KtTypeReference) return null
return userTypeMapping.getOrPut(userType) {
val typeReference = KtPsiFactory(ktElement.project).createType(userType.text)
LowLevelFirApiFacadeForResolveOnAir.onAirResolveTypeInPlace(ktElement, typeReference, firResolveSession)
}
fun getElement(ktElement: KtElement): FirElement? {
return mapping[ktElement]
}
fun getFirOfClosestParent(element: KtElement, firResolveSession: LLFirResolveSession): FirElement? {
fun getFirOfClosestParent(element: KtElement): FirElement? {
var current: PsiElement? = element
while (current != null && current !is KtFile) {
if (current is KtElement) {
getElement(current, firResolveSession)?.let { return it }
getElement(current)?.let { return it }
}
current = current.parent
}
@@ -105,11 +105,13 @@ internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement,
override fun visitErrorTypeRef(errorTypeRef: FirErrorTypeRef, data: MutableMap<KtElement, FirElement>) {
super.visitResolvedTypeRef(errorTypeRef, data)
recordTypeQualifiers(errorTypeRef, data)
errorTypeRef.delegatedTypeRef?.accept(this, data)
}
override fun visitResolvedTypeRef(resolvedTypeRef: FirResolvedTypeRef, data: MutableMap<KtElement, FirElement>) {
super.visitResolvedTypeRef(resolvedTypeRef, data)
recordTypeQualifiers(resolvedTypeRef, data)
resolvedTypeRef.delegatedTypeRef?.accept(this, data)
}
@@ -219,6 +221,17 @@ internal open class FirElementsRecorder : FirVisitor<Unit, MutableMap<KtElement,
}
}
private fun recordTypeQualifiers(resolvedTypeRef: FirResolvedTypeRef, data: MutableMap<KtElement, FirElement>) {
val userTypeRef = resolvedTypeRef.delegatedTypeRef as? FirUserTypeRef ?: return
val qualifiers = userTypeRef.qualifier
if (qualifiers.size <= 1) return
qualifiers.forEachIndexed { index, qualifierPart ->
if (index == qualifiers.lastIndex) return@forEachIndexed
val source = qualifierPart.source?.psi as? KtElement ?: return@forEachIndexed
cache(source, resolvedTypeRef, data)
}
}
companion object {
fun recordElementsFrom(firElement: FirElement, recorder: FirElementsRecorder): Map<KtElement, FirElement> =
buildMap { firElement.accept(recorder, this) }
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.fir.resolve.transformers.body.resolve.FirTowerDataCo
import org.jetbrains.kotlin.fir.resolve.transformers.platformSupertypeUpdater
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.name.ClassId
internal object LLFirSupertypeLazyResolver : LLFirLazyResolver(FirResolvePhase.SUPER_TYPES) {
override fun resolve(
@@ -3781,6 +3781,18 @@ public class DiagnosticCompilerTestFirTestdataTestGenerated extends AbstractDiag
runTest("compiler/fir/analysis-tests/testData/resolve/problems/inaccessibleJavaGetter.kt");
}
@Test
@TestMetadata("incompleteUserType.kt")
public void testIncompleteUserType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserType.kt");
}
@Test
@TestMetadata("incompleteUserTypeWithUnresovledTypeArgument.kt")
public void testIncompleteUserTypeWithUnresovledTypeArgument() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserTypeWithUnresovledTypeArgument.kt");
}
@Test
@TestMetadata("innerClassHierarchy.kt")
public void testInnerClassHierarchy() throws Exception {
@@ -3781,6 +3781,18 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFirTestDataTestGenerated
runTest("compiler/fir/analysis-tests/testData/resolve/problems/inaccessibleJavaGetter.kt");
}
@Test
@TestMetadata("incompleteUserType.kt")
public void testIncompleteUserType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserType.kt");
}
@Test
@TestMetadata("incompleteUserTypeWithUnresovledTypeArgument.kt")
public void testIncompleteUserTypeWithUnresovledTypeArgument() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserTypeWithUnresovledTypeArgument.kt");
}
@Test
@TestMetadata("innerClassHierarchy.kt")
public void testInnerClassHierarchy() throws Exception {
@@ -3326,6 +3326,16 @@ public class LazyBodyIsNotTouchedTestGenerated extends AbstractLazyBodyIsNotTouc
runTest("compiler/fir/analysis-tests/testData/resolve/problems/inaccessibleJavaGetter.kt");
}
@TestMetadata("incompleteUserType.kt")
public void testIncompleteUserType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserType.kt");
}
@TestMetadata("incompleteUserTypeWithUnresovledTypeArgument.kt")
public void testIncompleteUserTypeWithUnresovledTypeArgument() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserTypeWithUnresovledTypeArgument.kt");
}
@TestMetadata("innerClassHierarchy.kt")
public void testInnerClassHierarchy() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/innerClassHierarchy.kt");
@@ -0,0 +1,3 @@
FILE: incompleteUserType.kt
public final fun foo(a: <ERROR TYPE REF: Incomplete user type>): R|kotlin/Unit| {
}
@@ -0,0 +1 @@
fun foo(a : <!UNRESOLVED_REFERENCE!>Unresolved<!>.<!SYNTAX!><!>) {}
@@ -0,0 +1,9 @@
FILE: incompleteUserTypeWithUnresovledTypeArgument.kt
public final class A<T> : R|kotlin/Any| {
public constructor<T>(): R|A<T>| {
super<R|kotlin/Any|>()
}
}
public final fun foo(a: <ERROR TYPE REF: Incomplete user type>): R|kotlin/Unit| {
}
@@ -0,0 +1,3 @@
class A<T>
fun foo(a : A<<!UNRESOLVED_REFERENCE!>Unresolved<!>>.<!SYNTAX!><!>) {}
@@ -3781,6 +3781,18 @@ public class FirLightTreeDiagnosticsTestGenerated extends AbstractFirLightTreeDi
runTest("compiler/fir/analysis-tests/testData/resolve/problems/inaccessibleJavaGetter.kt");
}
@Test
@TestMetadata("incompleteUserType.kt")
public void testIncompleteUserType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserType.kt");
}
@Test
@TestMetadata("incompleteUserTypeWithUnresovledTypeArgument.kt")
public void testIncompleteUserTypeWithUnresovledTypeArgument() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserTypeWithUnresovledTypeArgument.kt");
}
@Test
@TestMetadata("innerClassHierarchy.kt")
public void testInnerClassHierarchy() throws Exception {
@@ -3781,6 +3781,18 @@ public class FirPsiDiagnosticTestGenerated extends AbstractFirPsiDiagnosticTest
runTest("compiler/fir/analysis-tests/testData/resolve/problems/inaccessibleJavaGetter.kt");
}
@Test
@TestMetadata("incompleteUserType.kt")
public void testIncompleteUserType() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserType.kt");
}
@Test
@TestMetadata("incompleteUserTypeWithUnresovledTypeArgument.kt")
public void testIncompleteUserTypeWithUnresovledTypeArgument() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/problems/incompleteUserTypeWithUnresovledTypeArgument.kt");
}
@Test
@TestMetadata("innerClassHierarchy.kt")
public void testInnerClassHierarchy() throws Exception {
@@ -2168,11 +2168,21 @@ class DeclarationsConverter(
}
}
if (identifier == null)
if (identifier == null) {
return buildErrorTypeRef {
source = typeRefSource
diagnostic = ConeSyntaxDiagnostic("Incomplete user type")
simpleFirUserType?.let { qualifierPart ->
if (qualifierPart.qualifier.isNotEmpty()) {
partiallyResolvedTypeRef = buildUserTypeRef {
source = qualifierPart.qualifier.last().source
isMarkedNullable = false
this.qualifier.addAll(qualifierPart.qualifier)
}
}
}
}
}
val qualifierPart = FirQualifierPartImpl(
identifierSource!!,
@@ -1979,6 +1979,17 @@ open class RawFirBuilder(
FirErrorTypeRefBuilder().apply {
this.source = source
diagnostic = ConeSyntaxDiagnostic("Incomplete user type")
val qualifier = unwrappedElement.qualifier
val reference = qualifier?.referenceExpression
if (qualifier != null && reference != null) {
partiallyResolvedTypeRef = convertKtTypeElement(
qualifier.toFirSourceElement(),
isNullable = false,
qualifier,
reference
).build()
}
}
}
}
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.resolve.typeResolver
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.builder.buildErrorTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildResolvedTypeRef
import org.jetbrains.kotlin.fir.types.builder.buildUserTypeRef
class FirSpecificTypeResolverTransformer(
override val session: FirSession,
@@ -82,7 +83,7 @@ class FirSpecificTypeResolverTransformer(
typeRef.transformChildren(this, data)
}
val (resolvedType, diagnostic) = resolveType(typeRef, data)
return transformType(typeRef, resolvedType, diagnostic)
return transformType(typeRef, resolvedType, diagnostic, data)
}
@OptIn(PrivateForInline::class)
@@ -135,6 +136,7 @@ class FirSpecificTypeResolverTransformer(
typeRef: FirTypeRef,
resolvedType: ConeKotlinType,
diagnostic: ConeDiagnostic?,
scopeClassDeclaration: ScopeClassDeclaration,
): FirResolvedTypeRef {
return when {
resolvedType is ConeErrorType -> {
@@ -154,6 +156,7 @@ class FirSpecificTypeResolverTransformer(
delegatedTypeRef = typeRef
type = resolvedType
partiallyResolvedTypeRef = tryCalculatingPartiallyResolvedTypeRef(typeRef, scopeClassDeclaration)
this.diagnostic = resolvedType.diagnostic
}
@@ -164,6 +167,7 @@ class FirSpecificTypeResolverTransformer(
this.diagnostic = diagnostic
type = resolvedType
delegatedTypeRef = typeRef
partiallyResolvedTypeRef = tryCalculatingPartiallyResolvedTypeRef(typeRef, scopeClassDeclaration)
}
}
else -> {
@@ -177,6 +181,41 @@ class FirSpecificTypeResolverTransformer(
}
}
/**
* Tries to calculate a partially resolved type reference for a type reference which was resolved to an error type.
* It will attempt to resolve the type with a decreasing number of qualifiers until it succeeds, allowing
* partial resolution in case of errors in the type reference.
*
* This is useful for providing better IDE support when resolving partially incorrect types.
*
* @param typeRef The type reference for which to try to calculate a partially resolved type reference.
* @param data The scope class declaration containing relevant information for resolving the reference.
* @return A partially resolved type reference if it was resolved, or `null` otherwise.
*/
private fun tryCalculatingPartiallyResolvedTypeRef(typeRef: FirTypeRef, data: ScopeClassDeclaration): FirTypeRef? {
if (typeRef !is FirUserTypeRef) return null
val qualifiers = typeRef.qualifier
if (qualifiers.size <= 1) {
return null
}
val qualifiersToTry = qualifiers.toMutableList()
while (qualifiersToTry.size > 1) {
qualifiersToTry.removeLast()
val typeRefToTry = buildUserTypeRef {
qualifier += qualifiersToTry
isMarkedNullable = false
}
val (resolvedType, diagnostic) = resolveType(typeRefToTry, data)
if (resolvedType is ConeErrorType || diagnostic != null) continue
return buildResolvedTypeRef {
source = qualifiersToTry.last().source
type = resolvedType
delegatedTypeRef = typeRefToTry
}
}
return null
}
private fun ConeKotlinType.takeIfAcceptable(): ConeKotlinType? = this.takeUnless {
!errorTypeAsResolved && it is ConeErrorType
}