[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:
committed by
Space Team
parent
a5e43c9e3f
commit
fab6cec93a
+1
-1
@@ -9,7 +9,7 @@ fun test_1(b: B<String, Number>) {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
+1
-1
@@ -79,6 +79,6 @@ fun Any.test_5(): Int = when {
|
||||
fun Any.test_6() {
|
||||
this as List<*>
|
||||
size
|
||||
this as String
|
||||
this <!CAST_NEVER_SUCCEEDS!>as<!> String
|
||||
length
|
||||
}
|
||||
|
||||
-121
@@ -5,139 +5,18 @@
|
||||
|
||||
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.declarations.utils.isLocal
|
||||
import org.jetbrains.kotlin.fir.resolve.defaultType
|
||||
import org.jetbrains.kotlin.fir.resolve.getClassAndItsOuterClassesWhenLocal
|
||||
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.FirTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes
|
||||
import org.jetbrains.kotlin.types.TypeCheckerState
|
||||
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 {
|
||||
val typeContext = context.session.typeContext
|
||||
|
||||
|
||||
+10
-6
@@ -32,11 +32,13 @@ internal fun FirExpression.unwrapToMoreUsefulExpression() = when (this) {
|
||||
internal class TypeInfo(
|
||||
val type: ConeKotlinType,
|
||||
val notNullType: ConeKotlinType,
|
||||
val directType: ConeKotlinType,
|
||||
val isEnumClass: Boolean,
|
||||
val isPrimitive: Boolean,
|
||||
val isBuiltin: Boolean,
|
||||
val isValueClass: Boolean,
|
||||
val isFinal: Boolean,
|
||||
val isClass: Boolean,
|
||||
val canHaveSubtypesAccordingToK1: Boolean,
|
||||
) {
|
||||
override fun toString() = "$type"
|
||||
@@ -64,20 +66,22 @@ internal fun ConeKotlinType.toTypeInfo(session: FirSession): TypeInfo {
|
||||
val type = bounds.ifNotEmpty { ConeTypeIntersector.intersectTypes(session.typeContext, this) }
|
||||
?: session.builtinTypes.nullableAnyType.type
|
||||
val notNullType = type.withNullability(ConeNullability.NOT_NULL, session.typeContext)
|
||||
val boundsSymbols = bounds.mapNotNull { it.toClassSymbol(session) }
|
||||
|
||||
return TypeInfo(
|
||||
type, notNullType,
|
||||
isEnumClass = bounds.any { it.isEnum(session) },
|
||||
type, notNullType, directType = this,
|
||||
isEnumClass = boundsSymbols.any { it.isEnumClass },
|
||||
isPrimitive = bounds.any { it.isPrimitiveOrNullablePrimitive },
|
||||
isBuiltin = bounds.any { it.toClassSymbol(session)?.isBuiltin == true },
|
||||
isValueClass = bounds.any { it.toClassSymbol(session)?.isInline == true },
|
||||
isFinal = bounds.any { it.toClassSymbol(session)?.isFinalClass == true },
|
||||
isBuiltin = boundsSymbols.any { it.isBuiltin },
|
||||
isValueClass = boundsSymbols.any { it.isInline },
|
||||
isFinal = boundsSymbols.any { it.isFinalClass },
|
||||
isClass = boundsSymbols.any { it.isClass },
|
||||
// In K1's intersector, `canHaveSubtypes()` is called for `nullabilityStripped`.
|
||||
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)
|
||||
return kotlinClassId?.constructClassLikeType(typeArguments, isNullable, attributes) ?: this
|
||||
}
|
||||
|
||||
+117
-38
@@ -8,54 +8,133 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
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.MppCheckerKind
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.checkCasting
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.*
|
||||
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.expressions.FirOperation
|
||||
import org.jetbrains.kotlin.fir.expressions.FirTypeOperatorCall
|
||||
import org.jetbrains.kotlin.fir.expressions.unwrapSmartcastExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.*
|
||||
import org.jetbrains.kotlin.fir.firPlatformSpecificCastChecker
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.types.ConeDynamicType
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.fir.types.resolvedType
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
|
||||
object FirCastOperatorsChecker : FirTypeOperatorCallChecker(MppCheckerKind.Common) {
|
||||
override fun check(expression: FirTypeOperatorCall, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val session = context.session
|
||||
val firstArgument = expression.argumentList.arguments[0]
|
||||
val actualType = firstArgument.unwrapSmartcastExpression().resolvedType.fullyExpandedType(session).finalApproximationOrSelf(context)
|
||||
val conversionTypeRef = expression.conversionTypeRef
|
||||
val targetType = conversionTypeRef.coneType.fullyExpandedType(session).finalApproximationOrSelf(context)
|
||||
val arguments = expression.argumentList.arguments
|
||||
require(arguments.size == 1) { "Type operator call with non-1 arguments" }
|
||||
|
||||
if (expression.operation in FirOperation.TYPES && targetType is ConeDynamicType) {
|
||||
reporter.reportOn(conversionTypeRef.source, FirErrors.DYNAMIC_NOT_ALLOWED, context)
|
||||
val l = arguments[0].toArgumentInfo(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
|
||||
if (expression.operation == FirOperation.AS || isSafeAs) {
|
||||
val castType = checkCasting(actualType, targetType, isSafeAs, context)
|
||||
if (castType == CastingType.Impossible) {
|
||||
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
||||
if (!session.firPlatformSpecificCastChecker.shouldSuppressImpossibleCast(session, actualType, targetType)) {
|
||||
reporter.reportOn(expression.source, FirErrors.CAST_NEVER_SUCCEEDS, context)
|
||||
}
|
||||
}
|
||||
} else if (castType == CastingType.Always) {
|
||||
if (context.languageVersionSettings.supportsFeature(LanguageFeature.EnableDfaWarningsInK2)) {
|
||||
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)
|
||||
}
|
||||
val checkApplicability = when (expression.operation) {
|
||||
FirOperation.IS, FirOperation.NOT_IS -> ::checkIsApplicability
|
||||
FirOperation.AS, FirOperation.SAFE_AS -> ::checkAsApplicability
|
||||
else -> error("Invalid operator of FirTypeOperatorCall")
|
||||
}
|
||||
|
||||
val rUserType = expression.conversionTypeRef.coneType.finalApproximationOrSelf(context)
|
||||
|
||||
// No need to check original types separately from smartcast types, because we only report warnings
|
||||
checkApplicability(l.smartCastTypeInfo, r, expression, context).ifInapplicable {
|
||||
return reporter.reportInapplicabilityDiagnostic(expression, it, l.originalTypeInfo, r.type, l.userType, rUserType, 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!>Array<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!>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
|
||||
import java.lang.CharSequence as JCS
|
||||
|
||||
|
||||
+46
@@ -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
|
||||
interface T1
|
||||
interface T2
|
||||
|
||||
+34
@@ -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
|
||||
interface Trait1
|
||||
interface Trait2
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ fun <E> foo(x: Any, y: Any) : Any {
|
||||
y <!UNCHECKED_CAST!>as 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<!>
|
||||
|
||||
return C()
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ public interface I<K> {}
|
||||
// FILE: main.kt
|
||||
fun main(z: I<String>) {
|
||||
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 <!UNRESOLVED_REFERENCE!>Any2<!>
|
||||
println(z)
|
||||
|
||||
Vendored
+1
-1
@@ -11,6 +11,6 @@ public class A {
|
||||
fun main(a: A, ml: Any) {
|
||||
if (ml is <!CANNOT_CHECK_FOR_ERASED!>MutableList<String><!>) {
|
||||
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?) {
|
||||
if (s != null) return
|
||||
s.gav()
|
||||
s as? String
|
||||
s <!USELESS_CAST!>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<!UNSAFE_CALL!>.<!>hashCode()
|
||||
aa.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||
(aa as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||
(aa as SomeSubClass).foo
|
||||
(aa <!USELESS_CAST!>as? SomeSubClass<!>)<!UNSAFE_CALL!>.<!>foo
|
||||
(aa <!CAST_NEVER_SUCCEEDS!>as<!> SomeSubClass).foo
|
||||
}
|
||||
val b = (aa as? SomeSubClass)?.foo
|
||||
aa = null
|
||||
@@ -52,8 +52,8 @@ fun f(a: SomeClass?) {
|
||||
// 'aa' cannot be cast to SomeSubClass
|
||||
aa<!UNSAFE_CALL!>.<!>hashCode()
|
||||
aa.<!UNRESOLVED_REFERENCE!>foo<!>
|
||||
(aa as? SomeSubClass)<!UNSAFE_CALL!>.<!>foo
|
||||
(aa as SomeSubClass).foo
|
||||
(aa <!USELESS_CAST!>as? SomeSubClass<!>)<!UNSAFE_CALL!>.<!>foo
|
||||
(aa <!CAST_NEVER_SUCCEEDS!>as<!> SomeSubClass).foo
|
||||
}
|
||||
aa = a
|
||||
val c = aa as? SomeSubClass
|
||||
|
||||
+1
-1
@@ -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(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(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<!>)
|
||||
}
|
||||
|
||||
+1
-1
@@ -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(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(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<!>)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// !DIAGNOSTICS: -UNCHECKED_CAST
|
||||
|
||||
fun <T> Collection<T>.toArray(): Array<T> = this as Array<T>
|
||||
fun Collection<String>.toArray2(): Array<String> = this as Array<String>
|
||||
fun <T> toArray3(x: Collection<T>): Array<T> = x as Array<T>
|
||||
fun <T> Collection<T>.toArray(): Array<T> = this <!CAST_NEVER_SUCCEEDS!>as<!> Array<T>
|
||||
fun Collection<String>.toArray2(): Array<String> = this <!CAST_NEVER_SUCCEEDS!>as<!> Array<String>
|
||||
fun <T> toArray3(x: Collection<T>): Array<T> = x <!CAST_NEVER_SUCCEEDS!>as<!> Array<T>
|
||||
|
||||
class Foo<T> {
|
||||
operator fun plus(x: Foo<T>): Array<T> {
|
||||
|
||||
Vendored
+1
-1
@@ -55,7 +55,7 @@ class Case8<K> {
|
||||
}
|
||||
|
||||
// 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
|
||||
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?) {
|
||||
if (x == null) {
|
||||
x as Int
|
||||
x <!CAST_NEVER_SUCCEEDS!>as<!> Int
|
||||
stringArg(<!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 {
|
||||
x = null
|
||||
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<!><!UNSAFE_CALL!>.<!>length
|
||||
}
|
||||
|
||||
@@ -106,10 +106,10 @@ fun case_6() {
|
||||
val e: Any?
|
||||
|
||||
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
|
||||
} -> when (if (true) {c.test1(); d = c; d as ClassLevel2} else {c.test1(); d = c; d as ClassLevel2}) {
|
||||
when (if (true) {d.test2(); e = d; e as ClassLevel3} else {d.test2(); e = d; e as ClassLevel3}) {
|
||||
} -> 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 <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel3} else {d.test2(); e = d; e <!CAST_NEVER_SUCCEEDS!>as<!> ClassLevel3}) {
|
||||
else -> ClassLevel2()
|
||||
} -> {
|
||||
<!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?) {
|
||||
val y = z.let {
|
||||
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<!>.toByte()
|
||||
@@ -164,7 +164,7 @@ fun case_13(z: Any?) {
|
||||
fun case_14(z: Any?) {
|
||||
val y = z.run {
|
||||
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<!>.toByte()
|
||||
|
||||
Reference in New Issue
Block a user