[FIR] Add CAST_NEVER_SUCCEEDS

This commit is contained in:
Ivan Kochurkin
2021-09-16 20:58:52 +03:00
committed by TeamCityServer
parent 4ca757446a
commit 2b5524b18f
42 changed files with 206 additions and 273 deletions
@@ -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)
@@ -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>()
@@ -132,7 +132,7 @@ object CommonExpressionCheckers : ExpressionCheckers() {
override val typeOperatorCallCheckers: Set<FirTypeOperatorCallChecker>
get() = setOf(
FirUselessTypeOperationCallChecker,
FirCannotCheckForErasedChecker
FirCastOperatorsChecker
)
override val resolvedQualifierCheckers: Set<FirResolvedQualifierChecker>
@@ -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
@@ -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)
}
}
}
@@ -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