[LL API] Add tests for FIR tree guards
^KT-59297 Fixed
This commit is contained in:
@@ -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
|
||||
}
|
||||
+179
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -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");
|
||||
}
|
||||
}
|
||||
+237
@@ -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()
|
||||
}
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+5
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user