[FIR] Add INITIALIZATION_BEFORE_DECLARATION diagnostic

This commit is contained in:
Andrey Zinovyev
2021-07-27 11:45:50 +03:00
committed by teamcityserver
parent b706e776ad
commit d84cc4333c
13 changed files with 123 additions and 32 deletions
@@ -939,6 +939,10 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val WRONG_IMPLIES_CONDITION by warning<PsiElement>()
val VARIABLE_WITH_NO_TYPE_NO_INITIALIZER by error<KtVariableDeclaration>(PositioningStrategy.DECLARATION_NAME)
val INITIALIZATION_BEFORE_DECLARATION by error<KtExpression>() {
parameter<Symbol>("property")
}
}
val NULLABILITY by object : DiagnosticGroup("Nullability") {
@@ -498,6 +498,7 @@ object FirErrors {
val LEAKED_IN_PLACE_LAMBDA by error1<PsiElement, FirBasedSymbol<*>>()
val WRONG_IMPLIES_CONDITION by warning0<PsiElement>()
val VARIABLE_WITH_NO_TYPE_NO_INITIALIZER by error0<KtVariableDeclaration>(SourceElementPositioningStrategies.DECLARATION_NAME)
val INITIALIZATION_BEFORE_DECLARATION by error1<KtExpression, FirBasedSymbol<*>>()
// Nullability
val UNSAFE_CALL by error2<PsiElement, ConeKotlinType, FirExpression?>(SourceElementPositioningStrategies.DOT_BY_QUALIFIED)
@@ -33,10 +33,11 @@ fun ControlFlowGraph.traverse(
// ---------------------- Path-sensitive data collection -----------------------
fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectDataForNode(
fun <I> ControlFlowGraph.collectDataForNode(
direction: TraverseDirection,
initialInfo: I,
visitor: ControlFlowGraphVisitor<I, Collection<Pair<EdgeLabel, I>>>
visitor: ControlFlowGraphVisitor<I, Collection<Pair<EdgeLabel, I>>>,
visitSubGraphs: Boolean = true
): Map<CFGNode<*>, I> {
val nodeMap = LinkedHashMap<CFGNode<*>, I>()
val startNode = getEnterNode(direction)
@@ -44,22 +45,23 @@ fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectDat
val changed = mutableMapOf<CFGNode<*>, Boolean>()
do {
collectDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed)
collectDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed, visitSubGraphs)
} while (changed.any { it.value })
return nodeMap
}
private fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.collectDataForNodeInternal(
private fun <I> ControlFlowGraph.collectDataForNodeInternal(
direction: TraverseDirection,
initialInfo: I,
visitor: ControlFlowGraphVisitor<I, Collection<Pair<EdgeLabel, I>>>,
nodeMap: MutableMap<CFGNode<*>, I>,
changed: MutableMap<CFGNode<*>, Boolean>
changed: MutableMap<CFGNode<*>, Boolean>,
visitSubGraphs: Boolean = true
) {
val nodes = getNodesInOrder(direction)
for (node in nodes) {
if (direction == TraverseDirection.Backward && node is CFGNodeWithCfgOwner<*>) {
if (visitSubGraphs && direction == TraverseDirection.Backward && node is CFGNodeWithCfgOwner<*>) {
node.subGraphs.forEach { it.collectDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed) }
}
val previousNodes = when (direction) {
@@ -83,7 +85,7 @@ private fun <I : ControlFlowInfo<I, K, V>, K : Any, V : Any> ControlFlowGraph.co
if (hasChanged) {
nodeMap[node] = newData
}
if (direction == TraverseDirection.Forward && node is CFGNodeWithCfgOwner<*>) {
if (visitSubGraphs && direction == TraverseDirection.Forward && node is CFGNodeWithCfgOwner<*>) {
node.subGraphs.forEach { it.collectDataForNodeInternal(direction, initialInfo, visitor, nodeMap, changed) }
}
}
@@ -90,7 +90,8 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
FirFunInterfaceDeclarationChecker,
FirNestedClassChecker,
FirInlineClassDeclarationChecker,
FirOuterClassArgumentsRequiredChecker
FirOuterClassArgumentsRequiredChecker,
FirPropertyInitializationChecker,
)
override val constructorCheckers: Set<FirConstructorChecker>
@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeLocalVariableNoTypeOrIni
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef
import org.jetbrains.kotlin.lexer.KtTokens
internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean {
@@ -111,8 +110,7 @@ internal fun checkPropertyInitializer(
val returnTypeRef = property.returnTypeRef
if (property.initializer == null &&
property.delegate == null &&
(returnTypeRef is FirImplicitTypeRef ||
(returnTypeRef is FirErrorTypeRef && returnTypeRef.diagnostic is ConeLocalVariableNoTypeOrInitializer))
returnTypeRef is FirErrorTypeRef && returnTypeRef.diagnostic is ConeLocalVariableNoTypeOrInitializer
) {
property.source?.let {
reporter.reportOn(it, FirErrors.PROPERTY_WITH_NO_TYPE_NO_INITIALIZER, context)
@@ -0,0 +1,81 @@
/*
* 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.checkers.declaration
import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection
import org.jetbrains.kotlin.fir.analysis.cfa.collectDataForNode
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirAnonymousInitializer
import org.jetbrains.kotlin.fir.declarations.FirProperty
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraphVisitor
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.VariableAssignmentNode
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirPropertyInitializationChecker : FirRegularClassChecker() {
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
val properties = mutableSetOf<FirPropertySymbol>()
val toReport = mutableSetOf<FirVariableAssignment>()
for (decl in declaration.declarations.asReversed()) {
when (decl) {
is FirProperty -> properties.add(decl.symbol)
is FirAnonymousInitializer ->
if (properties.isNotEmpty()) {
collectInitAssignments(decl, properties, toReport)
}
else -> {
}
}
}
toReport.forEach { exp ->
val propertySymbol = exp.lValue.toResolvedCallableSymbol() ?: return@forEach
reporter.reportOn(exp.lValue.source, FirErrors.INITIALIZATION_BEFORE_DECLARATION, propertySymbol, context)
}
}
private fun collectInitAssignments(
init: FirAnonymousInitializer,
properties: Properties,
assignments: MutableSet<FirVariableAssignment>
) {
val graph = init.controlFlowGraphReference?.controlFlowGraph ?: return
val visitor = AssignmentVisitor(properties) { assignments.add(it) }
graph.collectDataForNode(TraverseDirection.Forward, emptySet(), visitor, visitSubGraphs = false)
}
private class AssignmentVisitor(private val interestingProperties: Properties, private val acceptor: (FirVariableAssignment) -> Unit) :
ControlFlowGraphVisitor<Properties, Collection<Pair<EdgeLabel, Properties>>>() {
override fun visitNode(node: CFGNode<*>, data: Collection<Pair<EdgeLabel, Properties>>): Properties {
if (data.isEmpty()) return emptySet()
return data.map { it.second }.reduce { l, r -> l.intersect(r) }
}
override fun visitVariableAssignmentNode(node: VariableAssignmentNode, data: Collection<Pair<EdgeLabel, Properties>>): Properties {
val input = visitNode(node, data)
val propertySymbol = node.fir.lValue.toResolvedCallableSymbol().safeAs<FirPropertySymbol>() ?: return input
return if (propertySymbol in interestingProperties && propertySymbol !in input) {
acceptor(node.fir)
input + propertySymbol
} else {
input
}
}
}
}
private typealias Properties = Set<FirPropertySymbol>
@@ -208,6 +208,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCONSISTENT_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INCONSISTENT_TYPE_PARAMETER_VALUES
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFERENCE_ERROR
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INFIX_MODIFIER_REQUIRED
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZATION_BEFORE_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZER_REQUIRED_FOR_DESTRUCTURING_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INITIALIZER_TYPE_MISMATCH
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.INLINE_CLASS_CANNOT_BE_RECURSIVE
@@ -1131,6 +1132,8 @@ class FirDefaultErrorMessages {
map.put(VARIABLE_WITH_NO_TYPE_NO_INITIALIZER, "This variable must either have a type annotation or be initialized")
map.put(INITIALIZATION_BEFORE_DECLARATION, "Variable cannot be initialized before declaration", SYMBOL)
map.put(MUST_BE_INITIALIZED, "Property must be initialized")
map.put(MUST_BE_INITIALIZED_OR_BE_ABSTRACT, "Property must be initialized or be abstract")
map.put(EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, "Extension property must have accessors or be abstract")
@@ -24,7 +24,7 @@ class Test2 {
}
}
a = "2"
<!INITIALIZATION_BEFORE_DECLARATION!>a<!> = "2"
t.some()
}
@@ -1,20 +0,0 @@
//KT-897 Don't allow assignment to a property before it is defined
package kt897
class A() {
init {
i = 11
}
val i : Int? = null // must be an error
init {
j = 1
}
var j : Int = 2
init {
k = 3
}
val k : Int
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
//KT-897 Don't allow assignment to a property before it is defined
package kt897
@@ -2554,6 +2554,13 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.INITIALIZATION_BEFORE_DECLARATION) { firDiagnostic ->
InitializationBeforeDeclarationImpl(
firSymbolBuilder.buildSymbol(firDiagnostic.a.fir),
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirErrors.UNSAFE_CALL) { firDiagnostic ->
UnsafeCallImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
@@ -1790,6 +1790,11 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = VariableWithNoTypeNoInitializer::class
}
abstract class InitializationBeforeDeclaration : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = InitializationBeforeDeclaration::class
abstract val property: KtSymbol
}
abstract class UnsafeCall : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = UnsafeCall::class
abstract val receiverType: KtType
@@ -2883,6 +2883,14 @@ internal class VariableWithNoTypeNoInitializerImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class InitializationBeforeDeclarationImpl(
override val property: KtSymbol,
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.InitializationBeforeDeclaration(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class UnsafeCallImpl(
override val receiverType: KtType,
override val receiverExpression: KtExpression?,