[LL API] Add tests for FIR tree guards

^KT-59297 Fixed
This commit is contained in:
Yan Zhulanow
2023-05-11 22:00:48 +09:00
committed by Space Team
parent 91c3ebac27
commit d04ed56a4d
8 changed files with 490 additions and 1 deletions
@@ -0,0 +1,12 @@
// WITH_STDLIB
import broken.lib.Foo
fun test() {
buildList {
add(1)
add(Foo("foo", 1).result)
}
}
fun <T> consume(a: T) {}
@@ -0,0 +1,5 @@
// WITH_STDLIB
import broken.lib.Foo
val x by lazy { Foo("foo", 1).result }
@@ -0,0 +1,7 @@
// WITH_STDLIB
import broken.lib.Foo
val x = run {
Foo("foo", 1).result
}
@@ -0,0 +1,179 @@
/*
* Copyright 2010-2023 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.low.level.api.fir.resolve
import com.intellij.lang.java.JavaLanguage
import com.intellij.mock.MockApplication
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.psi.impl.file.PsiPackageImpl
import com.intellij.psi.impl.light.LightMethodBuilder
import com.intellij.psi.impl.light.LightModifierList
import com.intellij.psi.impl.light.LightPsiClassBase
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiSearchScopeUtil
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.DiagnosticCheckerFilter
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.collectDiagnosticsForFile
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolveWithClearCaches
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.base.AbstractLowLevelApiSingleFileTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.test.configurators.AnalysisApiFirSourceTestConfigurator
import org.jetbrains.kotlin.analysis.test.framework.test.configurators.AnalysisApiTestServiceRegistrar
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.TestInfrastructureInternals
import org.jetbrains.kotlin.test.impl.testConfiguration
import org.jetbrains.kotlin.test.services.TestModuleStructure
import org.jetbrains.kotlin.test.services.TestServices
import kotlin.test.fail
abstract class AbstractErrorResistanceTest : AbstractLowLevelApiSingleFileTest() {
override val configurator: AnalysisApiFirSourceTestConfigurator = ErrorResistanceConfigurator
override fun doTestByFileStructure(ktFile: KtFile, moduleStructure: TestModuleStructure, testServices: TestServices) {
resolveWithClearCaches(ktFile) { firResolveSession ->
ENABLE_INTERRUPTION.set(true)
try {
ktFile.collectDiagnosticsForFile(firResolveSession, DiagnosticCheckerFilter.ONLY_COMMON_CHECKERS)
fail("Analysis should be interrupted")
} catch (e: Throwable) {
val errors = generateSequence(e) { it.cause }
if (errors.none { it is AnalysisInterruptedException }) {
throw e
}
}
ENABLE_INTERRUPTION.set(false)
val diagnostics = ktFile.collectDiagnosticsForFile(firResolveSession, DiagnosticCheckerFilter.ONLY_COMMON_CHECKERS)
assert(diagnostics.isEmpty()) {
val messages = diagnostics.map { it.factoryName }
"There should be no diagnostics, found:\n" + messages.joinToString("\n")
}
}
}
}
private object ErrorResistanceConfigurator : AnalysisApiFirSourceTestConfigurator(analyseInDependentSession = false) {
override val serviceRegistrars: List<AnalysisApiTestServiceRegistrar>
get() = buildList {
addAll(super.serviceRegistrars)
add(ErrorResistanceServiceRegistrar)
}
}
private object ErrorResistanceServiceRegistrar : AnalysisApiTestServiceRegistrar() {
override fun registerApplicationServices(application: MockApplication, testServices: TestServices) {}
override fun registerProjectExtensionPoints(project: MockProject, testServices: TestServices) {}
override fun registerProjectServices(project: MockProject, testServices: TestServices) {}
@OptIn(TestInfrastructureInternals::class)
override fun registerProjectModelServices(project: MockProject, testServices: TestServices) {
with(PsiElementFinder.EP.getPoint(project)) {
registerExtension(BrokenLibraryElementFinder(project), testServices.testConfiguration.rootDisposable)
}
}
}
private class AnalysisInterruptedException : RuntimeException()
private var ENABLE_INTERRUPTION = ThreadLocal.withInitial { false }
private fun interruptAnalysis() {
if (ENABLE_INTERRUPTION.get()) {
throw AnalysisInterruptedException()
}
}
private class BrokenLibraryElementFinder(project: Project) : PsiElementFinder() {
private val manager = PsiManager.getInstance(project)
private val brokenPackage = BrokenPackage("broken.lib", manager)
private val brokenClass = BrokenClass(brokenPackage.qualifiedName, "Foo", manager)
override fun findPackage(qualifiedName: String): PsiPackage? {
return when (qualifiedName) {
brokenPackage.qualifiedName -> brokenPackage
else -> null
}
}
override fun findClass(qualifiedName: String, scope: GlobalSearchScope): PsiClass? {
val klass = when (qualifiedName) {
brokenClass.qualifiedName -> brokenClass
else -> null
}
return klass?.takeIf { PsiSearchScopeUtil.isInScope(scope, it) }
}
override fun findClasses(qualifiedName: String, scope: GlobalSearchScope): Array<PsiClass> {
val klass = findClass(qualifiedName, scope) ?: return PsiClass.EMPTY_ARRAY
return arrayOf(klass)
}
}
private class BrokenPackage(packageName: String, manager: PsiManager) : PsiPackageImpl(manager, packageName) {
override fun isValid(): Boolean = true
}
private class BrokenClass(
private val packageName: String,
name: String,
manager: PsiManager
) : LightPsiClassBase(manager, JavaLanguage.INSTANCE, name) {
private val modifierList: PsiModifierList = LightModifierList(manager, JavaLanguage.INSTANCE, PsiModifier.PUBLIC)
private val methods: Array<PsiMethod> = arrayOf(ConstructorMethod(this), GetterMethod(this))
override fun getQualifiedName(): String = "$packageName.$name"
override fun getModifierList(): PsiModifierList = modifierList
override fun getContainingClass(): PsiClass? = null
override fun getTypeParameterList(): PsiTypeParameterList? = null
override fun getMethods(): Array<PsiMethod> {
return methods
}
override fun getFields(): Array<PsiField> {
return PsiField.EMPTY_ARRAY
}
override fun getInnerClasses(): Array<PsiClass> = PsiClass.EMPTY_ARRAY
override fun getInitializers(): Array<PsiClassInitializer> = PsiClassInitializer.EMPTY_ARRAY
override fun getExtendsList(): PsiReferenceList? = null
override fun getImplementsList(): PsiReferenceList? = null
override fun getScope(): PsiElement? = null
private class ConstructorMethod(owner: PsiClass) : LightMethodBuilder(owner, JavaLanguage.INSTANCE) {
init {
isConstructor = true
setModifiers(PsiModifier.PUBLIC)
val projectScope = GlobalSearchScope.allScope(manager.project)
addParameter("first", PsiType.getJavaLangString(manager, projectScope))
addParameter("second", PsiType.INT)
}
}
private class GetterMethod(owner: PsiClass) : LightMethodBuilder(owner.manager, "getResult") {
init {
containingClass = owner
setModifiers(PsiModifier.PUBLIC)
setMethodReturnType(PsiType.BOOLEAN)
}
override fun getTypeParameterList(): PsiTypeParameterList? {
interruptAnalysis()
return super.getTypeParameterList()
}
override fun getTypeParameters(): Array<PsiTypeParameter> {
interruptAnalysis()
return super.getTypeParameters()
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2023 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.low.level.api.fir.resolve;
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 org.jetbrains.kotlin.generators.tests.analysis.api.GenerateAnalysisApiTestsKt}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("analysis/low-level-api-fir/testdata/errorResistance")
@TestDataPath("$PROJECT_ROOT")
public class ErrorResistanceTestGenerated extends AbstractErrorResistanceTest {
@Test
public void testAllFilesPresentInErrorResistance() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("analysis/low-level-api-fir/testdata/errorResistance"), Pattern.compile("^(.+)\\.kt$"), null, true);
}
@Test
@TestMetadata("builderInference.kt")
public void testBuilderInference() throws Exception {
runTest("analysis/low-level-api-fir/testdata/errorResistance/builderInference.kt");
}
@Test
@TestMetadata("propertyDelegate.kt")
public void testPropertyDelegate() throws Exception {
runTest("analysis/low-level-api-fir/testdata/errorResistance/propertyDelegate.kt");
}
@Test
@TestMetadata("propertyInitializer.kt")
public void testPropertyInitializer() throws Exception {
runTest("analysis/low-level-api-fir/testdata/errorResistance/propertyInitializer.kt");
}
}
@@ -0,0 +1,237 @@
/*
* Copyright 2010-2023 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.low.level.api.fir.resolve
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.StateKeeper
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.entity
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.entityList
import org.jetbrains.kotlin.analysis.low.level.api.fir.transformers.stateKeeper
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class StateKeeperTest {
@Test
fun testSimple() {
class Foo(private var value: String) {
fun getValue() = value
fun setValue(newValue: String) {
value = newValue
}
}
val keeper: StateKeeper<Foo> = stateKeeper {
add(Foo::getValue, Foo::setValue)
}
val foo = Foo("Foo")
keeper.withRestoration(foo) {
assertEquals("Foo", foo.getValue())
foo.setValue("Bar")
assertEquals("Bar", foo.getValue())
}
assertEquals("Foo", foo.getValue())
}
@Test
fun testProperty() {
class Foo(var value: String)
val keeper: StateKeeper<Foo> = stateKeeper {
add(Foo::value::get, Foo::value::set)
}
val foo = Foo("Foo")
keeper.withRestoration(foo) {
assertEquals("Foo", foo.value)
foo.value = "Bar"
assertEquals("Bar", foo.value)
}
assertEquals("Foo", foo.value)
}
@Test
fun testArranger() {
class Foo(var value: String)
val keeper: StateKeeper<Foo> = stateKeeper {
add(Foo::value::get, Foo::value::set) { "Baz" }
}
val foo = Foo("Foo")
keeper.withRestoration(foo) {
assertEquals("Baz", foo.value)
foo.value = "Bar"
assertEquals("Bar", foo.value)
}
assertEquals("Foo", foo.value)
}
@Test
fun testInclusion() {
open class Foo(var one: String)
class Bar(one: String, var two: Int) : Foo(one)
val fooKeeper: StateKeeper<Foo> = stateKeeper {
add(Foo::one::get, Foo::one::set)
}
val barKeeper: StateKeeper<Bar> = stateKeeper {
add(fooKeeper)
add(Bar::two::get, Bar::two::set)
}
val bar = Bar("Foo", 1)
barKeeper.withRestoration(bar) {
assertEquals("Foo", bar.one)
assertEquals(1, bar.two)
bar.one = "Bar"
bar.two = 2
assertEquals("Bar", bar.one)
assertEquals(2, bar.two)
}
assertEquals("Foo", bar.one)
assertEquals(1, bar.two)
}
@Test
fun testEntity() {
data class Foo(var value: Int)
class Bar(var one: String, var foo: Foo)
val barKeeper: StateKeeper<Bar> = stateKeeper { bar ->
add(Bar::one::get, Bar::one::set)
entity(bar.foo) {
add(Foo::value::get, Foo::value::set)
}
}
val foo = Foo(1)
val bar = Bar("Foo", foo)
barKeeper.withRestoration(bar) {
assertEquals("Foo", bar.one)
assertEquals(1, bar.foo.value)
bar.one = "Bar"
bar.foo.value = 2
assertEquals("Bar", bar.one)
assertEquals(2, bar.foo.value)
}
assertEquals("Foo", bar.one)
assertEquals(1, bar.foo.value)
assert(bar.foo === foo)
}
@Test
fun testEntityInclusion() {
data class Foo(var value: Int)
class Bar(var one: String, var foo: Foo)
val fooKeeper: StateKeeper<Foo> = stateKeeper {
add(Foo::value::get, Foo::value::set)
}
val barKeeper: StateKeeper<Bar> = stateKeeper { bar ->
add(Bar::one::get, Bar::one::set)
entity(bar.foo, fooKeeper)
}
val foo = Foo(1)
val bar = Bar("Foo", foo)
barKeeper.withRestoration(bar) {
assertEquals("Foo", bar.one)
assertEquals(1, bar.foo.value)
bar.one = "Bar"
bar.foo.value = 2
assertEquals("Bar", bar.one)
assertEquals(2, bar.foo.value)
}
assertEquals("Foo", bar.one)
assertEquals(1, bar.foo.value)
assert(bar.foo === foo)
}
@Test
fun testEntityList() {
data class Foo(var value: Int)
class Bar(var one: String, var foos: List<Foo>)
val barKeeper: StateKeeper<Bar> = stateKeeper { bar ->
add(Bar::one::get, Bar::one::set)
entityList(bar.foos) {
add(Foo::value::get, Foo::value::set) { it + 1 }
}
}
val foo0 = Foo(1)
val foo1 = Foo(2)
val bar = Bar("Foo", listOf(foo0, foo1))
barKeeper.withRestoration(bar) {
assertEquals("Foo", bar.one)
assertEquals(2, bar.foos[0].value) // +1 because of the arranger
assertEquals(3, bar.foos[1].value)
bar.one = "Bar"
bar.foos[0].value = 100
bar.foos[1].value = 200
assertEquals("Bar", bar.one)
assertEquals(100, bar.foos[0].value)
assertEquals(200, bar.foos[1].value)
}
assertEquals("Foo", bar.one)
assertEquals(1, bar.foos[0].value)
assertEquals(2, bar.foos[1].value)
assert(bar.foos[0] === foo0)
assert(bar.foos[1] === foo1)
}
@Test
fun testPostProcess() {
class Foo(var value: String)
val keeper: StateKeeper<Foo> = stateKeeper { foo ->
add(Foo::value::get, Foo::value::set) { "Baz" }
postProcess {
foo.value = "Boo"
}
}
val foo = Foo("Foo")
keeper.withRestoration(foo) {
assertEquals("Boo", foo.value)
foo.value = "Bar"
assertEquals("Bar", foo.value)
}
assertEquals("Foo", foo.value)
}
}
private fun <T : Any> StateKeeper<T>.withRestoration(owner: T, block: () -> Unit) {
val state = prepare(owner)
state.postProcess()
block()
state.restore()
}
@@ -10,7 +10,7 @@ import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtModuleFa
import org.jetbrains.kotlin.analysis.test.framework.project.structure.KtSourceModuleFactory
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
class AnalysisApiFirSourceTestConfigurator(
open class AnalysisApiFirSourceTestConfigurator(
analyseInDependentSession: Boolean
) : AnalysisApiFirSourceLikeTestConfigurator(analyseInDependentSession) {
override fun configureTest(builder: TestConfigurationBuilder, disposable: Disposable) {
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostic.compiler.based
import org.jetbrains.kotlin.analysis.low.level.api.fir.diagnostic.compiler.based.AbstractLLFirPreresolvedReversedDiagnosticCompilerTestDataTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.AbstractOutOfContentRootFileStructureTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.file.structure.AbstractSourceFileStructureTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractErrorResistanceTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractOutOfContentRootInnerDeclarationsResolvePhaseTest
import org.jetbrains.kotlin.analysis.low.level.api.fir.resolve.AbstractSourceInnerDeclarationsResolvePhaseTest
import org.jetbrains.kotlin.generators.TestGroup
@@ -52,6 +53,10 @@ internal fun TestGroupSuite.generateFirLowLevelApiTests() {
model("lazyResolve")
}
testClass<AbstractErrorResistanceTest> {
model("errorResistance")
}
testClass<AbstractSourceFileStructureTest> {
model("fileStructure")
}