[FIR IDE] Extract common components of the Analysis API to separate modules

This commit is contained in:
Yan Zhulanow
2021-10-05 19:08:06 +09:00
committed by teamcityserver
parent 44a1fe668e
commit e2c9be0932
100 changed files with 966 additions and 424 deletions
@@ -0,0 +1,29 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api(project(":compiler:psi"))
api(project(":analysis:analysis-api"))
api(project(":analysis:analysis-api-impl-barebone"))
api(intellijCoreDep()) { includeJars("intellij-core", rootProject = rootProject) }
testApiJUnit5()
testApi(project(":kotlin-test:kotlin-test-junit"))
testApi(project(":analysis:analysis-api"))
testApi(projectTests(":compiler:tests-common"))
testApi(projectTests(":compiler:test-infrastructure-utils"))
testApi(projectTests(":compiler:test-infrastructure"))
testApi(projectTests(":compiler:tests-common-new"))
testApi(projectTests(":analysis:analysis-api-impl-barebone"))
testImplementation(project(":kotlin-reflect"))
testImplementation(toolsJar())
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
testsJar()
@@ -0,0 +1,85 @@
/*
* 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
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ProjectRootModificationTracker
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.analysis.providers.createProjectWideOutOfBlockModificationTracker
import org.jetbrains.kotlin.analysis.api.InvalidWayOfUsingAnalysisSession
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.KtAnalysisSessionProvider
import org.jetbrains.kotlin.analysis.api.isValid
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.api.tokens.ValidityToken
import org.jetbrains.kotlin.analysis.api.tokens.ValidityTokenFactory
import org.jetbrains.kotlin.psi.KtElement
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
@OptIn(InvalidWayOfUsingAnalysisSession::class)
abstract class CachingKtAnalysisSessionProvider<State : Any>(private val project: Project) : KtAnalysisSessionProvider() {
private val cache = KtAnalysisSessionCache<Pair<State, KClass<out ValidityToken>>>(project)
protected abstract fun getResolveState(contextElement: KtElement): State
protected abstract fun getResolveState(contextSymbol: KtSymbol): State
protected abstract fun createAnalysisSession(
resolveState: State,
validityToken: ValidityToken,
contextElement: KtElement
): KtAnalysisSession
@InvalidWayOfUsingAnalysisSession
final override fun getAnalysisSession(contextElement: KtElement, factory: ValidityTokenFactory): KtAnalysisSession {
val resolveState = getResolveState(contextElement)
return cache.getAnalysisSession(resolveState to factory.identifier) {
val validityToken = factory.create(project)
createAnalysisSession(resolveState, validityToken, contextElement)
}
}
final override fun getAnalysisSessionBySymbol(contextSymbol: KtSymbol): KtAnalysisSession {
val resolveState = getResolveState(contextSymbol)
val token = contextSymbol.token
return getCachedAnalysisSession(resolveState, token)
?: error("analysis session was not found for ${contextSymbol::class}, symbol.isValid=${contextSymbol.isValid()}")
}
private fun getCachedAnalysisSession(resolveState: State, token: ValidityToken): KtAnalysisSession? {
return cache.getCachedAnalysisSession(resolveState to token::class)
}
@TestOnly
final override fun clearCaches() {
cache.clear()
}
}
private class KtAnalysisSessionCache<KEY : Any>(project: Project) {
private val cache = CachedValuesManager.getManager(project).createCachedValue {
CachedValueProvider.Result(
ConcurrentHashMap<KEY, KtAnalysisSession>(),
PsiModificationTracker.MODIFICATION_COUNT,
ProjectRootModificationTracker.getInstance(project),
project.createProjectWideOutOfBlockModificationTracker()
)
}
@TestOnly
fun clear() {
cache.value.clear()
}
inline fun getAnalysisSession(key: KEY, create: () -> KtAnalysisSession): KtAnalysisSession =
cache.value.getOrPut(key) { create() }
fun getCachedAnalysisSession(key: KEY): KtAnalysisSession? =
cache.value[key]
}
@@ -0,0 +1,14 @@
/*
* 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
import org.jetbrains.kotlin.analysis.api.symbols.KtTypeParameterSymbol
import org.jetbrains.kotlin.analysis.api.types.KtSubstitutor
import org.jetbrains.kotlin.analysis.api.types.KtType
interface KtMapBackedSubstitutor : KtSubstitutor {
fun getAsMap(): Map<KtTypeParameterSymbol, KtType>
}
@@ -0,0 +1,72 @@
/*
* 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.references
import com.intellij.psi.ContributedReferenceHost
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiReference
import com.intellij.psi.PsiReferenceService
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry
import com.intellij.psi.util.CachedValueProvider
import com.intellij.psi.util.CachedValuesManager
import com.intellij.psi.util.PsiModificationTracker
import com.intellij.util.containers.ConcurrentFactoryMap
import com.intellij.util.containers.MultiMap
import org.jetbrains.kotlin.idea.references.KotlinPsiReferenceProvider
import org.jetbrains.kotlin.idea.references.KotlinPsiReferenceRegistrar
import org.jetbrains.kotlin.idea.references.KotlinReferenceProviderContributor
import org.jetbrains.kotlin.psi.KotlinReferenceProvidersService
import org.jetbrains.kotlin.utils.SmartList
class HLApiReferenceProviderService : KotlinReferenceProvidersService() {
private val originalProvidersBinding: MultiMap<Class<out PsiElement>, KotlinPsiReferenceProvider>
private val providersBindingCache: Map<Class<out PsiElement>, List<KotlinPsiReferenceProvider>>
init {
val registrar = KotlinPsiReferenceRegistrar()
KotlinReferenceProviderContributor.getInstance().registerReferenceProviders(registrar)
originalProvidersBinding = registrar.providers
providersBindingCache = ConcurrentFactoryMap.createMap<Class<out PsiElement>, List<KotlinPsiReferenceProvider>> { klass ->
val result = SmartList<KotlinPsiReferenceProvider>()
for (bindingClass in originalProvidersBinding.keySet()) {
if (bindingClass.isAssignableFrom(klass)) {
result.addAll(originalProvidersBinding.get(bindingClass))
}
}
result
}
}
private fun doGetKotlinReferencesFromProviders(context: PsiElement): Array<PsiReference> {
val providers: List<KotlinPsiReferenceProvider>? = providersBindingCache[context.javaClass]
if (providers.isNullOrEmpty()) return PsiReference.EMPTY_ARRAY
val result = SmartList<PsiReference>()
for (provider in providers) {
result.addAll(provider.getReferencesByElement(context))
}
if (result.isEmpty()) {
return PsiReference.EMPTY_ARRAY
}
return result.toTypedArray()
}
override fun getReferences(psiElement: PsiElement): Array<PsiReference> {
if (psiElement is ContributedReferenceHost) {
return ReferenceProvidersRegistry.getReferencesFromProviders(psiElement, PsiReferenceService.Hints.NO_HINTS)
}
return CachedValuesManager.getCachedValue(psiElement) {
CachedValueProvider.Result.create(
doGetKotlinReferencesFromProviders(psiElement),
PsiModificationTracker.MODIFICATION_COUNT
)
}
}
}
@@ -0,0 +1,16 @@
/*
* 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.util
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.kotlin.analysis.api.tokens.HackToForceAllowRunningAnalyzeOnEDT
import org.jetbrains.kotlin.analysis.api.tokens.hackyAllowRunningOnEdt
@HackToForceAllowRunningAnalyzeOnEDT
inline fun <R> runInPossiblyEdtThread(action: () -> R): R = when {
!ApplicationManager.getApplication().isDispatchThread -> action()
else -> hackyAllowRunningOnEdt(action)
}
@@ -0,0 +1,132 @@
/*
* 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
import com.intellij.psi.PsiReference
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.components.KtDeclarationRendererOptions
import org.jetbrains.kotlin.analysis.api.components.RendererModifier
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtSymbolWithKind
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
abstract class AbstractReferenceResolveTest : AbstractHLApiSingleModuleTest() {
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
with(builder) {
defaultDirectives {
+JvmEnvironmentConfigurationDirectives.WITH_STDLIB
}
useDirectives(Directives)
}
}
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val mainKtFile = ktFiles.singleOrNull() ?: ktFiles.first { it.name == "main.kt" }
val caretPosition = testServices.expressionMarkerProvider.getCaretPosition(mainKtFile)
val ktReferences = findReferencesAtCaret(mainKtFile, caretPosition)
if (ktReferences.isEmpty()) {
testServices.assertions.fail { "No references at caret found" }
}
val resolvedTo =
analyseForTest(PsiTreeUtil.findElementOfClassAtOffset(mainKtFile, caretPosition, KtDeclaration::class.java, false) ?: mainKtFile) {
val symbols = ktReferences.flatMap { it.resolveToSymbols() }
checkReferenceResultForValidity(module, testServices, symbols)
renderResolvedTo(symbols)
}
if (Directives.UNRESOLVED_REFERENCE in module.directives) {
return
}
val actual = "Resolved to:\n$resolvedTo"
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
private fun findReferencesAtCaret(mainKtFile: KtFile, caretPosition: Int): List<KtReference> =
mainKtFile.findReferenceAt(caretPosition)?.unwrapMultiReferences().orEmpty().filterIsInstance<KtReference>()
private fun KtAnalysisSession.checkReferenceResultForValidity(
module: TestModule,
testServices: TestServices,
resolvedTo: List<KtSymbol>
) {
if (Directives.UNRESOLVED_REFERENCE in module.directives) {
testServices.assertions.assertTrue(resolvedTo.isEmpty()) {
"Reference should be unresolved, but was resolved to ${renderResolvedTo(resolvedTo)}"
}
} else {
testServices.assertions.assertTrue(resolvedTo.isNotEmpty()) { "Unresolved reference" }
}
}
private fun KtAnalysisSession.renderResolvedTo(symbols: List<KtSymbol>) =
symbols.map { renderResolveResult(it) }
.sorted()
.withIndex()
.joinToString(separator = "\n") { "${it.index}: ${it.value}" }
private fun KtAnalysisSession.renderResolveResult(symbol: KtSymbol): String {
return buildString {
symbolContainerFqName(symbol)?.let { fqName ->
append("(in $fqName) ")
}
append(symbol.render(renderingOptions))
}
}
@Suppress("unused")// KtAnalysisSession receiver
private fun KtAnalysisSession.symbolContainerFqName(symbol: KtSymbol): String? {
if (symbol is KtPackageSymbol || symbol is KtValueParameterSymbol) return null
val nonLocalFqName = when (symbol) {
is KtConstructorSymbol -> symbol.containingClassIdIfNonLocal?.asSingleFqName()
is KtCallableSymbol -> symbol.callableIdIfNonLocal?.asSingleFqName()?.parent()
is KtClassLikeSymbol -> symbol.classIdIfNonLocal?.asSingleFqName()?.parent()
else -> null
}
when (nonLocalFqName) {
null -> Unit
FqName.ROOT -> return "ROOT"
else -> return nonLocalFqName.asString()
}
val container = (symbol as? KtSymbolWithKind)?.getContainingSymbol() ?: return null
val parents = generateSequence(container) { it.getContainingSymbol() }
return "<local>: " + parents.joinToString(separator = ".") { (it as? KtNamedSymbol)?.name?.asString() ?: "<no name>" }
}
private object Directives : SimpleDirectivesContainer() {
val UNRESOLVED_REFERENCE by directive(
"Reference should be unresolved",
)
}
private val renderingOptions = KtDeclarationRendererOptions.DEFAULT.copy(
modifiers = RendererModifier.DEFAULT - RendererModifier.ANNOTATIONS,
sortNestedDeclarations = true
)
private fun PsiReference.unwrapMultiReferences(): List<PsiReference> = when (this) {
is KtReference -> listOf(this)
is PsiMultiReference -> references.flatMap { it.unwrapMultiReferences() }
else -> error("Unexpected reference $this")
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2020 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
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtNamedSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import java.io.File
import java.nio.file.Path
object SymbolByFqName {
fun getSymbolDataFromFile(filePath: Path): SymbolData {
val testFileText = FileUtil.loadFile(filePath.toFile())
val identifier = testFileText.lineSequence().first { line ->
SymbolData.identifiers.any { identifier ->
line.startsWith(identifier) || line.startsWith("// $identifier")
}
}
return SymbolData.create(identifier.removePrefix("// "))
}
fun textWithRenderedSymbolData(filePath: String, rendered: String): String = buildString {
val testFileText = FileUtil.loadFile(File(filePath))
val fileTextWithoutSymbolsData = testFileText.substringBeforeLast(SYMBOLS_TAG).trimEnd()
appendLine(fileTextWithoutSymbolsData)
appendLine()
appendLine(SYMBOLS_TAG)
append(rendered)
}
private const val SYMBOLS_TAG = "// SYMBOLS:"
}
sealed class SymbolData {
abstract fun KtAnalysisSession.toSymbols(): List<KtSymbol>
data class ClassData(val classId: ClassId) : SymbolData() {
override fun KtAnalysisSession.toSymbols(): List<KtSymbol> {
val symbol = classId.getCorrespondingToplevelClassOrObjectSymbol() ?: error("Class $classId is not found")
return listOf(symbol)
}
}
data class CallableData(val callableId: CallableId) : SymbolData() {
override fun KtAnalysisSession.toSymbols(): List<KtSymbol> {
val classId = callableId.classId
val symbols = if (classId == null) {
callableId.packageName.getContainingCallableSymbolsWithName(callableId.callableName).toList()
} else {
val classSymbol =
classId.getCorrespondingToplevelClassOrObjectSymbol()
?: error("Class $classId is not found")
classSymbol.getDeclaredMemberScope().getCallableSymbols()
.filter { (it as? KtNamedSymbol)?.name == callableId.callableName }
.toList()
}
if (symbols.isEmpty()) {
error("No callable with fqName $callableId found")
}
return symbols
}
}
companion object {
val identifiers = arrayOf("callable:", "class:")
fun create(data: String): SymbolData = when {
data.startsWith("class:") -> ClassData(ClassId.fromString(data.removePrefix("class:").trim()))
data.startsWith("callable:") -> {
val fullName = data.removePrefix("callable:").trim()
val name = if ('.' in fullName) fullName.substringAfterLast(".") else fullName.substringAfterLast('/')
val (packageName, className) = run {
val packageNameWithClassName = fullName.dropLast(name.length + 1)
when {
'.' in fullName ->
packageNameWithClassName.substringBeforeLast('/') to packageNameWithClassName.substringAfterLast('/')
else -> packageNameWithClassName to null
}
}
CallableData(CallableId(FqName(packageName.replace('/', '.')), className?.let { FqName(it) }, Name.identifier(name)))
}
else -> error("Invalid symbol")
}
}
}
@@ -0,0 +1,24 @@
/*
* 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
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.psi.KtElement
inline fun <T> runReadAction(crossinline runnable: () -> T): T {
return ApplicationManager.getApplication().runReadAction(Computable { runnable() })
}
fun <R> executeOnPooledThreadInReadAction(action: () -> R): R =
ApplicationManager.getApplication().executeOnPooledThread<R> { runReadAction(action) }.get()
inline fun <R> analyseOnPooledThreadInReadAction(context: KtElement, crossinline action: KtAnalysisSession.() -> R): R =
executeOnPooledThreadInReadAction {
analyse(context) { action() }
}
@@ -0,0 +1,73 @@
/*
* 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
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.analysis.api.symbols.markers.*
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
abstract class AbstractCompileTimeConstantEvaluatorTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val element = testServices.expressionMarkerProvider.getSelectedElement(ktFile)
val expression = when (element) {
is KtExpression -> element
is KtValueArgument -> element.getArgumentExpression()
else -> null
} ?: testServices.assertions.fail { "Unsupported expression: $element" }
val constantValue = executeOnPooledThreadInReadAction {
analyseForTest(expression) { expression.evaluate() }
}
val actual = buildString {
appendLine("expression: ${expression.text}")
appendLine("constant_value: ${analyseForTest(expression) { constantValue?.stringRepresentation() }}")
appendLine("constant: ${(constantValue as? KtLiteralConstantValue<*>)?.toConst()}")
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
private fun KtConstantValue.stringRepresentation(): String {
return when (this) {
is KtArrayConstantValue -> buildString {
appendLine("KtArrayConstantValue [")
appendLine(INDENT, values.joinToString(separator = "\n") { it.stringRepresentation() })
append("]")
}
is KtAnnotationConstantValue -> buildString {
append("KtAnnotationConstantValue(")
append(classId?.relativeClassName)
append(", ")
arguments.joinTo(this, separator = ", ", prefix = "(", postfix = ")") {
"${it.name} = ${it.expression.stringRepresentation()}"
}
append(")")
}
is KtEnumEntryConstantValue -> buildString {
append("KtEnumEntryConstantValue(")
append("$callableId")
append(")")
}
is KtLiteralConstantValue<*> -> buildString {
append("KtLiteralConstantValue(")
append("constantValueKind=${constantValueKind}")
append(", ")
append("value=${value})")
}
is KtUnsupportedConstantValue -> "KtUnsupportedConstantValue"
}
}
private fun StringBuilder.appendLine(indent: Int, value: String) {
appendLine(value.prependIndent(" ".repeat(indent)))
}
}
private const val INDENT = 2
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2020 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
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.psi.KtExpression
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 AbstractExpectedExpressionTypeTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val expressionAtCaret = testServices.expressionMarkerProvider.getElementOfTypAtCaret(ktFile) as KtExpression
val actualExpectedTypeText: String? = executeOnPooledThreadInReadAction {
analyseForTest(expressionAtCaret) {
expressionAtCaret.getExpectedType()?.asStringForDebugging()
}
}
val actual = buildString {
appendLine("expression: ${expressionAtCaret.text}")
appendLine("expected type: $actualExpectedTypeText")
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
}
@@ -0,0 +1,30 @@
/*
* 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
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.psi.KtExpression
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 AbstractHLExpressionTypeTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val expression = testServices.expressionMarkerProvider.getSelectedElement(ktFile) as KtExpression
val type = executeOnPooledThreadInReadAction {
analyseForTest(expression) { expression.getKtType()?.render() }
}
val actual = buildString {
appendLine("expression: ${expression.text}")
appendLine("type: $type")
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
}
@@ -0,0 +1,109 @@
/*
* 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
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
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 AbstractHasCommonSubtypeTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val errors = mutableListOf<String>()
val originalText = ktFile.text
val actualTextBuilder = StringBuilder()
analyseForTest(ktFile) {
val visitor = object : KtTreeVisitorVoid() {
override fun visitElement(element: PsiElement) {
if (element.firstChild == null) {
actualTextBuilder.append(element.text)
}
super.visitElement(element)
}
override fun visitCallExpression(expression: KtCallExpression) {
val haveCommonSubtype = when (expression.calleeExpression?.text) {
"typesHaveCommonSubtype" -> true
"typesHaveNoCommonSubtype" -> false
else -> {
super.visitCallExpression(expression)
return
}
}
val valueArguments = expression.valueArguments
require(valueArguments.size == 2) {
"Illegal call of ${expression.name} at ${expression.positionString}"
}
val a = valueArguments[0]
val aType = a.getArgumentExpression()?.getKtType()
if (aType == null) {
errors.add("'${a.text}' has no type at ${a.positionString}")
super.visitCallExpression(expression)
return
}
val b = valueArguments[1]
val bType = b.getArgumentExpression()?.getKtType()
if (bType == null) {
errors.add("'${b.text}' has no type at ${b.positionString}")
super.visitCallExpression(expression)
return
}
if (haveCommonSubtype != aType.hasCommonSubTypeWith(bType)) {
if (haveCommonSubtype) {
actualTextBuilder.append("typesHaveNoCommonSubtype")
} else {
actualTextBuilder.append("typesHaveCommonSubtype")
}
actualTextBuilder.append(expression.valueArgumentList!!.text)
} else {
super.visitCallExpression(expression)
}
}
}
visitor.visitFile(ktFile)
}
if (errors.isNotEmpty()) {
testServices.assertions.fail { errors.joinToString("\n") }
}
val actualText = actualTextBuilder.toString()
if (actualText != originalText) {
testServices.assertions.assertEqualsToFile(testDataPath, actualText)
}
}
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
builder.useAdditionalSourceProviders(::TestHelperProvider)
builder.defaultDirectives {
+JvmEnvironmentConfigurationDirectives.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/hasCommonSubtype/helpers.kt").toTestFile())
}
}
private val PsiElement.positionString: String
get() {
val illegalCallPos = StringUtil.offsetToLineColumn(containingFile.text, textRange.startOffset)
return "${containingFile.virtualFile.path}:${illegalCallPos.line + 1}:${illegalCallPos.column + 1}"
}
}
@@ -0,0 +1,73 @@
/*
* 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
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.components.KtTypeRendererOptions
import org.jetbrains.kotlin.analysis.api.impl.barebone.parentsOfType
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.test.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.analysis.api.symbols.KtCallableSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtFunctionSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSyntheticJavaPropertySymbol
import org.jetbrains.kotlin.psi.KtDeclaration
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 AbstractOverriddenDeclarationProviderTest : AbstractHLApiSingleModuleTest() {
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val declaration = testServices.expressionMarkerProvider.getElementOfTypAtCaret<KtDeclaration>(ktFiles.first())
val actual = executeOnPooledThreadInReadAction {
analyseForTest(declaration) {
val symbol = declaration.getSymbol() as KtCallableSymbol
val allOverriddenSymbols = symbol.getAllOverriddenSymbols().map { renderSignature(it) }
val directlyOverriddenSymbols = symbol.getDirectlyOverriddenSymbols().map { renderSignature(it) }
buildString {
appendLine("ALL:")
allOverriddenSymbols.forEach { appendLine(" $it") }
appendLine("DIRECT:")
directlyOverriddenSymbols.forEach { appendLine(" $it") }
}
}
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
private fun KtAnalysisSession.renderSignature(symbol: KtCallableSymbol): String = buildString {
append(getPath(symbol))
if (symbol is KtFunctionSymbol) {
append("(")
symbol.valueParameters.forEachIndexed { index, parameter ->
append(parameter.name.identifier)
append(": ")
append(parameter.annotatedType.type.render(KtTypeRendererOptions.SHORT_NAMES))
if (index != symbol.valueParameters.lastIndex) {
append(", ")
}
}
append(")")
}
append(": ")
append(symbol.annotatedType.type.render(KtTypeRendererOptions.SHORT_NAMES))
}
private fun getPath(symbol: KtCallableSymbol): String = when (symbol) {
is KtSyntheticJavaPropertySymbol -> symbol.callableIdIfNonLocal?.toString()!!
else -> {
val ktDeclaration = symbol.psi as KtDeclaration
ktDeclaration
.parentsOfType<KtDeclaration>(withSelf = true)
.map { it.name ?: "<no name>" }
.toList()
.asReversed()
.joinToString(separator = ".")
}
}
}
@@ -0,0 +1,40 @@
/*
* 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
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.components.KtDeclarationRendererOptions
import org.jetbrains.kotlin.analysis.api.components.KtTypeRendererOptions
import org.jetbrains.kotlin.analysis.api.impl.base.test.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
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 AbstractRendererTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val options = KtDeclarationRendererOptions.DEFAULT.copy(
approximateTypes = true,
renderContainingDeclarations = true,
typeRendererOptions = KtTypeRendererOptions.SHORT_NAMES,
sortNestedDeclarations = true
)
val actual = executeOnPooledThreadInReadAction {
buildString {
ktFile.declarations.forEach {
analyseForTest(it) {
append(it.getSymbol().render(options))
appendLine()
}
}
}
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".rendered"), actual)
}
}
@@ -0,0 +1,127 @@
/*
* 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.fir
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.calls.KtCall
import org.jetbrains.kotlin.analysis.api.calls.KtDelegatedConstructorCallKind
import org.jetbrains.kotlin.analysis.api.calls.KtErrorCallTarget
import org.jetbrains.kotlin.analysis.api.calls.KtSuccessCallTarget
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.impl.base.KtMapBackedSubstitutor
import org.jetbrains.kotlin.analysis.api.impl.base.test.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleModuleTest
import org.jetbrains.kotlin.analysis.api.symbols.*
import org.jetbrains.kotlin.analysis.api.symbols.markers.KtPossibleMemberSymbol
import org.jetbrains.kotlin.analysis.api.types.KtSubstitutor
import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
import kotlin.reflect.KProperty1
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaGetter
abstract class AbstractResolveCallTest : AbstractHLApiSingleModuleTest() {
override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val ktFile = ktFiles.first()
val expression = testServices.expressionMarkerProvider.getSelectedElement(ktFile)
val actual = executeOnPooledThreadInReadAction {
analyseForTest(expression) {
resolveCall(expression)?.stringRepresentation()
}
} ?: "null"
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
private fun KtAnalysisSession.resolveCall(element: PsiElement): KtCall? = when (element) {
is KtCallElement -> element.resolveCall()
is KtBinaryExpression -> element.resolveCall()
is KtUnaryExpression -> element.resolveCall()
is KtArrayAccessExpression -> element.resolveCall()
is KtSimpleNameExpression -> element.resolveAccessorCall()
is KtValueArgument -> resolveCall(element.getArgumentExpression()!!)
is KtDeclarationModifierList -> {
val annotationEntry = element.annotationEntries.singleOrNull()
?: error("Only single annotation entry is supported for now")
annotationEntry.resolveCall()
}
is KtFileAnnotationList -> {
val annotationEntry = element.annotationEntries.singleOrNull()
?: error("Only single annotation entry is supported for now")
annotationEntry.resolveCall()
}
else -> error("Selected element type (${element::class.simpleName}) is not supported for resolveCall()")
}
}
private fun KtCall.stringRepresentation(): String {
fun KtType.render() = substitutor.substituteOrSelf(this).asStringForDebugging().replace('/', '.')
fun Any.stringValue(): String = when (this) {
is KtFunctionLikeSymbol -> buildString {
append(
when (this@stringValue) {
is KtFunctionSymbol -> callableIdIfNonLocal ?: name
is KtSamConstructorSymbol -> callableIdIfNonLocal ?: name
is KtConstructorSymbol -> "<constructor>"
is KtPropertyGetterSymbol -> callableIdIfNonLocal ?: "<getter>"
is KtPropertySetterSymbol -> callableIdIfNonLocal ?: "<setter>"
else -> error("unexpected symbol kind in KtCall: ${this@stringValue::class.java}")
}
)
append("(")
(this@stringValue as? KtFunctionSymbol)?.receiverType?.let { receiver ->
append("<extension receiver>: ${receiver.type.render()}")
if (valueParameters.isNotEmpty()) append(", ")
}
(this@stringValue as? KtPossibleMemberSymbol)?.dispatchType?.let { dispatchReceiverType ->
append("<dispatch receiver>: ${dispatchReceiverType.render()}")
if (valueParameters.isNotEmpty()) append(", ")
}
valueParameters.joinTo(this) { it.stringValue() }
append(")")
append(": ${annotatedType.type.render()}")
}
is KtValueParameterSymbol -> "${if (isVararg) "vararg " else ""}$name: ${annotatedType.type.render()}"
is KtTypeParameterSymbol -> this.nameOrAnonymous.asString()
is KtVariableSymbol -> "${if (isVal) "val" else "var"} $name: ${annotatedType.type.render()}"
is KtSuccessCallTarget -> symbol.stringValue()
is KtErrorCallTarget -> "ERR<${this.diagnostic.defaultMessage}, [${candidates.joinToString { it.stringValue() }}]>"
is Boolean -> toString()
is Map<*, *> -> entries.joinToString(prefix = "{ ", postfix = " }") { (k, v) -> "${k?.stringValue()} -> (${v?.stringValue()})" }
is KtExpression -> this.text
is KtDelegatedConstructorCallKind -> toString()
is KtSubstitutor.Empty -> "<empty substitutor>"
is KtMapBackedSubstitutor -> {
val mappingText = getAsMap().orEmpty().entries
.joinToString(prefix = "{", postfix = "}") { (k, v) -> k.stringValue() + " = " + v.asStringForDebugging() }
"<map substitutor: $mappingText>"
}
is KtSubstitutor -> "<complex substitutor>"
else -> error("unexpected parameter type ${this::class}")
}
val callInfoClass = this::class
return buildString {
append(callInfoClass.simpleName!!)
append(":\n")
val propertyByName =
callInfoClass.memberProperties.associateBy(KProperty1<*, *>::name)
callInfoClass.primaryConstructor!!.parameters
.filter { it.name != "token" }
.joinTo(this, separator = "\n") { parameter ->
val name = parameter.name!!.removePrefix("_")
val value = propertyByName[name]!!.javaGetter!!(this@stringRepresentation)?.stringValue()
"$name = $value"
}
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2020 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.scopes
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.impl.base.test.executeOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.analysis.api.symbols.DebugSymbolRenderer
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 AbstractFileScopeTest : AbstractHLApiSingleFileTest() {
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val actual = executeOnPooledThreadInReadAction {
analyseForTest(ktFile) {
val symbol = ktFile.getFileSymbol()
val scope = symbol.getFileScope()
with(DebugSymbolRenderer) {
val renderedSymbol = renderExtra(symbol)
val callableNames = scope.getPossibleCallableNames()
val renderedCallables = scope.getCallableSymbols().map { renderExtra(it) }
val classifierNames = scope.getPossibleClassifierNames()
val renderedClassifiers = scope.getClassifierSymbols().map { renderExtra(it) }
"FILE SYMBOL:\n" + renderedSymbol +
"\nCALLABLE NAMES:\n" + callableNames.joinToString(prefix = "[", postfix = "]\n", separator = ", ") +
"\nCALLABLE SYMBOLS:\n" + renderedCallables.joinToString(separator = "\n") +
"\nCLASSIFIER NAMES:\n" + classifierNames.joinToString(prefix = "[", postfix = "]\n", separator = ", ") +
"\nCLASSIFIER SYMBOLS:\n" + renderedClassifiers.joinToString(separator = "\n")
}
}
}
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2020 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.scopes
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.impl.base.test.SymbolByFqName
import org.jetbrains.kotlin.analysis.api.impl.base.test.symbols.AbstractSymbolByFqNameTest
import org.jetbrains.kotlin.analysis.api.symbols.KtClassOrObjectSymbol
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractMemberScopeByFqNameTest : AbstractSymbolByFqNameTest() {
override fun KtAnalysisSession.collectSymbols(ktFile: KtFile, testServices: TestServices): List<KtSymbol> {
val symbolData = SymbolByFqName.getSymbolDataFromFile(testDataPath)
val symbols = with(symbolData) { toSymbols() }
val classSymbol = symbols.singleOrNull() as? KtClassOrObjectSymbol
?: error("Should be a single class symbol, but $symbols found")
return classSymbol.getMemberScope().getAllSymbols().toList()
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2020 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.symbols
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.impl.base.test.SymbolByFqName
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractSymbolByFqNameTest : AbstractSymbolTest() {
override fun KtAnalysisSession.collectSymbols(ktFile: KtFile, testServices: TestServices): List<KtSymbol> {
val symbolData = SymbolByFqName.getSymbolDataFromFile(testDataPath)
return with(symbolData) { toSymbols() }
}
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
with(builder) {
defaultDirectives {
+JvmEnvironmentConfigurationDirectives.WITH_STDLIB
}
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2020 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.symbols
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractSymbolByPsiTest : AbstractSymbolTest() {
override fun KtAnalysisSession.collectSymbols(ktFile: KtFile, testServices: TestServices): List<KtSymbol> {
return ktFile.collectDescendantsOfType<KtDeclaration> { it.isValidForSymbolCreation }.map { declaration ->
declaration.getSymbol()
}
}
private val KtDeclaration.isValidForSymbolCreation
get() =
when (this) {
is KtParameter -> !this.isFunctionTypeParameter
else -> true
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2020 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.symbols
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.expressionMarkerProvider
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractSymbolByReferenceTest : AbstractSymbolTest() {
override fun KtAnalysisSession.collectSymbols(ktFile: KtFile, testServices: TestServices): List<KtSymbol> {
val referenceExpression = testServices.expressionMarkerProvider.getElementOfTypAtCaret<KtNameReferenceExpression>(ktFile)
return listOfNotNull(referenceExpression.mainReference.resolveToSymbol())
}
}
@@ -0,0 +1,87 @@
/*
* 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.symbols
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.impl.base.test.analyseOnPooledThreadInReadAction
import org.jetbrains.kotlin.analysis.api.impl.base.test.test.framework.AbstractHLApiSingleFileTest
import org.jetbrains.kotlin.analysis.api.symbols.DebugSymbolRenderer
import org.jetbrains.kotlin.analysis.api.symbols.KtSymbol
import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
import org.jetbrains.kotlin.test.directives.model.DirectiveApplicability
import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
import org.jetbrains.kotlin.test.services.assertions
abstract class AbstractSymbolTest : AbstractHLApiSingleFileTest() {
override fun configureTest(builder: TestConfigurationBuilder) {
super.configureTest(builder)
with(builder) {
useDirectives(SymbolTestDirectives)
}
}
abstract fun KtAnalysisSession.collectSymbols(ktFile: KtFile, testServices: TestServices): List<KtSymbol>
abstract fun doOutOfBlockModification(ktFile: KtFile)
override fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices) {
val createPointers = SymbolTestDirectives.DO_NOT_CHECK_SYMBOL_RESTORE !in module.directives
val pointersWithRendered = analyseOnPooledThreadInReadAction(ktFile) {
collectSymbols(ktFile, testServices).map { symbol ->
PointerWithRenderedSymbol(
if (createPointers) symbol.createPointer() else null,
with(DebugSymbolRenderer) { renderExtra(symbol) }
)
}
}
compareResults(pointersWithRendered, testServices)
doOutOfBlockModification(ktFile)
if (createPointers) {
restoreSymbolsInOtherReadActionAndCompareResults(ktFile, pointersWithRendered, testServices)
}
}
private fun compareResults(
pointersWithRendered: List<PointerWithRenderedSymbol>,
testServices: TestServices,
) {
val actual = pointersWithRendered.joinToString(separator = "\n") { it.rendered }
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
private fun restoreSymbolsInOtherReadActionAndCompareResults(
ktFile: KtFile,
pointersWithRendered: List<PointerWithRenderedSymbol>,
testServices: TestServices,
) {
val restored = analyseOnPooledThreadInReadAction(ktFile) {
pointersWithRendered.map { (pointer, expectedRender) ->
val restored = pointer!!.restoreSymbol()
?: error("Symbol $expectedRender was not restored")
with(DebugSymbolRenderer) { renderExtra(restored) }
}
}
val actual = restored.joinToString(separator = "\n")
testServices.assertions.assertEqualsToFile(testDataFileSibling(".txt"), actual)
}
}
private object SymbolTestDirectives : SimpleDirectivesContainer() {
val DO_NOT_CHECK_SYMBOL_RESTORE by directive(
description = "Symbol restoring for some symbols in current test is not supported yet",
applicability = DirectiveApplicability.Global
)
}
private data class PointerWithRenderedSymbol(val pointer: KtSymbolPointer<*>?, val rendered: String)
@@ -0,0 +1,19 @@
/*
* 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.test.framework
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractHLApiSingleFileTest : AbstractHLApiSingleModuleTest() {
final override fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices) {
val singleFile = ktFiles.single()
doTestByFileStructure(singleFile, module, testServices)
}
protected abstract fun doTestByFileStructure(ktFile: KtFile, module: TestModule, testServices: TestServices)
}
@@ -0,0 +1,55 @@
/*
* 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.test.framework
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
import org.jetbrains.kotlin.analysis.api.KtAnalysisSession
import org.jetbrains.kotlin.analysis.api.analyse
import org.jetbrains.kotlin.analysis.api.analyseInDependedAnalysisSession
import org.jetbrains.kotlin.analysis.api.impl.barebone.test.AbstractFrontendApiTest
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.model.TestModule
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
abstract class AbstractHLApiSingleModuleTest : AbstractFrontendApiTest() {
final override fun doTestByFileStructure(ktFiles: List<KtFile>, moduleStructure: TestModuleStructure, testServices: TestServices) {
val singleModule = moduleStructure.modules.single()
doTestByFileStructure(ktFiles, singleModule, testServices)
}
protected abstract fun doTestByFileStructure(ktFiles: List<KtFile>, module: TestModule, testServices: TestServices)
protected inline fun <R> analyseOnPooledThreadInReadAction(context: KtElement, crossinline action: KtAnalysisSession.() -> R): R =
executeOnPooledThreadInReadAction {
analyseForTest(context) { action() }
}
protected inline fun <T> runReadAction(crossinline runnable: () -> T): T {
return ApplicationManager.getApplication().runReadAction(Computable { runnable() })
}
protected inline fun <R> executeOnPooledThreadInReadAction(crossinline action: () -> R): R =
ApplicationManager.getApplication().executeOnPooledThread<R> { runReadAction(action) }.get()
protected fun <R> analyseForTest(contextElement: KtElement, action: KtAnalysisSession.() -> R): R {
return if (useDependedAnalysisSession) {
// Depended mode does not support analysing a KtFile.
// See org.jetbrains.kotlin.analysis.low.level.api.fir.api.LowLevelFirApiFacadeForResolveOnAir#getResolveStateForDependentCopy
if (contextElement is KtFile) {
throw SkipDependedModeException()
}
require(!contextElement.isPhysical)
analyseInDependedAnalysisSession(configurator.getOriginalFile(contextElement.containingKtFile), contextElement, action)
} else {
analyse(contextElement, action)
}
}
}