[FIR] Add prototype of diagnostic collector

This commit is contained in:
Dmitriy Novozhilov
2019-10-28 15:29:20 +03:00
parent e909b63d30
commit 9b77dec99c
19 changed files with 451 additions and 2 deletions
+1
View File
@@ -25,6 +25,7 @@ dependencies {
testCompileOnly(project(":kotlin-reflect-api"))
testRuntime(project(":kotlin-reflect"))
implementation(kotlin("reflect"))
}
sourceSets {
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics
import org.jetbrains.kotlin.fir.FirSourceElement
class ConeDiagnosticFactory {
lateinit var name: String
fun on(source: FirSourceElement): ConeDiagnostic = ConeDiagnostic(this, source)
}
class ConeDiagnostic(
val factory: ConeDiagnosticFactory,
val source: FirSourceElement
)
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics
import kotlin.reflect.KProperty
object ConeDiagnostics {
val INFIX_MODIFIER_REQUIRED = ConeDiagnosticFactory()
val INAPPLICABLE_INFIX_MODIFIER = ConeDiagnosticFactory()
init {
ConeDiagnostics::class.members.filterIsInstance<KProperty<*>>().forEach { property ->
val factory = property.getter.call(this) as? ConeDiagnosticFactory
factory?.name = property.name
}
}
}
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics
abstract class DiagnosticReporter {
abstract fun report(diagnostic: ConeDiagnostic)
}
class SimpleDiagnosticReporter : DiagnosticReporter() {
val diagnostics: MutableList<ConeDiagnostic> = mutableListOf()
override fun report(diagnostic: ConeDiagnostic) {
diagnostics += diagnostic
}
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.checkers.call
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
abstract class FirCallChecker {
abstract fun check(functionCall: FirFunctionCall, reporter: DiagnosticReporter)
}
@@ -0,0 +1,14 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.checkers.declaration
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
object DeclarationCheckers {
val DECLARATIONS: List<FirDeclarationChecker<FirDeclaration>> = listOf()
val MEMBER_DECLARATIONS: List<FirDeclarationChecker<FirMemberDeclaration>> = DECLARATIONS + listOf(FirInfixFunctionDeclarationChecker)
}
@@ -0,0 +1,13 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.checkers.declaration
import org.jetbrains.kotlin.fir.declarations.FirDeclaration
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
abstract class FirDeclarationChecker<in D : FirDeclaration> {
abstract fun check(declaration: D, reporter: DiagnosticReporter)
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.checkers.declaration
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.declarations.isInfix
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnostics
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
object FirInfixFunctionDeclarationChecker : FirDeclarationChecker<FirMemberDeclaration>() {
override fun check(declaration: FirMemberDeclaration, reporter: DiagnosticReporter) {
if (declaration is FirSimpleFunction && declaration.isInfix) {
if (declaration.valueParameters.size != 1 || declaration.receiverTypeRef == null) {
reporter.report(declaration.source)
}
return
}
if (declaration.isInfix) {
reporter.report(declaration.source)
}
}
private fun DiagnosticReporter.report(source: FirSourceElement?) {
source?.let { report(ConeDiagnostics.INAPPLICABLE_INFIX_MODIFIER.on(it)) }
}
}
@@ -0,0 +1,61 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.collectors
import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.visitors.FirVisitorVoid
abstract class AbstractDiagnosticCollectorComponent(private val collector: AbstractDiagnosticCollector) : FirVisitorVoid() {
override fun visitElement(element: FirElement) {}
protected fun runCheck(block: (DiagnosticReporter) -> Unit) {
collector.runCheck(block)
}
}
abstract class AbstractDiagnosticCollector {
fun collectDiagnostics(firFile: FirFile): Iterable<ConeDiagnostic> {
if (!componentsInitialized) {
throw IllegalStateException("Components are not initialized")
}
initializeCollector()
firFile.accept(visitor)
return getCollectedDiagnostics()
}
protected abstract fun initializeCollector()
protected abstract fun getCollectedDiagnostics(): Iterable<ConeDiagnostic>
internal abstract fun runCheck(block: (DiagnosticReporter) -> Unit)
private val components: MutableList<AbstractDiagnosticCollectorComponent> = mutableListOf()
private var componentsInitialized = false
private val visitor = Visitor()
fun initializeComponents(vararg components: AbstractDiagnosticCollectorComponent) {
if (componentsInitialized) {
throw IllegalStateException()
}
this.components += components
componentsInitialized = true
}
private inner class Visitor : FirVisitorVoid() {
private fun <T : FirElement> T.runComponents() {
components.forEach {
this.accept(it)
}
}
override fun visitElement(element: FirElement) {
element.runComponents()
element.acceptChildren(this)
}
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.collectors
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.resolve.diagnostics.SimpleDiagnosticReporter
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicInteger
class ParallelDiagnosticsCollector(private val numberOfThreads: Int) : AbstractDiagnosticCollector() {
init {
require(numberOfThreads >= 1) {
"Number of threads should be at least 1"
}
}
private var reporters = initializeReporters()
private val collectorLocalIndex = ThreadLocal<Int>()
private val collectorIndexCounter = AtomicInteger()
private val futures = LinkedList<Future<*>>()
private val pool = Executors.newFixedThreadPool(numberOfThreads) { runnable ->
Thread {
collectorLocalIndex.set(collectorIndexCounter.getAndIncrement())
runnable.run()
}
}
private fun initializeReporters(): List<SimpleDiagnosticReporter> {
return (1..numberOfThreads).map { SimpleDiagnosticReporter() }
}
override fun initializeCollector() {
reporters = initializeReporters()
futures.clear()
}
override fun getCollectedDiagnostics(): Iterable<ConeDiagnostic> {
futures.forEach { it.get() }
return Iterable {
object : Iterator<ConeDiagnostic> {
private val globalIterator = reporters.iterator()
private var localIterator = globalIterator.next().diagnostics.iterator()
private fun update() {
while (!localIterator.hasNext() && globalIterator.hasNext()) {
localIterator = globalIterator.next().diagnostics.iterator()
}
}
override fun hasNext(): Boolean {
update()
return localIterator.hasNext()
}
override fun next(): ConeDiagnostic {
update()
return localIterator.next()
}
}
}
}
override fun runCheck(block: (DiagnosticReporter) -> Unit) {
futures += pool.submit {
val reporter = reporters[collectorLocalIndex.get()]
block(reporter)
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.collectors
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.resolve.diagnostics.SimpleDiagnosticReporter
class SimpleDiagnosticsCollector : AbstractDiagnosticCollector() {
private var reporter = SimpleDiagnosticReporter()
override fun initializeCollector() {
reporter = SimpleDiagnosticReporter()
}
override fun getCollectedDiagnostics(): Iterable<ConeDiagnostic> {
return reporter.diagnostics
}
override fun runCheck(block: (DiagnosticReporter) -> Unit) {
block(reporter)
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2019 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.fir.resolve.diagnostics.collectors.components
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.resolve.diagnostics.checkers.declaration.DeclarationCheckers
import org.jetbrains.kotlin.fir.resolve.diagnostics.checkers.declaration.FirDeclarationChecker
import org.jetbrains.kotlin.fir.resolve.diagnostics.collectors.AbstractDiagnosticCollector
import org.jetbrains.kotlin.fir.resolve.diagnostics.collectors.AbstractDiagnosticCollectorComponent
class DeclarationCheckersDiagnosticComponent(collector: AbstractDiagnosticCollector) : AbstractDiagnosticCollectorComponent(collector) {
override fun visitProperty(property: FirProperty) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(property, it) }
}
override fun visitRegularClass(regularClass: FirRegularClass) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(regularClass, it) }
}
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(simpleFunction, it) }
}
override fun visitTypeAlias(typeAlias: FirTypeAlias) {
runCheck { DeclarationCheckers.MEMBER_DECLARATIONS.check(typeAlias, it) }
}
private fun <D : FirDeclaration> List<FirDeclarationChecker<D>>.check(declaration: D, reporter: DiagnosticReporter) {
for (checker in this) {
checker.check(declaration, reporter)
}
}
}
@@ -0,0 +1,5 @@
<INAPPLICABLE_INFIX_MODIFIER>: infix fun Int.foo(x: Int, y: Int) {}
<INAPPLICABLE_INFIX_MODIFIER>: infix fun Int.bar() {}
<INAPPLICABLE_INFIX_MODIFIER>: infix fun baz(x: Int, y: Int) {}
@@ -0,0 +1,13 @@
infix fun Int.good(x: Int) {}
infix fun Int.foo(x: Int, y: Int) {}
infix fun Int.bar() {}
infix fun baz(x: Int, y: Int) {}
infix class A
infix typealias B = A
infix val x = 1
@@ -0,0 +1,18 @@
FILE: infixFunctions.kt
public final infix fun R|kotlin/Int|.good(x: R|kotlin/Int|): R|kotlin/Unit| {
}
public final infix fun R|kotlin/Int|.foo(x: R|kotlin/Int|, y: R|kotlin/Int|): R|kotlin/Unit| {
}
public final infix fun R|kotlin/Int|.bar(): R|kotlin/Unit| {
}
public final infix fun baz(x: R|kotlin/Int|, y: R|kotlin/Int|): R|kotlin/Unit| {
}
public final class A : R|kotlin/Any| {
public constructor(): R|A| {
super<R|kotlin/Any|>()
}
}
public final typealias B = R|A|
public final val x: R|kotlin/Int| = Int(1)
public get(): R|kotlin/Int|
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2019 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.fir
import org.jetbrains.kotlin.fir.declarations.FirFile
import org.jetbrains.kotlin.fir.resolve.diagnostics.*
import org.jetbrains.kotlin.fir.resolve.diagnostics.collectors.ParallelDiagnosticsCollector
import org.jetbrains.kotlin.fir.resolve.diagnostics.collectors.SimpleDiagnosticsCollector
import org.jetbrains.kotlin.fir.resolve.diagnostics.collectors.components.DeclarationCheckersDiagnosticComponent
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractFirResolveWithDiagnosticsTestCase : AbstractFirResolveTestCase() {
override fun doTest(path: String) {
val firFiles = processInputFile(path)
checkFir(path, firFiles)
checkDiagnostics(path, firFiles)
}
private fun checkDiagnostics(path: String, firFiles: List<FirFile>) {
// val collector = SimpleDiagnosticsCollector()
val collector = ParallelDiagnosticsCollector(4)
collector.initializeComponents(DeclarationCheckersDiagnosticComponent(collector))
val diagnostics = mutableListOf<ConeDiagnostic>()
for (file in firFiles) {
diagnostics += collector.collectDiagnostics(file)
}
val expectedPath = path.replace(".kt", ".diagnostics.txt")
val expectedFile = File(expectedPath)
if (diagnostics.isEmpty()) {
assertFalse("There is no diagnostics but expected file exists", expectedFile.exists())
return
}
val actual = diagnostics.joinToString("\n\n") {
val text = (it.source as FirPsiSourceElement).psi.text
"<${it.factory.name}>: $text"
}
KotlinTestUtils.assertEqualsToFile(expectedFile, actual)
}
}
@@ -25,7 +25,7 @@ public class FirResolveTestCaseGenerated extends AbstractFirResolveTestCase {
}
public void testAllFilesPresentInResolve() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/fir/resolve/testData/resolve"), Pattern.compile("^([^.]+)\\.kt$"), true, "stdlib", "cfg", "smartcasts");
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/fir/resolve/testData/resolve"), Pattern.compile("^([^.]+)\\.kt$"), true, "stdlib", "cfg", "smartcasts", "diagnostics");
}
@TestMetadata("cast.kt")
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2019 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.fir;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("compiler/fir/resolve/testData/resolve/diagnostics")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class FirResolveWithDiagnosticsTestCaseGenerated extends AbstractFirResolveWithDiagnosticsTestCase {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInDiagnostics() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/fir/resolve/testData/resolve/diagnostics"), Pattern.compile("^([^.]+)\\.kt$"), true);
}
@TestMetadata("infixFunctions.kt")
public void testInfixFunctions() throws Exception {
runTest("compiler/fir/resolve/testData/resolve/diagnostics/infixFunctions.kt");
}
}
@@ -513,7 +513,7 @@ fun main(args: Array<String>) {
testGroup("compiler/fir/resolve/tests", "compiler/fir/resolve/testData") {
testClass<AbstractFirResolveTestCase> {
model("resolve", pattern = KT_WITHOUT_DOTS_IN_NAME, excludeDirs = listOf("stdlib", "cfg", "smartcasts"))
model("resolve", pattern = KT_WITHOUT_DOTS_IN_NAME, excludeDirs = listOf("stdlib", "cfg", "smartcasts", "diagnostics"))
}
testClass<AbstractFirCfgBuildingTest> {
@@ -528,6 +528,10 @@ fun main(args: Array<String>) {
testClass<AbstractFirCfgBuildingWithStdlibTest> {
model("resolve/stdlib/contracts", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
testClass<AbstractFirResolveWithDiagnosticsTestCase> {
model("resolve/diagnostics", pattern = KT_WITHOUT_DOTS_IN_NAME)
}
}
testGroup("compiler/fir/resolve/tests", "compiler/testData") {