org.jetbrains.kotlin.resolve.ModifiersChecker.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-compiler-embeddable Show documentation
Show all versions of kotlin-compiler-embeddable Show documentation
the Kotlin compiler embeddable
/*
* Copyright 2010-2018 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.resolve
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.KotlinTarget
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
import org.jetbrains.kotlin.lexer.KtTokens.*
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.KtModifierList
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.resolve.calls.checkers.checkCoroutinesFeature
object ModifierCheckerCore {
fun check(
listOwner: KtModifierListOwner,
trace: BindingTrace,
descriptor: DeclarationDescriptor?,
languageVersionSettings: LanguageVersionSettings
) {
if (listOwner is KtDeclarationWithBody) {
// KtFunction or KtPropertyAccessor
for (parameter in listOwner.valueParameters) {
if (!parameter.hasValOrVar()) {
check(parameter, trace, trace[BindingContext.VALUE_PARAMETER, parameter], languageVersionSettings)
}
}
}
val actualTargets = AnnotationChecker.getDeclarationSiteActualTargetList(
listOwner, descriptor as? ClassDescriptor, trace.bindingContext
)
val list = listOwner.modifierList ?: return
checkModifierList(list, trace, descriptor?.containingDeclaration, actualTargets, languageVersionSettings)
}
private val MODIFIER_KEYWORD_SET = TokenSet.orSet(SOFT_KEYWORDS, TokenSet.create(IN_KEYWORD, FUN_KEYWORD))
private fun checkModifierList(
list: KtModifierList,
trace: BindingTrace,
parentDescriptor: DeclarationDescriptor?,
actualTargets: List,
languageVersionSettings: LanguageVersionSettings
) {
if (list.stub != null) return
// It's a list of all nodes with error already reported
// General strategy: report no more than one error but any number of warnings
val incorrectNodes = hashSetOf()
val children = list.node.getChildren(MODIFIER_KEYWORD_SET)
for (second in children) {
for (first in children) {
if (first == second) {
break
}
checkCompatibility(trace, first, second, list.owner, incorrectNodes)
}
if (second !in incorrectNodes) {
when {
!checkTarget(trace, second, actualTargets) -> incorrectNodes += second
!checkParent(trace, second, parentDescriptor, languageVersionSettings) -> incorrectNodes += second
!checkLanguageLevelSupport(trace, second, languageVersionSettings, actualTargets) -> incorrectNodes += second
}
}
}
}
private fun checkCompatibility(
trace: BindingTrace,
firstNode: ASTNode,
secondNode: ASTNode,
owner: PsiElement,
incorrectNodes: MutableSet
) {
val firstModifier = firstNode.elementType as KtModifierKeywordToken
val secondModifier = secondNode.elementType as KtModifierKeywordToken
when (val compatibility = compatibility(firstModifier, secondModifier)) {
Compatibility.COMPATIBLE -> {
}
Compatibility.REPEATED -> if (incorrectNodes.add(secondNode)) {
trace.report(Errors.REPEATED_MODIFIER.on(secondNode.psi, firstModifier))
}
Compatibility.REDUNDANT ->
trace.report(Errors.REDUNDANT_MODIFIER.on(secondNode.psi, secondModifier, firstModifier))
Compatibility.REVERSE_REDUNDANT ->
trace.report(Errors.REDUNDANT_MODIFIER.on(firstNode.psi, firstModifier, secondModifier))
Compatibility.DEPRECATED -> {
trace.report(Errors.DEPRECATED_MODIFIER_PAIR.on(firstNode.psi, firstModifier, secondModifier))
trace.report(Errors.DEPRECATED_MODIFIER_PAIR.on(secondNode.psi, secondModifier, firstModifier))
}
Compatibility.COMPATIBLE_FOR_CLASSES_ONLY, Compatibility.INCOMPATIBLE -> {
if (compatibility == Compatibility.COMPATIBLE_FOR_CLASSES_ONLY) {
if (owner is KtClassOrObject) return
}
if (incorrectNodes.add(firstNode)) {
trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(firstNode.psi, firstModifier, secondModifier))
}
if (incorrectNodes.add(secondNode)) {
trace.report(Errors.INCOMPATIBLE_MODIFIERS.on(secondNode.psi, secondModifier, firstModifier))
}
}
}
}
// Should return false if error is reported, true otherwise
private fun checkTarget(trace: BindingTrace, node: ASTNode, actualTargets: List): Boolean {
val modifier = node.elementType as KtModifierKeywordToken
val possibleTargets = possibleTargetMap[modifier] ?: emptySet()
if (!actualTargets.any { it in possibleTargets }) {
trace.report(Errors.WRONG_MODIFIER_TARGET.on(node.psi, modifier, actualTargets.firstOrNull()?.description ?: "this"))
return false
}
val deprecatedModifierReplacement = deprecatedKmpModifierMap[modifier]
val deprecatedTargets = deprecatedTargetMap[modifier] ?: emptySet()
val redundantTargets = redundantTargetMap[modifier] ?: emptySet()
when {
deprecatedModifierReplacement != null ->
trace.report(Errors.DEPRECATED_MODIFIER.on(node.psi, modifier, deprecatedModifierReplacement))
actualTargets.any { it in deprecatedTargets } ->
trace.report(
Errors.DEPRECATED_MODIFIER_FOR_TARGET.on(
node.psi,
modifier,
actualTargets.firstOrNull()?.description ?: "this"
)
)
actualTargets.any { it in redundantTargets } ->
trace.report(
Errors.REDUNDANT_MODIFIER_FOR_TARGET.on(
node.psi,
modifier,
actualTargets.firstOrNull()?.description ?: "this"
)
)
}
return true
}
// Should return false if error is reported, true otherwise
private fun checkParent(
trace: BindingTrace,
node: ASTNode,
parentDescriptor: DeclarationDescriptor?,
languageVersionSettings: LanguageVersionSettings
): Boolean {
val modifier = node.elementType as KtModifierKeywordToken
val actualParents: List = when (parentDescriptor) {
is ClassDescriptor -> KotlinTarget.classActualTargets(
parentDescriptor.kind,
isInnerClass = parentDescriptor.isInner,
isCompanionObject = parentDescriptor.isCompanionObject,
isLocalClass = DescriptorUtils.isLocal(parentDescriptor)
)
is PropertySetterDescriptor -> KotlinTarget.PROPERTY_SETTER_LIST
is PropertyGetterDescriptor -> KotlinTarget.PROPERTY_GETTER_LIST
is FunctionDescriptor -> KotlinTarget.FUNCTION_LIST
else -> KotlinTarget.FILE_LIST
}
val deprecatedParents = deprecatedParentTargetMap[modifier]
if (deprecatedParents != null && actualParents.any { it in deprecatedParents }) {
trace.report(
Errors.DEPRECATED_MODIFIER_CONTAINING_DECLARATION.on(
node.psi,
modifier,
actualParents.firstOrNull()?.description ?: "this scope"
)
)
return true
}
if (modifier == PROTECTED_KEYWORD && isFinalExpectClass(parentDescriptor)) {
trace.report(
Errors.WRONG_MODIFIER_CONTAINING_DECLARATION.on(
node.psi,
modifier,
"final expect class"
)
)
}
val possibleParentPredicate = possibleParentTargetPredicateMap[modifier] ?: return true
if (actualParents.any { possibleParentPredicate.isAllowed(it, languageVersionSettings) }) return true
trace.report(
Errors.WRONG_MODIFIER_CONTAINING_DECLARATION.on(
node.psi,
modifier,
actualParents.firstOrNull()?.description ?: "this scope"
)
)
return false
}
private fun checkLanguageLevelSupport(
trace: BindingTrace,
node: ASTNode,
languageVersionSettings: LanguageVersionSettings,
actualTargets: List
): Boolean {
val modifier = node.elementType as KtModifierKeywordToken
val dependencies = featureDependencies[modifier] ?: return true
for (dependency in dependencies) {
val restrictedTargets = featureDependenciesTargets[dependency]
if (restrictedTargets != null && actualTargets.intersect(restrictedTargets).isEmpty()) {
continue
}
if (dependency == LanguageFeature.Coroutines) {
checkCoroutinesFeature(languageVersionSettings, trace, node.psi)
continue
}
if (dependency == LanguageFeature.InlineClasses) {
if (languageVersionSettings.supportsFeature(LanguageFeature.JvmInlineValueClasses)) {
trace.report(Errors.INLINE_CLASS_DEPRECATED.on(node.psi))
continue
}
}
val featureSupport = languageVersionSettings.getFeatureSupport(dependency)
if (dependency == LanguageFeature.MultiPlatformProjects && featureSupport == LanguageFeature.State.DISABLED) {
trace.report(Errors.NOT_A_MULTIPLATFORM_COMPILATION.on(node.psi))
continue
}
val diagnosticData = dependency to languageVersionSettings
when (featureSupport) {
LanguageFeature.State.ENABLED_WITH_WARNING -> {
trace.report(Errors.EXPERIMENTAL_FEATURE_WARNING.on(node.psi, diagnosticData))
}
LanguageFeature.State.DISABLED -> {
trace.report(Errors.UNSUPPORTED_FEATURE.on(node.psi, diagnosticData))
return false
}
LanguageFeature.State.ENABLED -> {
}
}
}
return true
}
private fun isFinalExpectClass(d: DeclarationDescriptor?): Boolean {
return d is ClassDescriptor && d.isFinalOrEnum && d.isExpect
}
}