[FIR] Add CONCURRENT_HASH_MAP_CONTAINS_OPERATOR

This commit is contained in:
Ivan Kochurkin
2021-09-10 15:56:12 +03:00
committed by teamcityserver
parent 0ffd6ccb34
commit 5760a94ab4
11 changed files with 152 additions and 38 deletions
@@ -173,5 +173,6 @@ object JVM_DIAGNOSTICS_LIST : DiagnosticList("FirJvmErrors") {
PositioningStrategy.REFERENCE_BY_QUALIFIED
)
val SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC by error<PsiElement>(PositioningStrategy.REFERENCED_NAME_BY_QUALIFIED)
val CONCURRENT_HASH_MAP_CONTAINS_OPERATOR by deprecationError<PsiElement>(ProhibitConcurrentHashMapContains)
}
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.diagnostics.jvm
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.LanguageFeature.DefaultMethodsCallFromJava6TargetError
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitConcurrentHashMapContains
import org.jetbrains.kotlin.config.LanguageFeature.ProhibitJvmOverloadsOnConstructorsOfAnnotationClasses
import org.jetbrains.kotlin.config.LanguageFeature.RepeatableAnnotationContainerConstraints
import org.jetbrains.kotlin.fir.analysis.diagnostics.*
@@ -116,5 +117,6 @@ object FirJvmErrors {
val DEFAULT_METHOD_CALL_FROM_JAVA6_TARGET by deprecationError0<PsiElement>(DefaultMethodsCallFromJava6TargetError, SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val INTERFACE_STATIC_METHOD_CALL_FROM_JAVA6_TARGET by deprecationError0<PsiElement>(DefaultMethodsCallFromJava6TargetError, SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val SUBCLASS_CANT_CALL_COMPANION_PROTECTED_NON_STATIC by error0<PsiElement>(SourceElementPositioningStrategies.REFERENCED_NAME_BY_QUALIFIED)
val CONCURRENT_HASH_MAP_CONTAINS_OPERATOR by deprecationError0<PsiElement>(ProhibitConcurrentHashMapContains)
}
@@ -23,7 +23,8 @@ object JvmExpressionCheckers : ExpressionCheckers() {
get() = setOf(
FirJavaGenericVarianceViolationTypeChecker,
FirSuperCallWithDefaultsChecker,
FirJvmSuspensionPointInsideMutexLockChecker
FirJvmSuspensionPointInsideMutexLockChecker,
FirJvmInconsistentOperatorFromJavaCallChecker
)
override val annotationCheckers: Set<FirAnnotationChecker>
@@ -0,0 +1,64 @@
/*
* 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.jvm.checkers.expression
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirFunctionCallChecker
import org.jetbrains.kotlin.fir.analysis.checkers.overriddenFunctions
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.containingClass
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.isAny
import org.jetbrains.kotlin.fir.types.isNullableAny
import org.jetbrains.kotlin.fir.types.lowerBoundIfFlexible
import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
object FirJvmInconsistentOperatorFromJavaCallChecker : FirFunctionCallChecker() {
private val CONCURRENT_HASH_MAP_CALLABLE_ID = CallableId(
ClassId.fromString("java/util/concurrent/ConcurrentHashMap"),
Name.identifier("contains")
)
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
val callableSymbol = expression.calleeReference.toResolvedCallableSymbol() as? FirNamedFunctionSymbol ?: return
if (callableSymbol.name != OperatorNameConventions.CONTAINS) return
val valueParameterSymbol = callableSymbol.valueParameterSymbols.singleOrNull() ?: return
val type = valueParameterSymbol.resolvedReturnTypeRef.coneType.lowerBoundIfFlexible()
if (!type.isAny && !type.isNullableAny) return
if (expression.origin != FirFunctionCallOrigin.Operator || expression.origin.ordinal != 2) return
callableSymbol.check(expression.calleeReference.source, context, reporter)
}
fun FirNamedFunctionSymbol.check(source: FirSourceElement?, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
if (callableId == CONCURRENT_HASH_MAP_CALLABLE_ID) {
reporter.reportOn(source, FirJvmErrors.CONCURRENT_HASH_MAP_CONTAINS_OPERATOR, context)
return true
}
val containingClass = containingClass()?.toFirRegularClassSymbol(context.session) ?: return false
val overriddenFunctions = overriddenFunctions(containingClass, context)
for (overriddenFunction in overriddenFunctions) {
if (overriddenFunction is FirNamedFunctionSymbol && overriddenFunction.check(source, context, reporter)) {
return true
}
}
return false
}
}
@@ -5,12 +5,12 @@
package org.jetbrains.kotlin.fir.analysis.jvm.diagnostics
import org.jetbrains.kotlin.diagnostics.rendering.Renderers.NAME
import org.jetbrains.kotlin.diagnostics.rendering.CommonRenderers.STRING
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDefaultErrorMessages
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.RENDER_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.SYMBOL
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderers.TO_STRING
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.CONCURRENT_HASH_MAP_CONTAINS_OPERATOR
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.CONFLICTING_JVM_DECLARATIONS
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.DEFAULT_METHOD_CALL_FROM_JAVA6_TARGET
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.DELEGATION_BY_IN_JVM_RECORD
@@ -23,9 +23,9 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.EXTERNAL_D
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.FIELD_IN_JVM_RECORD
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.ILLEGAL_JVM_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_FIELD
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_FIELD_WARNING
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INAPPLICABLE_JVM_NAME
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INNER_JVM_RECORD
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INTERFACE_CANT_CALL_DEFAULT_METHOD_VIA_SUPER
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.INTERFACE_STATIC_METHOD_CALL_FROM_JAVA6_TARGET
@@ -264,6 +264,12 @@ object FirJvmDefaultErrorMessages {
"The ''{0}'' suspension point is inside a critical section",
SYMBOL
)
map.put(
CONCURRENT_HASH_MAP_CONTAINS_OPERATOR,
"Method 'contains' from ConcurrentHashMap may have unexpected semantics: it calls 'containsValue' instead of 'containsKey'. " +
"Use explicit form of the call to 'containsKey'/'containsValue'/'contains' or cast the value to kotlin.collections.Map instead. " +
"See https://youtrack.jetbrains.com/issue/KT-18053 for more details"
)
}
}
}
@@ -185,6 +185,13 @@ fun CheckerContext.findClosestClassOrObject(): FirClass? {
fun FirSimpleFunction.overriddenFunctions(
containingClass: FirClassSymbol<*>,
context: CheckerContext
): List<FirFunctionSymbol<*>> {
return symbol.overriddenFunctions(containingClass, context)
}
fun FirNamedFunctionSymbol.overriddenFunctions(
containingClass: FirClassSymbol<*>,
context: CheckerContext
): List<FirFunctionSymbol<*>> {
val firTypeScope = containingClass.unsubstitutedScope(
context.sessionHolder.session,
@@ -193,8 +200,8 @@ fun FirSimpleFunction.overriddenFunctions(
)
val overriddenFunctions = mutableListOf<FirFunctionSymbol<*>>()
firTypeScope.processFunctionsByName(symbol.callableId.callableName) { }
firTypeScope.processOverriddenFunctions(symbol) {
firTypeScope.processFunctionsByName(callableId.callableName) { }
firTypeScope.processOverriddenFunctions(this) {
overriddenFunctions.add(it)
ProcessorAction.NEXT
}
@@ -18,10 +18,10 @@ class C : java.util.concurrent.ConcurrentHashMap<String, Int>() {
fun main() {
val hm = java.util.concurrent.ConcurrentHashMap<String, Int>()
"" in hm
"" !in hm
1 !in hm
2 in hm
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> hm
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> hm
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> hm
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> hm
hm.contains("")
hm.contains(1)
@@ -32,10 +32,10 @@ fun main() {
1 !in (hm as Map<String, Int>)
val a = A()
"" in a
"" !in a
1 !in a
2 in a
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> a
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> a
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> a
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> a
' ' in a
' ' !in a
@@ -48,10 +48,10 @@ fun main() {
1 !in (a as Map<String, Int>)
val b = B()
"" in b
"" !in b
1 !in b
2 in b
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> b
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> b
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> b
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> b
b.contains("")
b.contains(1)
@@ -63,10 +63,10 @@ fun main() {
// Actually, we could've allow calls here because the owner explicitly declared as operator, but semantics is still weird
val c = C()
"" in c
"" !in c
1 !in c
2 in c
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> c
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> c
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>!in<!> c
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_WARNING!>in<!> c
c.contains("")
c.contains(1)
@@ -18,10 +18,10 @@ class C : java.util.concurrent.ConcurrentHashMap<String, Int>() {
fun main() {
val hm = java.util.concurrent.ConcurrentHashMap<String, Int>()
"" in hm
"" !in hm
1 !in hm
2 in hm
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> hm
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> hm
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> hm
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> hm
hm.contains("")
hm.contains(1)
@@ -32,10 +32,10 @@ fun main() {
1 !in (hm as Map<String, Int>)
val a = A()
"" in a
"" !in a
1 !in a
2 in a
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> a
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> a
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> a
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> a
' ' in a
' ' !in a
@@ -48,10 +48,10 @@ fun main() {
1 !in (a as Map<String, Int>)
val b = B()
"" in b
"" !in b
1 !in b
2 in b
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> b
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> b
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> b
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> b
b.contains("")
b.contains(1)
@@ -63,10 +63,10 @@ fun main() {
// Actually, we could've allow calls here because the owner explicitly declared as operator, but semantics is still weird
val c = C()
"" in c
"" !in c
1 !in c
2 in c
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> c
"" <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> c
1 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>!in<!> c
2 <!CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR!>in<!> c
c.contains("")
c.contains(1)
@@ -76,4 +76,3 @@ fun main() {
1 in (c as Map<String, Int>)
1 !in (c as Map<String, Int>)
}
@@ -4047,4 +4047,16 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirJvmErrors.CONCURRENT_HASH_MAP_CONTAINS_OPERATOR.errorFactory) { firDiagnostic ->
ConcurrentHashMapContainsOperatorErrorImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
add(FirJvmErrors.CONCURRENT_HASH_MAP_CONTAINS_OPERATOR.warningFactory) { firDiagnostic ->
ConcurrentHashMapContainsOperatorWarningImpl(
firDiagnostic as FirPsiDiagnostic,
token,
)
}
}
@@ -2816,4 +2816,12 @@ sealed class KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = SubclassCantCallCompanionProtectedNonStatic::class
}
abstract class ConcurrentHashMapContainsOperatorError : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConcurrentHashMapContainsOperatorError::class
}
abstract class ConcurrentHashMapContainsOperatorWarning : KtFirDiagnostic<PsiElement>() {
override val diagnosticClass get() = ConcurrentHashMapContainsOperatorWarning::class
}
}
@@ -4581,3 +4581,17 @@ internal class SubclassCantCallCompanionProtectedNonStaticImpl(
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ConcurrentHashMapContainsOperatorErrorImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.ConcurrentHashMapContainsOperatorError(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}
internal class ConcurrentHashMapContainsOperatorWarningImpl(
firDiagnostic: FirPsiDiagnostic,
override val token: ValidityToken,
) : KtFirDiagnostic.ConcurrentHashMapContainsOperatorWarning(), KtAbstractFirDiagnostic<PsiElement> {
override val firDiagnostic: FirPsiDiagnostic by weakRef(firDiagnostic)
}