From fab6cec93a1d85f2412186ced1ce64c23efdcf44 Mon Sep 17 00:00:00 2001 From: Nikolay Lunyak Date: Wed, 28 Feb 2024 14:17:35 +0200 Subject: [PATCH] [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). --- .../resolve/lambdaInLhsOfTypeOperatorCall.kt | 2 +- .../smartcasts/receivers/implicitReceivers.kt | 2 +- .../checkers/FirCastDiagnosticsHelpers.kt | 121 -------------- .../checkers/FirTypeCompatibilityHelpers.kt | 16 +- .../expression/FirCastOperatorsChecker.kt | 155 +++++++++++++----- .../cast/IsErasedUpcastToNonReified.fir.kt | 2 +- .../cast/neverSucceeds/MappedSubtypes.fir.kt | 21 +++ .../cast/neverSucceeds/MappedSubtypes.kt | 1 - .../neverSucceeds/NoGenericsRelated.fir.kt | 46 ++++++ .../cast/neverSucceeds/NoGenericsRelated.kt | 1 - .../neverSucceeds/NoGenericsUnrelated.fir.kt | 34 ++++ .../cast/neverSucceeds/NoGenericsUnrelated.kt | 1 - .../capturedParameters/uncheckedCast.fir.kt | 2 +- ...dontThrowEmptyIntersectionException.fir.kt | 2 +- .../genericVarianceViolation/smartCast.fir.kt | 2 +- .../tests/smartCasts/alwaysNull.fir.kt | 4 +- .../smartCasts/castchecks/variables.fir.kt | 8 +- ...entsInNamedFormFunDeprecation_after.fir.kt | 2 +- ...ntsInNamedFormFunDeprecation_before.fir.kt | 2 +- .../diagnostics/tests/varargs/kt48162.fir.kt | 6 +- .../introduction-1/p-8/pos/2.1.fir.kt | 2 +- .../diagnostics/notLinked/dfa/neg/32.fir.kt | 2 +- .../diagnostics/notLinked/dfa/neg/34.fir.kt | 2 +- .../diagnostics/notLinked/dfa/pos/34.fir.kt | 6 +- .../diagnostics/notLinked/dfa/pos/51.fir.kt | 4 +- 25 files changed, 253 insertions(+), 193 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.fir.kt create mode 100644 compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.fir.kt create mode 100644 compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.fir.kt diff --git a/compiler/fir/analysis-tests/testData/resolve/lambdaInLhsOfTypeOperatorCall.kt b/compiler/fir/analysis-tests/testData/resolve/lambdaInLhsOfTypeOperatorCall.kt index f39f37d3e0b..5fefe10e8e9 100644 --- a/compiler/fir/analysis-tests/testData/resolve/lambdaInLhsOfTypeOperatorCall.kt +++ b/compiler/fir/analysis-tests/testData/resolve/lambdaInLhsOfTypeOperatorCall.kt @@ -9,7 +9,7 @@ fun test_1(b: B) { } fun test_2(s: String) { - val func = { s.length } as B + val func = { s.length } as B } class B(val k: K, val v: V) diff --git a/compiler/fir/analysis-tests/testData/resolve/smartcasts/receivers/implicitReceivers.kt b/compiler/fir/analysis-tests/testData/resolve/smartcasts/receivers/implicitReceivers.kt index ddbf455364e..b9e1fb9bd98 100644 --- a/compiler/fir/analysis-tests/testData/resolve/smartcasts/receivers/implicitReceivers.kt +++ b/compiler/fir/analysis-tests/testData/resolve/smartcasts/receivers/implicitReceivers.kt @@ -79,6 +79,6 @@ fun Any.test_5(): Int = when { fun Any.test_6() { this as List<*> size - this as String + this as String length } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt index f7523283a99..c75a747e2e1 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirCastDiagnosticsHelpers.kt @@ -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 diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirTypeCompatibilityHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirTypeCompatibilityHelpers.kt index 1838c66f5ab..a3c8ef25398 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirTypeCompatibilityHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirTypeCompatibilityHelpers.kt @@ -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 } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCastOperatorsChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCastOperatorsChecker.kt index fb88ae6ee22..fe231d829be 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCastOperatorsChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirCastOperatorsChecker.kt @@ -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 + } } diff --git a/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.fir.kt b/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.fir.kt index bbdf67e835c..7c9bce3f777 100644 --- a/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.fir.kt +++ b/compiler/testData/diagnostics/tests/cast/IsErasedUpcastToNonReified.fir.kt @@ -28,7 +28,7 @@ inline fun test(x: T?, a: Any) { a is Box a is Array a as Box - a as Array + a as Array a is Box> a is Array> diff --git a/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.fir.kt b/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.fir.kt new file mode 100644 index 00000000000..f1a42ac3e9f --- /dev/null +++ b/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.fir.kt @@ -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 as JCS + + s as CharSequence + s as JCS + + js as Sub + s as JSub +} diff --git a/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.kt b/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.kt index 52ba2e8a856..b8584abd89a 100644 --- a/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.kt +++ b/compiler/testData/diagnostics/tests/cast/neverSucceeds/MappedSubtypes.kt @@ -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 diff --git a/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.fir.kt b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.fir.kt new file mode 100644 index 00000000000..0f2af05a4b7 --- /dev/null +++ b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.fir.kt @@ -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: OC1 +interface T5: T2 + +fun 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 as T5 + t2 as TP2 + + tp1 as FC1 + tp1 as OC1 + tp1 as OC2 + tp2 as T2 + tp2 as T5 + tp1 as TP3 +} diff --git a/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.kt b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.kt index 48a50f50add..ea29258077e 100644 --- a/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.kt +++ b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsRelated.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // !DIAGNOSTICS: -USELESS_CAST -UNCHECKED_CAST interface T1 interface T2 diff --git a/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.fir.kt b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.fir.kt new file mode 100644 index 00000000000..15460bf7499 --- /dev/null +++ b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.fir.kt @@ -0,0 +1,34 @@ +// !DIAGNOSTICS: -UNCHECKED_CAST +interface Trait1 +interface Trait2 +open class OClass1 +open class OClass2 +class FClass1 +class FClass2 + +fun test( + t1: Trait1, + oc1: OClass1, + fc1: FClass1, + tp1: TP1 +) { + t1 as Trait2 + t1 as OClass2 + t1 as FClass2 + t1 as TP2 + + oc1 as Trait2 + oc1 as OClass2 + oc1 as FClass2 + oc1 as TP2 + + fc1 as Trait2 + fc1 as OClass2 + fc1 as FClass2 + fc1 as TP2 + + tp1 as Trait2 + tp1 as OClass2 + tp1 as FClass2 + tp1 as TP2 +} diff --git a/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.kt b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.kt index ca53cd96c72..1ff4ef851a7 100644 --- a/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.kt +++ b/compiler/testData/diagnostics/tests/cast/neverSucceeds/NoGenericsUnrelated.kt @@ -1,4 +1,3 @@ -// FIR_IDENTICAL // !DIAGNOSTICS: -UNCHECKED_CAST interface Trait1 interface Trait2 diff --git a/compiler/testData/diagnostics/tests/generics/capturedParameters/uncheckedCast.fir.kt b/compiler/testData/diagnostics/tests/generics/capturedParameters/uncheckedCast.fir.kt index 3b07dcadf07..726c2dc10fb 100644 --- a/compiler/testData/diagnostics/tests/generics/capturedParameters/uncheckedCast.fir.kt +++ b/compiler/testData/diagnostics/tests/generics/capturedParameters/uncheckedCast.fir.kt @@ -37,7 +37,7 @@ fun foo(x: Any, y: Any) : Any { y as Outer<*> y as Outer - y as Outer<*>.Inner + y as Outer<*>.Inner y as Outer.Inner return C() diff --git a/compiler/testData/diagnostics/tests/inference/dontThrowEmptyIntersectionException.fir.kt b/compiler/testData/diagnostics/tests/inference/dontThrowEmptyIntersectionException.fir.kt index 6e21807d07c..9f0608356cc 100644 --- a/compiler/testData/diagnostics/tests/inference/dontThrowEmptyIntersectionException.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/dontThrowEmptyIntersectionException.fir.kt @@ -15,7 +15,7 @@ public interface I {} // FILE: main.kt fun main(z: I) { z as Test> - z as TestFoo>> + z as TestFoo>> z as Test<Foo> z as Any2 println(z) diff --git a/compiler/testData/diagnostics/tests/platformTypes/genericVarianceViolation/smartCast.fir.kt b/compiler/testData/diagnostics/tests/platformTypes/genericVarianceViolation/smartCast.fir.kt index 3b217869729..20d6abb457d 100644 --- a/compiler/testData/diagnostics/tests/platformTypes/genericVarianceViolation/smartCast.fir.kt +++ b/compiler/testData/diagnostics/tests/platformTypes/genericVarianceViolation/smartCast.fir.kt @@ -11,6 +11,6 @@ public class A { fun main(a: A, ml: Any) { if (ml is MutableList) { a.foo(ml) - a.foo(ml as List) + a.foo(ml as List) } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.fir.kt index fd9c94d4ff4..a692d90961b 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/alwaysNull.fir.kt @@ -16,7 +16,7 @@ fun String?.gav() {} fun bar(s: String?) { if (s != null) return s.gav() - s as? String + s as? String s as String? - s as String + s as String } diff --git a/compiler/testData/diagnostics/tests/smartCasts/castchecks/variables.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/castchecks/variables.fir.kt index f8c3d25df9e..9f183e8d80f 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/castchecks/variables.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/castchecks/variables.fir.kt @@ -43,8 +43,8 @@ fun f(a: SomeClass?) { // 'aa' cannot be cast to SomeSubClass aa.hashCode() aa.foo - (aa as? SomeSubClass).foo - (aa as SomeSubClass).foo + (aa as? SomeSubClass).foo + (aa 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.hashCode() aa.foo - (aa as? SomeSubClass).foo - (aa as SomeSubClass).foo + (aa as? SomeSubClass).foo + (aa as SomeSubClass).foo } aa = a val c = aa as? SomeSubClass diff --git a/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_after.fir.kt b/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_after.fir.kt index 73f882c5f06..002224c8963 100644 --- a/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_after.fir.kt +++ b/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_after.fir.kt @@ -55,5 +55,5 @@ fun testMany(a: Any) { manyFoo(v = a) manyFoo(s = a) manyFoo(v = a as Int) - manyFoo(s = a as String) + manyFoo(s = a as String) } diff --git a/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_before.fir.kt b/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_before.fir.kt index 25c32f77737..1515e4ff476 100644 --- a/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_before.fir.kt +++ b/compiler/testData/diagnostics/tests/varargs/assigningSingleElementsInNamedFormFunDeprecation_before.fir.kt @@ -55,5 +55,5 @@ fun testMany(a: Any) { manyFoo(v = a) manyFoo(s = a) manyFoo(v = a as Int) - manyFoo(s = a as String) + manyFoo(s = a as String) } diff --git a/compiler/testData/diagnostics/tests/varargs/kt48162.fir.kt b/compiler/testData/diagnostics/tests/varargs/kt48162.fir.kt index e00e31751c4..e004f428f0c 100644 --- a/compiler/testData/diagnostics/tests/varargs/kt48162.fir.kt +++ b/compiler/testData/diagnostics/tests/varargs/kt48162.fir.kt @@ -1,8 +1,8 @@ // !DIAGNOSTICS: -UNCHECKED_CAST -fun Collection.toArray(): Array = this as Array -fun Collection.toArray2(): Array = this as Array -fun toArray3(x: Collection): Array = x as Array +fun Collection.toArray(): Array = this as Array +fun Collection.toArray2(): Array = this as Array +fun toArray3(x: Collection): Array = x as Array class Foo { operator fun plus(x: Foo): Array { diff --git a/compiler/tests-spec/testData/diagnostics/linked/type-system/introduction-1/p-8/pos/2.1.fir.kt b/compiler/tests-spec/testData/diagnostics/linked/type-system/introduction-1/p-8/pos/2.1.fir.kt index d22dc58dc6d..279f4035c9f 100644 --- a/compiler/tests-spec/testData/diagnostics/linked/type-system/introduction-1/p-8/pos/2.1.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/linked/type-system/introduction-1/p-8/pos/2.1.fir.kt @@ -55,7 +55,7 @@ class Case8 { } // TESTCASE NUMBER: 9 -inline fun case_9(x: L) = x!! as Int +inline fun case_9(x: L) = x!! as Int // TESTCASE NUMBER: 10 fun case_10(x: Nothing) = x as Iterable diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/32.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/32.fir.kt index b4176428192..427473a08ba 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/32.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/32.fir.kt @@ -44,7 +44,7 @@ fun case_2(x: Int?, y: Nothing?) { */ fun case_3(x: Int?) { if (x == null) { - x as Int + x as Int stringArg(x) x } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/34.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/34.fir.kt index e76953afdd3..d0b8f85a5fe 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/34.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/neg/34.fir.kt @@ -124,7 +124,7 @@ fun case_10() { do { x = null break - } while ((x as String).length > 1) + } while ((x as String).length > 1) x x.length } diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/34.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/34.fir.kt index 47237eab1df..bed437fb3be 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/34.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/34.fir.kt @@ -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 as ClassLevel1;} else {b.inv(); c = b; c 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 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}) { else -> ClassLevel2() } -> { e diff --git a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/51.fir.kt b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/51.fir.kt index 686e04f4719..31f4e2913a7 100644 --- a/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/51.fir.kt +++ b/compiler/tests-spec/testData/diagnostics/notLinked/dfa/pos/51.fir.kt @@ -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 as? Float ?: 10f } ")!>y ")!>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 as? Float ?: 10f } ")!>y ")!>y.toByte()