FIR IDE: add API to get super types of a KtType

This commit is contained in:
Tianyu Geng
2021-10-06 15:49:27 -07:00
committed by Ilya Kirillov
parent 09d16ce849
commit 8b44a4685f
34 changed files with 568 additions and 1 deletions
@@ -20,12 +20,24 @@ import org.jetbrains.kotlin.analysis.api.withValidityAssertion
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LowLevelFirApiFacadeForResolveOnAir.getTowerContextProvider
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.getOrBuildFir
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.throwUnexpectedFirElementError
import org.jetbrains.kotlin.fir.FirRenderer
import org.jetbrains.kotlin.fir.analysis.checkers.ConeTypeCompatibilityChecker
import org.jetbrains.kotlin.fir.analysis.checkers.ConeTypeCompatibilityChecker.isCompatible
import org.jetbrains.kotlin.fir.analysis.checkers.fullyExpandedClass
import org.jetbrains.kotlin.fir.analysis.checkers.typeParameterSymbols
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.utils.superConeTypes
import org.jetbrains.kotlin.fir.expressions.FirCallableReferenceAccess
import org.jetbrains.kotlin.fir.expressions.FirDelegatedConstructorCall
import org.jetbrains.kotlin.fir.expressions.FirGetClassCall
import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeAliasSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
@@ -34,6 +46,8 @@ import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.psi.KtDoubleColonExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtTypeReference
import org.jetbrains.kotlin.types.TypeApproximatorConfiguration
import org.jetbrains.kotlin.types.model.CaptureStatus
internal class KtFirTypeProvider(
override val analysisSession: KtFirAnalysisSession,
@@ -110,5 +124,68 @@ internal class KtFirTypeProvider(
return analysisSession.firResolveState.getTowerContextProvider()
.getClosestAvailableParentContext(position)?.implicitReceiverStack?.map { it.type.asKtType() } ?: emptyList()
}
override fun getDirectSuperTypes(type: KtType, shouldApproximate: Boolean): List<KtType> {
require(type is KtFirType)
return type.coneType.getDirectSuperTypes(shouldApproximate).mapTo(mutableListOf()) { it.asKtType() }
}
private fun ConeKotlinType.getDirectSuperTypes(shouldApproximate: Boolean): Sequence<ConeKotlinType> {
return when (this) {
// We also need to collect those on `upperBound` due to nullability.
is ConeFlexibleType -> lowerBound.getDirectSuperTypes(shouldApproximate) + upperBound.getDirectSuperTypes(shouldApproximate)
is ConeDefinitelyNotNullType -> original.getDirectSuperTypes(shouldApproximate).map { ConeDefinitelyNotNullType(it) }
is ConeIntersectionType -> intersectedTypes.asSequence().flatMap { it.getDirectSuperTypes(shouldApproximate) }
is ConeClassErrorType -> emptySequence()
is ConeLookupTagBasedType -> getSubstitutedSuperTypes(shouldApproximate)
else -> emptySequence()
}.distinct()
}
private fun ConeLookupTagBasedType.getSubstitutedSuperTypes(shouldApproximate: Boolean): Sequence<ConeKotlinType> {
val session = analysisSession.firResolveState.rootModuleSession
val symbol = lookupTag.toSymbol(session)
val superTypes = when (symbol) {
is FirAnonymousObjectSymbol -> symbol.superConeTypes
is FirRegularClassSymbol -> symbol.superConeTypes
is FirTypeAliasSymbol -> symbol.fullyExpandedClass(session)?.superConeTypes ?: return emptySequence()
is FirTypeParameterSymbol -> symbol.resolvedBounds.map { it.type }
else -> return emptySequence()
}
val typeParameterSymbols = symbol.typeParameterSymbols ?: return superTypes.asSequence()
val argumentTypes = (session.typeContext.captureArguments(this, CaptureStatus.FROM_EXPRESSION)?.toList()
?: this.typeArguments.mapNotNull { it.type })
require(typeParameterSymbols.size == argumentTypes.size) {
"'${symbol.fir.render(FirRenderer.RenderMode.NoBodies)}' expects '${typeParameterSymbols.size}' type arguments " +
"but type '${this.render()}' has ${argumentTypes.size} type arguments."
}
val substitutor = substitutorByMap(typeParameterSymbols.zip(argumentTypes).toMap(), session)
return superTypes.asSequence().map {
val type = substitutor.substituteOrSelf(it)
if (shouldApproximate) {
session.inferenceComponents.approximator.approximateToSuperType(
type,
TypeApproximatorConfiguration.FinalApproximationAfterResolutionAndInference
) ?: type
} else {
type
}.withNullability(nullability, session.typeContext)
}
}
override fun getAllSuperTypes(type: KtType, shouldApproximate: Boolean): List<KtType> {
require(type is KtFirType)
val queue = type.coneType.getDirectSuperTypes(shouldApproximate).toCollection(ArrayDeque())
return buildSet {
while (queue.isNotEmpty()) {
val current = queue.removeFirst()
if (!add(current)) continue
queue.addAll(current.getDirectSuperTypes(shouldApproximate))
}
}.map { it.asKtType() }
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.fir.components
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.fir.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.fir.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.expressionMarkerProvider
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
abstract class AbstractGetSuperTypesTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val expression = testServices.expressionMarkerProvider.getSelectedElement(ktFile)
val actual = executeOnPooledThreadInReadAction {
analyse(expression) {
val expectedType = expression.getExpectedType() ?: error("expect to get type of expression '${expression.text}'")
val directSuperTypes = expectedType.getDirectSuperTypes()
val approximatedDirectSuperTypes = expectedType.getDirectSuperTypes(shouldApproximate = true)
val allSuperTypes = expectedType.getAllSuperTypes()
val approximatedAllSuperTypes = expectedType.getAllSuperTypes(shouldApproximate = true)
buildString {
fun List<KtType>.print(name: String) {
appendLine(name)
for (type in this) {
appendLine(type.render())
}
appendLine()
}
directSuperTypes.print("[direct super types]")
approximatedDirectSuperTypes.print("[approximated direct super types]")
allSuperTypes.print("[all super types]")
approximatedAllSuperTypes.print("[approximated all super types]")
}
}
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
}
@@ -0,0 +1,110 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.analysis.api.fir.components;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.util.KtTestUtil;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link GenerateNewCompilerTests.kt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("analysis/analysis-api/testData/components/getSuperTypes")
@TestDataPath("$PROJECT_ROOT")
public class GetSuperTypesTestGenerated extends AbstractGetSuperTypesTest {
@Test
public void testAllFilesPresentInGetSuperTypes() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/getSuperTypes"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("anonymousObject.kt")
public void testAnonymousObject() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/anonymousObject.kt");
}
@Test
@TestMetadata("any.kt")
public void testAny() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/any.kt");
}
@Test
@TestMetadata("contravariant.kt")
public void testContravariant() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/contravariant.kt");
}
@Test
@TestMetadata("covariant.kt")
public void testCovariant() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/covariant.kt");
}
@Test
@TestMetadata("errorType.kt")
public void testErrorType() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/errorType.kt");
}
@Test
@TestMetadata("flexibleType.kt")
public void testFlexibleType() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/flexibleType.kt");
}
@Test
@TestMetadata("int.kt")
public void testInt() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/int.kt");
}
@Test
@TestMetadata("invariant.kt")
public void testInvariant() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/invariant.kt");
}
@Test
@TestMetadata("recursiveTypeParameter.kt")
public void testRecursiveTypeParameter() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/recursiveTypeParameter.kt");
}
@Test
@TestMetadata("regularClass.kt")
public void testRegularClass() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/regularClass.kt");
}
@Test
@TestMetadata("regularClassWithBounds.kt")
public void testRegularClassWithBounds() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/regularClassWithBounds.kt");
}
@Test
@TestMetadata("star.kt")
public void testStar() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/star.kt");
}
@Test
@TestMetadata("string.kt")
public void testString() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/string.kt");
}
@Test
@TestMetadata("typeParameter.kt")
public void testTypeParameter() throws Exception {
runTest("analysis/analysis-api/testData/components/getSuperTypes/typeParameter.kt");
}
}