[FIR] Split CfaUtils to multiple files
This commit is contained in:
committed by
teamcityserver
parent
bdb61ec5d7
commit
bce62430ce
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.fir.analysis.cfa
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.expressions.FirDoWhileLoop
|
||||
import org.jetbrains.kotlin.fir.expressions.FirLoop
|
||||
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
||||
import org.jetbrains.kotlin.fir.expressions.FirWhileLoop
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.util.SetMultimap
|
||||
import org.jetbrains.kotlin.fir.util.setMultimapOf
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||
|
||||
// Note that [PreliminaryLoopVisitor] in FIR DFA collects assigned variable names.
|
||||
// This one collects declared variable symbols per capturing statements.
|
||||
class DeclaredVariableCollector {
|
||||
val declaredVariablesPerElement: SetMultimap<FirStatement, FirPropertySymbol> = setMultimapOf()
|
||||
|
||||
fun enterCapturingStatement(statement: FirStatement): Set<FirPropertySymbol> {
|
||||
assert(statement is FirLoop || statement is FirClass || statement is FirFunction)
|
||||
if (statement !in declaredVariablesPerElement) {
|
||||
statement.accept(visitor, null)
|
||||
}
|
||||
return declaredVariablesPerElement[statement]
|
||||
}
|
||||
|
||||
fun exitCapturingStatement(statement: FirStatement) {
|
||||
assert(statement is FirLoop || statement is FirClass || statement is FirFunction)
|
||||
declaredVariablesPerElement.removeKey(statement)
|
||||
}
|
||||
|
||||
fun resetState() {
|
||||
declaredVariablesPerElement.clear()
|
||||
}
|
||||
|
||||
// FirStatement -- closest statement (loop/lambda/local declaration) which may contain reassignments
|
||||
private val visitor = object : FirVisitor<Unit, FirStatement?>() {
|
||||
override fun visitElement(element: FirElement, data: FirStatement?) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitProperty(property: FirProperty, data: FirStatement?) {
|
||||
if (property.isLocal) {
|
||||
requireNotNull(data)
|
||||
declaredVariablesPerElement.put(data, property.symbol)
|
||||
}
|
||||
visitElement(property, data)
|
||||
}
|
||||
|
||||
override fun visitWhileLoop(whileLoop: FirWhileLoop, data: FirStatement?) {
|
||||
visitCapturingStatement(whileLoop, data)
|
||||
}
|
||||
|
||||
override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: FirStatement?) {
|
||||
visitCapturingStatement(doWhileLoop, data)
|
||||
}
|
||||
|
||||
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: FirStatement?) {
|
||||
visitCapturingStatement(anonymousFunction, data)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: FirStatement?) {
|
||||
visitCapturingStatement(simpleFunction, data)
|
||||
}
|
||||
|
||||
override fun visitRegularClass(regularClass: FirRegularClass, data: FirStatement?) {
|
||||
visitCapturingStatement(regularClass, data)
|
||||
}
|
||||
|
||||
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: FirStatement?) {
|
||||
visitCapturingStatement(anonymousObject, data)
|
||||
}
|
||||
|
||||
private fun visitCapturingStatement(statement: FirStatement, parent: FirStatement?) {
|
||||
visitElement(statement, statement)
|
||||
if (parent != null) {
|
||||
declaredVariablesPerElement.putAll(parent, declaredVariablesPerElement[statement])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.fir.analysis.cfa
|
||||
|
||||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFunction
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.referredPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
|
||||
class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() {
|
||||
companion object {
|
||||
fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> {
|
||||
val collector = LocalPropertyAndCapturedWriteCollector()
|
||||
graph.traverse(TraverseDirection.Forward, collector)
|
||||
return collector.symbols.keys to collector.capturedWrites
|
||||
}
|
||||
}
|
||||
|
||||
// Mapping from a property symbol to its declaration context
|
||||
// `true` if the (local) property is declared in the currently visited function.
|
||||
// `false` if it is declared in a lambda or a local function (inside the currently visited function).
|
||||
private val symbols: MutableMap<FirPropertySymbol, Boolean> = mutableMapOf()
|
||||
|
||||
private val lambdaOrLocalFunctionStack: MutableList<FirFunction> = mutableListOf()
|
||||
private val capturedWrites: MutableSet<FirVariableAssignment> = mutableSetOf()
|
||||
|
||||
override fun visitNode(node: CFGNode<*>) {}
|
||||
|
||||
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||
symbols[node.fir.symbol] = lambdaOrLocalFunctionStack.lastOrNull() == null
|
||||
}
|
||||
|
||||
override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) {
|
||||
lambdaOrLocalFunctionStack.add(node.fir)
|
||||
}
|
||||
|
||||
override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) {
|
||||
lambdaOrLocalFunctionStack.remove(node.fir.anonymousFunction)
|
||||
}
|
||||
|
||||
override fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: Nothing?) {
|
||||
lambdaOrLocalFunctionStack.add(node.fir)
|
||||
}
|
||||
|
||||
override fun visitFunctionExitNode(node: FunctionExitNode) {
|
||||
lambdaOrLocalFunctionStack.remove(node.fir)
|
||||
}
|
||||
|
||||
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||
// Check if this variable assignment is inside a lambda or a local function.
|
||||
if (lambdaOrLocalFunctionStack.isEmpty()) return
|
||||
|
||||
// Check if the assigned variable doesn't belong to any lambda or local function.
|
||||
val symbol = node.fir.referredPropertySymbol ?: return
|
||||
if (symbol !in symbols || symbols[symbol] == false) return
|
||||
|
||||
// If all nested declarations are lambdas that are invoked in-place (according to the contract),
|
||||
// this variable assignment is not a captured write.
|
||||
if (lambdaOrLocalFunctionStack.all { it is FirAnonymousFunction && it.invocationKind.isInPlace }) return
|
||||
|
||||
capturedWrites.add(node.fir)
|
||||
}
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.fir.analysis.cfa
|
||||
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
|
||||
abstract class EventOccurrencesRangeInfo<E : EventOccurrencesRangeInfo<E, K>, K : Any>(
|
||||
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
|
||||
) : ControlFlowInfo<E, K, EventOccurrencesRange>(map) {
|
||||
|
||||
override fun merge(other: E): E =
|
||||
operation(other, EventOccurrencesRange::or)
|
||||
|
||||
fun plus(other: E): E =
|
||||
when {
|
||||
isEmpty() -> other
|
||||
other.isEmpty() ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this as E
|
||||
else -> operation(other, EventOccurrencesRange::plus)
|
||||
}
|
||||
|
||||
private inline fun operation(other: E, op: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange): E {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
var result = this as E
|
||||
for (symbol in keys.union(other.keys)) {
|
||||
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
|
||||
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
|
||||
result = result.put(symbol, op.invoke(kind1, kind2))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyInitializationInfo(
|
||||
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
||||
) : EventOccurrencesRangeInfo<PropertyInitializationInfo, FirPropertySymbol>(map) {
|
||||
companion object {
|
||||
val EMPTY = PropertyInitializationInfo()
|
||||
}
|
||||
|
||||
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
|
||||
::PropertyInitializationInfo
|
||||
|
||||
override val empty: () -> PropertyInitializationInfo =
|
||||
::EMPTY
|
||||
}
|
||||
|
||||
class PathAwarePropertyInitializationInfo(
|
||||
map: PersistentMap<EdgeLabel, PropertyInitializationInfo> = persistentMapOf()
|
||||
) : PathAwareControlFlowInfo<PathAwarePropertyInitializationInfo, PropertyInitializationInfo>(map) {
|
||||
companion object {
|
||||
val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY))
|
||||
}
|
||||
|
||||
override val constructor: (PersistentMap<EdgeLabel, PropertyInitializationInfo>) -> PathAwarePropertyInitializationInfo =
|
||||
::PathAwarePropertyInitializationInfo
|
||||
|
||||
override val empty: () -> PathAwarePropertyInitializationInfo =
|
||||
::EMPTY
|
||||
}
|
||||
+3
-188
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -8,126 +8,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa
|
||||
import kotlinx.collections.immutable.PersistentMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.referredPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.fir.util.SetMultimap
|
||||
import org.jetbrains.kotlin.fir.util.setMultimapOf
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||
|
||||
abstract class EventOccurrencesRangeInfo<E : EventOccurrencesRangeInfo<E, K>, K : Any>(
|
||||
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
|
||||
) : ControlFlowInfo<E, K, EventOccurrencesRange>(map) {
|
||||
|
||||
override fun merge(other: E): E =
|
||||
operation(other, EventOccurrencesRange::or)
|
||||
|
||||
fun plus(other: E): E =
|
||||
when {
|
||||
isEmpty() -> other
|
||||
other.isEmpty() ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this as E
|
||||
else -> operation(other, EventOccurrencesRange::plus)
|
||||
}
|
||||
|
||||
private inline fun operation(other: E, op: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange): E {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
var result = this as E
|
||||
for (symbol in keys.union(other.keys)) {
|
||||
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
|
||||
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
|
||||
result = result.put(symbol, op.invoke(kind1, kind2))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class PropertyInitializationInfo(
|
||||
map: PersistentMap<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
|
||||
) : EventOccurrencesRangeInfo<PropertyInitializationInfo, FirPropertySymbol>(map) {
|
||||
companion object {
|
||||
val EMPTY = PropertyInitializationInfo()
|
||||
}
|
||||
|
||||
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
|
||||
::PropertyInitializationInfo
|
||||
|
||||
override val empty: () -> PropertyInitializationInfo =
|
||||
::EMPTY
|
||||
}
|
||||
|
||||
class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() {
|
||||
companion object {
|
||||
fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> {
|
||||
val collector = LocalPropertyAndCapturedWriteCollector()
|
||||
graph.traverse(TraverseDirection.Forward, collector)
|
||||
return collector.symbols.keys to collector.capturedWrites
|
||||
}
|
||||
}
|
||||
|
||||
// Mapping from a property symbol to its declaration context
|
||||
// `true` if the (local) property is declared in the currently visited function.
|
||||
// `false` if it is declared in a lambda or a local function (inside the currently visited function).
|
||||
private val symbols: MutableMap<FirPropertySymbol, Boolean> = mutableMapOf()
|
||||
|
||||
private val lambdaOrLocalFunctionStack: MutableList<FirFunction> = mutableListOf()
|
||||
private val capturedWrites: MutableSet<FirVariableAssignment> = mutableSetOf()
|
||||
|
||||
override fun visitNode(node: CFGNode<*>) {}
|
||||
|
||||
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
|
||||
symbols[node.fir.symbol] = lambdaOrLocalFunctionStack.lastOrNull() == null
|
||||
}
|
||||
|
||||
override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) {
|
||||
lambdaOrLocalFunctionStack.add(node.fir)
|
||||
}
|
||||
|
||||
override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) {
|
||||
lambdaOrLocalFunctionStack.remove(node.fir.anonymousFunction)
|
||||
}
|
||||
|
||||
override fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: Nothing?) {
|
||||
lambdaOrLocalFunctionStack.add(node.fir)
|
||||
}
|
||||
|
||||
override fun visitFunctionExitNode(node: FunctionExitNode) {
|
||||
lambdaOrLocalFunctionStack.remove(node.fir)
|
||||
}
|
||||
|
||||
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
|
||||
// Check if this variable assignment is inside a lambda or a local function.
|
||||
if (lambdaOrLocalFunctionStack.isEmpty()) return
|
||||
|
||||
// Check if the assigned variable doesn't belong to any lambda or local function.
|
||||
val symbol = node.fir.referredPropertySymbol ?: return
|
||||
if (symbol !in symbols || symbols[symbol] == false) return
|
||||
|
||||
// If all nested declarations are lambdas that are invoked in-place (according to the contract),
|
||||
// this variable assignment is not a captured write.
|
||||
if (lambdaOrLocalFunctionStack.all { it is FirAnonymousFunction && it.invocationKind.isInPlace }) return
|
||||
|
||||
capturedWrites.add(node.fir)
|
||||
}
|
||||
}
|
||||
|
||||
class PathAwarePropertyInitializationInfo(
|
||||
map: PersistentMap<EdgeLabel, PropertyInitializationInfo> = persistentMapOf()
|
||||
) : PathAwareControlFlowInfo<PathAwarePropertyInitializationInfo, PropertyInitializationInfo>(map) {
|
||||
companion object {
|
||||
val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY))
|
||||
}
|
||||
|
||||
override val constructor: (PersistentMap<EdgeLabel, PropertyInitializationInfo>) -> PathAwarePropertyInitializationInfo =
|
||||
::PathAwarePropertyInitializationInfo
|
||||
|
||||
override val empty: () -> PathAwarePropertyInitializationInfo =
|
||||
::EMPTY
|
||||
}
|
||||
|
||||
class PropertyInitializationInfoCollector(
|
||||
private val localProperties: Set<FirPropertySymbol>,
|
||||
@@ -260,75 +144,6 @@ class PropertyInitializationInfoCollector(
|
||||
}
|
||||
}
|
||||
|
||||
// Note that [PreliminaryLoopVisitor] in FIR DFA collects assigned variable names.
|
||||
// This one collects declared variable symbols per capturing statements.
|
||||
class DeclaredVariableCollector {
|
||||
val declaredVariablesPerElement: SetMultimap<FirStatement, FirPropertySymbol> = setMultimapOf()
|
||||
|
||||
fun enterCapturingStatement(statement: FirStatement): Set<FirPropertySymbol> {
|
||||
assert(statement is FirLoop || statement is FirClass || statement is FirFunction)
|
||||
if (statement !in declaredVariablesPerElement) {
|
||||
statement.accept(visitor, null)
|
||||
}
|
||||
return declaredVariablesPerElement[statement]
|
||||
}
|
||||
|
||||
fun exitCapturingStatement(statement: FirStatement) {
|
||||
assert(statement is FirLoop || statement is FirClass || statement is FirFunction)
|
||||
declaredVariablesPerElement.removeKey(statement)
|
||||
}
|
||||
|
||||
fun resetState() {
|
||||
declaredVariablesPerElement.clear()
|
||||
}
|
||||
|
||||
// FirStatement -- closest statement (loop/lambda/local declaration) which may contain reassignments
|
||||
private val visitor = object : FirVisitor<Unit, FirStatement?>() {
|
||||
override fun visitElement(element: FirElement, data: FirStatement?) {
|
||||
element.acceptChildren(this, data)
|
||||
}
|
||||
|
||||
override fun visitProperty(property: FirProperty, data: FirStatement?) {
|
||||
if (property.isLocal) {
|
||||
requireNotNull(data)
|
||||
declaredVariablesPerElement.put(data, property.symbol)
|
||||
}
|
||||
visitElement(property, data)
|
||||
}
|
||||
|
||||
override fun visitWhileLoop(whileLoop: FirWhileLoop, data: FirStatement?) {
|
||||
visitCapturingStatement(whileLoop, data)
|
||||
}
|
||||
|
||||
override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: FirStatement?) {
|
||||
visitCapturingStatement(doWhileLoop, data)
|
||||
}
|
||||
|
||||
override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: FirStatement?) {
|
||||
visitCapturingStatement(anonymousFunction, data)
|
||||
}
|
||||
|
||||
override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: FirStatement?) {
|
||||
visitCapturingStatement(simpleFunction, data)
|
||||
}
|
||||
|
||||
override fun visitRegularClass(regularClass: FirRegularClass, data: FirStatement?) {
|
||||
visitCapturingStatement(regularClass, data)
|
||||
}
|
||||
|
||||
override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: FirStatement?) {
|
||||
visitCapturingStatement(anonymousObject, data)
|
||||
}
|
||||
|
||||
private fun visitCapturingStatement(statement: FirStatement, parent: FirStatement?) {
|
||||
visitElement(statement, statement)
|
||||
if (parent != null) {
|
||||
declaredVariablesPerElement.putAll(parent, declaredVariablesPerElement[statement])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> addRange(
|
||||
pathAwareInfo: P,
|
||||
key: K,
|
||||
@@ -341,7 +156,7 @@ internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, Even
|
||||
return updateRange(pathAwareInfo, key, { existingKind -> existingKind + range }, constructor)
|
||||
}
|
||||
|
||||
internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> overwriteRange(
|
||||
private fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> overwriteRange(
|
||||
pathAwareInfo: P,
|
||||
key: K,
|
||||
range: EventOccurrencesRange,
|
||||
+1
@@ -6,6 +6,7 @@
|
||||
package org.jetbrains.kotlin.test.runners
|
||||
|
||||
import org.jetbrains.kotlin.config.ExplicitApiMode
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.test.TestJdkKind
|
||||
import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder
|
||||
|
||||
Reference in New Issue
Block a user