[FIR] Cleanup and reorganize utils in :compiler:fir:checkers
This commit is contained in:
committed by
teamcityserver
parent
9d09b9605f
commit
0f6b6dbca3
@@ -209,23 +209,7 @@ fun FirNamedFunctionSymbol.overriddenFunctions(
|
||||
return overriddenFunctions
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility by given KtModifierList
|
||||
*/
|
||||
fun KtModifierList?.getVisibility() = this?.visibilityModifierType()?.toVisibilityOrNull()
|
||||
|
||||
/**
|
||||
* Returns Visibility by token or null
|
||||
*/
|
||||
fun KtModifierKeywordToken.toVisibilityOrNull(): Visibility? {
|
||||
return when (this) {
|
||||
KtTokens.PUBLIC_KEYWORD -> Visibilities.Public
|
||||
KtTokens.PRIVATE_KEYWORD -> Visibilities.Private
|
||||
KtTokens.PROTECTED_KEYWORD -> Visibilities.Protected
|
||||
KtTokens.INTERNAL_KEYWORD -> Visibilities.Internal
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the modality of the class
|
||||
@@ -774,4 +758,4 @@ fun getActualTargetList(annotated: FirDeclaration): AnnotationTargetList {
|
||||
}
|
||||
}
|
||||
|
||||
private typealias TargetLists = AnnotationTargetLists
|
||||
private typealias TargetLists = AnnotationTargetLists
|
||||
|
||||
+1
-3
@@ -41,8 +41,6 @@ private data class FirSinceKotlinValue(
|
||||
val wasExperimentalMarkerClasses: List<FirRegularClassSymbol>
|
||||
)
|
||||
|
||||
private val SINCE_KOTLIN_VERSION_NAME = Name.identifier("version")
|
||||
|
||||
fun FirAnnotatedDeclaration.checkSinceKotlinVersionAccessibility(context: CheckerContext): FirSinceKotlinAccessibility {
|
||||
val value = getOwnSinceKotlinVersion(context.session)
|
||||
val version = value?.apiVersion
|
||||
@@ -67,7 +65,7 @@ private fun FirAnnotatedDeclaration.getOwnSinceKotlinVersion(session: FirSession
|
||||
|
||||
// TODO: use-site targeted annotations
|
||||
fun FirAnnotatedDeclaration.consider() {
|
||||
val sinceKotlinSingleArgument = getAnnotationByClassId(StandardClassIds.Annotations.SinceKotlin)?.findArgumentByName(SINCE_KOTLIN_VERSION_NAME)
|
||||
val sinceKotlinSingleArgument = getAnnotationByClassId(StandardClassIds.Annotations.SinceKotlin)?.findArgumentByName(StandardClassIds.Annotations.ParameterNames.sinceKotlinVersion)
|
||||
val apiVersion = ((sinceKotlinSingleArgument as? FirConstExpression<*>)?.value as? String)?.let(ApiVersion.Companion::parse)
|
||||
if (apiVersion != null) {
|
||||
// TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version
|
||||
|
||||
@@ -8,6 +8,12 @@ package org.jetbrains.kotlin.fir.analysis.checkers
|
||||
import com.intellij.lang.LighterASTNode
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.util.diff.FlyweightCapableTreeStructure
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtModifierList
|
||||
import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType
|
||||
|
||||
internal fun LighterASTNode.getChildren(tree: FlyweightCapableTreeStructure<LighterASTNode>): List<LighterASTNode?> {
|
||||
val children = Ref<Array<LighterASTNode?>>()
|
||||
@@ -15,3 +21,20 @@ internal fun LighterASTNode.getChildren(tree: FlyweightCapableTreeStructure<Ligh
|
||||
return if (count > 0) children.get().filterNotNull() else emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visibility by given KtModifierList
|
||||
*/
|
||||
fun KtModifierList?.getVisibility() = this?.visibilityModifierType()?.toVisibilityOrNull()
|
||||
|
||||
/**
|
||||
* Returns Visibility by token or null
|
||||
*/
|
||||
fun KtModifierKeywordToken.toVisibilityOrNull(): Visibility? {
|
||||
return when (this) {
|
||||
KtTokens.PUBLIC_KEYWORD -> Visibilities.Public
|
||||
KtTokens.PRIVATE_KEYWORD -> Visibilities.Private
|
||||
KtTokens.PROTECTED_KEYWORD -> Visibilities.Protected
|
||||
KtTokens.INTERNAL_KEYWORD -> Visibilities.Internal
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
-256
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* 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.declaration
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifierList
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.hasModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.modality
|
||||
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.containingClassForStaticMemberAttr
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeLocalVariableNoTypeOrInitializer
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean {
|
||||
return isInsideSpecificClass(containingClass, context) { klass -> klass is FirRegularClass && klass.isExpect }
|
||||
}
|
||||
|
||||
internal fun isInsideExternalClass(containingClass: FirClass, context: CheckerContext): Boolean {
|
||||
return isInsideSpecificClass(containingClass, context) { klass -> klass is FirRegularClass && klass.isExternal }
|
||||
}
|
||||
|
||||
// Note that the class that contains the currently visiting declaration will *not* be in the context's containing declarations *yet*.
|
||||
private inline fun isInsideSpecificClass(
|
||||
containingClass: FirClass,
|
||||
context: CheckerContext,
|
||||
predicate: (FirClass) -> Boolean
|
||||
): Boolean {
|
||||
return predicate.invoke(containingClass) ||
|
||||
context.containingDeclarations.asReversed().any { it is FirRegularClass && predicate.invoke(it) }
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyFinal(context: CheckerContext): Boolean {
|
||||
if (this.isFinal) return true
|
||||
val containingClass = context.containingDeclarations.lastOrNull() as? FirRegularClass ?: return true
|
||||
if (containingClass.isEnumClass) {
|
||||
// Enum class has enum entries and hence is not considered final.
|
||||
return false
|
||||
}
|
||||
return containingClass.isFinal
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyExpect(
|
||||
containingClass: FirClass?,
|
||||
context: CheckerContext,
|
||||
): Boolean {
|
||||
if (this.isExpect) return true
|
||||
|
||||
return containingClass != null && isInsideExpectClass(containingClass, context)
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyExternal(
|
||||
containingClass: FirClass?,
|
||||
context: CheckerContext,
|
||||
): Boolean {
|
||||
if (this.isExternal) return true
|
||||
|
||||
if (this is FirPropertyAccessor) {
|
||||
// Check containing property
|
||||
val property = context.containingDeclarations.last() as FirProperty
|
||||
return property.isEffectivelyExternal(containingClass, context)
|
||||
}
|
||||
|
||||
if (this is FirProperty) {
|
||||
// Property is effectively external if all accessors are external
|
||||
if (getter?.isExternal == true && (!isVar || setter?.isExternal == true)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return containingClass != null && isInsideExternalClass(containingClass, context)
|
||||
}
|
||||
|
||||
// TODO: check class too
|
||||
internal fun checkExpectDeclarationVisibilityAndBody(
|
||||
declaration: FirMemberDeclaration,
|
||||
source: FirSourceElement,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext
|
||||
) {
|
||||
if (declaration.isExpect) {
|
||||
if (Visibilities.isPrivate(declaration.visibility)) {
|
||||
reporter.reportOn(source, FirErrors.EXPECTED_PRIVATE_DECLARATION, context)
|
||||
}
|
||||
if (declaration is FirSimpleFunction && declaration.hasBody) {
|
||||
reporter.reportOn(source, FirErrors.EXPECTED_DECLARATION_WITH_BODY, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matched FE 1.0's [DeclarationsChecker#checkPropertyInitializer].
|
||||
internal fun checkPropertyInitializer(
|
||||
containingClass: FirClass?,
|
||||
property: FirProperty,
|
||||
modifierList: FirModifierList?,
|
||||
isInitialized: Boolean,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext,
|
||||
reachable: Boolean = true
|
||||
) {
|
||||
val inInterface = containingClass?.isInterface == true
|
||||
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
|
||||
val isAbstract = property.isAbstract || hasAbstractModifier
|
||||
if (isAbstract) {
|
||||
val returnTypeRef = property.returnTypeRef
|
||||
if (property.initializer == null &&
|
||||
property.delegate == null &&
|
||||
returnTypeRef is FirErrorTypeRef && returnTypeRef.diagnostic is ConeLocalVariableNoTypeOrInitializer
|
||||
) {
|
||||
property.source?.let {
|
||||
reporter.reportOn(it, FirErrors.PROPERTY_WITH_NO_TYPE_NO_INITIALIZER, context)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val backingFieldRequired = property.hasBackingField
|
||||
if (inInterface && backingFieldRequired && property.hasAccessorImplementation) {
|
||||
property.source?.let {
|
||||
reporter.reportOn(it, FirErrors.BACKING_FIELD_IN_INTERFACE, context)
|
||||
}
|
||||
}
|
||||
|
||||
val isExpect = property.isEffectivelyExpect(containingClass, context)
|
||||
|
||||
when {
|
||||
property.initializer != null -> {
|
||||
property.initializer?.source?.let {
|
||||
when {
|
||||
inInterface -> {
|
||||
reporter.reportOn(it, FirErrors.PROPERTY_INITIALIZER_IN_INTERFACE, context)
|
||||
}
|
||||
isExpect -> {
|
||||
reporter.reportOn(it, FirErrors.EXPECTED_PROPERTY_INITIALIZER, context)
|
||||
}
|
||||
!backingFieldRequired -> {
|
||||
reporter.reportOn(it, FirErrors.PROPERTY_INITIALIZER_NO_BACKING_FIELD, context)
|
||||
}
|
||||
property.receiverTypeRef != null -> {
|
||||
reporter.reportOn(it, FirErrors.EXTENSION_PROPERTY_WITH_BACKING_FIELD, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
property.delegate != null -> {
|
||||
property.delegate?.source?.let {
|
||||
when {
|
||||
inInterface -> {
|
||||
reporter.reportOn(it, FirErrors.DELEGATED_PROPERTY_IN_INTERFACE, context)
|
||||
}
|
||||
isExpect -> {
|
||||
reporter.reportOn(it, FirErrors.EXPECTED_DELEGATED_PROPERTY, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val propertySource = property.source ?: return
|
||||
val isExternal = property.isEffectivelyExternal(containingClass, context)
|
||||
if (
|
||||
backingFieldRequired &&
|
||||
!inInterface &&
|
||||
!property.isLateInit &&
|
||||
!isExpect &&
|
||||
!isInitialized &&
|
||||
!isExternal &&
|
||||
!property.hasExplicitBackingField
|
||||
) {
|
||||
if (property.receiverTypeRef != null && !property.hasAccessorImplementation) {
|
||||
reporter.reportOn(propertySource, FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, context)
|
||||
} else if (reachable) { // TODO: can be suppressed not to report diagnostics about no body
|
||||
if (containingClass == null || property.hasAccessorImplementation) {
|
||||
reporter.reportOn(propertySource, FirErrors.MUST_BE_INITIALIZED, context)
|
||||
} else {
|
||||
reporter.reportOn(propertySource, FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (property.isLateInit) {
|
||||
if (isExpect) {
|
||||
reporter.reportOn(propertySource, FirErrors.EXPECTED_LATEINIT_PROPERTY, context)
|
||||
}
|
||||
// TODO: like [BindingContext.MUST_BE_LATEINIT], we should consider variable with uninitialized error.
|
||||
if (backingFieldRequired && !inInterface && isInitialized) {
|
||||
reporter.reportOn(propertySource, FirErrors.UNNECESSARY_LATEINIT, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
internal fun FirRegularClass.isInlineOrValueClass(): Boolean {
|
||||
if (this.classKind != ClassKind.CLASS) return false
|
||||
|
||||
return isInline || hasModifier(KtTokens.VALUE_KEYWORD)
|
||||
}
|
||||
|
||||
internal fun FirRegularClassSymbol.isInlineOrValueClass(): Boolean {
|
||||
if (this.classKind != ClassKind.CLASS) return false
|
||||
|
||||
return isInline
|
||||
}
|
||||
|
||||
internal val FirDeclaration.isEnumEntryInitializer: Boolean
|
||||
get() {
|
||||
if (this !is FirConstructor || !this.isPrimary) return false
|
||||
return (containingClassForStaticMemberAttr as? ConeClassLookupTagWithFixedSymbol)?.symbol?.classKind == ClassKind.ENUM_ENTRY
|
||||
}
|
||||
|
||||
// contract: returns(true) implies (this is FirMemberDeclaration<*>)
|
||||
internal val FirDeclaration.isLocalMember: Boolean
|
||||
get() = when (this) {
|
||||
is FirProperty -> this.isLocal
|
||||
is FirRegularClass -> this.isLocal
|
||||
is FirSimpleFunction -> this.isLocal
|
||||
else -> false
|
||||
}
|
||||
|
||||
internal val FirBasedSymbol<*>.isLocalMember: Boolean
|
||||
get() = when (this) {
|
||||
is FirPropertySymbol -> this.isLocal
|
||||
is FirRegularClassSymbol -> this.isLocal
|
||||
is FirNamedFunctionSymbol -> this.isLocal
|
||||
else -> false
|
||||
}
|
||||
|
||||
internal val FirCallableDeclaration.isExtensionMember: Boolean
|
||||
get() {
|
||||
return receiverTypeRef != null && dispatchReceiverType != null
|
||||
}
|
||||
|
||||
internal val FirCallableSymbol<*>.isExtensionMember: Boolean
|
||||
get() {
|
||||
return resolvedReceiverTypeRef != null && dispatchReceiverType != null
|
||||
}
|
||||
+14
-1
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.fir.*
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isInlineOnly
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.util.checkChildrenWithCustomVisitor
|
||||
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
||||
@@ -35,6 +35,7 @@ import org.jetbrains.kotlin.fir.types.isMarkedNullable
|
||||
import org.jetbrains.kotlin.fir.types.isNullable
|
||||
import org.jetbrains.kotlin.fir.types.toSymbol
|
||||
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||
@@ -503,4 +504,16 @@ object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||
reporter.reportOn(declaration.source, FirErrors.OVERRIDE_BY_INLINE, context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun FirElement.checkChildrenWithCustomVisitor(
|
||||
parentContext: CheckerContext,
|
||||
visitorVoid: FirVisitor<Unit, CheckerContext>
|
||||
) {
|
||||
val collectingVisitor = object : AbstractDiagnosticCollectorVisitor(parentContext) {
|
||||
override fun checkElement(element: FirElement) {
|
||||
element.accept(visitorVoid, context)
|
||||
}
|
||||
}
|
||||
this.accept(collectingVisitor, null)
|
||||
}
|
||||
}
|
||||
|
||||
+28
-28
@@ -26,41 +26,41 @@ object FirOuterClassArgumentsRequiredChecker : FirRegularClassChecker() {
|
||||
checkOuterClassArgumentsRequired(superTypeRef, declaration, context, reporter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkOuterClassArgumentsRequired(
|
||||
typeRef: FirTypeRef,
|
||||
declaration: FirRegularClass?,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
if (typeRef !is FirResolvedTypeRef || typeRef is FirErrorTypeRef) {
|
||||
return
|
||||
}
|
||||
private fun checkOuterClassArgumentsRequired(
|
||||
typeRef: FirTypeRef,
|
||||
declaration: FirRegularClass?,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter
|
||||
) {
|
||||
if (typeRef !is FirResolvedTypeRef || typeRef is FirErrorTypeRef) {
|
||||
return
|
||||
}
|
||||
|
||||
val type: ConeKotlinType = typeRef.type
|
||||
val delegatedTypeRef = typeRef.delegatedTypeRef
|
||||
val type: ConeKotlinType = typeRef.type
|
||||
val delegatedTypeRef = typeRef.delegatedTypeRef
|
||||
|
||||
if (delegatedTypeRef is FirUserTypeRef && type is ConeClassLikeType) {
|
||||
val symbol = type.lookupTag.toSymbol(context.session)
|
||||
if (delegatedTypeRef is FirUserTypeRef && type is ConeClassLikeType) {
|
||||
val symbol = type.lookupTag.toSymbol(context.session)
|
||||
|
||||
if (symbol is FirRegularClassSymbol) {
|
||||
val typeArguments = delegatedTypeRef.qualifier.toTypeProjections()
|
||||
val typeParameters = symbol.typeParameterSymbols
|
||||
if (symbol is FirRegularClassSymbol) {
|
||||
val typeArguments = delegatedTypeRef.qualifier.toTypeProjections()
|
||||
val typeParameters = symbol.typeParameterSymbols
|
||||
|
||||
for (index in typeArguments.size until typeParameters.size) {
|
||||
val typeParameter = typeParameters[index]
|
||||
if (!isValidTypeParameterFromOuterClass(typeParameter, declaration, context.session)) {
|
||||
val outerClass = typeParameter.containingDeclarationSymbol as? FirRegularClassSymbol ?: break
|
||||
reporter.reportOn(typeRef.source, FirErrors.OUTER_CLASS_ARGUMENTS_REQUIRED, outerClass, context)
|
||||
break
|
||||
for (index in typeArguments.size until typeParameters.size) {
|
||||
val typeParameter = typeParameters[index]
|
||||
if (!isValidTypeParameterFromOuterClass(typeParameter, declaration, context.session)) {
|
||||
val outerClass = typeParameter.containingDeclarationSymbol as? FirRegularClassSymbol ?: break
|
||||
reporter.reportOn(typeRef.source, FirErrors.OUTER_CLASS_ARGUMENTS_REQUIRED, outerClass, context)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (index in type.typeArguments.indices) {
|
||||
val firTypeRefSource = extractArgumentTypeRefAndSource(typeRef, index) ?: continue
|
||||
firTypeRefSource.typeRef?.let { checkOuterClassArgumentsRequired(it, declaration, context, reporter) }
|
||||
for (index in type.typeArguments.indices) {
|
||||
val firTypeRefSource = extractArgumentTypeRefAndSource(typeRef, index) ?: continue
|
||||
firTypeRefSource.typeRef?.let { checkOuterClassArgumentsRequired(it, declaration, context, reporter) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+134
-2
@@ -5,13 +5,23 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.fir.FirFakeSourceElementKind
|
||||
import org.jetbrains.kotlin.fir.FirSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.FirModifierList
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.contains
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
|
||||
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.analysis.diagnostics.withSuppressedDiagnostics
|
||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.impl.FirDefaultPropertyAccessor
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeLocalVariableNoTypeOrInitializer
|
||||
import org.jetbrains.kotlin.fir.types.FirErrorTypeRef
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
// See old FE's [DeclarationsChecker]
|
||||
object FirTopLevelPropertiesChecker : FirFileChecker() {
|
||||
@@ -43,3 +53,125 @@ object FirTopLevelPropertiesChecker : FirFileChecker() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check class too
|
||||
internal fun checkExpectDeclarationVisibilityAndBody(
|
||||
declaration: FirMemberDeclaration,
|
||||
source: FirSourceElement,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext
|
||||
) {
|
||||
if (declaration.isExpect) {
|
||||
if (Visibilities.isPrivate(declaration.visibility)) {
|
||||
reporter.reportOn(source, FirErrors.EXPECTED_PRIVATE_DECLARATION, context)
|
||||
}
|
||||
if (declaration is FirSimpleFunction && declaration.hasBody) {
|
||||
reporter.reportOn(source, FirErrors.EXPECTED_DECLARATION_WITH_BODY, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Matched FE 1.0's [DeclarationsChecker#checkPropertyInitializer].
|
||||
internal fun checkPropertyInitializer(
|
||||
containingClass: FirClass?,
|
||||
property: FirProperty,
|
||||
modifierList: FirModifierList?,
|
||||
isInitialized: Boolean,
|
||||
reporter: DiagnosticReporter,
|
||||
context: CheckerContext,
|
||||
reachable: Boolean = true
|
||||
) {
|
||||
val inInterface = containingClass?.isInterface == true
|
||||
val hasAbstractModifier = KtTokens.ABSTRACT_KEYWORD in modifierList
|
||||
val isAbstract = property.isAbstract || hasAbstractModifier
|
||||
if (isAbstract) {
|
||||
val returnTypeRef = property.returnTypeRef
|
||||
if (property.initializer == null &&
|
||||
property.delegate == null &&
|
||||
returnTypeRef is FirErrorTypeRef && returnTypeRef.diagnostic is ConeLocalVariableNoTypeOrInitializer
|
||||
) {
|
||||
property.source?.let {
|
||||
reporter.reportOn(it, FirErrors.PROPERTY_WITH_NO_TYPE_NO_INITIALIZER, context)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val backingFieldRequired = property.hasBackingField
|
||||
if (inInterface && backingFieldRequired && property.hasAccessorImplementation) {
|
||||
property.source?.let {
|
||||
reporter.reportOn(it, FirErrors.BACKING_FIELD_IN_INTERFACE, context)
|
||||
}
|
||||
}
|
||||
|
||||
val isExpect = property.isEffectivelyExpect(containingClass, context)
|
||||
|
||||
when {
|
||||
property.initializer != null -> {
|
||||
property.initializer?.source?.let {
|
||||
when {
|
||||
inInterface -> {
|
||||
reporter.reportOn(it, FirErrors.PROPERTY_INITIALIZER_IN_INTERFACE, context)
|
||||
}
|
||||
isExpect -> {
|
||||
reporter.reportOn(it, FirErrors.EXPECTED_PROPERTY_INITIALIZER, context)
|
||||
}
|
||||
!backingFieldRequired -> {
|
||||
reporter.reportOn(it, FirErrors.PROPERTY_INITIALIZER_NO_BACKING_FIELD, context)
|
||||
}
|
||||
property.receiverTypeRef != null -> {
|
||||
reporter.reportOn(it, FirErrors.EXTENSION_PROPERTY_WITH_BACKING_FIELD, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
property.delegate != null -> {
|
||||
property.delegate?.source?.let {
|
||||
when {
|
||||
inInterface -> {
|
||||
reporter.reportOn(it, FirErrors.DELEGATED_PROPERTY_IN_INTERFACE, context)
|
||||
}
|
||||
isExpect -> {
|
||||
reporter.reportOn(it, FirErrors.EXPECTED_DELEGATED_PROPERTY, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val propertySource = property.source ?: return
|
||||
val isExternal = property.isEffectivelyExternal(containingClass, context)
|
||||
if (
|
||||
backingFieldRequired &&
|
||||
!inInterface &&
|
||||
!property.isLateInit &&
|
||||
!isExpect &&
|
||||
!isInitialized &&
|
||||
!isExternal &&
|
||||
!property.hasExplicitBackingField
|
||||
) {
|
||||
if (property.receiverTypeRef != null && !property.hasAccessorImplementation) {
|
||||
reporter.reportOn(propertySource, FirErrors.EXTENSION_PROPERTY_MUST_HAVE_ACCESSORS_OR_BE_ABSTRACT, context)
|
||||
} else if (reachable) { // TODO: can be suppressed not to report diagnostics about no body
|
||||
if (containingClass == null || property.hasAccessorImplementation) {
|
||||
reporter.reportOn(propertySource, FirErrors.MUST_BE_INITIALIZED, context)
|
||||
} else {
|
||||
reporter.reportOn(propertySource, FirErrors.MUST_BE_INITIALIZED_OR_BE_ABSTRACT, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (property.isLateInit) {
|
||||
if (isExpect) {
|
||||
reporter.reportOn(propertySource, FirErrors.EXPECTED_LATEINIT_PROPERTY, context)
|
||||
}
|
||||
// TODO: like [BindingContext.MUST_BE_LATEINIT], we should consider variable with uninitialized error.
|
||||
if (backingFieldRequired && !inInterface && isInitialized) {
|
||||
reporter.reportOn(propertySource, FirErrors.UNNECESSARY_LATEINIT, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val FirProperty.hasAccessorImplementation: Boolean
|
||||
get() = (getter !is FirDefaultPropertyAccessor && getter?.hasBody == true) ||
|
||||
(setter !is FirDefaultPropertyAccessor && setter?.hasBody == true)
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* 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.declaration
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.hasModifier
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.modality
|
||||
import org.jetbrains.kotlin.fir.containingClassForStaticMemberAttr
|
||||
import org.jetbrains.kotlin.fir.declarations.*
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.*
|
||||
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
|
||||
internal fun isInsideExpectClass(containingClass: FirClass, context: CheckerContext): Boolean {
|
||||
return isInsideSpecificClass(containingClass, context) { klass -> klass is FirRegularClass && klass.isExpect }
|
||||
}
|
||||
|
||||
internal fun isInsideExternalClass(containingClass: FirClass, context: CheckerContext): Boolean {
|
||||
return isInsideSpecificClass(containingClass, context) { klass -> klass is FirRegularClass && klass.isExternal }
|
||||
}
|
||||
|
||||
// Note that the class that contains the currently visiting declaration will *not* be in the context's containing declarations *yet*.
|
||||
private inline fun isInsideSpecificClass(
|
||||
containingClass: FirClass,
|
||||
context: CheckerContext,
|
||||
predicate: (FirClass) -> Boolean
|
||||
): Boolean {
|
||||
return predicate.invoke(containingClass) ||
|
||||
context.containingDeclarations.asReversed().any { it is FirRegularClass && predicate.invoke(it) }
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyFinal(context: CheckerContext): Boolean {
|
||||
if (this.isFinal) return true
|
||||
val containingClass = context.containingDeclarations.lastOrNull() as? FirRegularClass ?: return true
|
||||
if (containingClass.isEnumClass) {
|
||||
// Enum class has enum entries and hence is not considered final.
|
||||
return false
|
||||
}
|
||||
return containingClass.isFinal
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyExpect(
|
||||
containingClass: FirClass?,
|
||||
context: CheckerContext,
|
||||
): Boolean {
|
||||
if (this.isExpect) return true
|
||||
|
||||
return containingClass != null && isInsideExpectClass(containingClass, context)
|
||||
}
|
||||
|
||||
internal fun FirMemberDeclaration.isEffectivelyExternal(
|
||||
containingClass: FirClass?,
|
||||
context: CheckerContext,
|
||||
): Boolean {
|
||||
if (this.isExternal) return true
|
||||
|
||||
if (this is FirPropertyAccessor) {
|
||||
// Check containing property
|
||||
val property = context.containingDeclarations.last() as FirProperty
|
||||
return property.isEffectivelyExternal(containingClass, context)
|
||||
}
|
||||
|
||||
if (this is FirProperty) {
|
||||
// Property is effectively external if all accessors are external
|
||||
if (getter?.isExternal == true && (!isVar || setter?.isExternal == true)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return containingClass != null && isInsideExternalClass(containingClass, context)
|
||||
}
|
||||
|
||||
internal val FirClass.canHaveOpenMembers: Boolean get() = modality() != Modality.FINAL || classKind == ClassKind.ENUM_CLASS
|
||||
|
||||
internal fun FirRegularClass.isInlineOrValueClass(): Boolean {
|
||||
if (this.classKind != ClassKind.CLASS) return false
|
||||
|
||||
return isInline || hasModifier(KtTokens.VALUE_KEYWORD)
|
||||
}
|
||||
|
||||
internal fun FirRegularClassSymbol.isInlineOrValueClass(): Boolean {
|
||||
if (this.classKind != ClassKind.CLASS) return false
|
||||
|
||||
return isInline
|
||||
}
|
||||
|
||||
internal val FirDeclaration.isEnumEntryInitializer: Boolean
|
||||
get() {
|
||||
if (this !is FirConstructor || !this.isPrimary) return false
|
||||
return (containingClassForStaticMemberAttr as? ConeClassLookupTagWithFixedSymbol)?.symbol?.classKind == ClassKind.ENUM_ENTRY
|
||||
}
|
||||
|
||||
// contract: returns(true) implies (this is FirMemberDeclaration<*>)
|
||||
internal val FirDeclaration.isLocalMember: Boolean
|
||||
get() = symbol.isLocalMember
|
||||
|
||||
internal val FirBasedSymbol<*>.isLocalMember: Boolean
|
||||
get() = when (this) {
|
||||
is FirPropertySymbol -> this.isLocal
|
||||
is FirRegularClassSymbol -> this.isLocal
|
||||
is FirNamedFunctionSymbol -> this.isLocal
|
||||
else -> false
|
||||
}
|
||||
|
||||
internal val FirCallableDeclaration.isExtensionMember: Boolean
|
||||
get() = symbol.isExtensionMember
|
||||
|
||||
internal val FirCallableSymbol<*>.isExtensionMember: Boolean
|
||||
get() = resolvedReceiverTypeRef != null && dispatchReceiverType != null
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirElement
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.collectors.AbstractDiagnosticCollectorVisitor
|
||||
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||
|
||||
fun FirElement.checkChildrenWithCustomVisitor(
|
||||
parentContext: CheckerContext,
|
||||
visitorVoid: FirVisitor<Unit, CheckerContext>
|
||||
) {
|
||||
val collectingVisitor = object : AbstractDiagnosticCollectorVisitor(parentContext) {
|
||||
override fun checkElement(element: FirElement) {
|
||||
element.accept(visitorVoid, context)
|
||||
}
|
||||
}
|
||||
this.accept(collectingVisitor, null)
|
||||
}
|
||||
@@ -173,6 +173,8 @@ object StandardClassIds {
|
||||
val targetAllowedTargets = Name.identifier("allowedTargets")
|
||||
val jvmNameName = Name.identifier("name")
|
||||
|
||||
val sinceKotlinVersion = Name.identifier("version")
|
||||
|
||||
val deprecatedMessage = Name.identifier("message")
|
||||
val deprecatedLevel = Name.identifier("level")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user