[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) {
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)
@@ -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
}
@@ -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
@@ -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
}
@@ -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
@@ -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
@@ -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
@@ -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()
@@ -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)
@@ -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
@@ -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<!>)
}
@@ -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<!>)
}
+3 -3
View File
@@ -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> {
@@ -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()