From 5fda933c33637ce5751c7ec9f5a7c4a3157025ba Mon Sep 17 00:00:00 2001 From: Ivan Kochurkin Date: Wed, 10 Nov 2021 14:52:07 +0300 Subject: [PATCH] [FIR] Unify code of type unification, fix intersection types processing in FirCastDiagnosticsHelpers.kt --- .../checkers/FirCastDiagnosticsHelpers.kt | 105 ++++++------ .../fir/analysis/checkers/TypeUnifier.kt | 157 ------------------ .../kotlin/fir/types/TypeUnification.kt | 132 +++++++++++++++ .../transformers/body/resolve/bareTypes.kt | 131 +-------------- .../tests/smartCasts/kt10444.fir.kt | 5 + .../diagnostics/tests/smartCasts/kt10444.kt | 5 + .../diagnostics/tests/smartCasts/kt10444.txt | 9 + 7 files changed, 214 insertions(+), 330 deletions(-) delete mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/TypeUnifier.kt create mode 100644 compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt 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 51995f9bbd3..b45b46fcbe8 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 @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutorByMap import org.jetbrains.kotlin.fir.scopes.platformClassMapper import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.types.AbstractTypeChecker import org.jetbrains.kotlin.types.AbstractTypeChecker.findCorrespondingSupertypes @@ -145,14 +146,14 @@ fun isCastErased(supertype: ConeKotlinType, subtype: ConeKotlinType, context: Ch // NOTE: this does not account for 'as Array>' if (subtype.allParameterReified()) return false - val staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype, context).first ?: return true + val staticallyKnownSubtype = findStaticallyKnownSubtype(supertype, subtype, context) // If the substitution failed, it means that the result is an impossible type, e.g. something like Out // In this case, we can't guarantee anything, so the cast is considered to be erased // If the type we calculated is a subtype of the cast target, it's OK to use the cast target instead. // If not, it's wrong to use it - return !AbstractTypeChecker.isSubtypeOf(context.session.typeContext, staticallyKnownSubtype, subtype) + return !AbstractTypeChecker.isSubtypeOf(context.session.typeContext, staticallyKnownSubtype, subtype, stubTypesEqualToAnything = false) } private fun ConeKotlinType.allParameterReified(): Boolean { @@ -179,7 +180,7 @@ fun findStaticallyKnownSubtype( supertype: ConeKotlinType, subtype: ConeKotlinType, context: CheckerContext -): Pair { +): ConeKotlinType { assert(!supertype.isMarkedNullable) { "This method only makes sense for non-nullable types" } val session = context.session @@ -194,60 +195,70 @@ fun findStaticallyKnownSubtype( // in this case it will be Collection val typeCheckerState = context.session.typeContext.newTypeCheckerState( errorTypesEqualToAnything = false, - stubTypesEqualToAnything = true + stubTypesEqualToAnything = false ) - fun getFirstNotIntersectedType(type: ConeKotlinType): ConeKotlinType? { - if (type is ConeIntersectionType) { - for (intersectionType in type.intersectedTypes) { - val result = getFirstNotIntersectedType(intersectionType) - if (result != null) { - return result - } - } - return null - } - return type - } - - // Obtaining not intersected type to get not null typeConstructor. - // Not sure if it's correct - val notIntersectedSupertype = getFirstNotIntersectedType(supertype) ?: supertype - val supertypeWithVariables = - findCorrespondingSupertypes( - typeCheckerState, - subtypeWithVariablesType, - notIntersectedSupertype.typeConstructor(typeContext) - ).firstOrNull() - - val variables = subtypeWithVariables.typeParameterSymbols - - val substitution = if (supertypeWithVariables != null) { - // Now, let's try to unify Collection and Collection solution is a map from T to Foo - val typeUnifier = TypeUnifier(session, variables) - val unificationResult = typeUnifier.unify(supertype, supertypeWithVariables as ConeKotlinTypeProjection) - unificationResult.substitution.toMutableMap() + val normalizedTypes = if (supertype is ConeIntersectionType) { + supertype.intersectedTypes } else { - mutableMapOf() + ArrayList(1).also { it.add(supertype) } } - // If some of the parameters are not determined by unification, it means that these parameters are lost, - // let's put stars instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*> - var allArgumentsInferred = true - for (variable in variables) { - val value = substitution[variable] - if (value == null) { - substitution[variable] = context.session.builtinTypes.nullableAnyType.type - allArgumentsInferred = false + val resultSubstitution = mutableMapOf() + + for (normalizedType in normalizedTypes) { + val supertypeWithVariables = + findCorrespondingSupertypes( + typeCheckerState, + subtypeWithVariablesType, + normalizedType.typeConstructor(typeContext) + ).firstOrNull() + + val variables: List = subtypeWithVariables.typeParameterSymbols + + val substitution = if (supertypeWithVariables != null) { + // Now, let's try to unify Collection and Collection solution is a map from T to Foo + val result = mutableMapOf() + if (context.session.doUnify( + supertype, + supertypeWithVariables as ConeKotlinTypeProjection, + variables.toSet(), + result + ) + ) { + result + } else { + mutableMapOf() + } + } else { + mutableMapOf() + } + + // If some parameters are not determined by unification, it means that these parameters are lost, + // let's put ConeStubType instead, so that we can only cast to something like List<*>, e.g. (a: Any) as List<*> + for (variable in variables) { + val value = substitution[variable] + val resultValue = if (value == null) { + if (!resultSubstitution.containsKey(variable)) { + context.session.builtinTypes.nullableAnyType.type + } else { + null + } + } else if (value is ConeStarProjection) { + ConeStubTypeForTypeVariableInSubtyping(ConeTypeVariable("", variable.toLookupTag()), ConeNullability.NULLABLE) + } else { + value.type + } + if (resultValue != null) { + resultSubstitution[variable] = resultValue + } } } // At this point we have values for all type parameters of List // Let's make a type by substituting them: List -> List - val substitutor = ConeSubstitutorByMap(substitution, session) - val substituted = substitutor.substituteOrSelf(subtypeWithVariablesType) - - return Pair(substituted, allArgumentsInferred) + val substitutor = ConeSubstitutorByMap(resultSubstitution, session) + return substitutor.substituteOrSelf(subtypeWithVariablesType) } fun ConeKotlinType.isNonReifiedTypeParameter(): Boolean { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/TypeUnifier.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/TypeUnifier.kt deleted file mode 100644 index 916161f4ac3..00000000000 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/TypeUnifier.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.fir.analysis.checkers - -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol -import org.jetbrains.kotlin.fir.types.* -import org.jetbrains.kotlin.types.AbstractTypeChecker - -class TypeUnifier(private val session: FirSession, private val typeParameterSymbols: List) { - /** - * Finds a substitution S that turns {@code projectWithVariables} to {@code knownProjection}. - * - * Example: - * known = List - * withVariables = List - * variables = {X} - * - * result = X -> String - * - * Only types accepted by {@code isVariable} are considered variables. - */ - fun unify(knownProjection: ConeKotlinTypeProjection, projectWithVariables: ConeKotlinTypeProjection): UnificationResult { - val result = UnificationResult() - doUnify(knownProjection, projectWithVariables, result) - return result - } - - fun doUnify(knownProjection: ConeTypeProjection, projectWithVariables: ConeTypeProjection, result: UnificationResult) { - val firstType = knownProjection.type - - if (firstType is ConeIntersectionType) { - val intersectionResult = mutableMapOf() - - for (intersectedType in firstType.intersectedTypes) { - val localResult = UnificationResult() - doUnify(intersectedType, projectWithVariables, localResult) - - for ((typeParameterSymbol, typeParameterType) in localResult.substitution) { - val existingTypeParameterType = intersectionResult[typeParameterSymbol] - if (existingTypeParameterType == null || - AbstractTypeChecker.isSubtypeOf(session.typeContext, typeParameterType, existingTypeParameterType) - ) { - intersectionResult[typeParameterSymbol] = typeParameterType - } - } - } - - for ((typeParameterSymbol, typeParameterType) in intersectionResult) { - result.put(typeParameterSymbol, typeParameterType) - } - - return - } - - val known = firstType?.lowerBoundIfFlexible() ?: session.builtinTypes.nullableAnyType.type - val withVariables = projectWithVariables.type?.lowerBoundIfFlexible() ?: session.builtinTypes.nullableAnyType.type - - // in Foo ~ in X => Foo ~ X - val knownProjectionKind = knownProjection.kind - val withVariablesProjectionKind = projectWithVariables.kind - if (knownProjectionKind == withVariablesProjectionKind && knownProjectionKind != ProjectionKind.INVARIANT) { - doUnify(known, withVariables, result) - return - } - - // Foo? ~ X? => Foo ~ X - if (known.isMarkedNullable && withVariables.isMarkedNullable) { - doUnify( - known.withNullability(ConeNullability.NOT_NULL, session.typeContext).toTypeProjection( - knownProjectionKind - ) as ConeKotlinTypeProjection, - withVariables.withNullability(ConeNullability.NOT_NULL, session.typeContext).toTypeProjection( - withVariablesProjectionKind - ) as ConeKotlinTypeProjection, - result - ) - } - - // in Foo ~ out X => fail - // in Foo ~ X => may be OK - if (knownProjectionKind != withVariablesProjectionKind && withVariablesProjectionKind != ProjectionKind.INVARIANT) { - result.fail() - return - } - - // Foo ~ X? => fail - if (!known.isMarkedNullable && withVariables.isMarkedNullable) { - result.fail() - return - } - - // Foo ~ X => x |-> Foo - // * ~ X => x |-> * - val maybeVariable = withVariables.toSymbol(session) - if (maybeVariable is FirTypeParameterSymbol && typeParameterSymbols.contains(maybeVariable)) { - result.put(maybeVariable, known) - return - } - - // Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar - val structuralMismatch = known.isMarkedNullable != withVariables.isMarkedNullable || - knownProjectionKind != withVariablesProjectionKind || - known.toSymbol(session) != maybeVariable - if (structuralMismatch) { - result.fail() - return - } - - // Foo ~ Foo - if (known.typeArguments.size != withVariables.typeArguments.size) { - result.fail() - return - } - - // Foo ~ Foo - if (known.typeArguments.isEmpty()) { - return - } - - // Foo<...> ~ Foo<...> - val knownArguments = known.typeArguments - val withVariablesArguments = withVariables.typeArguments - for (index in knownArguments.indices) { - val knownArg = knownArguments[index] - val withVariablesArg = withVariablesArguments[index] - doUnify(knownArg, withVariablesArg, result) - } - } -} - -class UnificationResult { - private var success: Boolean = true - private var failedVariables: MutableSet = mutableSetOf() - private val _substitution: MutableMap = mutableMapOf() - val substitution: Map - get() = _substitution - - fun fail() { - success = false - } - - fun put(key: FirTypeParameterSymbol, value: ConeKotlinType) { - if (failedVariables.contains(key)) return - - if (substitution.containsKey(key)) { - _substitution.remove(key) - failedVariables.add(key) - fail() - } else { - _substitution[key] = value - } - } -} \ No newline at end of file diff --git a/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt new file mode 100644 index 00000000000..c9839e4abe3 --- /dev/null +++ b/compiler/fir/providers/src/org/jetbrains/kotlin/fir/types/TypeUnification.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.types + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol +import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.model.KotlinTypeMarker + +/** + * @return false does only mean that there were conflicted values for some type parameter. In all other cases, it returns true. + * "fail" result in the comments below means that we can't infer anything meaningful in that branch of unification. + * See more at org.jetbrains.kotlin.types.TypeUnifier.doUnify. + * NB: "Failed@ result of UnificationResultImpl is effectively unused in production. + */ +fun FirSession.doUnify( + originalTypeProjection: ConeTypeProjection, + typeWithParametersProjection: ConeTypeProjection, + targetTypeParameters: Set, + result: MutableMap, +): Boolean { + val originalType = originalTypeProjection.type?.lowerBoundIfFlexible() + val typeWithParameters = typeWithParametersProjection.type + + if (originalType is ConeIntersectionType) { + val intersectionResult = mutableMapOf() + for (intersectedType in originalType.intersectedTypes) { + val localResult = mutableMapOf() + if (!doUnify(intersectedType, typeWithParametersProjection, targetTypeParameters, localResult)) return false + for ((typeParameter, typeProjection) in localResult) { + val existingTypeProjection = intersectionResult[typeParameter] + if (existingTypeProjection == null + || (typeProjection is KotlinTypeMarker && + existingTypeProjection is KotlinTypeMarker && + AbstractTypeChecker.isSubtypeOf(typeContext, typeProjection, existingTypeProjection)) + ) { + intersectionResult[typeParameter] = typeProjection + } + } + } + for ((key, value) in intersectionResult) { + result[key] = value + } + return true + } + + // in Foo ~ in X => Foo ~ X + if (originalTypeProjection.kind == typeWithParametersProjection.kind && + originalTypeProjection.kind != ProjectionKind.INVARIANT && originalTypeProjection.kind != ProjectionKind.STAR) { + return doUnify(originalType!!, typeWithParameters!!, targetTypeParameters, result) + } + + // Foo? ~ X? => Foo ~ X + if (originalType?.nullability == ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) { + return doUnify( + originalTypeProjection.removeQuestionMark(typeContext), + typeWithParametersProjection.removeQuestionMark(typeContext), + targetTypeParameters, result, + ) + } + + // in Foo ~ out X => fail + // in Foo ~ X => may be OK + if (originalTypeProjection.kind != typeWithParametersProjection.kind && typeWithParametersProjection.kind != ProjectionKind.INVARIANT) { + return true + } + + if (typeWithParameters is ConeFlexibleType) { + return doUnify( + originalTypeProjection, + typeWithParametersProjection.replaceType(typeWithParameters.lowerBound), + targetTypeParameters, result, + ) + } + + // Foo ~ X? => fail + if (originalType?.nullability != ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) { + return true + } + + // Foo ~ X => x |-> Foo + // * ~ X => x |-> * + val typeParameter = (typeWithParameters as? ConeTypeParameterType)?.lookupTag?.typeParameterSymbol + if (typeParameter != null && typeParameter in targetTypeParameters) { + if (typeParameter in result && result[typeParameter] != originalTypeProjection) return false + result[typeParameter] = originalTypeProjection + return true + } + + // Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar + if (originalType?.nullability?.isNullable != typeWithParameters?.nullability?.isNullable) return true + if (originalTypeProjection.kind != typeWithParametersProjection.kind) return true + if ((originalType as? ConeLookupTagBasedType)?.lookupTag != (typeWithParameters as? ConeLookupTagBasedType)?.lookupTag) return true + if (originalType == null || typeWithParameters == null) return true + + // Foo ~ Foo + if (originalType.typeArguments.size != typeWithParameters.typeArguments.size) { + return true + } + + // Foo ~ Foo + if (originalType.typeArguments.isEmpty()) { + return true + } + + // Foo<...> ~ Foo<...> + for ((originalTypeArgument, typeWithParametersArgument) in originalType.typeArguments.zip(typeWithParameters.typeArguments)) { + if (!doUnify(originalTypeArgument, typeWithParametersArgument, targetTypeParameters, result)) return false + } + + return true +} + +private fun ConeTypeProjection.removeQuestionMark(typeContext: ConeTypeContext): ConeTypeProjection { + val type = type + require(type != null && type.nullability.isNullable) { + "Expected nullable type, got $type" + } + + return replaceType(type.withNullability(ConeNullability.NOT_NULL, typeContext)) +} + +private fun ConeTypeProjection.replaceType(newType: ConeKotlinType): ConeTypeProjection = + when (kind) { + ProjectionKind.INVARIANT -> newType + ProjectionKind.IN -> ConeKotlinTypeProjectionIn(newType) + ProjectionKind.OUT -> ConeKotlinTypeProjectionOut(newType) + ProjectionKind.STAR -> error("Should not be a star projection") + } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt index b2caf13ffd3..2f28132ba52 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/bareTypes.kt @@ -8,15 +8,14 @@ package org.jetbrains.kotlin.fir.resolve.transformers.body.resolve import org.jetbrains.kotlin.fir.declarations.FirRegularClass import org.jetbrains.kotlin.fir.declarations.FirTypeAlias import org.jetbrains.kotlin.fir.declarations.FirTypeParameter -import org.jetbrains.kotlin.fir.declarations.FirTypeParameterRef import org.jetbrains.kotlin.fir.declarations.utils.expandedConeType import org.jetbrains.kotlin.fir.resolve.BodyResolveComponents import org.jetbrains.kotlin.fir.resolve.defaultType import org.jetbrains.kotlin.fir.resolve.fullyExpandedType import org.jetbrains.kotlin.fir.resolve.toSymbol +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.model.KotlinTypeMarker fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLikeType, originalType: ConeKotlinType): ConeKotlinType? { originalType.lowerBoundIfFlexible().fullyExpandedType(session).let { @@ -49,11 +48,11 @@ fun BodyResolveComponents.computeRepresentativeTypeForBareType(type: ConeClassLi correspondingSupertype } - val substitution = mutableMapOf() - val typeParameters = castClass.typeParameters.mapTo(mutableSetOf()) { it.symbol.fir } - if (!doUnify(originalType, superTypeWithParameters, typeParameters, substitution)) return null + val substitution = mutableMapOf() + val typeParameters = castClass.typeParameters.mapTo(mutableSetOf()) { it.symbol } + if (!session.doUnify(originalType, superTypeWithParameters, typeParameters, substitution)) return null - val newArguments = castClass.typeParameters.map { substitution[it.symbol.fir] ?: return@computeRepresentativeTypeForBareType null } + val newArguments = castClass.typeParameters.map { substitution[it.symbol] ?: return@computeRepresentativeTypeForBareType null } return expandedCastType.withArguments(newArguments.toTypedArray()) } @@ -76,123 +75,3 @@ private fun canBeUsedAsBareType(firTypeAlias: FirTypeAlias): Boolean { return true } -/** - * @return false does only mean that there were conflicted values for some type parameter. In all other cases, it returns true. - * "fail" result in the comments below means that we can't infer anything meaningful in that branch of unification. - * See more at org.jetbrains.kotlin.types.TypeUnifier.doUnify. - * NB: "Failed@ result of UnificationResultImpl is effectively unused in production. - */ -private fun BodyResolveComponents.doUnify( - originalTypeProjection: ConeTypeProjection, - typeWithParametersProjection: ConeTypeProjection, - targetTypeParameters: Set, - result: MutableMap, -): Boolean { - val originalType = originalTypeProjection.type - val typeWithParameters = typeWithParametersProjection.type - - if (originalType is ConeIntersectionType) { - val intersectionResult = mutableMapOf() - for (intersectedType in originalType.intersectedTypes) { - val localResult = mutableMapOf() - if (!doUnify(intersectedType, typeWithParametersProjection, targetTypeParameters, localResult)) return false - for ((typeParameter, typeProjection) in localResult) { - val existingTypeProjection = intersectionResult[typeParameter] - if (existingTypeProjection == null - || (typeProjection is KotlinTypeMarker && - existingTypeProjection is KotlinTypeMarker && - AbstractTypeChecker.isSubtypeOf(session.typeContext, typeProjection, existingTypeProjection)) - ) { - intersectionResult[typeParameter] = typeProjection - } - } - } - for ((key, value) in intersectionResult) { - result[key] = value - } - return true - } - - // in Foo ~ in X => Foo ~ X - if (originalTypeProjection.kind == typeWithParametersProjection.kind && - originalTypeProjection.kind != ProjectionKind.INVARIANT && originalTypeProjection.kind != ProjectionKind.STAR) { - return doUnify(originalType!!, typeWithParameters!!, targetTypeParameters, result) - } - - // Foo? ~ X? => Foo ~ X - if (originalType?.nullability == ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) { - return doUnify( - originalTypeProjection.removeQuestionMark(session.typeContext), - typeWithParametersProjection.removeQuestionMark(session.typeContext), - targetTypeParameters, result, - ) - } - - // in Foo ~ out X => fail - // in Foo ~ X => may be OK - if (originalTypeProjection.kind != typeWithParametersProjection.kind && typeWithParametersProjection.kind != ProjectionKind.INVARIANT) { - return true - } - - if (typeWithParameters is ConeFlexibleType) { - return doUnify( - originalTypeProjection, - typeWithParametersProjection.replaceType(typeWithParameters.lowerBound), - targetTypeParameters, result, - ) - } - - // Foo ~ X? => fail - if (originalType?.nullability != ConeNullability.NULLABLE && typeWithParameters?.nullability == ConeNullability.NULLABLE) { - return true - } - - // Foo ~ X => x |-> Foo - // * ~ X => x |-> * - val typeParameter = (typeWithParameters as? ConeTypeParameterType)?.lookupTag?.typeParameterSymbol?.fir - if (typeParameter != null && typeParameter in targetTypeParameters) { - if (typeParameter in result && result[typeParameter] != originalTypeProjection) return false - result[typeParameter] = originalTypeProjection - return true - } - - // Foo? ~ Foo || in Foo ~ Foo || Foo ~ Bar - if (originalType?.nullability?.isNullable != typeWithParameters?.nullability?.isNullable) return true - if (originalTypeProjection.kind != typeWithParametersProjection.kind) return true - if ((originalType as? ConeLookupTagBasedType)?.lookupTag != (typeWithParameters as? ConeLookupTagBasedType)?.lookupTag) return true - if (originalType == null || typeWithParameters == null) return true - - // Foo ~ Foo - if (originalType.typeArguments.size != typeWithParameters.typeArguments.size) { - return true - } - - // Foo ~ Foo - if (originalType.typeArguments.isEmpty()) { - return true - } - - // Foo<...> ~ Foo<...> - for ((originalTypeArgument, typeWithParametersArgument) in originalType.typeArguments.zip(typeWithParameters.typeArguments)) { - if (!doUnify(originalTypeArgument, typeWithParametersArgument, targetTypeParameters, result)) return false - } - - return true -} - -private fun ConeTypeProjection.removeQuestionMark(typeContext: ConeTypeContext): ConeTypeProjection { - val type = type - require(type != null && type.nullability.isNullable) { - "Expected nullable type, got $type" - } - - return replaceType(type.withNullability(ConeNullability.NOT_NULL, typeContext)) -} - -private fun ConeTypeProjection.replaceType(newType: ConeKotlinType): ConeTypeProjection = - when (kind) { - ProjectionKind.INVARIANT -> newType - ProjectionKind.IN -> ConeKotlinTypeProjectionIn(newType) - ProjectionKind.OUT -> ConeKotlinTypeProjectionOut(newType) - ProjectionKind.STAR -> error("Should not be a star projection") - } diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt10444.fir.kt b/compiler/testData/diagnostics/tests/smartCasts/kt10444.fir.kt index b96614a413b..2f96dbc0e95 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/kt10444.fir.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/kt10444.fir.kt @@ -1,6 +1,7 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER // KT-10444 Do not ignore smart (unchecked) casts to the same classifier +class Base class Qwe(val a: T?) { fun test1(obj: Any) { obj as Qwe @@ -12,6 +13,10 @@ class Qwe(val a: T?) { check(obj.a) } + fun test2(b: Base<*>) { + b as Base + } + fun check(a: T?) { } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt10444.kt b/compiler/testData/diagnostics/tests/smartCasts/kt10444.kt index 0cd9f6b2b56..f84b524686b 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/kt10444.kt +++ b/compiler/testData/diagnostics/tests/smartCasts/kt10444.kt @@ -1,6 +1,7 @@ // !DIAGNOSTICS: -UNUSED_PARAMETER // KT-10444 Do not ignore smart (unchecked) casts to the same classifier +class Base class Qwe(val a: T?) { fun test1(obj: Any) { obj as Qwe @@ -12,6 +13,10 @@ class Qwe(val a: T?) { check(obj.a) } + fun test2(b: Base<*>) { + b as Base + } + fun check(a: T?) { } } diff --git a/compiler/testData/diagnostics/tests/smartCasts/kt10444.txt b/compiler/testData/diagnostics/tests/smartCasts/kt10444.txt index 38ce0c968ae..b437f0cbd0f 100644 --- a/compiler/testData/diagnostics/tests/smartCasts/kt10444.txt +++ b/compiler/testData/diagnostics/tests/smartCasts/kt10444.txt @@ -11,6 +11,13 @@ public open class Bar { public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +public final class Base { + public constructor Base() + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} + public open class Foo { public constructor Foo() public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean @@ -26,5 +33,7 @@ public final class Qwe { public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int public final fun test1(/*0*/ obj: Qwe<*>): kotlin.Unit public final fun test1(/*0*/ obj: kotlin.Any): kotlin.Unit + public final fun test2(/*0*/ b: Base<*>): kotlin.Unit public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String } +