[FIR] Check top-level property initialization via CFG
Now that files have a CFG, use it to validate properties are initialized correctly. Update FirTopLevelPropertiesChecker to collect initialization info for the property being checked - similar to FirMemberPropertiesChecker - and validate proper initialization. #KT-56683 Fixed #KT-58531 Fixed
This commit is contained in:
Vendored
+3
@@ -4,3 +4,6 @@ class A {
|
|||||||
abstract fun foo()
|
abstract fun foo()
|
||||||
}
|
}
|
||||||
class B: A
|
class B: A
|
||||||
|
|
||||||
|
// IGNORE_FIR
|
||||||
|
// Does not pass because of KT-61296
|
||||||
|
|||||||
+6
@@ -3242,6 +3242,12 @@ public class DiagnosticCompilerTestFE10TestdataTestGenerated extends AbstractDia
|
|||||||
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("TopLevelPropertyInitialization.kt")
|
||||||
|
public void testTopLevelPropertyInitialization() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/backingField/TopLevelPropertyInitialization.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
||||||
public void testValDeferredInitInFinalClass() throws Exception {
|
public void testValDeferredInitInFinalClass() throws Exception {
|
||||||
|
|||||||
+6
@@ -3242,6 +3242,12 @@ public class LLFirPreresolvedReversedDiagnosticCompilerFE10TestDataTestGenerated
|
|||||||
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("TopLevelPropertyInitialization.kt")
|
||||||
|
public void testTopLevelPropertyInitialization() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/backingField/TopLevelPropertyInitialization.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
||||||
public void testValDeferredInitInFinalClass() throws Exception {
|
public void testValDeferredInitInFinalClass() throws Exception {
|
||||||
|
|||||||
+6
@@ -3242,6 +3242,12 @@ public class FirLightTreeOldFrontendDiagnosticsTestGenerated extends AbstractFir
|
|||||||
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("TopLevelPropertyInitialization.kt")
|
||||||
|
public void testTopLevelPropertyInitialization() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/backingField/TopLevelPropertyInitialization.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
||||||
public void testValDeferredInitInFinalClass() throws Exception {
|
public void testValDeferredInitInFinalClass() throws Exception {
|
||||||
|
|||||||
+6
@@ -3248,6 +3248,12 @@ public class FirPsiOldFrontendDiagnosticsTestGenerated extends AbstractFirPsiDia
|
|||||||
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("TopLevelPropertyInitialization.kt")
|
||||||
|
public void testTopLevelPropertyInitialization() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/backingField/TopLevelPropertyInitialization.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
||||||
public void testValDeferredInitInFinalClass() throws Exception {
|
public void testValDeferredInitInFinalClass() throws Exception {
|
||||||
|
|||||||
+2
-1
@@ -17,6 +17,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
|||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
|
import org.jetbrains.kotlin.fir.declarations.utils.hasBackingField
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField
|
import org.jetbrains.kotlin.fir.declarations.utils.hasExplicitBackingField
|
||||||
|
import org.jetbrains.kotlin.fir.declarations.utils.isExternal
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.isLateInit
|
import org.jetbrains.kotlin.fir.declarations.utils.isLateInit
|
||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.isCatchParameter
|
import org.jetbrains.kotlin.fir.isCatchParameter
|
||||||
@@ -126,7 +127,7 @@ private fun PropertyInitializationInfoData.checkPropertyAccesses(
|
|||||||
node is QualifiedAccessNode -> {
|
node is QualifiedAccessNode -> {
|
||||||
if (doNotReportUninitializedVariable) continue
|
if (doNotReportUninitializedVariable) continue
|
||||||
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: continue
|
val symbol = node.fir.calleeReference.toResolvedPropertySymbol() ?: continue
|
||||||
if (!symbol.isLateInit && node.fir.hasCorrectReceiver() && symbol in properties &&
|
if (!symbol.isLateInit && !symbol.isExternal && node.fir.hasCorrectReceiver() && symbol in properties &&
|
||||||
getValue(node).values.any { it[symbol]?.isDefinitelyVisited() != true }
|
getValue(node).values.any { it[symbol]?.isDefinitelyVisited() != true }
|
||||||
) {
|
) {
|
||||||
reporter.reportOn(node.fir.source, FirErrors.UNINITIALIZED_VARIABLE, symbol, context)
|
reporter.reportOn(node.fir.source, FirErrors.UNINITIALIZED_VARIABLE, symbol, context)
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,6 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
|||||||
FirPropertyFromParameterChecker,
|
FirPropertyFromParameterChecker,
|
||||||
FirLocalVariableTypeParametersSyntaxChecker,
|
FirLocalVariableTypeParametersSyntaxChecker,
|
||||||
FirDelegateUsesExtensionPropertyTypeParameterChecker,
|
FirDelegateUsesExtensionPropertyTypeParameterChecker,
|
||||||
FirTopLevelPropertiesChecker,
|
|
||||||
FirLocalExtensionPropertyChecker,
|
FirLocalExtensionPropertyChecker,
|
||||||
ContractSyntaxV2PropertyChecker,
|
ContractSyntaxV2PropertyChecker,
|
||||||
FirVolatileAnnotationChecker,
|
FirVolatileAnnotationChecker,
|
||||||
@@ -144,6 +143,7 @@ object CommonDeclarationCheckers : DeclarationCheckers() {
|
|||||||
get() = setOf(
|
get() = setOf(
|
||||||
FirImportsChecker,
|
FirImportsChecker,
|
||||||
FirUnresolvedInMiddleOfImportChecker,
|
FirUnresolvedInMiddleOfImportChecker,
|
||||||
|
FirTopLevelPropertiesChecker,
|
||||||
)
|
)
|
||||||
|
|
||||||
override val controlFlowAnalyserCheckers: Set<FirControlFlowChecker>
|
override val controlFlowAnalyserCheckers: Set<FirControlFlowChecker>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.fir.expressions.impl.FirEmptyExpressionBlock
|
|||||||
import org.jetbrains.kotlin.fir.references.FirSuperReference
|
import org.jetbrains.kotlin.fir.references.FirSuperReference
|
||||||
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
||||||
import org.jetbrains.kotlin.fir.resolve.*
|
import org.jetbrains.kotlin.fir.resolve.*
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.providers.firProvider
|
||||||
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
|
||||||
import org.jetbrains.kotlin.fir.scopes.*
|
import org.jetbrains.kotlin.fir.scopes.*
|
||||||
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
|
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
|
||||||
@@ -151,6 +152,14 @@ fun FirBasedSymbol<*>.getContainingClassSymbol(session: FirSession): FirClassLik
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the containing class or file if the callable is top-level.
|
||||||
|
*/
|
||||||
|
fun FirCallableSymbol<*>.getContainingSymbol(session: FirSession): FirBasedSymbol<*>? {
|
||||||
|
return getContainingClassSymbol(session)
|
||||||
|
?: session.firProvider.getFirCallableContainerFile(this)?.symbol
|
||||||
|
}
|
||||||
|
|
||||||
fun FirDeclaration.getContainingClassSymbol(session: FirSession) = symbol.getContainingClassSymbol(session)
|
fun FirDeclaration.getContainingClassSymbol(session: FirSession) = symbol.getContainingClassSymbol(session)
|
||||||
|
|
||||||
fun FirClassLikeSymbol<*>.outerClassSymbol(context: CheckerContext): FirClassLikeSymbol<*>? {
|
fun FirClassLikeSymbol<*>.outerClassSymbol(context: CheckerContext): FirClassLikeSymbol<*>? {
|
||||||
|
|||||||
+24
-22
@@ -55,31 +55,33 @@ object FirMemberPropertiesChecker : FirClassChecker() {
|
|||||||
data.checkPropertyAccesses(isForClassInitialization = true, context, reporter)
|
data.checkPropertyAccesses(isForClassInitialization = true, context, reporter)
|
||||||
return data.getValue(graph.exitNode)[NormalPath]
|
return data.getValue(graph.exitNode)[NormalPath]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkProperty(
|
internal fun checkProperty(
|
||||||
containingDeclaration: FirClass,
|
containingDeclaration: FirClass?,
|
||||||
property: FirProperty,
|
property: FirProperty,
|
||||||
isDefinitelyAssignedInConstructor: Boolean,
|
isDefinitelyAssigned: Boolean,
|
||||||
context: CheckerContext,
|
context: CheckerContext,
|
||||||
reporter: DiagnosticReporter,
|
reporter: DiagnosticReporter,
|
||||||
reachable: Boolean
|
reachable: Boolean,
|
||||||
) {
|
) {
|
||||||
val source = property.source ?: return
|
val source = property.source ?: return
|
||||||
if (source.kind is KtFakeSourceElementKind) return
|
if (source.kind is KtFakeSourceElementKind) return
|
||||||
// If multiple (potentially conflicting) modality modifiers are specified, not all modifiers are recorded at `status`.
|
// If multiple (potentially conflicting) modality modifiers are specified, not all modifiers are recorded at `status`.
|
||||||
// So, our source of truth should be the full modifier list retrieved from the source.
|
// So, our source of truth should be the full modifier list retrieved from the source.
|
||||||
val modifierList = property.source.getModifierList()
|
val modifierList = property.source.getModifierList()
|
||||||
|
|
||||||
checkPropertyInitializer(
|
checkPropertyInitializer(
|
||||||
containingDeclaration,
|
containingDeclaration,
|
||||||
property,
|
property,
|
||||||
modifierList,
|
modifierList,
|
||||||
isDefinitelyAssignedInConstructor,
|
isDefinitelyAssigned,
|
||||||
reporter,
|
reporter,
|
||||||
context,
|
context,
|
||||||
reachable
|
reachable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (containingDeclaration != null) {
|
||||||
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
|
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
|
||||||
val isAbstract = property.isAbstract || hasAbstractModifier
|
val isAbstract = property.isAbstract || hasAbstractModifier
|
||||||
if (containingDeclaration.isInterface &&
|
if (containingDeclaration.isInterface &&
|
||||||
|
|||||||
+48
-27
@@ -5,46 +5,67 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||||
|
|
||||||
import org.jetbrains.kotlin.KtFakeSourceElementKind
|
|
||||||
import org.jetbrains.kotlin.KtSourceElement
|
import org.jetbrains.kotlin.KtSourceElement
|
||||||
import org.jetbrains.kotlin.config.LanguageFeature
|
import org.jetbrains.kotlin.config.LanguageFeature
|
||||||
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
import org.jetbrains.kotlin.config.LanguageVersionSettings
|
||||||
|
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||||
import org.jetbrains.kotlin.descriptors.Modality
|
import org.jetbrains.kotlin.descriptors.Modality
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory0
|
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory0
|
||||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.checkPropertyAccesses
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.requiresInitialization
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfo
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.cfa.util.PropertyInitializationInfoData
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifierList
|
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifierList
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
|
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeLocalVariableNoTypeOrInitializer
|
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeLocalVariableNoTypeOrInitializer
|
||||||
|
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
|
||||||
// See old FE's [DeclarationsChecker]
|
// See old FE's [DeclarationsChecker]
|
||||||
object FirTopLevelPropertiesChecker : FirPropertyChecker() {
|
object FirTopLevelPropertiesChecker : FirFileChecker() {
|
||||||
override fun check(declaration: FirProperty, context: CheckerContext, reporter: DiagnosticReporter) {
|
override fun check(declaration: FirFile, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||||
// Only report on top level callable declarations
|
val info = declaration.collectionInitializationInfo(context, reporter)
|
||||||
if (context.containingDeclarations.size > 1) return
|
for (innerDeclaration in declaration.declarations) {
|
||||||
|
if (innerDeclaration is FirProperty) {
|
||||||
|
val symbol = innerDeclaration.symbol
|
||||||
|
val isDefinitelyAssigned = info?.get(symbol)?.isDefinitelyVisited() == true
|
||||||
|
checkProperty(containingDeclaration = null, innerDeclaration, isDefinitelyAssigned, context, reporter, reachable = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val source = declaration.source ?: return
|
private fun FirFile.collectionInitializationInfo(
|
||||||
if (source.kind is KtFakeSourceElementKind) return
|
context: CheckerContext,
|
||||||
// If multiple (potentially conflicting) modality modifiers are specified, not all modifiers are recorded at `status`.
|
reporter: DiagnosticReporter,
|
||||||
// So, our source of truth should be the full modifier list retrieved from the source.
|
): PropertyInitializationInfo? {
|
||||||
val modifierList = source.getModifierList()
|
val graph = (this as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph ?: return null
|
||||||
|
|
||||||
checkPropertyInitializer(
|
// Scripts are nested as a single declaration under FirFiles and contain their own statements. To properly check all "top-level"
|
||||||
containingClass = null,
|
// properties, script statements need to be unwrapped.
|
||||||
declaration,
|
val topLevelProperties = when (val script = declarations.singleOrNull()) {
|
||||||
modifierList,
|
is FirScript -> script.statements.filterIsInstance<FirProperty>()
|
||||||
isDefinitelyAssignedInConstructor = false, // Only member properties can be assigned in constructors
|
else -> declarations.filterIsInstance<FirProperty>()
|
||||||
reporter,
|
}
|
||||||
context
|
|
||||||
)
|
val propertySymbols = topLevelProperties.mapNotNullTo(mutableSetOf()) { declaration ->
|
||||||
|
(declaration.symbol as? FirPropertySymbol)?.takeIf { it.requiresInitialization(false) }
|
||||||
|
}
|
||||||
|
if (propertySymbols.isEmpty()) return null
|
||||||
|
|
||||||
|
// TODO, KT-59803: merge with `FirPropertyInitializationAnalyzer` for fewer passes.
|
||||||
|
val data = PropertyInitializationInfoData(propertySymbols, receiver = null, graph)
|
||||||
|
data.checkPropertyAccesses(isForClassInitialization = false, context, reporter)
|
||||||
|
return data.getValue(graph.exitNode)[NormalPath]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,10 +74,10 @@ internal fun checkPropertyInitializer(
|
|||||||
containingClass: FirClass?,
|
containingClass: FirClass?,
|
||||||
property: FirProperty,
|
property: FirProperty,
|
||||||
modifierList: FirModifierList?,
|
modifierList: FirModifierList?,
|
||||||
isDefinitelyAssignedInConstructor: Boolean,
|
isDefinitelyAssigned: Boolean,
|
||||||
reporter: DiagnosticReporter,
|
reporter: DiagnosticReporter,
|
||||||
context: CheckerContext,
|
context: CheckerContext,
|
||||||
reachable: Boolean = true
|
reachable: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val inInterface = containingClass?.isInterface == true
|
val inInterface = containingClass?.isInterface == true
|
||||||
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
|
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
|
||||||
@@ -118,7 +139,7 @@ internal fun checkPropertyInitializer(
|
|||||||
val propertySource = property.source ?: return
|
val propertySource = property.source ?: return
|
||||||
val isExternal = property.isEffectivelyExternal(containingClass, context)
|
val isExternal = property.isEffectivelyExternal(containingClass, context)
|
||||||
val isCorrectlyInitialized =
|
val isCorrectlyInitialized =
|
||||||
property.initializer != null || isDefinitelyAssignedInConstructor && !property.hasSetterAccessorImplementation &&
|
property.initializer != null || isDefinitelyAssigned && !property.hasSetterAccessorImplementation &&
|
||||||
property.getEffectiveModality(containingClass, context.languageVersionSettings) != Modality.OPEN
|
property.getEffectiveModality(containingClass, context.languageVersionSettings) != Modality.OPEN
|
||||||
if (
|
if (
|
||||||
backingFieldRequired &&
|
backingFieldRequired &&
|
||||||
@@ -135,14 +156,14 @@ internal fun checkPropertyInitializer(
|
|||||||
val isOpenValDeferredInitDeprecationWarning =
|
val isOpenValDeferredInitDeprecationWarning =
|
||||||
!context.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitOpenValDeferredInitialization) &&
|
!context.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitOpenValDeferredInitialization) &&
|
||||||
property.getEffectiveModality(containingClass, context.languageVersionSettings) == Modality.OPEN && property.isVal &&
|
property.getEffectiveModality(containingClass, context.languageVersionSettings) == Modality.OPEN && property.isVal &&
|
||||||
isDefinitelyAssignedInConstructor
|
isDefinitelyAssigned
|
||||||
// KT-61228
|
// KT-61228
|
||||||
val isFalsePositiveDeferredInitDeprecationWarning = isOpenValDeferredInitDeprecationWarning &&
|
val isFalsePositiveDeferredInitDeprecationWarning = isOpenValDeferredInitDeprecationWarning &&
|
||||||
property.getEffectiveModality(containingClass) == Modality.FINAL
|
property.getEffectiveModality(containingClass) == Modality.FINAL
|
||||||
if (!isFalsePositiveDeferredInitDeprecationWarning) {
|
if (!isFalsePositiveDeferredInitDeprecationWarning) {
|
||||||
reportMustBeInitialized(
|
reportMustBeInitialized(
|
||||||
property,
|
property,
|
||||||
isDefinitelyAssignedInConstructor,
|
isDefinitelyAssigned,
|
||||||
containingClass,
|
containingClass,
|
||||||
propertySource,
|
propertySource,
|
||||||
isOpenValDeferredInitDeprecationWarning,
|
isOpenValDeferredInitDeprecationWarning,
|
||||||
@@ -169,7 +190,7 @@ internal fun checkPropertyInitializer(
|
|||||||
|
|
||||||
private fun reportMustBeInitialized(
|
private fun reportMustBeInitialized(
|
||||||
property: FirProperty,
|
property: FirProperty,
|
||||||
isDefinitelyAssignedInConstructor: Boolean,
|
isDefinitelyAssigned: Boolean,
|
||||||
containingClass: FirClass?,
|
containingClass: FirClass?,
|
||||||
propertySource: KtSourceElement,
|
propertySource: KtSourceElement,
|
||||||
isOpenValDeferredInitDeprecationWarning: Boolean,
|
isOpenValDeferredInitDeprecationWarning: Boolean,
|
||||||
@@ -180,7 +201,7 @@ private fun reportMustBeInitialized(
|
|||||||
val suggestMakingItFinal = containingClass != null &&
|
val suggestMakingItFinal = containingClass != null &&
|
||||||
!property.hasSetterAccessorImplementation &&
|
!property.hasSetterAccessorImplementation &&
|
||||||
property.getEffectiveModality(containingClass, context.languageVersionSettings) != Modality.FINAL &&
|
property.getEffectiveModality(containingClass, context.languageVersionSettings) != Modality.FINAL &&
|
||||||
isDefinitelyAssignedInConstructor
|
isDefinitelyAssigned
|
||||||
val suggestMakingItAbstract = containingClass != null && !property.hasAnyAccessorImplementation
|
val suggestMakingItAbstract = containingClass != null && !property.hasAnyAccessorImplementation
|
||||||
if (isOpenValDeferredInitDeprecationWarning && !suggestMakingItFinal && suggestMakingItAbstract) {
|
if (isOpenValDeferredInitDeprecationWarning && !suggestMakingItFinal && suggestMakingItAbstract) {
|
||||||
error("Not reachable case. Every \"open val + deferred init\" case that could be made `abstract`, also could be made `final`")
|
error("Not reachable case. Every \"open val + deferred init\" case that could be made `abstract`, also could be made `final`")
|
||||||
@@ -189,7 +210,7 @@ private fun reportMustBeInitialized(
|
|||||||
!context.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitMissedMustBeInitializedWhenThereIsNoPrimaryConstructor) &&
|
!context.languageVersionSettings.supportsFeature(LanguageFeature.ProhibitMissedMustBeInitializedWhenThereIsNoPrimaryConstructor) &&
|
||||||
containingClass != null &&
|
containingClass != null &&
|
||||||
containingClass.primaryConstructorIfAny(context.session) == null &&
|
containingClass.primaryConstructorIfAny(context.session) == null &&
|
||||||
isDefinitelyAssignedInConstructor
|
isDefinitelyAssigned
|
||||||
val factory = when {
|
val factory = when {
|
||||||
suggestMakingItFinal && suggestMakingItAbstract -> FirErrors.MUST_BE_INITIALIZED_OR_FINAL_OR_ABSTRACT
|
suggestMakingItFinal && suggestMakingItAbstract -> FirErrors.MUST_BE_INITIALIZED_OR_FINAL_OR_ABSTRACT
|
||||||
suggestMakingItFinal -> FirErrors.MUST_BE_INITIALIZED_OR_BE_FINAL
|
suggestMakingItFinal -> FirErrors.MUST_BE_INITIALIZED_OR_BE_FINAL
|
||||||
|
|||||||
+22
-2
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.fir.analysis.cfa.evaluatedInPlace
|
|||||||
import org.jetbrains.kotlin.fir.analysis.cfa.requiresInitialization
|
import org.jetbrains.kotlin.fir.analysis.cfa.requiresInitialization
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
|
import org.jetbrains.kotlin.fir.analysis.checkers.context.findClosest
|
||||||
|
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingSymbol
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.fir.declarations.*
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
import org.jetbrains.kotlin.fir.declarations.utils.visibility
|
||||||
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.fir.diagnostics.DiagnosticKind
|
|||||||
import org.jetbrains.kotlin.fir.expressions.*
|
import org.jetbrains.kotlin.fir.expressions.*
|
||||||
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
|
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
|
||||||
import org.jetbrains.kotlin.fir.references.*
|
import org.jetbrains.kotlin.fir.references.*
|
||||||
|
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnosticWithCandidates
|
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeDiagnosticWithCandidates
|
||||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
|
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeUnresolvedNameError
|
||||||
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
|
||||||
@@ -137,8 +139,12 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
|
|||||||
val property = expression.calleeReference?.toResolvedPropertySymbol() ?: return
|
val property = expression.calleeReference?.toResolvedPropertySymbol() ?: return
|
||||||
if (property.isVar) return
|
if (property.isVar) return
|
||||||
// Assignments of uninitialized `val`s must be checked via CFG, since the first one is OK.
|
// Assignments of uninitialized `val`s must be checked via CFG, since the first one is OK.
|
||||||
// See `FirPropertyInitializationAnalyzer` for locals and `FirMemberPropertiesChecker` for backing fields in initializers.
|
// See `FirPropertyInitializationAnalyzer` for locals, `FirMemberPropertiesChecker` for backing fields in initializers,
|
||||||
if (property.isLocal && property.requiresInitialization(isForClassInitialization = false)) return
|
// and `FirTopLevelPropertiesChecker` for top-level properties.
|
||||||
|
if (
|
||||||
|
(property.isLocal || isInFileGraph(property, context))
|
||||||
|
&& property.requiresInitialization(isForClassInitialization = false)
|
||||||
|
) return
|
||||||
if (
|
if (
|
||||||
isInOwnersInitializer(expression.dispatchReceiver.unwrapSmartcastExpression(), context)
|
isInOwnersInitializer(expression.dispatchReceiver.unwrapSmartcastExpression(), context)
|
||||||
&& property.requiresInitialization(isForClassInitialization = true)
|
&& property.requiresInitialization(isForClassInitialization = true)
|
||||||
@@ -147,6 +153,20 @@ object FirReassignmentAndInvisibleSetterChecker : FirVariableAssignmentChecker()
|
|||||||
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
|
reporter.reportOn(expression.lValue.source, FirErrors.VAL_REASSIGNMENT, property, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isInFileGraph(property: FirPropertySymbol, context: CheckerContext): Boolean {
|
||||||
|
val declarations = context.containingDeclarations.dropWhile { it !is FirFile }
|
||||||
|
val file = declarations.firstOrNull() as? FirFile ?: return false
|
||||||
|
if (file.symbol != property.getContainingSymbol(context.session)) return false
|
||||||
|
|
||||||
|
// Starting with the CFG for the containing FirFile, check if all following declarations are contained as sub-CFGs.
|
||||||
|
// If there is a break in the chain, then the variable assignment is not part of the file CFG, and VAL_REASSIGNMENT should be
|
||||||
|
// reported by this checker.
|
||||||
|
val containingGraph = declarations
|
||||||
|
.map { (it as? FirControlFlowGraphOwner)?.controlFlowGraphReference?.controlFlowGraph }
|
||||||
|
.reduceOrNull { acc, graph -> graph?.takeIf { acc != null && it in acc.subGraphs } }
|
||||||
|
return containingGraph != null
|
||||||
|
}
|
||||||
|
|
||||||
private fun isInOwnersInitializer(receiver: FirExpression, context: CheckerContext): Boolean {
|
private fun isInOwnersInitializer(receiver: FirExpression, context: CheckerContext): Boolean {
|
||||||
val uninitializedThisSymbol = (receiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol ?: return false
|
val uninitializedThisSymbol = (receiver as? FirThisReceiverExpression)?.calleeReference?.boundSymbol ?: return false
|
||||||
val containingDeclarations = context.containingDeclarations
|
val containingDeclarations = context.containingDeclarations
|
||||||
|
|||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
|
||||||
|
// WITH_STDLIB
|
||||||
|
|
||||||
|
val a: Int
|
||||||
|
val b = 1.also { a = 2 }
|
||||||
|
<!MUST_BE_INITIALIZED!>val c: Int<!>
|
||||||
|
val d by lazy { <!CAPTURED_VAL_INITIALIZATION!>c<!> = 2; 1 }
|
||||||
|
val e: Int
|
||||||
|
get() {
|
||||||
|
<!VAL_REASSIGNMENT!>c<!> = 3
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
class Class {
|
||||||
|
val i: Int
|
||||||
|
val j = 1.also { i = 2 }
|
||||||
|
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val k: Int<!>
|
||||||
|
val l by lazy { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>k<!> = 2; 1 }
|
||||||
|
val n: Int
|
||||||
|
get() {
|
||||||
|
<!VAL_REASSIGNMENT!>k<!> = 3
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val x: Int
|
||||||
|
val y = 1.also { x = 2 }
|
||||||
|
val z: Int
|
||||||
|
val w by lazy { <!CAPTURED_VAL_INITIALIZATION!>z<!> = 2; 1 }
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
// !DIAGNOSTICS: -UNUSED_PARAMETER -UNUSED_VARIABLE -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE
|
||||||
|
// WITH_STDLIB
|
||||||
|
|
||||||
|
val a: Int
|
||||||
|
val b = 1.also { a = 2 }
|
||||||
|
<!MUST_BE_INITIALIZED!>val c: Int<!>
|
||||||
|
val d by lazy { c = 2; 1 }
|
||||||
|
val e: Int
|
||||||
|
get() {
|
||||||
|
<!VAL_REASSIGNMENT!>c<!> = 3
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
class Class {
|
||||||
|
val i: Int
|
||||||
|
val j = 1.also { i = 2 }
|
||||||
|
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val k: Int<!>
|
||||||
|
val l by lazy { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>k<!> = 2; 1 }
|
||||||
|
val n: Int
|
||||||
|
get() {
|
||||||
|
<!VAL_REASSIGNMENT!>k<!> = 3
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val x: Int
|
||||||
|
val y = 1.also { x = 2 }
|
||||||
|
val z: Int
|
||||||
|
val w by lazy { <!CAPTURED_VAL_INITIALIZATION!>z<!> = 2; 1 }
|
||||||
|
}
|
||||||
Generated
+6
@@ -3248,6 +3248,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
|
|||||||
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
runTest("compiler/testData/diagnostics/tests/backingField/SetterWithExplicitType.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestMetadata("TopLevelPropertyInitialization.kt")
|
||||||
|
public void testTopLevelPropertyInitialization() throws Exception {
|
||||||
|
runTest("compiler/testData/diagnostics/tests/backingField/TopLevelPropertyInitialization.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
@TestMetadata("ValDeferredInitInFinalClass.kt")
|
||||||
public void testValDeferredInitInFinalClass() throws Exception {
|
public void testValDeferredInitInFinalClass() throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user