[FIR] Make captureFromTypeParameterUpperBoundIfNeeded smarter

Before this change `ARGUMENT_TYPE_MISMATCH` would complain that
`Y` "is not a subtype of" `Inv<Y>`, because the function would only
check immediate bounds of the type parameter `Y`. `chosenSupertype`
would be `X`, not `Inv<out kotlin/String>`.

^KT-60056
This commit is contained in:
Nikolay Lunyak
2023-11-23 13:01:39 +02:00
committed by Space Team
parent 5e1e8e8f61
commit 69fba8d33b
10 changed files with 56 additions and 59 deletions
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.declarations.utils.*
import org.jetbrains.kotlin.fir.isPrimitiveType
import org.jetbrains.kotlin.fir.languageVersionSettings
@@ -34,7 +34,6 @@ import org.jetbrains.kotlin.fir.resolve.providers.symbolProvider
import org.jetbrains.kotlin.fir.scopes.*
import org.jetbrains.kotlin.fir.scopes.impl.declaredMemberScope
import org.jetbrains.kotlin.fir.scopes.impl.multipleDelegatesWithTheSameSignature
import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.SymbolInternals
import org.jetbrains.kotlin.fir.symbols.impl.*
@@ -810,46 +809,6 @@ fun FirElement.isLhsOfAssignment(context: CheckerContext): Boolean {
return lastQualified is FirVariableAssignment && lastQualified.lValue == this
}
/**
* Collects the upper bounds as [ConeClassLikeType].
*/
fun ConeKotlinType?.collectUpperBounds(): Set<ConeClassLikeType> {
if (this == null) return emptySet()
val upperBounds = mutableSetOf<ConeClassLikeType>()
val seen = mutableSetOf<ConeKotlinType>()
fun collect(type: ConeKotlinType) {
if (!seen.add(type)) return // Avoid infinite recursion.
when (type) {
is ConeErrorType -> return // Ignore error types
is ConeLookupTagBasedType -> when (type) {
is ConeClassLikeType -> upperBounds.add(type)
is ConeTypeParameterType -> {
val symbol = type.lookupTag.typeParameterSymbol
symbol.resolvedBounds.forEach { collect(it.coneType) }
}
else -> error("missing branch for ${javaClass.name}")
}
is ConeTypeVariableType -> {
val symbol = (type.typeConstructor.originalTypeParameter as? ConeTypeParameterLookupTag)?.typeParameterSymbol ?: return
symbol.resolvedBounds.forEach { collect(it.coneType) }
}
is ConeDefinitelyNotNullType -> collect(type.original)
is ConeIntersectionType -> type.intersectedTypes.forEach(::collect)
is ConeFlexibleType -> collect(type.upperBound)
is ConeCapturedType -> type.constructor.supertypes?.forEach(::collect)
is ConeIntegerConstantOperatorType -> upperBounds.add(type.getApproximatedType())
is ConeStubType, is ConeIntegerLiteralConstantType -> {
error("$type should not reach here")
}
}
}
collect(this)
return upperBounds
}
fun ConeKotlinType.leastUpperBound(session: FirSession): ConeKotlinType {
val upperBounds = collectUpperBounds().takeIf { it.isNotEmpty() } ?: return session.builtinTypes.nullableAnyType.type
return ConeTypeIntersector.intersectTypes(session.typeContext, upperBounds)
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.collectUpperBounds
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.typeParameterSymbols
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -8,7 +8,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.declaration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.collectUpperBounds
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
@@ -12,7 +12,7 @@ import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactory2
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.analysis.checkers.collectUpperBounds
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.utils.isEnumClass
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.KtSourceElement
import org.jetbrains.kotlin.config.AnalysisFlags
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.diagnostics.reportOn
import org.jetbrains.kotlin.fir.analysis.checkers.collectUpperBounds
import org.jetbrains.kotlin.fir.collectUpperBounds
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
@@ -668,11 +668,9 @@ internal fun captureFromTypeParameterUpperBoundIfNeeded(
): ConeKotlinType {
val expectedTypeClassId = expectedType.upperBoundIfFlexible().classId ?: return argumentType
val simplifiedArgumentType = argumentType.lowerBoundIfFlexible() as? ConeTypeParameterType ?: return argumentType
val typeParameter = simplifiedArgumentType.lookupTag.typeParameterSymbol.fir
val context = session.typeContext
val chosenSupertype = typeParameter.symbol.resolvedBounds.map { it.coneType }
val chosenSupertype = simplifiedArgumentType.collectUpperBounds()
.singleOrNull { it.hasSupertypeWithGivenClassId(expectedTypeClassId, context) } ?: return argumentType
val capturedType = context.captureFromExpression(chosenSupertype) ?: return argumentType
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2023 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
import org.jetbrains.kotlin.fir.symbols.ConeTypeParameterLookupTag
import org.jetbrains.kotlin.fir.types.*
/**
* Collects the upper bounds as [ConeClassLikeType].
*/
fun ConeKotlinType?.collectUpperBounds(): Set<ConeClassLikeType> {
if (this == null) return emptySet()
val upperBounds = mutableSetOf<ConeClassLikeType>()
val seen = mutableSetOf<ConeKotlinType>()
fun collect(type: ConeKotlinType) {
if (!seen.add(type)) return // Avoid infinite recursion.
when (type) {
is ConeErrorType -> return // Ignore error types
is ConeLookupTagBasedType -> when (type) {
is ConeClassLikeType -> upperBounds.add(type)
is ConeTypeParameterType -> {
val symbol = type.lookupTag.typeParameterSymbol
symbol.resolvedBounds.forEach { collect(it.coneType) }
}
else -> error("missing branch for ${javaClass.name}")
}
is ConeTypeVariableType -> {
val symbol = (type.typeConstructor.originalTypeParameter as? ConeTypeParameterLookupTag)?.typeParameterSymbol ?: return
symbol.resolvedBounds.forEach { collect(it.coneType) }
}
is ConeDefinitelyNotNullType -> collect(type.original)
is ConeIntersectionType -> type.intersectedTypes.forEach(::collect)
is ConeFlexibleType -> collect(type.upperBound)
is ConeCapturedType -> type.constructor.supertypes?.forEach(::collect)
is ConeIntegerConstantOperatorType -> upperBounds.add(type.getApproximatedType())
is ConeStubType, is ConeIntegerLiteralConstantType -> {
error("$type should not reach here")
}
}
}
collect(this)
return upperBounds
}
@@ -1,11 +0,0 @@
interface Inv<T>
fun <Y: X, X : Inv<out String>> foo(x: X, y: Y) {
val rX = bar(x)
rX.length
val rY = bar(<!ARGUMENT_TYPE_MISMATCH!>y<!>)
rY.<!UNRESOLVED_REFERENCE!>length<!>
}
fun <Y> bar(l: Inv<Y>): Y = TODO()
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
interface Inv<T>
fun <Y: X, X : Inv<out String>> foo(x: X, y: Y) {