[K2] Report CONCURRENT_HASH_MAP_CONTAINS_OPERATOR_ERROR for class hierarchies
Previously it wasn't reported in FIR for ConcurrentHashMap inheritors because the receiver id hasn't matched CHM.contains id Fixed by unwrapping origin of the call in case of fake overrides ^KT-55606 fixed
This commit is contained in:
committed by
Space Team
parent
b09561c3c3
commit
21fe0e80ff
+16
-6
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.containingClassLookupTag
|
||||
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
||||
import org.jetbrains.kotlin.fir.expressions.FirFunctionCallOrigin
|
||||
import org.jetbrains.kotlin.fir.originalOrSelf
|
||||
import org.jetbrains.kotlin.fir.references.toResolvedCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.resolve.toFirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol
|
||||
@@ -27,30 +28,39 @@ import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
/**
|
||||
* This checker detects if a call by operator 'contains' convention to a Java method violates the expected contract:
|
||||
* * "key in map" commonly resolves to stdlib extension that calls Map.containsKey(),
|
||||
* but there's a member in ConcurrentHashMap with acceptable signature that delegates to `containsValue` instead,
|
||||
* leading to an unexpected result. See KT-18053
|
||||
*/
|
||||
object FirJvmInconsistentOperatorFromJavaCallChecker : FirFunctionCallChecker() {
|
||||
private val CONCURRENT_HASH_MAP_CALLABLE_ID = CallableId(
|
||||
ClassId.fromString("java/util/concurrent/ConcurrentHashMap"),
|
||||
Name.identifier("contains")
|
||||
OperatorNameConventions.CONTAINS
|
||||
)
|
||||
|
||||
override fun check(expression: FirFunctionCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
// Filter out non-operators
|
||||
if (expression.origin != FirFunctionCallOrigin.Operator) return
|
||||
val callableSymbol = expression.calleeReference.toResolvedCallableSymbol() as? FirNamedFunctionSymbol ?: return
|
||||
// Filter out non-contains
|
||||
if (callableSymbol.name != OperatorNameConventions.CONTAINS) return
|
||||
val valueParameterSymbol = callableSymbol.valueParameterSymbols.singleOrNull() ?: return
|
||||
val type = valueParameterSymbol.resolvedReturnTypeRef.coneType.lowerBoundIfFlexible()
|
||||
// Filter out handrolled contains with non-Any type
|
||||
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: KtSourceElement?, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
|
||||
if (callableId == CONCURRENT_HASH_MAP_CALLABLE_ID) {
|
||||
private fun FirNamedFunctionSymbol.check(source: KtSourceElement?, context: CheckerContext, reporter: DiagnosticReporter): Boolean {
|
||||
// Unwrap SubstitutionOverride origin if necessary
|
||||
if (originalOrSelf().callableId == CONCURRENT_HASH_MAP_CALLABLE_ID) {
|
||||
reporter.reportOn(source, FirJvmErrors.CONCURRENT_HASH_MAP_CONTAINS_OPERATOR, context)
|
||||
return true
|
||||
}
|
||||
|
||||
// Check explicitly overridden contains
|
||||
val containingClass = containingClassLookupTag()?.toFirRegularClassSymbol(context.session) ?: return false
|
||||
val overriddenFunctions = overriddenFunctions(containingClass, context)
|
||||
for (overriddenFunction in overriddenFunctions) {
|
||||
|
||||
Reference in New Issue
Block a user