From f0f1e2d945916b454346090616edc98152fb2f87 Mon Sep 17 00:00:00 2001 From: Jinseong Jeon Date: Tue, 11 May 2021 15:41:56 -0700 Subject: [PATCH] FIR checker: report CAPTURED_VAL_INITIALIZATION --- .../initialization/fromLocalMembers.kt | 4 +- .../diagnostics/FirDiagnosticsList.kt | 6 +++ .../fir/analysis/diagnostics/FirErrors.kt | 2 + ...bstractFirPropertyInitializationChecker.kt | 2 + .../kotlin/fir/analysis/cfa/CfaUtils.kt | 53 +++++++++++++++---- .../analysis/cfa/FirControlFlowAnalyzer.kt | 4 +- .../cfa/FirPropertyInitializationAnalyzer.kt | 19 +++++-- .../checkers/extended/CanBeValChecker.kt | 2 + .../checkers/extended/UnusedChecker.kt | 2 +- .../diagnostics/FirDefaultErrorMessages.kt | 12 +++++ .../kotlin/fir/resolve/dfa/cfg/CfgUtils.kt | 11 ++++ .../dfa/cfg/ControlFlowGraphBuilder.kt | 3 -- .../fir/declarations/FirDeclarationUtil.kt | 9 ++++ .../initializationInLambda.fir.kt | 8 +-- .../initializationInLocalClass.fir.kt | 2 +- .../valIndefiniteInitialization.fir.kt | 6 +-- .../varIndefiniteInitialization.fir.kt | 4 +- .../diagnostics/KtFirDataClassConverters.kt | 14 +++++ .../api/fir/diagnostics/KtFirDiagnostics.kt | 10 ++++ .../fir/diagnostics/KtFirDiagnosticsImpl.kt | 16 ++++++ 20 files changed, 158 insertions(+), 31 deletions(-) create mode 100644 compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CfgUtils.kt diff --git a/compiler/fir/analysis-tests/testData/resolveWithStdlib/initialization/fromLocalMembers.kt b/compiler/fir/analysis-tests/testData/resolveWithStdlib/initialization/fromLocalMembers.kt index b6af8bd6979..53f34debc9f 100644 --- a/compiler/fir/analysis-tests/testData/resolveWithStdlib/initialization/fromLocalMembers.kt +++ b/compiler/fir/analysis-tests/testData/resolveWithStdlib/initialization/fromLocalMembers.kt @@ -2,7 +2,7 @@ fun test1() { val x: Int fun func() { - x = 0 + x = 0 } println(x) @@ -43,4 +43,4 @@ fun test3() { println(x) println(x) -} \ No newline at end of file +} diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt index 463aee873b9..a6408595e3c 100644 --- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt +++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirDiagnosticsList.kt @@ -691,6 +691,12 @@ object DIAGNOSTICS_LIST : DiagnosticList() { val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error { parameter("property") } + val CAPTURED_VAL_INITIALIZATION by error { + parameter("property") + } + val CAPTURED_MEMBER_VAL_INITIALIZATION by error { + parameter("property") + } val WRONG_INVOCATION_KIND by warning { parameter("declaration") parameter("requiredRange") diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index 9f7935aaac1..e69d13092e6 100644 --- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -411,6 +411,8 @@ object FirErrors { val VAL_REASSIGNMENT by error1>() val VAL_REASSIGNMENT_VIA_BACKING_FIELD by warning1() val VAL_REASSIGNMENT_VIA_BACKING_FIELD_ERROR by error1() + val CAPTURED_VAL_INITIALIZATION by error1() + val CAPTURED_MEMBER_VAL_INITIALIZATION by error1() val WRONG_INVOCATION_KIND by warning3, EventOccurrencesRange, EventOccurrencesRange>() val LEAKED_IN_PLACE_LAMBDA by error1>() val WRONG_IMPLIES_CONDITION by warning0() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/AbstractFirPropertyInitializationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/AbstractFirPropertyInitializationChecker.kt index 768d3a3cb26..55d01cf096f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/AbstractFirPropertyInitializationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/AbstractFirPropertyInitializationChecker.kt @@ -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.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.ControlFlowGraph import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol @@ -17,6 +18,7 @@ abstract class AbstractFirPropertyInitializationChecker { reporter: DiagnosticReporter, data: Map, PathAwarePropertyInitializationInfo>, properties: Set, + capturedWrites: Set, context: CheckerContext ) } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt index d7540c7ab4d..552a18ce162 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt @@ -11,7 +11,6 @@ import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.declarations.* 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.symbols.impl.FirPropertySymbol import org.jetbrains.kotlin.fir.util.SetMultimap @@ -60,21 +59,58 @@ class PropertyInitializationInfo( ::EMPTY } -class LocalPropertyCollector private constructor() : ControlFlowGraphVisitorVoid() { +class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() { companion object { - fun collect(graph: ControlFlowGraph): MutableSet { - val collector = LocalPropertyCollector() + fun collect(graph: ControlFlowGraph): Pair, Set> { + val collector = LocalPropertyAndCapturedWriteCollector() graph.traverse(TraverseDirection.Forward, collector) - return collector.symbols + return collector.symbols.keys to collector.capturedWrites } } - private val symbols: MutableSet = 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 = mutableMapOf() + + private val lambdaOrLocalFunctionStack: MutableList> = mutableListOf() + private val capturedWrites: MutableSet = mutableSetOf() override fun visitNode(node: CFGNode<*>) {} 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> ): PathAwarePropertyInitializationInfo { val dataForNode = visitNode(node, data) - val reference = node.fir.lValue as? FirResolvedNamedReference ?: return dataForNode - val symbol = reference.resolvedSymbol as? FirPropertySymbol ?: return dataForNode + val symbol = node.fir.referredPropertySymbol ?: return dataForNode return if (symbol !in localProperties) { dataForNode } else { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirControlFlowAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirControlFlowAnalyzer.kt index 9c2fb40cf1b..cc68ca25142 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirControlFlowAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirControlFlowAnalyzer.kt @@ -61,9 +61,9 @@ class FirControlFlowAnalyzer( } private fun runAssignmentCfaCheckers(graph: ControlFlowGraph, reporter: DiagnosticReporter, context: CheckerContext) { - val properties = LocalPropertyCollector.collect(graph) + val (properties, capturedWrites) = LocalPropertyAndCapturedWriteCollector.collect(graph) if (properties.isEmpty()) return 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) } } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt index b79e83fd713..2a7a6fbf7d0 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/FirPropertyInitializationAnalyzer.kt @@ -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.diagnostics.DiagnosticReporter 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.referredPropertySymbol 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.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol @@ -24,6 +27,7 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec reporter: DiagnosticReporter, data: Map, PathAwarePropertyInitializationInfo>, properties: Set, + capturedWrites: Set, context: CheckerContext ) { 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 reporterVisitor = PropertyReporter(localData, localProperties, reporter, context) + val reporterVisitor = PropertyReporter(localData, localProperties, capturedWrites, reporter, context) graph.traverse(TraverseDirection.Forward, reporterVisitor) } private class PropertyReporter( val data: Map, PathAwarePropertyInitializationInfo>, val localProperties: Set, + val capturedWrites: Set, val reporter: DiagnosticReporter, val context: CheckerContext ) : ControlFlowGraphVisitorVoid() { override fun visitNode(node: CFGNode<*>) {} private fun getPropertySymbol(node: CFGNode<*>): FirPropertySymbol? { - val reference = (node.fir as? FirQualifiedAccess)?.calleeReference as? FirResolvedNamedReference ?: return null - return reference.resolvedSymbol as? FirPropertySymbol + return (node.fir as? FirQualifiedAccess)?.referredPropertySymbol } override fun visitVariableAssignmentNode(node: VariableAssignmentNode) { @@ -66,10 +70,17 @@ object FirPropertyInitializationAnalyzer : AbstractFirPropertyInitializationChec symbol: FirPropertySymbol, node: VariableAssignmentNode ): 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 if (symbol.fir.isVal && kind.canBeRevisited()) { node.fir.lValue.source?.let { - // TODO: differentiate CAPTURED_VAL_INITIALIZATION reporter.report(FirErrors.VAL_REASSIGNMENT.on(it, symbol), context) return true } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt index a1fca131da8..8068a831517 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeValChecker.kt @@ -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.FirErrors 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.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol @@ -28,6 +29,7 @@ object CanBeValChecker : AbstractFirPropertyInitializationChecker() { reporter: DiagnosticReporter, data: Map, PathAwarePropertyInitializationInfo>, properties: Set, + capturedWrites: Set, context: CheckerContext ) { val unprocessedProperties = mutableSetOf() diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt index 03b5fb9b7ca..5bd383cf050 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/UnusedChecker.kt @@ -36,7 +36,7 @@ object UnusedChecker : FirControlFlowChecker() { !it.symbol.classId.isLocal } != null ) return - val properties = LocalPropertyCollector.collect(graph) + val (properties, _) = LocalPropertyAndCapturedWriteCollector.collect(graph) if (properties.isEmpty()) return val data = ValueWritesWithoutReading(context.session, properties).getData(graph) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt index 9a98a1f4b5c..9b3a2691232 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirDefaultErrorMessages.kt @@ -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.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.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.CLASS_IN_SUPERTYPE_FOR_ENUM 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_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( + 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( WRONG_INVOCATION_KIND, "{2} wrong invocation kind: given {3} case, but {4} case is possible", diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CfgUtils.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CfgUtils.kt new file mode 100644 index 00000000000..6035b2088e4 --- /dev/null +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/CfgUtils.kt @@ -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 diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index c4c1b59e08e..f2f94234470 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -306,9 +306,6 @@ class ControlFlowGraphBuilder { else -> false } - private val EventOccurrencesRange?.isInPlace: Boolean - get() = this != null - fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): Triple { val symbol = anonymousFunction.symbol val exitNode = exitsOfAnonymousFunctions.remove(symbol)!!.also { diff --git a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt index 8c57ef149a2..85b67da278b 100644 --- a/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt +++ b/compiler/fir/tree/src/org/jetbrains/kotlin/fir/declarations/FirDeclarationUtil.kt @@ -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.impl.FirFileImpl 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.render import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol 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.types.ConeClassLikeType 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 get() = origin == FirDeclarationOrigin.Library inline val FirDeclaration.isSynthetic: Boolean diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLambda.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLambda.fir.kt index b3532f50417..574f6088f64 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLambda.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLambda.fir.kt @@ -17,14 +17,14 @@ fun foo() { fun bar() { val x: Int exec { - x = 13 + x = 13 } } fun bar2() { val x: Int fun foo() { - x = 3 + x = 3 } foo() } @@ -60,7 +60,7 @@ class Your { val y = if (true) { val xx: Int exec { - xx = 42 + xx = 42 } 24 } @@ -70,7 +70,7 @@ class Your { val z = if (true) { val xx: Int exec { - xx = 24 + xx = 24 } 42 } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLocalClass.fir.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLocalClass.fir.kt index 07912a36765..ff3640e8e12 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLocalClass.fir.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/initializationInLocalClass.fir.kt @@ -59,7 +59,7 @@ fun gau() { object: Any() { init { // Error! - y = "" + y = "" } } } diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valIndefiniteInitialization.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valIndefiniteInitialization.fir.kt index de7b344033d..c3b4b879195 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valIndefiniteInitialization.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/valIndefiniteInitialization.fir.kt @@ -36,7 +36,7 @@ fun branchingIndetermineFlow(a: Any?) { fun nonAnonymousLambdas() { val x: Int - val initializer = { x = 42 } + val initializer = { x = 42 } myRun(initializer) x.inc() } @@ -55,7 +55,7 @@ fun funWithUnknownInvocations(block: () -> Unit) = block() fun nestedIndefiniteAssignment() { val x: Int // Captured val initialization reported, because we don't know anything about funWithUnknownInvocations - funWithUnknownInvocations { myRun { x = 42 } } + funWithUnknownInvocations { myRun { x = 42 } } x.inc() } @@ -65,4 +65,4 @@ class InitializationForbiddenInNonInitSection { fun setup() { myRun { x = 42 } } -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/varIndefiniteInitialization.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/varIndefiniteInitialization.fir.kt index 923f73b7eda..760ebe4eb9f 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/varIndefiniteInitialization.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/controlflow/initialization/exactlyOnce/varIndefiniteInitialization.fir.kt @@ -46,6 +46,6 @@ fun funWithUnknownInvocations(block: () -> Unit) = block() fun nestedIndefiniteAssignment() { val x: Int - funWithUnknownInvocations { myRun { x = 42 } } + funWithUnknownInvocations { myRun { x = 42 } } x.inc() -} \ No newline at end of file +} diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt index 73b48ef58e7..aeea10bd856 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDataClassConverters.kt @@ -1988,6 +1988,20 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert 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 -> WrongInvocationKindImpl( firSymbolBuilder.buildSymbol(firDiagnostic.a.fir as FirDeclaration), diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt index 1288bb6ba30..56423332606 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnostics.kt @@ -1395,6 +1395,16 @@ sealed class KtFirDiagnostic : KtDiagnosticWithPsi { abstract val property: KtVariableSymbol } + abstract class CapturedValInitialization : KtFirDiagnostic() { + override val diagnosticClass get() = CapturedValInitialization::class + abstract val property: KtVariableSymbol + } + + abstract class CapturedMemberValInitialization : KtFirDiagnostic() { + override val diagnosticClass get() = CapturedMemberValInitialization::class + abstract val property: KtVariableSymbol + } + abstract class WrongInvocationKind : KtFirDiagnostic() { override val diagnosticClass get() = WrongInvocationKind::class abstract val declaration: KtSymbol diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt index 6437052af1d..a8234f3474a 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/diagnostics/KtFirDiagnosticsImpl.kt @@ -2269,6 +2269,22 @@ internal class ValReassignmentViaBackingFieldErrorImpl( override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) } +internal class CapturedValInitializationImpl( + override val property: KtVariableSymbol, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.CapturedValInitialization(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + +internal class CapturedMemberValInitializationImpl( + override val property: KtVariableSymbol, + firDiagnostic: FirPsiDiagnostic<*>, + override val token: ValidityToken, +) : KtFirDiagnostic.CapturedMemberValInitialization(), KtAbstractFirDiagnostic { + override val firDiagnostic: FirPsiDiagnostic<*> by weakRef(firDiagnostic) +} + internal class WrongInvocationKindImpl( override val declaration: KtSymbol, override val requiredRange: EventOccurrencesRange,