Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.KtFakeSourceElementKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.isEnumEntryInitializer
import org.jetbrains.kotlin.fir.analysis.checkers.getContainingClassSymbol
import org.jetbrains.kotlin.fir.analysis.checkers.outerClassSymbol
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction
import org.jetbrains.kotlin.fir.declarations.FirResolvePhase
import org.jetbrains.kotlin.fir.declarations.utils.isCompanion
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.resolvedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.ensureResolved
import org.jetbrains.kotlin.fir.symbols.impl.FirEnumEntrySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
object FirUninitializedEnumChecker : FirQualifiedAccessExpressionChecker() {
// Initialization order: member property initializers, enum entries, companion object (including members in it).
//
// When JVM loads a class, the corresponding class initializer, a.k.a. , is executed first.
// Kotlin (and Java as well) converts enum entries as static final field, which is initialized in that :
// enum class E(...) {
// E1, E2, ...
// }
// ~>
// class E {
// final static E1, E2, ...
// static { //
// E1 = new E(...)
// ...
// }
// }
//
// Note that, when initializing enum entries, now we call the enum class's constructor, a.k.a. , to initialize non-final
// instance members. Therefore, if there is a member property in the enum class, and if that member has an access to enum entries,
// that is an illegal access since enum entries are not yet initialized:
// enum class E(...) {
// E1, E2, ...
// val m1 = E1
// }
// ~>
// class E {
// E m1 ...
// E(...) { //
// m1 = E1
// }
// final static E1, E2, ...
// static { //
// E1 = new E(...)
// ...
// }
// }
//
// A companion object is desugared to a static final singleton, and initialized in too. However, enum lowering goes first,
// or in other words, companion object lowering goes last. Thus, any other things initialized in , including enum entries,
// should not have access to companion object and members in it.
//
// See related discussions:
// https://youtrack.jetbrains.com/issue/KT-6054
// https://youtrack.jetbrains.com/issue/KT-11769
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
val source = expression.source ?: return
if (source.kind is KtFakeSourceElementKind) return
val calleeSymbol = expression.calleeReference.resolvedSymbol ?: return
val calleeContainingClassSymbol = calleeSymbol.getContainingClassSymbol(context.session) as? FirRegularClassSymbol ?: return
// We're looking for members/entries/companion object in an enum class or members in companion object of an enum class.
val calleeIsInsideEnum = calleeContainingClassSymbol.isEnumClass
val calleeIsInsideEnumCompanion =
calleeContainingClassSymbol.isCompanion && (calleeContainingClassSymbol.outerClassSymbol(context) as? FirRegularClassSymbol)?.isEnumClass == true
if (!calleeIsInsideEnum && !calleeIsInsideEnumCompanion) return
val enumClassSymbol =
if (calleeIsInsideEnum) calleeContainingClassSymbol
else calleeContainingClassSymbol.outerClassSymbol(context) as? FirRegularClassSymbol ?: return
// An accessed context within the enum class of interest. We should look up until either enum members or enum entries are found,
// not just last containing declaration. For example,
// enum class Fruit(...) {
// APPLE(...);
// companion object {
// val common = ...
// }
// val score = ... common
// val score2 = { ... common }()
// }
// companion object is not initialized for both member properties. The former has the property itself as the last containing
// declaration, whereas the latter has an anonymous function instead. For both cases, we're looking for member properties as an
// accessed context.
val accessedContext = context.containingDeclarations.lastOrNull {
// To not raise an error for an access from another enum class, e.g.,
// enum class EnumCompanion4(...) {
// INSTANCE(EnumCompanion2.foo())
// }
// find an accessed context within the same enum class.
it.getContainingClassSymbol(context.session) == enumClassSymbol
}?.symbol ?: return
val declarationSymbols = enumClassSymbol.declarationSymbols
val enumMemberProperties = declarationSymbols.filterIsInstance()
val enumEntries = declarationSymbols.filterIsInstance()
// When checking enum member properties, accesses to enum entries in lazy delegation is legitimate, e.g.,
// enum JvmTarget(...) {
// JVM_1_6, ...
// val bytecodeVersion: ... by lazy {
// when (this) {
// JVM_1_6 -> ...
// }
// }
if (accessedContext in enumMemberProperties) {
val lazyDelegation = (accessedContext as FirPropertySymbol).lazyDelegation
if (lazyDelegation != null && lazyDelegation == context.containingDeclarations.lastOrNull()) {
return
}
}
// When checking enum entries, only entry initializer matters. For example,
// enum class EnumCompanion(...) {
// ANOTHER {
// override fun bar() = foo() // At this point, companion object is initialized
// }
// abstract bar(): ...
// companion object {
// fun foo() = ...
// }
// }
if (accessedContext in enumEntries && context.containingDeclarations.lastOrNull()?.isEnumEntryInitializer != true) {
return
}
// Members inside the companion object of an enum class
if (calleeContainingClassSymbol == enumClassSymbol.companionObjectSymbol) {
// Uninitialized from the point of view of members or enum entries of that enum class
if (accessedContext in enumMemberProperties || accessedContext in enumEntries) {
if (calleeSymbol is FirPropertySymbol) {
// From KT-11769
// enum class Fruit(...) {
// APPLE(...);
// companion object {
// val common = ...
// }
// val score = ... common
// }
reporter.reportOn(source, FirErrors.UNINITIALIZED_VARIABLE, calleeSymbol, context)
} else {
// enum class EnumCompanion2(...) {
// INSTANCE(foo())
// companion object {
// fun foo() = ...
// }
// }
// Companion.foo() v.s. foo()
if ((expression.explicitReceiver as? FirResolvedQualifier)?.symbol == enumClassSymbol.companionObjectSymbol) {
reporter.reportOn(
expression.explicitReceiver!!.source,
FirErrors.UNINITIALIZED_ENUM_COMPANION,
enumClassSymbol,
context
)
} else {
reporter.reportOn(
expression.calleeReference.source,
FirErrors.UNINITIALIZED_ENUM_COMPANION,
enumClassSymbol,
context
)
}
}
}
}
// The enum entries of an enum class
if (calleeSymbol in enumEntries) {
val calleeEnumEntry = calleeSymbol as FirEnumEntrySymbol
// Uninitialized from the point of view of members of that enum class
if (accessedContext in enumMemberProperties) {
// From KT-6054
// enum class MyEnum {
// A, B;
// val x = when(this) {
// A -> ...
// B -> ...
// }
// }
reporter.reportOn(source, FirErrors.UNINITIALIZED_ENUM_ENTRY, calleeEnumEntry, context)
}
// enum class A(...) {
// A1(A2),
// A2(...),
// A3(A3)
// }
if (accessedContext in enumEntries) {
// Technically, this is equal to `enumEntries.indexOf(accessedContext) <= enumEntries.indexOf(calleeDeclaration)`.
// Instead of double `indexOf`, we can iterate entries just once until either one appears.
var precedingEntry: FirEnumEntrySymbol? = null
enumEntries.forEach {
if (precedingEntry != null) return@forEach
if (it == calleeEnumEntry || it == accessedContext) {
precedingEntry = it
}
}
if (precedingEntry == accessedContext) {
reporter.reportOn(source, FirErrors.UNINITIALIZED_ENUM_ENTRY, calleeEnumEntry, context)
}
}
}
}
private val FirPropertySymbol.lazyDelegation: FirAnonymousFunction?
get() {
ensureResolved(FirResolvePhase.BODY_RESOLVE)
@OptIn(SymbolInternals::class)
val property = this.fir
if (property.delegate == null || property.delegate !is FirFunctionCall) return null
val delegateCall = property.delegate as FirFunctionCall
val calleeSymbol =
delegateCall.calleeReference.resolvedSymbol as? FirNamedFunctionSymbol ?: return null
if (calleeSymbol.callableId.asSingleFqName().asString() != "kotlin.lazy") return null
val lazyCallArgument = delegateCall.argumentList.arguments.singleOrNull() as? FirLambdaArgumentExpression ?: return null
return (lazyCallArgument.expression as? FirAnonymousFunctionExpression)?.anonymousFunction
}
}