[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
|
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
|
* 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>
|
val wasExperimentalMarkerClasses: List<FirRegularClassSymbol>
|
||||||
)
|
)
|
||||||
|
|
||||||
private val SINCE_KOTLIN_VERSION_NAME = Name.identifier("version")
|
|
||||||
|
|
||||||
fun FirAnnotatedDeclaration.checkSinceKotlinVersionAccessibility(context: CheckerContext): FirSinceKotlinAccessibility {
|
fun FirAnnotatedDeclaration.checkSinceKotlinVersionAccessibility(context: CheckerContext): FirSinceKotlinAccessibility {
|
||||||
val value = getOwnSinceKotlinVersion(context.session)
|
val value = getOwnSinceKotlinVersion(context.session)
|
||||||
val version = value?.apiVersion
|
val version = value?.apiVersion
|
||||||
@@ -67,7 +65,7 @@ private fun FirAnnotatedDeclaration.getOwnSinceKotlinVersion(session: FirSession
|
|||||||
|
|
||||||
// TODO: use-site targeted annotations
|
// TODO: use-site targeted annotations
|
||||||
fun FirAnnotatedDeclaration.consider() {
|
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)
|
val apiVersion = ((sinceKotlinSingleArgument as? FirConstExpression<*>)?.value as? String)?.let(ApiVersion.Companion::parse)
|
||||||
if (apiVersion != null) {
|
if (apiVersion != null) {
|
||||||
// TODO: combine wasExperimentalMarkerClasses in case of several associated declarations with the same maximal API version
|
// 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.lang.LighterASTNode
|
||||||
import com.intellij.openapi.util.Ref
|
import com.intellij.openapi.util.Ref
|
||||||
import com.intellij.util.diff.FlyweightCapableTreeStructure
|
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?> {
|
internal fun LighterASTNode.getChildren(tree: FlyweightCapableTreeStructure<LighterASTNode>): List<LighterASTNode?> {
|
||||||
val children = Ref<Array<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()
|
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.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.isInlineOnly
|
import org.jetbrains.kotlin.fir.analysis.checkers.isInlineOnly
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.unsubstitutedScope
|
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.DiagnosticReporter
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
|
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.isNullable
|
||||||
import org.jetbrains.kotlin.fir.types.toSymbol
|
import org.jetbrains.kotlin.fir.types.toSymbol
|
||||||
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
|
import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitor
|
||||||
|
import org.jetbrains.kotlin.fir.visitors.FirVisitor
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
|
|
||||||
object FirInlineDeclarationChecker : FirFunctionChecker() {
|
object FirInlineDeclarationChecker : FirFunctionChecker() {
|
||||||
@@ -503,4 +504,16 @@ object FirInlineDeclarationChecker : FirFunctionChecker() {
|
|||||||
reporter.reportOn(declaration.source, FirErrors.OVERRIDE_BY_INLINE, context)
|
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)
|
checkOuterClassArgumentsRequired(superTypeRef, declaration, context, reporter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkOuterClassArgumentsRequired(
|
private fun checkOuterClassArgumentsRequired(
|
||||||
typeRef: FirTypeRef,
|
typeRef: FirTypeRef,
|
||||||
declaration: FirRegularClass?,
|
declaration: FirRegularClass?,
|
||||||
context: CheckerContext,
|
context: CheckerContext,
|
||||||
reporter: DiagnosticReporter
|
reporter: DiagnosticReporter
|
||||||
) {
|
) {
|
||||||
if (typeRef !is FirResolvedTypeRef || typeRef is FirErrorTypeRef) {
|
if (typeRef !is FirResolvedTypeRef || typeRef is FirErrorTypeRef) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val type: ConeKotlinType = typeRef.type
|
val type: ConeKotlinType = typeRef.type
|
||||||
val delegatedTypeRef = typeRef.delegatedTypeRef
|
val delegatedTypeRef = typeRef.delegatedTypeRef
|
||||||
|
|
||||||
if (delegatedTypeRef is FirUserTypeRef && type is ConeClassLikeType) {
|
if (delegatedTypeRef is FirUserTypeRef && type is ConeClassLikeType) {
|
||||||
val symbol = type.lookupTag.toSymbol(context.session)
|
val symbol = type.lookupTag.toSymbol(context.session)
|
||||||
|
|
||||||
if (symbol is FirRegularClassSymbol) {
|
if (symbol is FirRegularClassSymbol) {
|
||||||
val typeArguments = delegatedTypeRef.qualifier.toTypeProjections()
|
val typeArguments = delegatedTypeRef.qualifier.toTypeProjections()
|
||||||
val typeParameters = symbol.typeParameterSymbols
|
val typeParameters = symbol.typeParameterSymbols
|
||||||
|
|
||||||
for (index in typeArguments.size until typeParameters.size) {
|
for (index in typeArguments.size until typeParameters.size) {
|
||||||
val typeParameter = typeParameters[index]
|
val typeParameter = typeParameters[index]
|
||||||
if (!isValidTypeParameterFromOuterClass(typeParameter, declaration, context.session)) {
|
if (!isValidTypeParameterFromOuterClass(typeParameter, declaration, context.session)) {
|
||||||
val outerClass = typeParameter.containingDeclarationSymbol as? FirRegularClassSymbol ?: break
|
val outerClass = typeParameter.containingDeclarationSymbol as? FirRegularClassSymbol ?: break
|
||||||
reporter.reportOn(typeRef.source, FirErrors.OUTER_CLASS_ARGUMENTS_REQUIRED, outerClass, context)
|
reporter.reportOn(typeRef.source, FirErrors.OUTER_CLASS_ARGUMENTS_REQUIRED, outerClass, context)
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (index in type.typeArguments.indices) {
|
for (index in type.typeArguments.indices) {
|
||||||
val firTypeRefSource = extractArgumentTypeRefAndSource(typeRef, index) ?: continue
|
val firTypeRefSource = extractArgumentTypeRefAndSource(typeRef, index) ?: continue
|
||||||
firTypeRefSource.typeRef?.let { checkOuterClassArgumentsRequired(it, declaration, context, reporter) }
|
firTypeRefSource.typeRef?.let { checkOuterClassArgumentsRequired(it, declaration, context, reporter) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+134
-2
@@ -5,13 +5,23 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
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.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.context.CheckerContext
|
||||||
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
|
import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList
|
||||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
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.analysis.diagnostics.withSuppressedDiagnostics
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirFile
|
import org.jetbrains.kotlin.fir.declarations.*
|
||||||
import org.jetbrains.kotlin.fir.declarations.FirProperty
|
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]
|
// See old FE's [DeclarationsChecker]
|
||||||
object FirTopLevelPropertiesChecker : FirFileChecker() {
|
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 targetAllowedTargets = Name.identifier("allowedTargets")
|
||||||
val jvmNameName = Name.identifier("name")
|
val jvmNameName = Name.identifier("name")
|
||||||
|
|
||||||
|
val sinceKotlinVersion = Name.identifier("version")
|
||||||
|
|
||||||
val deprecatedMessage = Name.identifier("message")
|
val deprecatedMessage = Name.identifier("message")
|
||||||
val deprecatedLevel = Name.identifier("level")
|
val deprecatedLevel = Name.identifier("level")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user