[FIR] Add CAST_NEVER_SUCCEEDS
This commit is contained in:
committed by
TeamCityServer
parent
4ca757446a
commit
2b5524b18f
+1
-1
@@ -9,7 +9,7 @@ fun test_1(b: B<String, Number>) {
|
||||
}
|
||||
|
||||
fun test_2(s: String) {
|
||||
val func = { s.length } as B<Int, Int>
|
||||
val func = { s.length } <!CAST_NEVER_SUCCEEDS!>as<!> B<Int, Int>
|
||||
}
|
||||
|
||||
class B<out K, V>(val k: K, val v: V)
|
||||
|
||||
+1
@@ -1105,6 +1105,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
|
||||
val CANNOT_CHECK_FOR_ERASED by error<PsiElement> {
|
||||
parameter<ConeKotlinType>("type")
|
||||
}
|
||||
val CAST_NEVER_SUCCEEDS by warning<KtBinaryExpressionWithTypeRHS>(PositioningStrategy.OPERATOR)
|
||||
val USELESS_CAST by warning<KtBinaryExpressionWithTypeRHS>(PositioningStrategy.AS_TYPE)
|
||||
val USELESS_IS_CHECK by warning<KtElement> {
|
||||
parameter<Boolean>("compileTimeCheckResult")
|
||||
|
||||
@@ -591,6 +591,7 @@ object FirErrors {
|
||||
|
||||
// Casts and is-checks
|
||||
val CANNOT_CHECK_FOR_ERASED by error1<PsiElement, ConeKotlinType>()
|
||||
val CAST_NEVER_SUCCEEDS by warning0<KtBinaryExpressionWithTypeRHS>(SourceElementPositioningStrategies.OPERATOR)
|
||||
val USELESS_CAST by warning0<KtBinaryExpressionWithTypeRHS>(SourceElementPositioningStrategies.AS_TYPE)
|
||||
val USELESS_IS_CHECK by warning1<KtElement, Boolean>()
|
||||
val IS_ENUM_ENTRY by error0<KtTypeReference>()
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
|
||||
override val typeOperatorCallCheckers: Set<FirTypeOperatorCallChecker>
|
||||
get() = setOf(
|
||||
FirUselessTypeOperationCallChecker,
|
||||
FirCannotCheckForErasedChecker
|
||||
FirCastOperatorsChecker
|
||||
)
|
||||
|
||||
override val resolvedQualifierCheckers: Set<FirResolvedQualifierChecker>
|
||||
|
||||
+87
-2
@@ -5,19 +5,104 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers
|
||||
|
||||
import org.jetbrains.kotlin.fir.FirSession
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||
import org.jetbrains.kotlin.fir.resolve.platformClassMapper
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
|
||||
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.typeContext
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes
|
||||
import org.jetbrains.kotlin.types.model.typeConstructor
|
||||
|
||||
fun isCastPossible(
|
||||
lhsType: ConeKotlinType,
|
||||
rhsType: ConeKotlinType,
|
||||
isSafeCase: Boolean,
|
||||
context: CheckerContext
|
||||
): Boolean {
|
||||
val lhsLowerType = lhsType.lowerBoundIfFlexible()
|
||||
val rhsLowerType = rhsType.lowerBoundIfFlexible()
|
||||
val session = context.session
|
||||
|
||||
if (lhsLowerType is ConeIntersectionType) {
|
||||
var result = false
|
||||
for (intersectedType in lhsLowerType.intersectedTypes) {
|
||||
val isIntersectedCastPossible = isCastPossible(intersectedType, rhsLowerType, isSafeCase, context)
|
||||
val intersectedTypeSymbol = intersectedType.toRegularClassSymbol(context.session)
|
||||
if (intersectedTypeSymbol?.isInterface == false && !isIntersectedCastPossible) {
|
||||
return false // Any class type in intersection type should be subtype of RHS
|
||||
}
|
||||
result = result or isIntersectedCastPossible
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
val lhsNullable = lhsLowerType.canBeNull
|
||||
val rhsNullable = rhsLowerType.canBeNull
|
||||
if (lhsLowerType.isNothing) return true
|
||||
if (lhsLowerType.isNullableNothing && !rhsNullable) {
|
||||
return isSafeCase
|
||||
}
|
||||
if (rhsLowerType.isNothing) return false
|
||||
if (rhsLowerType.isNullableNothing) return lhsNullable
|
||||
if (lhsNullable && rhsNullable) return true
|
||||
val lhsClassSymbol = lhsLowerType.toRegularClassSymbol(context.session)
|
||||
val rhsClassSymbol = rhsLowerType.toRegularClassSymbol(context.session)
|
||||
if (isRelated(lhsLowerType, rhsLowerType, lhsClassSymbol, rhsClassSymbol, context)) return true
|
||||
// This is an oversimplification (which does not render the method incomplete):
|
||||
// we consider any type parameter capable of taking any value, which may be made more precise if we considered bounds
|
||||
if (lhsLowerType is ConeTypeParameterType || rhsLowerType is ConeTypeParameterType) return true
|
||||
|
||||
if (isFinal(lhsLowerType, session) || isFinal(rhsLowerType, session)) return false
|
||||
if (lhsClassSymbol?.isInterface == true || rhsClassSymbol?.isInterface == true) return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Two types are related, roughly, when one of them is a subtype of the other constructing class
|
||||
*
|
||||
* Note that some types have platform-specific counterparts, i.e. kotlin.String is mapped to java.lang.String,
|
||||
* such types (and all their sub- and supertypes) are related too.
|
||||
*
|
||||
* Due to limitations in PlatformToKotlinClassMap, we only consider mapping of platform classes to Kotlin classed
|
||||
* (i.e. java.lang.String -> kotlin.String) and ignore mappings that go the other way.
|
||||
*/
|
||||
private fun isRelated(
|
||||
aType: ConeKotlinType,
|
||||
bType: ConeKotlinType,
|
||||
aClassSymbol: FirRegularClassSymbol?,
|
||||
bClassSymbol: FirRegularClassSymbol?,
|
||||
context: CheckerContext
|
||||
): Boolean {
|
||||
val typeContext = context.session.typeContext
|
||||
|
||||
if (AbstractTypeChecker.isSubtypeOf(typeContext, aType, bType) ||
|
||||
AbstractTypeChecker.isSubtypeOf(typeContext, bType, aType)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
fun getCorrespondingKotlinClass(type: ConeKotlinType): ConeKotlinType {
|
||||
return context.session.platformClassMapper.getCorrespondingKotlinClass(type.classId)?.defaultType(listOf()) ?: type
|
||||
}
|
||||
|
||||
val aNormalizedType = getCorrespondingKotlinClass(aClassSymbol?.defaultType() ?: aType)
|
||||
val bNormalizedType = getCorrespondingKotlinClass(bClassSymbol?.defaultType() ?: bType)
|
||||
|
||||
return AbstractTypeChecker.isSubtypeOf(typeContext, aNormalizedType, bNormalizedType) ||
|
||||
AbstractTypeChecker.isSubtypeOf(typeContext, bNormalizedType, aNormalizedType)
|
||||
}
|
||||
|
||||
private fun isFinal(type: ConeKotlinType, session: FirSession): Boolean {
|
||||
return !type.canHaveSubtypes(session)
|
||||
}
|
||||
|
||||
fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: CheckerContext): Boolean {
|
||||
val typeContext = context.session.typeContext
|
||||
|
||||
|
||||
-31
@@ -1,31 +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.expression
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isCastErased
|
||||
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.expressions.FirOperation
|
||||
import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
|
||||
object FirCannotCheckForErasedChecker : FirTypeOperatorCallChecker() {
|
||||
override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
if (expression.operation != FirOperation.IS) return
|
||||
|
||||
val session = context.session
|
||||
val subjectType = expression.argumentList.arguments[0].typeRef.coneType.fullyExpandedType(session)
|
||||
val conversionTypeRef = expression.conversionTypeRef
|
||||
val targetType = conversionTypeRef.coneType.fullyExpandedType(session)
|
||||
|
||||
if (isCastErased(subjectType, targetType, context)) {
|
||||
reporter.reportOn(conversionTypeRef.source, FirErrors.CANNOT_CHECK_FOR_ERASED, targetType, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,13 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.CastingType
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.isCastErased
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.checkCasting
|
||||
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.expressions.FirExpressionWithSmartcast
|
||||
import org.jetbrains.kotlin.fir.expressions.FirOperation
|
||||
import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall
|
||||
|
||||
Reference in New Issue
Block a user