FIR checker: report uninitialized member/extension properties
This commit is contained in:
committed by
Dmitriy Novozhilov
parent
e8028e7825
commit
e009b71f88
@@ -8,8 +8,8 @@ class KotlinType
|
||||
class KClassValue(value: Value) : ConstantValue<KClassValue.Value>(value) {
|
||||
sealed class Value {
|
||||
data class NormalClass(val value: ClassLiteralValue) : Value() {
|
||||
val classId: ClassId
|
||||
val arrayDimensions: Int
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val classId: ClassId<!>
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val arrayDimensions: Int<!>
|
||||
}
|
||||
|
||||
data class LocalClass(val type: KotlinType) : Value()
|
||||
|
||||
+1
-1
@@ -2,6 +2,6 @@ annotation class A() {
|
||||
<!ANNOTATION_CLASS_MEMBER!><!PRIMARY_CONSTRUCTOR_DELEGATION_CALL_EXPECTED!>constructor(s: Nothing?)<!> {}<!>
|
||||
<!ANNOTATION_CLASS_MEMBER!>init {}<!>
|
||||
<!ANNOTATION_CLASS_MEMBER!>fun foo() {}<!>
|
||||
<!ANNOTATION_CLASS_MEMBER!>val bar: Nothing?<!>
|
||||
<!ANNOTATION_CLASS_MEMBER, MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val bar: Nothing?<!>
|
||||
<!ANNOTATION_CLASS_MEMBER!>val baz get() = Unit<!>
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,6 +10,6 @@ class A {
|
||||
class B {
|
||||
val field: String = ""
|
||||
|
||||
val x: Int
|
||||
<!MUST_BE_INITIALIZED!>val x: Int<!>
|
||||
get() = field.<!UNRESOLVED_REFERENCE!>length<!> // should be an error
|
||||
}
|
||||
|
||||
@@ -15,5 +15,5 @@ class SomeClass : SomeInterface {
|
||||
get() = true
|
||||
set(value) {}
|
||||
|
||||
var fau: Double
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var fau: Double<!>
|
||||
}
|
||||
|
||||
+6
-6
@@ -14,10 +14,10 @@ class A {
|
||||
}
|
||||
|
||||
class Property {
|
||||
var var1: String
|
||||
var var2: String
|
||||
var var3: Int
|
||||
<!EXPOSED_PROPERTY_TYPE{LT}!>var <!EXPOSED_PROPERTY_TYPE{PSI}!>var4<!>: A.AInnerPrivate<!>
|
||||
var var5: A.AInnerPublic
|
||||
<!EXPOSED_PROPERTY_TYPE{LT}!>var <!EXPOSED_PROPERTY_TYPE{PSI}!>var6<!>: A.AInnerProtectedEnum<!>
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var var1: String<!>
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var var2: String<!>
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var var3: Int<!>
|
||||
<!EXPOSED_PROPERTY_TYPE{LT}, MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var <!EXPOSED_PROPERTY_TYPE{PSI}!>var4<!>: A.AInnerPrivate<!>
|
||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var var5: A.AInnerPublic<!>
|
||||
<!EXPOSED_PROPERTY_TYPE{LT}, MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>var <!EXPOSED_PROPERTY_TYPE{PSI}!>var6<!>: A.AInnerProtectedEnum<!>
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
private var Int.readOnlyWrapper: CharSequence? get() = null
|
||||
private var Int.mutableWrapper: CharSequence? get() = null
|
||||
<!MUST_BE_INITIALIZED!>private var Int.readOnlyWrapper: CharSequence?<!> get() = null
|
||||
<!MUST_BE_INITIALIZED!>private var Int.mutableWrapper: CharSequence?<!> get() = null
|
||||
|
||||
fun main(x: Int) {
|
||||
val x = if (x > 1) x::readOnlyWrapper else x::mutableWrapper
|
||||
|
||||
+4
@@ -319,6 +319,10 @@ object DIAGNOSTICS_LIST : DiagnosticList() {
|
||||
val PROPERTY_INITIALIZER_IN_INTERFACE by error<FirSourceElement, KtExpression>()
|
||||
val PROPERTY_WITH_NO_TYPE_NO_INITIALIZER by error<FirSourceElement, KtProperty>(PositioningStrategy.DECLARATION_SIGNATURE)
|
||||
|
||||
val MUST_BE_INITIALIZED by error<FirSourceElement, KtProperty>(PositioningStrategy.DECLARATION_SIGNATURE)
|
||||
val MUST_BE_INITIALIZED_OR_BE_ABSTRACT by error<FirSourceElement, KtProperty>(PositioningStrategy.DECLARATION_SIGNATURE)
|
||||
val EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT by error<FirSourceElement, KtProperty>(PositioningStrategy.DECLARATION_SIGNATURE)
|
||||
|
||||
val BACKING_FIELD_IN_INTERFACE by error<FirSourceElement, KtProperty>(PositioningStrategy.DECLARATION_SIGNATURE)
|
||||
val EXTENSION_PROPERTY_WITH_BACKING_FIELD by error<FirSourceElement, KtExpression>()
|
||||
val PROPERTY_INITIALIZER_NO_BACKING_FIELD by error<FirSourceElement, KtExpression>()
|
||||
|
||||
@@ -215,6 +215,9 @@ object FirErrors {
|
||||
val ABSTRACT_PROPERTY_WITH_INITIALIZER by error0<FirSourceElement, KtExpression>()
|
||||
val PROPERTY_INITIALIZER_IN_INTERFACE by error0<FirSourceElement, KtExpression>()
|
||||
val PROPERTY_WITH_NO_TYPE_NO_INITIALIZER by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
|
||||
val MUST_BE_INITIALIZED by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
|
||||
val MUST_BE_INITIALIZED_OR_BE_ABSTRACT by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
|
||||
val EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
|
||||
val BACKING_FIELD_IN_INTERFACE by error0<FirSourceElement, KtProperty>(SourceElementPositioningStrategies.DECLARATION_SIGNATURE)
|
||||
val EXTENSION_PROPERTY_WITH_BACKING_FIELD by error0<FirSourceElement, KtExpression>()
|
||||
val PROPERTY_INITIALIZER_NO_BACKING_FIELD by error0<FirSourceElement, KtExpression>()
|
||||
|
||||
@@ -22,13 +22,25 @@ abstract class EventOccurrencesRangeInfo<E : EventOccurrencesRangeInfo<E, K>, K
|
||||
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
|
||||
) : ControlFlowInfo<E, K, EventOccurrencesRange>(map) {
|
||||
|
||||
override fun merge(other: E): E {
|
||||
override fun merge(other: E): E =
|
||||
operation(other, EventOccurrencesRange::or)
|
||||
|
||||
fun plus(other: E): E =
|
||||
when {
|
||||
isEmpty() -> other
|
||||
other.isEmpty() ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
this as E
|
||||
else -> operation(other, EventOccurrencesRange::plus)
|
||||
}
|
||||
|
||||
private inline fun operation(other: E, op: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange): E {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
var result = this as E
|
||||
for (symbol in keys.union(other.keys)) {
|
||||
val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO
|
||||
val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO
|
||||
result = result.put(symbol, kind1 or kind2)
|
||||
result = result.put(symbol, op.invoke(kind1, kind2))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+50
-15
@@ -19,9 +19,45 @@ import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.types.FirImplicitTypeRef
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
internal fun isInsideExpectClass(containingClass: FirRegularClass, context: CheckerContext): Boolean =
|
||||
isInsideSpecificClass(containingClass, context) { klass -> klass.isExpect }
|
||||
|
||||
internal fun isInsideExternalClass(containingClass: FirRegularClass, context: CheckerContext): Boolean =
|
||||
isInsideSpecificClass(containingClass, context) { klass -> klass.isExternal }
|
||||
|
||||
// Note that the class that contains the currently visiting declaration will *not* be in the context's containing declarations *yet*.
|
||||
internal fun isInsideExpectClass(containingDeclaration: FirRegularClass, context: CheckerContext): Boolean =
|
||||
containingDeclaration.isExpect || context.containingDeclarations.asReversed().any { it is FirRegularClass && it.isExpect }
|
||||
private inline fun isInsideSpecificClass(
|
||||
containingClass: FirRegularClass,
|
||||
context: CheckerContext,
|
||||
specificStatus: (FirRegularClass) -> Boolean
|
||||
): Boolean =
|
||||
specificStatus.invoke(containingClass) ||
|
||||
context.containingDeclarations.asReversed().any { it is FirRegularClass && specificStatus.invoke(it) }
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyExpect(
|
||||
containingClass: FirRegularClass?,
|
||||
modifierList: FirModifierList? = null,
|
||||
context: CheckerContext,
|
||||
): Boolean {
|
||||
val isExpect = this.isExpect || modifierList?.modifiers?.any { it.token == KtTokens.EXPECT_KEYWORD } == true
|
||||
if (isExpect) return true
|
||||
|
||||
return containingClass != null && isInsideExpectClass(containingClass, context)
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyExternal(
|
||||
containingClass: FirRegularClass?,
|
||||
modifierList: FirModifierList? = null,
|
||||
context: CheckerContext,
|
||||
): Boolean {
|
||||
val isExternal = this.isExternal || modifierList?.modifiers?.any { it.token == KtTokens.EXTERNAL_KEYWORD } == true
|
||||
if (isExternal) return true
|
||||
|
||||
// NB: [MemberDescriptor.isEffectivelyExternal] checks property accessors for property and vice versa.
|
||||
// But, raw FIR creation already did such upward/downward propagation of modifiers.
|
||||
|
||||
return containingClass != null && isInsideExternalClass(containingClass, context)
|
||||
}
|
||||
|
||||
// TODO: check class too
|
||||
internal fun checkExpectDeclarationVisibilityAndBody(
|
||||
@@ -45,17 +81,19 @@ internal fun checkProperty(
|
||||
containingClass: FirRegularClass?,
|
||||
property: FirProperty,
|
||||
modifierList: FirModifierList?,
|
||||
isInitialized: Boolean,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext
|
||||
) {
|
||||
checkPropertyInitializer(containingClass, modifierList, property, reporter, context)
|
||||
checkPropertyInitializer(containingClass, property, modifierList, isInitialized, reporter, context)
|
||||
checkPropertyAccessors(property, reporter, context)
|
||||
}
|
||||
|
||||
private fun checkPropertyInitializer(
|
||||
containingClass: FirRegularClass?,
|
||||
modifierList: FirModifierList?,
|
||||
property: FirProperty,
|
||||
modifierList: FirModifierList?,
|
||||
isInitialized: Boolean,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext
|
||||
) {
|
||||
@@ -78,7 +116,7 @@ private fun checkPropertyInitializer(
|
||||
}
|
||||
}
|
||||
|
||||
val isExpect = property.isExpect || modifierList?.modifiers?.any { it.token == KtTokens.EXPECT_KEYWORD } == true
|
||||
val isExpect = property.isEffectivelyExpect(containingClass, modifierList, context)
|
||||
|
||||
when {
|
||||
property.initializer != null -> {
|
||||
@@ -112,18 +150,16 @@ private fun checkPropertyInitializer(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val isExternal = property.isExternal || modifierList?.modifiers?.any { it.token == KtTokens.EXTERNAL_KEYWORD } == true
|
||||
// TODO: need to analyze class anonymous initializer to see if the property is initialized there.
|
||||
val isUninitialized = false
|
||||
if (backingFieldRequired && !inInterface && !property.isLateInit && !isExpect && isUninitialized && !isExternal) {
|
||||
val isExternal = property.isEffectivelyExternal(containingClass, modifierList, context)
|
||||
if (backingFieldRequired && !inInterface && !property.isLateInit && !isExpect && !isInitialized && !isExternal) {
|
||||
property.source?.let {
|
||||
if (property.receiverTypeRef != null && !property.hasAccessorImplementation) {
|
||||
// reporter.reportOn(it, FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, context)
|
||||
} else {
|
||||
if (containingClass != null || property.hasAccessorImplementation) {
|
||||
// reporter.reportOn(it, FirErrors.MUST_BE_INITIALIZED, context)
|
||||
reporter.reportOn(it, FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, context)
|
||||
} else { // TODO: can be suppressed not to report diagnostics about no body
|
||||
if (containingClass == null || property.hasAccessorImplementation) {
|
||||
reporter.reportOn(it, FirErrors.MUST_BE_INITIALIZED, context)
|
||||
} else {
|
||||
// reporter.reportOn(it, FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT, context)
|
||||
reporter.reportOn(it, FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,5 +184,4 @@ private val FirProperty.hasAccessorImplementation: Boolean
|
||||
get() = (getter !is FirDefaultPropertyAccessor && getter?.hasBody == true) ||
|
||||
(setter !is FirDefaultPropertyAccessor && setter?.hasBody == true)
|
||||
|
||||
|
||||
internal val FirClass<*>.canHaveOpenMembers: Boolean get() = modality() != Modality.FINAL || classKind == ClassKind.ENUM_CLASS
|
||||
|
||||
+112
-4
@@ -5,9 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange
|
||||
import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfo
|
||||
import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfoCollector
|
||||
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
|
||||
@@ -15,22 +19,126 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.ControlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath
|
||||
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertyAccessorSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
// See old FE's [DeclarationsChecker]
|
||||
object FirMemberPropertyChecker : FirRegularClassChecker() {
|
||||
override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
for (member in declaration.declarations) {
|
||||
if (member is FirProperty) {
|
||||
checkProperty(declaration, member, context, reporter)
|
||||
val memberPropertySymbols = declaration.declarations.filterIsInstance<FirProperty>().map { it.symbol }.toSet()
|
||||
val initializedInConstructor =
|
||||
mutableMapOf<FirPropertySymbol, EventOccurrencesRange>().withDefault { EventOccurrencesRange.ZERO }
|
||||
val initializedInInitOrOtherProperty =
|
||||
mutableMapOf<FirPropertySymbol, EventOccurrencesRange>().withDefault { EventOccurrencesRange.ZERO }
|
||||
|
||||
// If all member properties have its own initializer, we don't need to collect property initialization info at all.
|
||||
if (memberPropertySymbols.any { it.fir.initializer == null }) {
|
||||
collectPropertyInitialization(declaration, memberPropertySymbols, initializedInConstructor, initializedInInitOrOtherProperty)
|
||||
}
|
||||
|
||||
for (propertySymbol in memberPropertySymbols) {
|
||||
val property = propertySymbol.fir
|
||||
val isInitialized =
|
||||
property.initializer != null ||
|
||||
initializedInConstructor.getValue(propertySymbol).isDefinitelyVisited() ||
|
||||
initializedInInitOrOtherProperty.getValue(propertySymbol).isDefinitelyVisited()
|
||||
checkProperty(declaration, property, isInitialized, context, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectPropertyInitialization(
|
||||
klass: FirRegularClass,
|
||||
memberPropertySymbols: Set<FirPropertySymbol>,
|
||||
initializedInConstructor: MutableMap<FirPropertySymbol, EventOccurrencesRange>,
|
||||
initializedInInitOrOtherProperty: MutableMap<FirPropertySymbol, EventOccurrencesRange>
|
||||
) {
|
||||
// A property is known to be initialized only if it is initialized
|
||||
// 1) with its own initializing expression;
|
||||
// 2) at every class constructor;
|
||||
// 3) at any of class's anonymous initializers; or
|
||||
// 4) at other property's initializing expression
|
||||
|
||||
// 2) Property can be initialized at constructors. Since it's unknown what constructor will be used, the property can be determined
|
||||
// as initialized only if it is initialized at every constructor. We should consider a delegated constructor, e.g.,
|
||||
// constructor() { x = ... }
|
||||
// constructor(...): this() { ... } // x will be initialized via this() delegation
|
||||
// We need to topologically sort constructors so that we can process delegated ones before the use sites.
|
||||
|
||||
// 3) Property can be initialized at any of class's anonymous initializers (all of initializers will be executed), e.g.,
|
||||
// init { x = ... }
|
||||
// ...
|
||||
// init { y = ... }
|
||||
|
||||
// 4) Property can be initialized at other property's initializing expression too, e.g.,
|
||||
// val initX = inlineMe { x = ... } // where inlineMe returns the value of the last expression of the lambda
|
||||
|
||||
// To handle the delegated constructor call, we need a cache from constructor to (analyzed) property init info.
|
||||
val constructorToData =
|
||||
mutableMapOf<FirConstructor, PropertyInitializationInfo>().withDefault { PropertyInitializationInfo.EMPTY }
|
||||
|
||||
fun collectInfoFromGraph(
|
||||
graph: ControlFlowGraph,
|
||||
map: MutableMap<FirPropertySymbol, EventOccurrencesRange>,
|
||||
acc: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange,
|
||||
delegatedConstructor: FirConstructor? = null,
|
||||
) {
|
||||
val delegatedInfo = delegatedConstructor?.let { constructorToData.getValue(it) } ?: PropertyInitializationInfo.EMPTY
|
||||
|
||||
val data = PropertyInitializationInfoCollector(memberPropertySymbols).getData(graph)
|
||||
val infoAtExitNode = data[graph.exitNode]?.get(NormalPath) ?: PropertyInitializationInfo.EMPTY
|
||||
|
||||
// NB: it's not [merge], which is conducted at merging points, such as loop condition or when conditions.
|
||||
// Rather, delegated constructor call is the predecessor of the current constructor call, so we should accumulate.
|
||||
val info = delegatedInfo.plus(infoAtExitNode)
|
||||
|
||||
if (graph.declaration is FirConstructor) {
|
||||
constructorToData.putIfAbsent(graph.declaration as FirConstructor, info)
|
||||
}
|
||||
|
||||
for (propertySymbol in memberPropertySymbols) {
|
||||
if (map.containsKey(propertySymbol)) {
|
||||
// Accumulation:
|
||||
// range join for class constructors, range plus for class's anonymous initializers and property initializations
|
||||
map[propertySymbol] = acc.invoke(map[propertySymbol]!!, info[propertySymbol] ?: EventOccurrencesRange.ZERO)
|
||||
} else {
|
||||
// Initial assignment.
|
||||
// NB: we should not use `acc` here to not weaken ranges. For example, if we visit one and only constructor where
|
||||
// a property of interest is correctly initialized (a.k.a. [EXACTLY_ONCE]), and if `acc` is ...Range::or,
|
||||
// merging with the default [ZERO] makes the result [AT_MOST_ONCE], which will be regarded as uninitialized.
|
||||
map[propertySymbol] = info[propertySymbol] ?: EventOccurrencesRange.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val constructorGraphs = klass.constructorsSortedByDelegation.mapNotNull { it.controlFlowGraphReference?.controlFlowGraph }
|
||||
for (graph in constructorGraphs) {
|
||||
collectInfoFromGraph(
|
||||
graph,
|
||||
initializedInConstructor,
|
||||
EventOccurrencesRange::or,
|
||||
(graph.declaration as? FirConstructor)?.delegatedThisConstructor
|
||||
)
|
||||
}
|
||||
|
||||
val initGraphs = klass.anonymousInitializers.mapNotNull { it.controlFlowGraphReference?.controlFlowGraph }
|
||||
for (graph in initGraphs) {
|
||||
collectInfoFromGraph(graph, initializedInInitOrOtherProperty, EventOccurrencesRange::plus)
|
||||
}
|
||||
|
||||
val propertyInitGraphs = memberPropertySymbols.mapNotNull { it.fir.controlFlowGraphReference?.controlFlowGraph }
|
||||
for (graph in propertyInitGraphs) {
|
||||
collectInfoFromGraph(graph, initializedInInitOrOtherProperty, EventOccurrencesRange::plus)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkProperty(
|
||||
containingDeclaration: FirRegularClass,
|
||||
property: FirProperty,
|
||||
isInitialized: Boolean,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
@@ -40,7 +148,7 @@ object FirMemberPropertyChecker : FirRegularClassChecker() {
|
||||
// So, our source of truth should be the full modifier list retrieved from the source.
|
||||
val modifierList = with(FirModifierList) { property.source.getModifierList() }
|
||||
|
||||
checkProperty(containingDeclaration, property, modifierList, reporter, context)
|
||||
checkProperty(containingDeclaration, property, modifierList, isInitialized, reporter, context)
|
||||
checkExpectDeclarationVisibilityAndBody(property, source, modifierList, reporter, context)
|
||||
|
||||
val hasAbstractModifier = modifierList?.modifiers?.any { it.token == KtTokens.ABSTRACT_KEYWORD } == true
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ object FirTopLevelPropertyChecker : FirFileChecker() {
|
||||
// So, our source of truth should be the full modifier list retrieved from the source.
|
||||
val modifierList = with(FirModifierList) { source.getModifierList() }
|
||||
|
||||
checkProperty(null, property, modifierList, reporter, context)
|
||||
checkProperty(null, property, modifierList, property.initializer != null, reporter, context)
|
||||
checkExpectDeclarationVisibilityAndBody(property, source, modifierList, reporter, context)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -75,6 +75,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_SUPER_CLA
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_SUPER_INTERFACE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_TYPEALIAS_EXPANDED_TYPE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXPOSED_TYPE_PARAMETER_BOUND
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.EXTENSION_PROPERTY_WITH_BACKING_FIELD
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FORBIDDEN_VARARG_PARAMETER_TYPE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.FUNCTION_DECLARATION_WITH_NO_NAME
|
||||
@@ -101,6 +102,8 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MANY_COMPANION_OB
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MISSING_VAL_ON_ANNOTATION_PARAMETER
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NESTED_CLASS_NOT_ALLOWED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MULTIPLE_VARARG_PARAMETERS
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NONE_APPLICABLE
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_ABSTRACT_FUNCTION_WITH_NO_BODY
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.NON_FINAL_MEMBER_IN_FINAL_CLASS
|
||||
@@ -471,16 +474,19 @@ class FirDefaultErrorMessages : DefaultErrorMessages.Extension {
|
||||
map.put(PRIVATE_PROPERTY_IN_INTERFACE, "Abstract property in an interface cannot be private")
|
||||
|
||||
map.put(ABSTRACT_PROPERTY_WITH_INITIALIZER, "Property with initializer cannot be abstract")
|
||||
|
||||
map.put(EXTENSION_PROPERTY_WITH_BACKING_FIELD, "Extension property cannot be initialized because it has no backing field")
|
||||
map.put(PROPERTY_INITIALIZER_NO_BACKING_FIELD, "Initializer is not allowed here because this property has no backing field")
|
||||
|
||||
map.put(PROPERTY_INITIALIZER_IN_INTERFACE, "Property initializers are not allowed in interfaces")
|
||||
map.put(
|
||||
PROPERTY_WITH_NO_TYPE_NO_INITIALIZER,
|
||||
"This property must either have a type annotation, be initialized or be delegated"
|
||||
)
|
||||
|
||||
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")
|
||||
|
||||
map.put(BACKING_FIELD_IN_INTERFACE, "Property in an interface cannot have a backing field")
|
||||
map.put(EXTENSION_PROPERTY_WITH_BACKING_FIELD, "Extension property cannot be initialized because it has no backing field")
|
||||
map.put(PROPERTY_INITIALIZER_NO_BACKING_FIELD, "Initializer is not allowed here because this property has no backing field")
|
||||
|
||||
map.put(ABSTRACT_DELEGATED_PROPERTY, "Delegated property cannot be abstract")
|
||||
map.put(DELEGATED_PROPERTY_IN_INTERFACE, "Delegated properties are not allowed in interfaces")
|
||||
|
||||
@@ -13,6 +13,7 @@ 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.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirAnonymousObjectSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
@@ -117,8 +118,34 @@ val FirClassSymbol<*>.superConeTypes
|
||||
|
||||
val FirClass<*>.superConeTypes get() = superTypeRefs.mapNotNull { it.coneTypeSafe<ConeClassLikeType>() }
|
||||
|
||||
val FirClass<*>.anonymousInitializers: List<FirAnonymousInitializer>
|
||||
get() = declarations.filterIsInstance<FirAnonymousInitializer>()
|
||||
|
||||
val FirClass<*>.constructors: List<FirConstructor>
|
||||
get() = declarations.filterIsInstance<FirConstructor>()
|
||||
|
||||
val FirConstructor.delegatedThisConstructor: FirConstructor?
|
||||
get() = delegatedConstructor?.takeIf { it.isThis }
|
||||
?.let { (it.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol?.fir as? FirConstructor }
|
||||
|
||||
private object ConstructorDelegationComparator : Comparator<FirConstructor> {
|
||||
override fun compare(p0: FirConstructor?, p1: FirConstructor?): Int {
|
||||
if (p0 == null && p1 == null) return 0
|
||||
if (p0 == null) return -1
|
||||
if (p1 == null) return 1
|
||||
if (p0.delegatedThisConstructor == p1) return 1
|
||||
if (p1.delegatedThisConstructor == p0) return -1
|
||||
// If neither is a delegation to each other, the order doesn't matter.
|
||||
// Here we return 0 to preserve the original order.
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
val FirClass<*>.constructorsSortedByDelegation: List<FirConstructor>
|
||||
get() = constructors.sortedWith(ConstructorDelegationComparator)
|
||||
|
||||
fun FirClass<*>.getPrimaryConstructorIfAny(): FirConstructor? =
|
||||
declarations.filterIsInstance<FirConstructor>().firstOrNull()?.takeIf { it.isPrimary }
|
||||
constructors.firstOrNull()?.takeIf { it.isPrimary }
|
||||
|
||||
fun FirRegularClass.collectEnumEntries(): Collection<FirEnumEntry> {
|
||||
assert(classKind == ClassKind.ENUM_CLASS)
|
||||
|
||||
Reference in New Issue
Block a user