Analysis API: add KtType.isDenotable()

This commit is contained in:
Tianyu Geng
2021-12-07 13:26:32 -08:00
committed by teamcity
parent 2f393cdd02
commit 5fbe5981f7
20 changed files with 405 additions and 5 deletions
@@ -15,6 +15,9 @@ import org.jetbrains.kotlin.analysis.api.withValidityAssertion
import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.builtins.getFunctionalClassKind
import org.jetbrains.kotlin.load.java.sam.JavaSingleAbstractMethodUtils
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.types.DefinitelyNotNullType
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
internal class KtFe10TypeInfoProvider(
@@ -37,4 +40,17 @@ internal class KtFe10TypeInfoProvider(
require(type is KtFe10Type)
return TypeUtils.isNullableType(type.type)
}
override fun isDenotable(type: KtType): Boolean {
require(type is KtFe10Type)
val kotlinType = type.type
return kotlinType.isDenotable()
}
private fun KotlinType.isDenotable(): Boolean {
if (this is DefinitelyNotNullType) return false
return constructor.isDenotable &&
constructor.declarationDescriptor?.name != SpecialNames.NO_NAME_PROVIDED &&
arguments.all { it.type.isDenotable() }
}
}
@@ -27,6 +27,9 @@ import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.compilerConfigurationProvider
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.nameWithoutExtension
object KtFe10FrontendApiTestConfiguratorService : FrontendApiTestConfiguratorService {
override val testPrefix: String
@@ -64,4 +67,10 @@ object KtFe10FrontendApiTestConfiguratorService : FrontendApiTestConfiguratorSer
override fun doOutOfBlockModification(file: KtFile) {
// TODO not supported yet
}
override fun preprocessTestDataPath(path: Path): Path {
val newPath = path.resolveSibling(path.nameWithoutExtension + "." + testPrefix + "." + path.extension)
if (newPath.toFile().exists()) return newPath
return path
}
}
@@ -0,0 +1,11 @@
/*
* 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.descriptors.test.components.typeProvider
import org.jetbrains.kotlin.analysis.api.descriptors.test.KtFe10FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.typeInfoProvider.AbstractIsDenotableTest
abstract class AbstractKtFe10IsDenotableTest : AbstractIsDenotableTest(KtFe10FrontendApiTestConfiguratorService)
@@ -0,0 +1,50 @@
/*
* 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.descriptors.test.components.typeProvider;
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/typeInfoProvider/isDenotable")
@TestDataPath("$PROJECT_ROOT")
public class KtFe10IsDenotableTestGenerated extends AbstractKtFe10IsDenotableTest {
@Test
public void testAllFilesPresentInIsDenotable() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile(".*\\.descriptors\\.kt$"), true);
}
@Test
@TestMetadata("localTypes.kt")
public void testLocalTypes() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/localTypes.kt");
}
@Test
@TestMetadata("simpleTypes.kt")
public void testSimpleTypes() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/simpleTypes.kt");
}
@Test
@TestMetadata("smartcast.kt")
public void testSmartcast() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/smartcast.kt");
}
@Test
@TestMetadata("typeParameter.kt")
public void testTypeParameter() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/typeParameter.kt");
}
}
@@ -5,16 +5,18 @@
package org.jetbrains.kotlin.analysis.api.fir.components
import org.jetbrains.kotlin.fir.resolve.FirSamResolverImpl
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.types.canBeNull
import org.jetbrains.kotlin.analysis.api.components.KtTypeInfoProvider
import org.jetbrains.kotlin.analysis.api.fir.KtFirAnalysisSession
import org.jetbrains.kotlin.analysis.api.fir.types.KtFirType
import org.jetbrains.kotlin.analysis.api.fir.types.PublicTypeApproximator
import org.jetbrains.kotlin.analysis.api.tokens.ValidityToken
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.builtins.functions.FunctionClassKind
import org.jetbrains.kotlin.fir.resolve.FirSamResolverImpl
import org.jetbrains.kotlin.fir.resolve.ScopeSession
import org.jetbrains.kotlin.fir.types.canBeNull
import org.jetbrains.kotlin.fir.types.functionClassKind
import org.jetbrains.kotlin.fir.types.typeApproximator
internal class KtFirTypeInfoProvider(
override val analysisSession: KtFirAnalysisSession,
@@ -33,4 +35,12 @@ internal class KtFirTypeInfoProvider(
}
override fun canBeNull(type: KtType): Boolean = (type as KtFirType).coneType.canBeNull
override fun isDenotable(type: KtType): Boolean {
val coneType = (type as KtFirType).coneType
return analysisSession.rootModuleSession.typeApproximator.approximateToSuperType(
coneType,
PublicTypeApproximator.PublicApproximatorConfiguration(false)
) == null
}
}
@@ -20,7 +20,7 @@ internal object PublicTypeApproximator {
return approximator.approximateToSuperType(type, PublicApproximatorConfiguration(approximateLocalTypes))
}
private class PublicApproximatorConfiguration(
internal class PublicApproximatorConfiguration(
override val localTypes: Boolean
) : TypeApproximatorConfiguration.AllFlexibleSameValue() {
override val allFlexible: Boolean get() = false
@@ -0,0 +1,11 @@
/*
* 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.typeProvider
import org.jetbrains.kotlin.analysis.api.fir.FirFrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.base.test.components.typeInfoProvider.AbstractIsDenotableTest
abstract class AbstractFirIsDenotableTest : AbstractIsDenotableTest(FirFrontendApiTestConfiguratorService)
@@ -0,0 +1,50 @@
/*
* 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.typeProvider;
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/typeInfoProvider/isDenotable")
@TestDataPath("$PROJECT_ROOT")
public class FirIsDenotableTestGenerated extends AbstractFirIsDenotableTest {
@Test
public void testAllFilesPresentInIsDenotable() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable"), Pattern.compile("^(.+)\\.kt$"), Pattern.compile(".*\\.descriptors\\.kt$"), true);
}
@Test
@TestMetadata("localTypes.kt")
public void testLocalTypes() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/localTypes.kt");
}
@Test
@TestMetadata("simpleTypes.kt")
public void testSimpleTypes() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/simpleTypes.kt");
}
@Test
@TestMetadata("smartcast.kt")
public void testSmartcast() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/smartcast.kt");
}
@Test
@TestMetadata("typeParameter.kt")
public void testTypeParameter() throws Exception {
runTest("analysis/analysis-api/testData/components/typeInfoProvider/isDenotable/typeParameter.kt");
}
}
@@ -55,6 +55,8 @@ interface FrontendApiTestConfiguratorService {
fun prepareTestFiles(files: List<KtFile>, module: TestModule, testServices: TestServices) {}
fun doOutOfBlockModification(file: KtFile)
fun preprocessTestDataPath(path: Path) : Path = path
}
abstract class AbstractFrontendApiTest(val configurator: FrontendApiTestConfiguratorService) : TestWithDisposable() {
@@ -138,7 +140,7 @@ abstract class AbstractFrontendApiTest(val configurator: FrontendApiTestConfigur
}
protected fun runTest(@TestDataFile path: String) {
testDataPath = Paths.get(path)
testDataPath = configurator.preprocessTestDataPath(Paths.get(path))
val testConfiguration = testConfiguration(path, configure)
Disposer.register(disposable, testConfiguration.rootDisposable)
val testServices = testConfiguration.testServices
@@ -0,0 +1,94 @@
/*
* 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.impl.base.test.components.typeInfoProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.impl.source.tree.LeafPsiElement
import org.jetbrains.kotlin.analysis.api.impl.barebone.parentOfType
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.FrontendApiTestConfiguratorService
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtAnnotatedExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiUtil.deparenthesize
import org.jetbrains.kotlin.psi.KtQualifiedExpression
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.ConfigurationDirectives
import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives
import org.jetbrains.kotlin.test.model.TestFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.AdditionalSourceProvider
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
import java.io.File
abstract class AbstractIsDenotableTest(configurator: FrontendApiTestConfiguratorService) : AbstractHLApiSingleFileTest(configurator) {
val denotableName = Name.identifier("Denotable")
val undenotableName = Name.identifier("Nondenotable")
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val actualText = buildString {
ktFile.accept(object : KtTreeVisitorVoid() {
override fun visitElement(element: PsiElement) {
if (element is LeafPsiElement) {
append(element.text)
}
super.visitElement(element)
}
override fun visitAnnotatedExpression(expression: KtAnnotatedExpression) {
val base = expression.baseExpression
if (base == null || expression.annotationEntries.none {
it.shortName == denotableName || it.shortName == undenotableName
}) {
super.visitAnnotatedExpression(expression)
return
}
analyseForTest(expression) {
val parent = expression.parentOfType<KtQualifiedExpression>()
// Try locating the containing PSI that is a receiver of a qualified expression because the smart cast information
// is only available at that level for FE1.0. For example, consider
// ```
// if (a is String) {
// (@Denotable("...") a).length
// }
// ```
// smart cast is available for `(@Denotable("...") a)` and not for `a` or `@Denotable("...") a`.
val ktType = if (parent != null && deparenthesize(parent.receiverExpression) == deparenthesize(base)) {
parent.receiverExpression.getKtType()
} else {
expression.getKtType()
}
val actualHasDenotableType = ktType?.isDenotable ?: error("${base.text} does not have a type.")
when (actualHasDenotableType) {
true -> append("@Denotable")
false -> append("@Nondenotable")
}
append("(\"${ktType.render()}\") ")
append(base.text)
}
}
})
}
testServices.assertions.assertEqualsToFile(testDataPath, actualText)
}
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
builder.useAdditionalSourceProviders(AbstractIsDenotableTest::TestHelperProvider)
builder.defaultDirectives {
+ConfigurationDirectives.WITH_STDLIB
}
}
private class TestHelperProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) {
override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List<TestFile> {
return listOf(File("analysis/analysis-api/testData/helpers/isDenotable/helpers.kt").toTestFile())
}
}
}
@@ -19,9 +19,17 @@ public abstract class KtTypeInfoProvider : KtAnalysisSessionComponent() {
public abstract fun isFunctionalInterfaceType(type: KtType): Boolean
public abstract fun getFunctionClassKind(type: KtType): FunctionClassKind?
public abstract fun canBeNull(type: KtType): Boolean
public abstract fun isDenotable(type: KtType): Boolean
}
public interface KtTypeInfoProviderMixIn : KtAnalysisSessionMixIn {
/**
* Returns true if this type is denotable. A denotable type is a type that can be written in Kotlin by end users. See
* https://kotlinlang.org/spec/type-system.html#type-kinds for more details.
*/
public val KtType.isDenotable: Boolean
get() = analysisSession.typeInfoProvider.isDenotable(this)
/**
* Returns true if this type is a functional interface type, a.k.a. SAM type, e.g., Runnable.
*/
@@ -0,0 +1,7 @@
fun test() {
class A
@Denotable("A") A()
@Denotable("kotlin.collections.List<A>") listOf(A())
@Nondenotable("<no name provided>") object {}
@Nondenotable("kotlin.collections.List<<no name provided>>") listOf(object {})
}
@@ -0,0 +1,7 @@
fun test() {
class A
@Denotable("A") A()
@Denotable("kotlin.collections.List<A>") listOf(A())
@Nondenotable("<anonymous>") object {}
@Nondenotable("kotlin.collections.List<<anonymous>>") listOf(object {})
}
@@ -0,0 +1,6 @@
interface A
fun test(a: A) {
@Denotable("kotlin.Int") 1
@Denotable("kotlin.String") ""
Denotable("A") a
}
@@ -0,0 +1,28 @@
fun test(a: Any?) {
if (a is String) {
(@Denotable("kotlin.String") a).length
if (a is Int) {
(@Denotable("kotlin.Int") a).inc()
}
if (a is String) {
(@Denotable("kotlin.String") a).length
}
}
if (a != null) {
(@Denotable("kotlin.Any") a).hashCode()
}
if (a == null) {
(@Denotable("kotlin.Any?") a).isNothing()
}
if (a is String || a is Int) {
(@Denotable("kotlin.Any?") a).length
(@Denotable("kotlin.Any?") a).inc()
}
@Nondenotable("(kotlin.Comparable<*> & java.io.Serializable)") if (true) {
""
} else {
1
}
}
fun Nothing?.isNothing() {}
@@ -0,0 +1,28 @@
fun test(a: Any?) {
if (a is String) {
(@Denotable("kotlin.String") a).length
if (a is Int) {
(@Nondenotable("(kotlin.String&kotlin.Int)") a).inc()
}
if (a is String) {
(@Denotable("kotlin.String") a).length
}
}
if (a != null) {
(@Denotable("kotlin.Any") a).hashCode()
}
if (a == null) {
(@Denotable("kotlin.Nothing?") a).isNothing()
}
if (a is String || a is Int) {
(@Nondenotable("(kotlin.Comparable<(kotlin.String&kotlin.Int)>&java.io.Serializable)") a).length
(@Nondenotable("(kotlin.Comparable<(kotlin.String&kotlin.Int)>&java.io.Serializable)") a).inc()
}
@Nondenotable("(kotlin.Comparable<*>&java.io.Serializable)") if (true) {
""
} else {
1
}
}
fun Nothing?.isNothing() {}
@@ -0,0 +1,25 @@
interface A
fun <T> test(t: T) {
@Denotable("T") t
if (t != null) {
(@Nondenotable("T & Any") t).equals("")
}
val outs = take(getOutProjection())
@Denotable("A") outs
val ins = take(getInProjection())
@Denotable(kotlin.Any?) ins
}
fun getOutProjection(): MutableList<out A> {
TODO()
}
fun getInProjection(): MutableList<in A> {
TODO()
}
fun <T> take(l: MutableList<T>): T {
TODO()
}
@@ -0,0 +1,25 @@
interface A
fun <T> test(t: T) {
@Denotable("T") t
if (t != null) {
(@Nondenotable("T!!") t).equals("")
}
val outs = take(getOutProjection())
@Denotable("A") outs
val ins = take(getInProjection())
@Denotable(kotlin.Any?) ins
}
fun getOutProjection(): MutableList<out A> {
TODO()
}
fun getInProjection(): MutableList<in A> {
TODO()
}
fun <T> take(l: MutableList<T>): T {
TODO()
}
@@ -0,0 +1,8 @@
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Denotable(val type: String)
@Target(AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
annotation class Nondenotable(val type: String)
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.analysis.api.descriptors.test.components.symbolDecla
import org.jetbrains.kotlin.analysis.api.descriptors.test.components.typeCreator.AbstractKtFe10TypeParameterTypeTest
import org.jetbrains.kotlin.analysis.api.descriptors.test.components.typeProvider.AbstractKtFe10HasCommonSubtypeTest
import org.jetbrains.kotlin.analysis.api.descriptors.test.scopes.AbstractKtFe10SubstitutionOverridesUnwrappingTest
import org.jetbrains.kotlin.analysis.api.descriptors.test.components.typeProvider.AbstractKtFe10IsDenotableTest
import org.jetbrains.kotlin.analysis.api.descriptors.test.symbols.AbstractKtFe10SymbolByFqNameTest
import org.jetbrains.kotlin.analysis.api.descriptors.test.symbols.AbstractKtFe10SymbolByPsiTest
import org.jetbrains.kotlin.analysis.api.descriptors.test.symbols.AbstractKtFe10SymbolByReferenceTest
@@ -46,6 +47,7 @@ import org.jetbrains.kotlin.analysis.api.fir.components.typeCreator.AbstractFirT
import org.jetbrains.kotlin.analysis.api.fir.components.typeInfoProvider.AbstractFirFunctionClassKindTest
import org.jetbrains.kotlin.analysis.api.fir.components.typeProvider.AbstractFirGetSuperTypesTest
import org.jetbrains.kotlin.analysis.api.fir.components.typeProvider.AbstractFirHasCommonSubtypeTest
import org.jetbrains.kotlin.analysis.api.fir.components.typeProvider.AbstractFirIsDenotableTest
import org.jetbrains.kotlin.analysis.api.fir.scopes.AbstractFirDelegateMemberScopeTest
import org.jetbrains.kotlin.analysis.api.fir.scopes.AbstractFirFileScopeTest
import org.jetbrains.kotlin.analysis.api.fir.scopes.AbstractFirMemberScopeByFqNameTest
@@ -257,6 +259,9 @@ private fun TestGroupSuite.generateAnalysisApiComponentsTests() {
test(fir = AbstractFirGetSuperTypesTest::class, fe10 = null) {
model("superTypes")
}
test(fir = AbstractFirIsDenotableTest::class, fe10 = AbstractKtFe10IsDenotableTest::class) {
model("isDenotable", excludedPattern = ".*\\.descriptors\\.kt$")
}
}
component("typeProvider") {