FIR checker: report CAPTURED_VAL_INITIALIZATION

This commit is contained in:
Jinseong Jeon
2021-05-11 15:41:56 -07:00
committed by TeamCityServer
parent d38effcbbe
commit f0f1e2d945
20 changed files with 158 additions and 31 deletions
@@ -2,7 +2,7 @@ fun test1() {
val x: Int val x: Int
fun func() { fun func() {
x = 0 <!CAPTURED_VAL_INITIALIZATION!>x<!> = 0
} }
println(<!UNINITIALIZED_VARIABLE!>x<!>) println(<!UNINITIALIZED_VARIABLE!>x<!>)
@@ -691,6 +691,12 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error<KtExpression> { val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error<KtExpression> {
parameter<FirPropertySymbol>("property") parameter<FirPropertySymbol>("property")
} }
val CAPTURED_VAL_INITIALIZATION by error<KtExpression> {
parameter<FirPropertySymbol>("property")
}
val CAPTURED_MEMBER_VAL_INITIALIZATION by error<KtExpression> {
parameter<FirPropertySymbol>("property")
}
val WRONG_INVOCATION_KIND by warning<PsiElement> { val WRONG_INVOCATION_KIND by warning<PsiElement> {
parameter<Symbol>("declaration") parameter<Symbol>("declaration")
parameter<EventOccurrencesRange>("requiredRange") parameter<EventOccurrencesRange>("requiredRange")
@@ -411,6 +411,8 @@ object FirErrors {
val VAL_REASSIGNMENT by error1<KtExpression, FirVariableSymbol<*>>() val VAL_REASSIGNMENT by error1<KtExpression, FirVariableSymbol<*>>()
val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning1<KtExpression, FirPropertySymbol>() val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning1<KtExpression, FirPropertySymbol>()
val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error1<KtExpression, FirPropertySymbol>() val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error1<KtExpression, FirPropertySymbol>()
val CAPTURED_VAL_INITIALIZATION by error1<KtExpression, FirPropertySymbol>()
val CAPTURED_MEMBER_VAL_INITIALIZATION by error1<KtExpression, FirPropertySymbol>()
val WRONG_INVOCATION_KIND by warning3<PsiElement, AbstractFirBasedSymbol<*>, EventOccurrencesRange, EventOccurrencesRange>() val WRONG_INVOCATION_KIND by warning3<PsiElement, AbstractFirBasedSymbol<*>, EventOccurrencesRange, EventOccurrencesRange>()
val LEAKED_IN_PLACE_LAMBDA by error1<PsiElement, AbstractFirBasedSymbol<*>>() val LEAKED_IN_PLACE_LAMBDA by error1<PsiElement, AbstractFirBasedSymbol<*>>()
val WRONG_IMPLIES_CONDITION by warning0<PsiElement>() val WRONG_IMPLIES_CONDITION by warning0<PsiElement>()
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.cfa
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode import org.jetbrains.kotlin.fir.resolve.dfa.cfg.CFGNode
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
@@ -17,6 +18,7 @@ abstract class AbstractFirPropertyInitializationChecker {
reporter: DiagnosticReporter, reporter: DiagnosticReporter,
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>, data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
properties: Set<FirPropertySymbol>, properties: Set<FirPropertySymbol>,
capturedWrites: Set<FirVariableAssignment>,
context: CheckerContext context: CheckerContext
) )
} }
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.FirElement
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.* import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.util.SetMultimap import org.jetbrains.kotlin.fir.util.SetMultimap
@@ -60,21 +59,58 @@ class PropertyInitializationInfo(
::EMPTY ::EMPTY
} }
class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() { class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() {
companion object { companion object {
fun collect(graph: ControlFlowGraph): MutableSet<FirPropertySymbol> { fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> {
val collector = LocalPropertyCollector() val collector = LocalPropertyAndCapturedWriteCollector()
graph.traverse(TraverseDirection.Forward, collector) graph.traverse(TraverseDirection.Forward, collector)
return collector.symbols return collector.symbols.keys to collector.capturedWrites
} }
} }
private val symbols: MutableSet<FirPropertySymbol> = mutableSetOf() // 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 visitNode(node: CFGNode<*>) {}
override fun visitVariableDeclarationNode(node: VariableDeclarationNode) { override fun visitVariableDeclarationNode(node: VariableDeclarationNode) {
symbols += node.fir.symbol 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)
}
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)
} }
} }
@@ -110,8 +146,7 @@ class PropertyInitializationInfoCollector(
data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>> data: Collection<Pair<EdgeLabel, PathAwarePropertyInitializationInfo>>
): PathAwarePropertyInitializationInfo { ): PathAwarePropertyInitializationInfo {
val dataForNode = visitNode(node, data) val dataForNode = visitNode(node, data)
val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode val symbol = node.fir.referredPropertySymbol ?: return dataForNode
val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode
return if (symbol !in localProperties) { return if (symbol !in localProperties) {
dataForNode dataForNode
} else { } else {
@@ -61,9 +61,9 @@ class FirControlFlowAnalyzer(
} }
private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter, context: CheckerContext) { private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter, context: CheckerContext) {
val properties = LocalPropertyCollector.collect(graph) val (properties, capturedWrites) = LocalPropertyAndCapturedWriteCollector.collect(graph)
if (properties.isEmpty()) return if (properties.isEmpty()) return
val data = PropertyInitializationInfoCollector(properties).getData(graph) val data = PropertyInitializationInfoCollector(properties).getData(graph)
variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties, context) } variableAssignmentCheckers.forEach { it.analyze(graph, reporter, data, properties, capturedWrites, context) }
} }
} }
@@ -11,8 +11,11 @@ import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.isLateInit import org.jetbrains.kotlin.fir.declarations.isLateInit
import org.jetbrains.kotlin.fir.declarations.referredPropertySymbol
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
@@ -24,6 +27,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
reporter: DiagnosticReporter, reporter: DiagnosticReporter,
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>, data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
properties: Set<FirPropertySymbol>, properties: Set<FirPropertySymbol>,
capturedWrites: Set<FirVariableAssignment>,
context: CheckerContext context: CheckerContext
) { ) {
val localData = data.filter { val localData = data.filter {
@@ -33,21 +37,21 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
val localProperties = properties.filter { it.fir.initializer == null && it.fir.delegate == null }.toSet() val localProperties = properties.filter { it.fir.initializer == null && it.fir.delegate == null }.toSet()
val reporterVisitor = PropertyReporter(localData, localProperties, reporter, context) val reporterVisitor = PropertyReporter(localData, localProperties, capturedWrites, reporter, context)
graph.traverse(TraverseDirection.Forward, reporterVisitor) graph.traverse(TraverseDirection.Forward, reporterVisitor)
} }
private class PropertyReporter( private class PropertyReporter(
val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>, val data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
val localProperties: Set<FirPropertySymbol>, val localProperties: Set<FirPropertySymbol>,
val capturedWrites: Set<FirVariableAssignment>,
val reporter: DiagnosticReporter, val reporter: DiagnosticReporter,
val context: CheckerContext val context: CheckerContext
) : ControlFlowGraphVisitorVoid() { ) : ControlFlowGraphVisitorVoid() {
override fun visitNode(node: CFGNode<*>) {} override fun visitNode(node: CFGNode<*>) {}
private fun getPropertySymbol(node: CFGNode<*>): FirPropertySymbol? { private fun getPropertySymbol(node: CFGNode<*>): FirPropertySymbol? {
val reference = (node.fir as? FirQualifiedAccess)?.calleeReference as? FirResolvedNamedReference ?: return null return (node.fir as? FirQualifiedAccess)?.referredPropertySymbol
return reference.resolvedSymbol as? FirPropertySymbol
} }
override fun visitVariableAssignmentNode(node: VariableAssignmentNode) { override fun visitVariableAssignmentNode(node: VariableAssignmentNode) {
@@ -66,10 +70,17 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec
symbol: FirPropertySymbol, symbol: FirPropertySymbol,
node: VariableAssignmentNode node: VariableAssignmentNode
): Boolean { ): Boolean {
if (symbol.fir.isVal && node.fir in capturedWrites) {
if (symbol.fir.isLocal) {
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_VAL_INITIALIZATION, symbol, context)
} else {
reporter.reportOn(node.fir.lValue.source, FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION, symbol, context)
}
return true
}
val kind = info[symbol] ?: EventOccurrencesRange.ZERO val kind = info[symbol] ?: EventOccurrencesRange.ZERO
if (symbol.fir.isVal && kind.canBeRevisited()) { if (symbol.fir.isVal && kind.canBeRevisited()) {
node.fir.lValue.source?.let { node.fir.lValue.source?.let {
// TODO: differentiate CAPTURED_VAL_INITIALIZATION
reporter.report(FirErrors.VAL_REASSIGNMENT.on(it, symbol), context) reporter.report(FirErrors.VAL_REASSIGNMENT.on(it, symbol), context)
return true return true
} }
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.fir.analysis.checkers.getChildren
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.resolve.dfa.cfg.*
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
@@ -28,6 +29,7 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() {
reporter: DiagnosticReporter, reporter: DiagnosticReporter,
data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>, data: Map<CFGNode<*>, PathAwarePropertyInitializationInfo>,
properties: Set<FirPropertySymbol>, properties: Set<FirPropertySymbol>,
capturedWrites: Set<FirVariableAssignment>,
context: CheckerContext context: CheckerContext
) { ) {
val unprocessedProperties = mutableSetOf<FirPropertySymbol>() val unprocessedProperties = mutableSetOf<FirPropertySymbol>()
@@ -36,7 +36,7 @@ object UnusedChecker : FirControlFlowChecker() {
!it.symbol.classId.isLocal !it.symbol.classId.isLocal
} != null } != null
) return ) return
val properties = LocalPropertyCollector.collect(graph) val (properties, _) = LocalPropertyAndCapturedWriteCollector.collect(graph)
if (properties.isEmpty()) return if (properties.isEmpty()) return
val data = ValueWritesWithoutReading(context.session, properties).getData(graph) val data = ValueWritesWithoutReading(context.session, properties).getData(graph)
@@ -58,6 +58,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CANNOT_INFER_PARA
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CANNOT_WEAKEN_ACCESS_PRIVILEGE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CANNOT_WEAKEN_ACCESS_PRIVILEGE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_VAL import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAN_BE_VAL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CAPTURED_VAL_INITIALIZATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CATCH_PARAMETER_WITH_DEFAULT_VALUE import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CATCH_PARAMETER_WITH_DEFAULT_VALUE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_IN_SUPERTYPE_FOR_ENUM import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_IN_SUPERTYPE_FOR_ENUM
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_LITERAL_LHS_NOT_A_CLASS import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.CLASS_LITERAL_LHS_NOT_A_CLASS
@@ -941,6 +943,16 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", VARIABLE_NAME) map.put(VAL_REASSIGNMENT, "Val cannot be reassigned", VARIABLE_NAME)
map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD, "Reassignment of read-only property via backing field is deprecated", VARIABLE_NAME) map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD, "Reassignment of read-only property via backing field is deprecated", VARIABLE_NAME)
map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR, "Reassignment of read-only property via backing field", VARIABLE_NAME) map.put(VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR, "Reassignment of read-only property via backing field", VARIABLE_NAME)
map.put(
CAPTURED_VAL_INITIALIZATION,
"Captured values initialization is forbidden due to possible reassignment",
VARIABLE_NAME
)
map.put(
CAPTURED_MEMBER_VAL_INITIALIZATION,
"Captured member values initialization is forbidden due to possible reassignment",
VARIABLE_NAME
)
map.put( map.put(
WRONG_INVOCATION_KIND, WRONG_INVOCATION_KIND,
"{2} wrong invocation kind: given {3} case, but {4} case is possible", "{2} wrong invocation kind: given {3} case, but {4} case is possible",
@@ -0,0 +1,11 @@
/*
* 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.resolve.dfa.cfg
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
val EventOccurrencesRange?.isInPlace: Boolean
get() = this != null
@@ -306,9 +306,6 @@ class ControlFlowGraphBuilder {
else -> false else -> false
} }
private val EventOccurrencesRange?.isInPlace: Boolean
get() = this != null
fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): Triple<FunctionExitNode, PostponedLambdaExitNode?, ControlFlowGraph> { fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): Triple<FunctionExitNode, PostponedLambdaExitNode?, ControlFlowGraph> {
val symbol = anonymousFunction.symbol val symbol = anonymousFunction.symbol
val exitNode = exitsOfAnonymousFunctions.remove(symbol)!!.also { val exitNode = exitsOfAnonymousFunctions.remove(symbol)!!.also {
@@ -11,10 +11,13 @@ import org.jetbrains.kotlin.fir.declarations.builder.FirRegularClassBuilder
import org.jetbrains.kotlin.fir.declarations.builder.FirTypeParameterBuilder import org.jetbrains.kotlin.fir.declarations.builder.FirTypeParameterBuilder
import org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl import org.jetbrains.kotlin.fir.declarations.impl.FirFileImpl
import org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl import org.jetbrains.kotlin.fir.declarations.impl.FirRegularClassImpl
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccess
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
import org.jetbrains.kotlin.fir.render import org.jetbrains.kotlin.fir.render
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.types.ConeClassLikeType import org.jetbrains.kotlin.fir.types.ConeClassLikeType
import org.jetbrains.kotlin.fir.types.ConeFlexibleType import org.jetbrains.kotlin.fir.types.ConeFlexibleType
@@ -216,6 +219,12 @@ val FirProperty.hasBackingField: Boolean
} }
} }
val FirQualifiedAccess.referredPropertySymbol: FirPropertySymbol?
get() {
val reference = calleeReference as? FirResolvedNamedReference ?: return null
return reference.resolvedSymbol as? FirPropertySymbol
}
inline val FirDeclaration.isFromLibrary: Boolean inline val FirDeclaration.isFromLibrary: Boolean
get() = origin == FirDeclarationOrigin.Library get() = origin == FirDeclarationOrigin.Library
inline val FirDeclaration.isSynthetic: Boolean inline val FirDeclaration.isSynthetic: Boolean
@@ -17,14 +17,14 @@ fun foo() {
fun bar() { fun bar() {
val x: Int val x: Int
exec { exec {
x = 13 <!CAPTURED_VAL_INITIALIZATION!>x<!> = 13
} }
} }
fun bar2() { fun bar2() {
val x: Int val x: Int
fun foo() { fun foo() {
x = 3 <!CAPTURED_VAL_INITIALIZATION!>x<!> = 3
} }
foo() foo()
} }
@@ -60,7 +60,7 @@ class Your {
val y = if (true) { val y = if (true) {
val xx: Int val xx: Int
exec { exec {
xx = 42 <!CAPTURED_VAL_INITIALIZATION!>xx<!> = 42
} }
24 24
} }
@@ -70,7 +70,7 @@ class Your {
val z = if (true) { val z = if (true) {
val xx: Int val xx: Int
exec { exec {
xx = 24 <!CAPTURED_VAL_INITIALIZATION!>xx<!> = 24
} }
42 42
} }
@@ -59,7 +59,7 @@ fun gau() {
object: Any() { object: Any() {
init { init {
// Error! // Error!
y = "" <!CAPTURED_VAL_INITIALIZATION!>y<!> = ""
} }
} }
} }
@@ -36,7 +36,7 @@ fun branchingIndetermineFlow(a: Any?) {
fun nonAnonymousLambdas() { fun nonAnonymousLambdas() {
val x: Int val x: Int
val initializer = { x = 42 } val initializer = { <!CAPTURED_VAL_INITIALIZATION!>x<!> = 42 }
myRun(initializer) myRun(initializer)
<!UNINITIALIZED_VARIABLE!>x<!>.inc() <!UNINITIALIZED_VARIABLE!>x<!>.inc()
} }
@@ -55,7 +55,7 @@ fun funWithUnknownInvocations(block: () -> Unit) = block()
fun nestedIndefiniteAssignment() { fun nestedIndefiniteAssignment() {
val x: Int val x: Int
// Captured val initialization reported, because we don't know anything about funWithUnknownInvocations // Captured val initialization reported, because we don't know anything about funWithUnknownInvocations
funWithUnknownInvocations { myRun { x = 42 } } funWithUnknownInvocations { myRun { <!CAPTURED_VAL_INITIALIZATION!>x<!> = 42 } }
<!UNINITIALIZED_VARIABLE!>x<!>.inc() <!UNINITIALIZED_VARIABLE!>x<!>.inc()
} }
@@ -46,6 +46,6 @@ fun funWithUnknownInvocations(block: () -> Unit) = block()
fun nestedIndefiniteAssignment() { fun nestedIndefiniteAssignment() {
val x: Int val x: Int
funWithUnknownInvocations { myRun { x = 42 } } funWithUnknownInvocations { myRun { <!CAPTURED_VAL_INITIALIZATION!>x<!> = 42 } }
<!UNINITIALIZED_VARIABLE!>x<!>.inc() <!UNINITIALIZED_VARIABLE!>x<!>.inc()
} }
@@ -1988,6 +1988,20 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token, token,
) )
} }
add(FirErrors.CAPTURED_VAL_INITIALIZATION) { firDiagnostic ->
CapturedValInitializationImpl(
firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir),
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.CAPTURED_MEMBER_VAL_INITIALIZATION) { firDiagnostic ->
CapturedMemberValInitializationImpl(
firSymbolBuilder.variableLikeBuilder.buildVariableSymbol(firDiagnostic.a.fir),
firDiagnostic as FirPsiDiagnostic<*>,
token,
)
}
add(FirErrors.WRONG_INVOCATION_KIND) { firDiagnostic -> add(FirErrors.WRONG_INVOCATION_KIND) { firDiagnostic ->
WrongInvocationKindImpl( WrongInvocationKindImpl(
firSymbolBuilder.buildSymbol(firDiagnostic.a.fir as FirDeclaration), firSymbolBuilder.buildSymbol(firDiagnostic.a.fir as FirDeclaration),
@@ -1395,6 +1395,16 @@ sealed class KtFirDiagnostic<PSI: PsiElement> : KtDiagnosticWithPsi<PSI> {
abstract val property: KtVariableSymbol abstract val property: KtVariableSymbol
} }
abstract class CapturedValInitialization : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = CapturedValInitialization::class
abstract val property: KtVariableSymbol
}
abstract class CapturedMemberValInitialization : KtFirDiagnostic<KtExpression>() {
override val diagnosticClass get() = CapturedMemberValInitialization::class
abstract val property: KtVariableSymbol
}
abstract class WrongInvocationKind : KtFirDiagnostic<PsiElement>() { abstract class WrongInvocationKind : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = WrongInvocationKind::class override val diagnosticClass get() = WrongInvocationKind::class
abstract val declaration: KtSymbol abstract val declaration: KtSymbol
@@ -2269,6 +2269,22 @@ internal class ValReassignmentViaBackingFieldErrorImpl(
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
} }
internal class CapturedValInitializationImpl(
override val property: KtVariableSymbol,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.CapturedValInitialization(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class CapturedMemberValInitializationImpl(
override val property: KtVariableSymbol,
firDiagnostic: FirPsiDiagnostic<*>,
override val token: ValidityToken,
) : KtFirDiagnostic.CapturedMemberValInitialization(), KtAbstractFirDiagnostic<KtExpression> {
override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic)
}
internal class WrongInvocationKindImpl( internal class WrongInvocationKindImpl(
override val declaration: KtSymbol, override val declaration: KtSymbol,
override val requiredRange: EventOccurrencesRange, override val requiredRange: EventOccurrencesRange,