[FIR] Utilize equality compatibility logic for cast checks

This makes it more consistent and fixes some
overlooked corner cases. Also it was decided
on the last equality applicability DM
(KT-62646) that we'd like
`is`/`!is`/`as`/`as?` to work similarly
to `===`/`!==`.

Also note that it now gives a clearer
explaination of why some corner cases work
the way they do. For example,
`FirPsiDiagnosticTestGenerated.testLambdaInLhsOfTypeOperatorCall`
yields `UNCHECKED_CAST` instead of
`CAST_NEVER_SUCCEEDS`, because
`toTypeInfo()` replaces all type arguments
with star projections, even when the argument
is not a type parameter. This is because
it has been desided to work this way in
KT-57779.

In
`FirPsiOldFrontendDiagnosticsTestGenerated..NeverSucceeds#testNoGenericsRelated`
the diagnostic is introduced, because
`t2 as FC1` and `FC1` is a final class with
no `T5` supertype.

`UNCHECKED_CAST` in
`FirPsiOldFrontendDiagnosticsTestGenerated.testSmartCast`
disappeared, because previously we didn't
take smartcasts into account.

Note that
`FirPsiOldFrontendDiagnosticsTestGenerated.testMappedSubtypes`
is a false positive. It appears because `isSubtypeOf()` doesn't
take into account platform types in supertypes of the given types
(doesn't map them).
This commit is contained in:
Nikolay Lunyak
2024-02-28 14:17:35 +02:00
committed by Space Team
parent a5e43c9e3f
commit fab6cec93a
25 changed files with 253 additions and 193 deletions
@@ -9,7 +9,7 @@ fun test_1(b: B<String, Number>) {
} }
fun test_2(s: String) { fun test_2(s: String) {
val func = { s.length } <!UNCHECKED_CAST!>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) class B<out K, V>(val k: K, val v: V)
@@ -79,6 +79,6 @@ fun Any.test_5(): Int = when {
fun Any.test_6() { fun Any.test_6() {
this as List<*> this as List<*>
size size
this as String this <!CAST_NEVER_SUCCEEDS!>as<!> String
length length
} }
@@ -5,139 +5,18 @@
package org.jetbrains.kotlin.fir.analysis.checkers 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.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.declarations.utils.isInterface
import org.jetbrains.kotlin.fir.declarations.utils.isLocal import org.jetbrains.kotlin.fir.declarations.utils.isLocal
import org.jetbrains.kotlin.fir.resolve.defaultType import org.jetbrains.kotlin.fir.resolve.defaultType
import org.jetbrains.kotlin.fir.resolve.getClassAndItsOuterClassesWhenLocal import org.jetbrains.kotlin.fir.resolve.getClassAndItsOuterClassesWhenLocal
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap
import org.jetbrains.kotlin.fir.scopes.platformClassMapper
import org.jetbrains.kotlin.fir.symbols.impl.FirClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.types.AbstractTypeChecker import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes
import org.jetbrains.kotlin.types.TypeCheckerState
import org.jetbrains.kotlin.types.model.typeConstructor import org.jetbrains.kotlin.types.model.typeConstructor
enum class CastingType {
Possible,
Impossible,
Always
}
fun checkCasting(
lhsType: ConeKotlinType,
rhsType: ConeKotlinType,
isSafeCase: Boolean,
context: CheckerContext
): CastingType {
val lhsLowerType = lhsType.lowerBoundIfFlexible().originalIfDefinitelyNotNullable()
val rhsLowerType = rhsType.lowerBoundIfFlexible().originalIfDefinitelyNotNullable()
if (lhsLowerType is ConeErrorType || rhsLowerType is ConeErrorType) return CastingType.Possible
val session = context.session
if (lhsLowerType is ConeIntersectionType) {
var result = false
for (intersectedType in lhsLowerType.intersectedTypes) {
val isIntersectedCastPossible = checkCasting(intersectedType, rhsLowerType, isSafeCase, context)
val intersectedTypeSymbol = intersectedType.toRegularClassSymbol(session)
if (intersectedTypeSymbol?.isInterface == false && isIntersectedCastPossible == CastingType.Impossible) {
return CastingType.Impossible // Any class type in intersection type should be subtype of RHS
}
result = result or (isIntersectedCastPossible != CastingType.Impossible)
}
return if (result) CastingType.Possible else CastingType.Impossible
}
val lhsNullable = lhsLowerType.canBeNull(session)
val rhsNullable = rhsLowerType.canBeNull(session)
if (lhsLowerType.isNothing) return CastingType.Possible
if (lhsLowerType.isNullableNothing && !rhsNullable) {
return if (isSafeCase) CastingType.Always else CastingType.Impossible
}
if (rhsLowerType.isNothing) return CastingType.Impossible
if (rhsLowerType.isNullableNothing) {
return if (lhsNullable) CastingType.Possible else CastingType.Impossible
}
if (lhsNullable && rhsNullable) return CastingType.Possible
// 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 CastingType.Possible
val lhsClassSymbol = lhsLowerType.toRegularClassSymbol(session)
val rhsClassSymbol = rhsLowerType.toRegularClassSymbol(session)
val lhsNormalizedType = getCorrespondingKotlinClass(lhsClassSymbol?.defaultType() ?: lhsLowerType, session)
val rhsNormalizedType = getCorrespondingKotlinClass(rhsClassSymbol?.defaultType() ?: rhsLowerType, session)
val state = session.typeContext.newTypeCheckerState(errorTypesEqualToAnything = false, stubTypesEqualToAnything = false)
// It's an optimization, the code below with `isRoughSubtypeOf` also checks subtyping, but it's slower
if (AbstractTypeChecker.isSubtypeOf(state, lhsNormalizedType, rhsNormalizedType) ||
AbstractTypeChecker.isSubtypeOf(state, rhsNormalizedType, lhsNormalizedType)
) {
return CastingType.Possible
}
if (isRoughSubtypeOf(lhsNormalizedType, rhsNormalizedType, state, session) ||
isRoughSubtypeOf(rhsNormalizedType, lhsNormalizedType, state, session)
) {
return CastingType.Possible
}
if (isFinal(lhsNormalizedType, session) || isFinal(rhsNormalizedType, session)) return CastingType.Impossible
val lhsNormalizedTypeSymbol = lhsNormalizedType.toSymbol(session) as? FirClassSymbol<*>
val rhsNormalizedTypeSymbol = rhsNormalizedType.toSymbol(session) as? FirClassSymbol<*>
if (lhsNormalizedTypeSymbol?.isInterface == true || rhsNormalizedTypeSymbol?.isInterface == true) return CastingType.Possible
return CastingType.Impossible
}
/**
* One type is roughly subtype of another superType when one of type's supertype constructor equals another superType constructor.
*
* 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 isRoughSubtypeOf(
type: ConeSimpleKotlinType,
superType: ConeSimpleKotlinType,
state: TypeCheckerState,
session: FirSession
): Boolean {
var result = false
val superTypeConstructor = superType.typeConstructor(state.typeSystemContext)
state.anySupertype(type, { typeMarker ->
val correspondingKotlinClass = getCorrespondingKotlinClass(typeMarker as ConeSimpleKotlinType, session)
if (correspondingKotlinClass.typeConstructor(state.typeSystemContext) == superTypeConstructor) {
result = true
true
} else {
false
}
}, { TypeCheckerState.SupertypesPolicy.LowerIfFlexible })
return result
}
private fun getCorrespondingKotlinClass(type: ConeSimpleKotlinType, session: FirSession): ConeSimpleKotlinType {
return session.platformClassMapper.getCorrespondingKotlinClass(type.classId)?.defaultType(emptyList()) ?: type
}
private fun isFinal(type: ConeSimpleKotlinType, session: FirSession): Boolean {
return !type.canHaveSubtypesAccordingToK1(session)
}
fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: CheckerContext): Boolean { fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: CheckerContext): Boolean {
val typeContext = context.session.typeContext val typeContext = context.session.typeContext
@@ -32,11 +32,13 @@ internal fun FirExpression.unwrapToMoreUsefulExpression() = when (this) {
internal class TypeInfo( internal class TypeInfo(
val type: ConeKotlinType, val type: ConeKotlinType,
val notNullType: ConeKotlinType, val notNullType: ConeKotlinType,
val directType: ConeKotlinType,
val isEnumClass: Boolean, val isEnumClass: Boolean,
val isPrimitive: Boolean, val isPrimitive: Boolean,
val isBuiltin: Boolean, val isBuiltin: Boolean,
val isValueClass: Boolean, val isValueClass: Boolean,
val isFinal: Boolean, val isFinal: Boolean,
val isClass: Boolean,
val canHaveSubtypesAccordingToK1: Boolean, val canHaveSubtypesAccordingToK1: Boolean,
) { ) {
override fun toString() = "$type" override fun toString() = "$type"
@@ -64,20 +66,22 @@ internal fun ConeKotlinType.toTypeInfo(session: FirSession): TypeInfo {
val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) } val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) }
?: session.builtinTypes.nullableAnyType.type ?: session.builtinTypes.nullableAnyType.type
val notNullType = type.withNullability(ConeNullability.NOT_NULL, session.typeContext) val notNullType = type.withNullability(ConeNullability.NOT_NULL, session.typeContext)
val boundsSymbols = bounds.mapNotNull { it.toClassSymbol(session) }
return TypeInfo( return TypeInfo(
type, notNullType, type, notNullType, directType = this,
isEnumClass = bounds.any { it.isEnum(session) }, isEnumClass = boundsSymbols.any { it.isEnumClass },
isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive }, isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive },
isBuiltin = bounds.any { it.toClassSymbol(session)?.isBuiltin == true }, isBuiltin = boundsSymbols.any { it.isBuiltin },
isValueClass = bounds.any { it.toClassSymbol(session)?.isInline == true }, isValueClass = boundsSymbols.any { it.isInline },
isFinal = bounds.any { it.toClassSymbol(session)?.isFinalClass == true }, isFinal = boundsSymbols.any { it.isFinalClass },
isClass = boundsSymbols.any { it.isClass },
// In K1's intersector, `canHaveSubtypes()` is called for `nullabilityStripped`. // In K1's intersector, `canHaveSubtypes()` is called for `nullabilityStripped`.
withNullability(ConeNullability.NOT_NULL, session.typeContext).canHaveSubtypesAccordingToK1(session), withNullability(ConeNullability.NOT_NULL, session.typeContext).canHaveSubtypesAccordingToK1(session),
) )
} }
private fun ConeClassLikeType.toKotlinTypeIfPlatform(session: FirSession): ConeClassLikeType { internal fun ConeClassLikeType.toKotlinTypeIfPlatform(session: FirSession): ConeClassLikeType {
val kotlinClassId = session.platformClassMapper.getCorrespondingKotlinClass(lookupTag.classId) val kotlinClassId = session.platformClassMapper.getCorrespondingKotlinClass(lookupTag.classId)
return kotlinClassId?.constructClassLikeType(typeArguments, isNullable, attributes) ?: this return kotlinClassId?.constructClassLikeType(typeArguments, isNullable, attributes) ?: this
} }
@@ -8,54 +8,133 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.CastingType import org.jetbrains.kotlin.fir.analysis.checkers.*
import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.checkCasting
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.finalApproximationOrSelf
import org.jetbrains.kotlin.fir.analysis.checkers.isCastErased
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.expressions.FirOperation import org.jetbrains.kotlin.fir.expressions.*
import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall
import org.jetbrains.kotlin.fir.expressions.unwrapSmartcastExpression
import org.jetbrains.kotlin.fir.firPlatformSpecificCastChecker import org.jetbrains.kotlin.fir.firPlatformSpecificCastChecker
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
import org.jetbrains.kotlin.fir.types.ConeDynamicType import org.jetbrains.kotlin.fir.types.*
import org.jetbrains.kotlin.fir.types.coneType
import org.jetbrains.kotlin.fir.types.resolvedType
object FirCastOperatorsChecker : FirTypeOperatorCallChecker(MppCheckerKind.Common) { object FirCastOperatorsChecker : FirTypeOperatorCallChecker(MppCheckerKind.Common) {
override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) { override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
val session = context.session val arguments = expression.argumentList.arguments
val firstArgument = expression.argumentList.arguments[0] require(arguments.size == 1) { "Type operator call with non-1 arguments" }
val actualType = firstArgument.unwrapSmartcastExpression().resolvedType.fullyExpandedType(session).finalApproximationOrSelf(context)
val conversionTypeRef = expression.conversionTypeRef
val targetType = conversionTypeRef.coneType.fullyExpandedType(session).finalApproximationOrSelf(context)
if (expression.operation in FirOperation.TYPES && targetType is ConeDynamicType) { val l = arguments[0].toArgumentInfo(context)
reporter.reportOn(conversionTypeRef.source, FirErrors.DYNAMIC_NOT_ALLOWED, context) val r = expression.conversionTypeRef.coneType
.fullyExpandedType(context.session)
.finalApproximationOrSelf(context)
.toTypeInfo(context.session)
if (expression.operation in FirOperation.TYPES && r.directType is ConeDynamicType) {
reporter.reportOn(expression.conversionTypeRef.source, FirErrors.DYNAMIC_NOT_ALLOWED, context)
} }
val isSafeAs = expression.operation == FirOperation.SAFE_AS val checkApplicability = when (expression.operation) {
if (expression.operation == FirOperation.AS || isSafeAs) { FirOperation.IS, FirOperation.NOT_IS -> ::checkIsApplicability
val castType = checkCasting(actualType, targetType, isSafeAs, context) FirOperation.AS, FirOperation.SAFE_AS -> ::checkAsApplicability
if (castType == CastingType.Impossible) { else -> error("Invalid operator of FirTypeOperatorCall")
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) { }
if (!session.firPlatformSpecificCastChecker.shouldSuppressImpossibleCast(session, actualType, targetType)) {
reporter.reportOn(expression.source, FirErrors.CAST_NEVER_SUCCEEDS, context) val rUserType = expression.conversionTypeRef.coneType.finalApproximationOrSelf(context)
}
} // No need to check original types separately from smartcast types, because we only report warnings
} else if (castType == CastingType.Always) { checkApplicability(l.smartCastTypeInfo, r, expression, context).ifInapplicable {
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) { return reporter.reportInapplicabilityDiagnostic(expression, it, l.originalTypeInfo, r.type, l.userType, rUserType, context)
reporter.reportOn(expression.source, FirErrors.USELESS_CAST, context)
}
} else if (isCastErased(actualType, targetType, context)) {
reporter.reportOn(expression.source, FirErrors.UNCHECKED_CAST, actualType, targetType, context)
}
} else if (expression.operation == FirOperation.IS) {
if (isCastErased(actualType, targetType, context)) {
reporter.reportOn(conversionTypeRef.source, FirErrors.CANNOT_CHECK_FOR_ERASED, targetType, context)
}
} }
} }
// IDE doesn't care this function is referenced as :: and then used polymorphicly
@Suppress("UNUSED_PARAMETER")
private fun checkIsApplicability(l: TypeInfo, r: TypeInfo, expression: FirTypeOperatorCall, context: CheckerContext): Applicability {
return when {
isCastErased(l.directType, r.directType, context) -> Applicability.CAST_ERASED
else -> Applicability.APPLICABLE
}
}
private fun checkAsApplicability(l: TypeInfo, r: TypeInfo, expression: FirTypeOperatorCall, context: CheckerContext): Applicability {
val oneIsFinal = l.isFinal || r.isFinal
val oneIsNotNull = !l.type.isNullable || !r.type.isNullable
val isNullableNothingWithNotNull = !l.type.isNullable && r.type.isNullableNothing
|| l.type.isNullableNothing && !r.type.isNullable
return when {
l.type.isNothing && r.type.isNothingOrNullableNothing -> Applicability.APPLICABLE
r.type.isNothing -> Applicability.IMPOSSIBLE
isNullableNothingWithNotNull -> when (expression.operation) {
// (null as? WhatEver) == null
FirOperation.SAFE_AS -> Applicability.USELESS
else -> Applicability.IMPOSSIBLE
}
oneIsNotNull && oneIsFinal && areUnrelated(l, r, context) -> Applicability.IMPOSSIBLE
isCastErased(l.directType, r.directType, context) -> Applicability.CAST_ERASED
else -> Applicability.APPLICABLE
}
}
private fun areUnrelated(a: TypeInfo, b: TypeInfo, context: CheckerContext) =
!a.isSubtypeOf(b, context) && !b.isSubtypeOf(a, context)
private fun TypeInfo.isSubtypeOf(other: TypeInfo, context: CheckerContext) =
notNullType.isSubtypeOf(other.notNullType, context.session)
/**
* K1 reports different diagnostics for different
* cases, and this enum helps to replicate the K1's
* choice of diagnostics.
*
* Should the K2's diagnostic severity differ,
* the proper version will be picked later
* when reporting the diagnostic.
*/
private enum class Applicability {
APPLICABLE,
IMPOSSIBLE,
USELESS,
CAST_ERASED,
}
private inline fun Applicability.ifInapplicable(block: (Applicability) -> Unit) = when (this) {
Applicability.APPLICABLE -> {}
else -> block(this)
}
private fun DiagnosticReporter.reportInapplicabilityDiagnostic(
expression: FirTypeOperatorCall,
applicability: Applicability,
l: TypeInfo,
r: ConeKotlinType,
lUserType: ConeKotlinType,
rUserType: ConeKotlinType,
context: CheckerContext,
) {
when (applicability) {
Applicability.IMPOSSIBLE -> getImpossibilityDiagnostic(l, r, context)?.let {
reportOn(expression.source, it, context)
}
Applicability.USELESS -> getUselessnessDiagnostic(context)?.let {
reportOn(expression.source, it, context)
}
Applicability.CAST_ERASED -> when {
expression.operation == FirOperation.AS || expression.operation == FirOperation.SAFE_AS -> {
reportOn(expression.source, FirErrors.UNCHECKED_CAST, lUserType, rUserType, context)
}
else -> reportOn(expression.conversionTypeRef.source, FirErrors.CANNOT_CHECK_FOR_ERASED, rUserType, context)
}
else -> error("Shouldn't be here")
}
}
private fun getImpossibilityDiagnostic(l: TypeInfo, rType: ConeKotlinType, context: CheckerContext) = when {
!context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2) -> null
context.session.firPlatformSpecificCastChecker.shouldSuppressImpossibleCast(context.session, l.type, rType) -> null
else -> FirErrors.CAST_NEVER_SUCCEEDS
}
private fun getUselessnessDiagnostic(context: CheckerContext) = when {
!context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2) -> null
else -> FirErrors.USELESS_CAST
}
} }
@@ -28,7 +28,7 @@ inline fun <reified T> test(x: T?, a: Any) {
a is <!CANNOT_CHECK_FOR_ERASED!>Box<T><!> a is <!CANNOT_CHECK_FOR_ERASED!>Box<T><!>
a is <!CANNOT_CHECK_FOR_ERASED!>Array<T><!> a is <!CANNOT_CHECK_FOR_ERASED!>Array<T><!>
a <!UNCHECKED_CAST!>as Box<T><!> a <!UNCHECKED_CAST!>as Box<T><!>
a <!UNCHECKED_CAST!>as Array<T><!> a <!CAST_NEVER_SUCCEEDS!>as<!> Array<T>
a is <!CANNOT_CHECK_FOR_ERASED!>Box<List<T>><!> a is <!CANNOT_CHECK_FOR_ERASED!>Box<List<T>><!>
a is <!CANNOT_CHECK_FOR_ERASED!>Array<List<T>><!> a is <!CANNOT_CHECK_FOR_ERASED!>Array<List<T>><!>
@@ -0,0 +1,21 @@
// !DIAGNOSTICS: -PLATFORM_CLASS_MAPPED_TO_KOTLIN -UNUSED_PARAMETER -ABSTRACT_MEMBER_NOT_IMPLEMENTED -USELESS_CAST
import java.lang.CharSequence as JCS
class JSub: JCS
class Sub: CharSequence
fun test(
s: Sub,
js: JSub,
cs: CharSequence,
jcs: JCS
) {
// js as CharSequence // - this case is not supported due to limitation in PlatformToKotlinClassMap
js <!CAST_NEVER_SUCCEEDS!>as<!> JCS
s as CharSequence
s as JCS
js <!CAST_NEVER_SUCCEEDS!>as<!> Sub
s <!CAST_NEVER_SUCCEEDS!>as<!> JSub
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -PLATFORM_CLASS_MAPPED_TO_KOTLIN -UNUSED_PARAMETER -ABSTRACT_MEMBER_NOT_IMPLEMENTED -USELESS_CAST // !DIAGNOSTICS: -PLATFORM_CLASS_MAPPED_TO_KOTLIN -UNUSED_PARAMETER -ABSTRACT_MEMBER_NOT_IMPLEMENTED -USELESS_CAST
import java.lang.CharSequence as JCS import java.lang.CharSequence as JCS
@@ -0,0 +1,46 @@
// !DIAGNOSTICS: -USELESS_CAST -UNCHECKED_CAST
interface T1
interface T2
interface T3
open class OC1: T1
open class OC2: OC1(), T2
class FC1: OC2(), T3
interface T4: <!INTERFACE_WITH_SUPERCLASS!>OC1<!>
interface T5: T2
fun <TP1: OC1, TP2: T2, TP3: OC2> test(
t2: T2,
t4: T4,
fc1: FC1,
oc1: OC1,
oc2: OC2,
tp1: TP1,
tp2: TP2
) {
fc1 as FC1
fc1 as OC1
fc1 as T1
fc1 as TP1
oc1 as FC1
oc1 as OC2
oc2 as OC1
oc1 as T2
oc1 as T1
oc1 as TP1
oc1 as TP2
t2 as FC1
t2 as OC2
t4 as OC1
t2 as T2
t2 <!CAST_NEVER_SUCCEEDS!>as<!> T5
t2 as TP2
tp1 as FC1
tp1 as OC1
tp1 as OC2
tp2 as T2
tp2 as T5
tp1 as TP3
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -USELESS_CAST -UNCHECKED_CAST // !DIAGNOSTICS: -USELESS_CAST -UNCHECKED_CAST
interface T1 interface T1
interface T2 interface T2
@@ -0,0 +1,34 @@
// !DIAGNOSTICS: -UNCHECKED_CAST
interface Trait1
interface Trait2
open class OClass1
open class OClass2
class FClass1
class FClass2
fun <TP1: OClass1, TP2: OClass2> test(
t1: Trait1,
oc1: OClass1,
fc1: FClass1,
tp1: TP1
) {
t1 as Trait2
t1 as OClass2
t1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
t1 as TP2
oc1 as Trait2
oc1 as OClass2
oc1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
oc1 as TP2
fc1 <!CAST_NEVER_SUCCEEDS!>as<!> Trait2
fc1 <!CAST_NEVER_SUCCEEDS!>as<!> OClass2
fc1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
fc1 as TP2
tp1 as Trait2
tp1 as OClass2
tp1 <!CAST_NEVER_SUCCEEDS!>as<!> FClass2
tp1 as TP2
}
@@ -1,4 +1,3 @@
// FIR_IDENTICAL
// !DIAGNOSTICS: -UNCHECKED_CAST // !DIAGNOSTICS: -UNCHECKED_CAST
interface Trait1 interface Trait1
interface Trait2 interface Trait2
@@ -37,7 +37,7 @@ fun <E> foo(x: Any, y: Any) : Any {
y <!UNCHECKED_CAST!>as Outer<*><!> y <!UNCHECKED_CAST!>as Outer<*><!>
y as <!NO_TYPE_ARGUMENTS_ON_RHS!>Outer<!> y as <!NO_TYPE_ARGUMENTS_ON_RHS!>Outer<!>
y <!UNCHECKED_CAST!>as Outer<*>.Inner<!> y <!CAST_NEVER_SUCCEEDS!>as<!> Outer<*>.Inner
y as <!NO_TYPE_ARGUMENTS_ON_RHS!>Outer.Inner<!> y as <!NO_TYPE_ARGUMENTS_ON_RHS!>Outer.Inner<!>
return C() return C()
@@ -15,7 +15,7 @@ public interface I<K> {}
// FILE: main.kt // FILE: main.kt
fun main(z: I<String>) { fun main(z: I<String>) {
z <!UNCHECKED_CAST!>as Test<Test2<Int, *>><!> z <!UNCHECKED_CAST!>as Test<Test2<Int, *>><!>
z <!UNCHECKED_CAST!>as Test<Test2<Int, <!UNRESOLVED_REFERENCE!>Foo<!>>><!> z as Test<Test2<Int, <!UNRESOLVED_REFERENCE!>Foo<!>>>
z as Test<<!UNRESOLVED_REFERENCE!>Foo<!>> z as Test<<!UNRESOLVED_REFERENCE!>Foo<!>>
z as <!UNRESOLVED_REFERENCE!>Any2<!> z as <!UNRESOLVED_REFERENCE!>Any2<!>
println(z) println(z)
@@ -11,6 +11,6 @@ public class A {
fun main(a: A, ml: Any) { fun main(a: A, ml: Any) {
if (ml is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!>) { if (ml is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!>) {
a.foo(<!JAVA_TYPE_MISMATCH!>ml<!>) a.foo(<!JAVA_TYPE_MISMATCH!>ml<!>)
a.foo(ml <!UNCHECKED_CAST!>as List<Any><!>) a.foo(ml as List<Any>)
} }
} }
@@ -16,7 +16,7 @@ fun String?.gav() {}
fun bar(s: String?) { fun bar(s: String?) {
if (s != null) return if (s != null) return
s.gav() s.gav()
s as? String s <!USELESS_CAST!>as? String<!>
s as String? s as String?
s as String s <!CAST_NEVER_SUCCEEDS!>as<!> String
} }
@@ -43,8 +43,8 @@ fun f(a: SomeClass?) {
// 'aa' cannot be cast to SomeSubClass // 'aa' cannot be cast to SomeSubClass
aa<!UNSAFE_CALL!>.<!>hashCode() aa<!UNSAFE_CALL!>.<!>hashCode()
aa.<!UNRESOLVED_REFERENCE!>foo<!> aa.<!UNRESOLVED_REFERENCE!>foo<!>
(aa as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo (aa <!USELESS_CAST!>as? SomeSubClass<!>)<!UNSAFE_CALL!>.<!>foo
(aa as SomeSubClass).foo (aa <!CAST_NEVER_SUCCEEDS!>as<!> SomeSubClass).foo
} }
val b = (aa as? SomeSubClass)?.foo val b = (aa as? SomeSubClass)?.foo
aa = null aa = null
@@ -52,8 +52,8 @@ fun f(a: SomeClass?) {
// 'aa' cannot be cast to SomeSubClass // 'aa' cannot be cast to SomeSubClass
aa<!UNSAFE_CALL!>.<!>hashCode() aa<!UNSAFE_CALL!>.<!>hashCode()
aa.<!UNRESOLVED_REFERENCE!>foo<!> aa.<!UNRESOLVED_REFERENCE!>foo<!>
(aa as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo (aa <!USELESS_CAST!>as? SomeSubClass<!>)<!UNSAFE_CALL!>.<!>foo
(aa as SomeSubClass).foo (aa <!CAST_NEVER_SUCCEEDS!>as<!> SomeSubClass).foo
} }
aa = a aa = a
val c = aa as? SomeSubClass val c = aa as? SomeSubClass
@@ -55,5 +55,5 @@ fun testMany(a: Any) {
manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a<!>) manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a<!>)
manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a<!>) manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a<!>)
manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a as Int<!>) manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a as Int<!>)
manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a as String<!>) manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_ERROR!>a <!CAST_NEVER_SUCCEEDS!>as<!> String<!>)
} }
@@ -55,5 +55,5 @@ fun testMany(a: Any) {
manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a<!>) manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a<!>)
manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a<!>) manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a<!>)
manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a as Int<!>) manyFoo(v = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a as Int<!>)
manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a as String<!>) manyFoo(s = <!ARGUMENT_TYPE_MISMATCH, ASSIGNING_SINGLE_ELEMENT_TO_VARARG_IN_NAMED_FORM_FUNCTION_WARNING!>a <!CAST_NEVER_SUCCEEDS!>as<!> String<!>)
} }
+3 -3
View File
@@ -1,8 +1,8 @@
// !DIAGNOSTICS: -UNCHECKED_CAST // !DIAGNOSTICS: -UNCHECKED_CAST
fun <T> Collection<T>.toArray(): Array<T> = this as Array<T> fun <T> Collection<T>.toArray(): Array<T> = this <!CAST_NEVER_SUCCEEDS!>as<!> Array<T>
fun Collection<String>.toArray2(): Array<String> = this as Array<String> fun Collection<String>.toArray2(): Array<String> = this <!CAST_NEVER_SUCCEEDS!>as<!> Array<String>
fun <T> toArray3(x: Collection<T>): Array<T> = x as Array<T> fun <T> toArray3(x: Collection<T>): Array<T> = x <!CAST_NEVER_SUCCEEDS!>as<!> Array<T>
class Foo<T> { class Foo<T> {
operator fun plus(x: Foo<T>): Array<T> { operator fun plus(x: Foo<T>): Array<T> {
@@ -55,7 +55,7 @@ class Case8<K> {
} }
// TESTCASE NUMBER: 9 // TESTCASE NUMBER: 9
inline fun <reified L: Nothing?> case_9(x: L) = x!! as Int inline fun <reified L: Nothing?> case_9(x: L) = x!! <!CAST_NEVER_SUCCEEDS!>as<!> Int
// TESTCASE NUMBER: 10 // TESTCASE NUMBER: 10
fun <K> case_10(x: Nothing) = x as Iterable<K> fun <K> case_10(x: Nothing) = x as Iterable<K>
@@ -44,7 +44,7 @@ fun case_2(x: Int?, y: Nothing?) {
*/ */
fun case_3(x: Int?) { fun case_3(x: Int?) {
if (x == null) { if (x == null) {
x as Int x <!CAST_NEVER_SUCCEEDS!>as<!> Int
stringArg(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Nothing")!>x<!>) stringArg(<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Nothing")!>x<!>)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Nothing")!>x<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Int? & kotlin.Nothing")!>x<!>
} }
@@ -124,7 +124,7 @@ fun case_10() {
do { do {
x = null x = null
break break
} while ((x as String).length > 1) } while ((x <!CAST_NEVER_SUCCEEDS!>as<!> String).length > 1)
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>length <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.String? & kotlin.Nothing?")!>x<!><!UNSAFE_CALL!>.<!>length
} }
@@ -106,10 +106,10 @@ fun case_6() {
val e: Any? val e: Any?
when (if (true) {b = 11} else {b = 12}) { when (if (true) {b = 11} else {b = 12}) {
when (if (true) {b.inv(); c = b; c as ClassLevel1;} else {b.inv(); c = b; c as ClassLevel1;}) { when (if (true) {b.inv(); c = b; c <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel1;} else {b.inv(); c = b; c <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel1;}) {
else -> kotlin.Unit else -> kotlin.Unit
} -> when (if (true) {c.test1(); d = c; d as ClassLevel2} else {c.test1(); d = c; d as ClassLevel2}) { } -> when (if (true) {c.test1(); d = c; d <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel2} else {c.test1(); d = c; d <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel2}) {
when (if (true) {d.test2(); e = d; e as ClassLevel3} else {d.test2(); e = d; e as ClassLevel3}) { when (if (true) {d.test2(); e = d; e <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel3} else {d.test2(); e = d; e <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel3}) {
else -> ClassLevel2() else -> ClassLevel2()
} -> { } -> {
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Any? & kotlin.Int & ClassLevel3")!>e<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Any? & kotlin.Int & ClassLevel3")!>e<!>
@@ -140,7 +140,7 @@ fun case_11(z: Any?, x: Any?) {
fun case_12(z: Any?) { fun case_12(z: Any?) {
val y = z.let { val y = z.let {
return@let it as Int return@let it as Int
it as? Float ?: 10f it <!CAST_NEVER_SUCCEEDS!>as?<!> Float ?: 10f
} }
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!>.toByte() <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!>.toByte()
@@ -164,7 +164,7 @@ fun case_13(z: Any?) {
fun case_14(z: Any?) { fun case_14(z: Any?) {
val y = z.run { val y = z.run {
return@run this as Int return@run this as Int
this as? Float ?: 10f this <!CAST_NEVER_SUCCEEDS!>as?<!> Float ?: 10f
} }
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!> <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!>
<!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!>.toByte() <!DEBUG_INFO_EXPRESSION_TYPE("kotlin.Number & kotlin.Comparable<*>")!>y<!>.toByte()