[FIR] Add INITIALIZATION_BEFORE_DECLARATION diagnostic
This commit is contained in:
committed by
teamcityserver
parent
b706e776ad
commit
d84cc4333c
+4
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -90,7 +90,8 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
||||
FirFunInterfaceDeclarationChecker,
|
||||
FirNestedClassChecker,
|
||||
FirInlineClassDeclarationChecker,
|
||||
FirOuterClassArgumentsRequiredChecker
|
||||
FirOuterClassArgumentsRequiredChecker,
|
||||
FirPropertyInitializationChecker,
|
||||
)
|
||||
|
||||
override val constructorCheckers: Set<FirConstructorChecker>
|
||||
|
||||
+1
-3
@@ -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)
|
||||
|
||||
+81
@@ -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>
|
||||
+3
@@ -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")
|
||||
|
||||
Vendored
+1
-1
@@ -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
|
||||
|
||||
+7
@@ -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),
|
||||
|
||||
+5
@@ -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
|
||||
|
||||
+8
@@ -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?,
|
||||
|
||||
Reference in New Issue
Block a user