From 0f6b6dbca316a03d295e020c316e4c53ec4d684a Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Tue, 14 Sep 2021 16:26:51 +0300 Subject: [PATCH] [FIR] Cleanup and reorganize utils in :compiler:fir:checkers --- .../fir/analysis/checkers/FirHelpers.kt | 18 +- .../checkers/FirSinceKotlinHelpers.kt | 4 +- .../fir/analysis/checkers/SourceHelpers.kt | 23 ++ .../declaration/FirDeclarationCheckerUtils.kt | 256 ------------------ .../FirInlineDeclarationChecker.kt | 15 +- .../FirOuterClassArgumentsRequiredChecker.kt | 56 ++-- ...kt => FirPropertyAccessorsTypesChecker.kt} | 0 ...cker.kt => FirPropertyFieldTypeChecker.kt} | 0 .../FirTopLevelPropertiesChecker.kt | 136 +++++++++- .../checkers/declaration/declarationUtils.kt | 115 ++++++++ ...inueJumpsAcrossFunctionBoundaryChecker.kt} | 0 ...SuperCallCheckers.kt => referenceUtils.kt} | 0 ...eChecker.kt => RedundantReturnUnitType.kt} | 0 .../checkers/util/firCheckersUtils.kt | 23 -- .../jetbrains/kotlin/name/StandardClassIds.kt | 2 + 15 files changed, 318 insertions(+), 330 deletions(-) delete mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt rename compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/{FirPropertyAccessorChecker.kt => FirPropertyAccessorsTypesChecker.kt} (100%) rename compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/{FirPropertyFieldDeclarationChecker.kt => FirPropertyFieldTypeChecker.kt} (100%) create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt rename compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/{FirLoopJumpChecker.kt => FirBreakOrContinueJumpsAcrossFunctionBoundaryChecker.kt} (100%) rename compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/{SuperCallCheckers.kt => referenceUtils.kt} (100%) rename compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/{RedundantReturnUnitTypeChecker.kt => RedundantReturnUnitType.kt} (100%) delete mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/util/firCheckersUtils.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 2df8c90e746..44b31f2298f 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -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 \ No newline at end of file +private typealias TargetLists = AnnotationTargetLists diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt index 183ffb583f2..5c566ca1493 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirSinceKotlinHelpers.kt @@ -41,8 +41,6 @@ private data class FirSinceKotlinValue( val wasExperimentalMarkerClasses: List ) -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 diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceHelpers.kt index eeddac06ddc..11730b2f4c1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/SourceHelpers.kt @@ -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): List { val children = Ref>() @@ -15,3 +21,20 @@ internal fun LighterASTNode.getChildren(tree: FlyweightCapableTreeStructure 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 + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt deleted file mode 100644 index aae9b5d456c..00000000000 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirDeclarationCheckerUtils.kt +++ /dev/null @@ -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 - } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt index bb00e2422fb..58866637b05 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirInlineDeclarationChecker.kt @@ -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 + ) { + val collectingVisitor = object : AbstractDiagnosticCollectorVisitor(parentContext) { + override fun checkElement(element: FirElement) { + element.accept(visitorVoid, context) + } + } + this.accept(collectingVisitor, null) + } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOuterClassArgumentsRequiredChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOuterClassArgumentsRequiredChecker.kt index dd601d03a31..fa58f064983 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOuterClassArgumentsRequiredChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirOuterClassArgumentsRequiredChecker.kt @@ -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) } + } } -} \ No newline at end of file +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyAccessorChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyAccessorsTypesChecker.kt similarity index 100% rename from compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyAccessorChecker.kt rename to compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyAccessorsTypesChecker.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyFieldDeclarationChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyFieldTypeChecker.kt similarity index 100% rename from compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyFieldDeclarationChecker.kt rename to compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirPropertyFieldTypeChecker.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTopLevelPropertiesChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTopLevelPropertiesChecker.kt index 4353c4434a1..790ef3f10f3 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTopLevelPropertiesChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/FirTopLevelPropertiesChecker.kt @@ -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) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt new file mode 100644 index 00000000000..9f57b74b2e6 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/declaration/declarationUtils.kt @@ -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 diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirLoopJumpChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirBreakOrContinueJumpsAcrossFunctionBoundaryChecker.kt similarity index 100% rename from compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirLoopJumpChecker.kt rename to compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirBreakOrContinueJumpsAcrossFunctionBoundaryChecker.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/SuperCallCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/referenceUtils.kt similarity index 100% rename from compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/SuperCallCheckers.kt rename to compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/referenceUtils.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitTypeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitType.kt similarity index 100% rename from compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitTypeChecker.kt rename to compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitType.kt diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/util/firCheckersUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/util/firCheckersUtils.kt deleted file mode 100644 index a812bcbde85..00000000000 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/util/firCheckersUtils.kt +++ /dev/null @@ -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 -) { - val collectingVisitor = object : AbstractDiagnosticCollectorVisitor(parentContext) { - override fun checkElement(element: FirElement) { - element.accept(visitorVoid, context) - } - } - this.accept(collectingVisitor, null) -} diff --git a/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt b/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt index 7cbf4286be5..c3c02e240c8 100644 --- a/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt +++ b/core/compiler.common/src/org/jetbrains/kotlin/name/StandardClassIds.kt @@ -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")