FIR: Refactor checkUpperBoundViolated
There just should be a different facades for cases A<T1, T2> and foo<T1, T2>() Test data has changed for type alias constructors since previously, it was working by mistake because of assumption that type alias arguments are linearly mapped to the type parameters of the corresponding constructors ^KT-50703 Open
This commit is contained in:
+64
-93
@@ -6,11 +6,12 @@
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers
|
||||
|
||||
import org.jetbrains.kotlin.KtSourceElement
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.diagnostics.reportOn
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.ConeSubstitutor
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
|
||||
import org.jetbrains.kotlin.fir.resolve.toSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
@@ -20,142 +21,112 @@ import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||
|
||||
/**
|
||||
* Recursively analyzes type parameters and reports the diagnostic on the given source calculated using typeRef
|
||||
* Returns true if an error occurred
|
||||
*/
|
||||
fun checkUpperBoundViolated(
|
||||
typeRef: FirTypeRef?,
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter,
|
||||
typeParameters: List<FirTypeParameterSymbol>? = null,
|
||||
typeArguments: List<Any>? = null,
|
||||
typeArgumentRefsAndSources: List<FirTypeRefSource?>? = null,
|
||||
isTypeAlias: Boolean = false,
|
||||
isIgnoreTypeParameters: Boolean = false
|
||||
) {
|
||||
require(typeArguments == null || typeArgumentRefsAndSources == null) {
|
||||
"Only one of those arguments can be not null"
|
||||
}
|
||||
val notExpandedType = typeRef?.coneTypeSafe<ConeClassLikeType>() ?: return
|
||||
|
||||
val type = typeRef?.coneTypeSafe<ConeKotlinType>() ?: return
|
||||
// Everything should be reported on the typealias expansion
|
||||
if (notExpandedType.typeArguments.isEmpty()) return
|
||||
|
||||
val typeArgumentsCount = typeArguments?.size ?: type.typeArguments.size
|
||||
if (typeArgumentsCount == 0) {
|
||||
return
|
||||
}
|
||||
val type = notExpandedType.fullyExpandedType(context.session)
|
||||
val isAbbreviatedType = notExpandedType !== type
|
||||
|
||||
val typeParameterSymbols = typeParameters
|
||||
?: if (type is ConeClassLikeType) {
|
||||
val fullyExpandedType = type.fullyExpandedType(context.session)
|
||||
val prototypeClassSymbol = fullyExpandedType.lookupTag.toSymbol(context.session) as? FirRegularClassSymbol ?: return
|
||||
if (type != fullyExpandedType) {
|
||||
// special check for type aliases
|
||||
checkUpperBoundViolated(
|
||||
typeRef,
|
||||
context,
|
||||
reporter,
|
||||
prototypeClassSymbol.typeParameterSymbols,
|
||||
fullyExpandedType.typeArguments.toList(),
|
||||
null,
|
||||
isTypeAlias = true,
|
||||
isIgnoreTypeParameters = isIgnoreTypeParameters
|
||||
)
|
||||
return
|
||||
}
|
||||
val prototypeClassSymbol = type.lookupTag.toSymbol(context.session) as? FirRegularClassSymbol ?: return
|
||||
|
||||
prototypeClassSymbol.typeParameterSymbols
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
val typeParameterSymbols = prototypeClassSymbol.typeParameterSymbols
|
||||
|
||||
if (typeParameterSymbols.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val count = minOf(typeParameterSymbols.size, typeArgumentsCount)
|
||||
val count = minOf(typeParameterSymbols.size, type.typeArguments.size)
|
||||
val substitution = mutableMapOf<FirTypeParameterSymbol, ConeKotlinType>()
|
||||
|
||||
for (index in 0 until count) {
|
||||
val typeArgument = typeArguments?.elementAt(index) ?: type.typeArguments[index]
|
||||
val typeArgument = type.typeArguments[index]
|
||||
val typeParameterSymbol = typeParameterSymbols[index]
|
||||
|
||||
if (typeArgument is FirTypeProjectionWithVariance) {
|
||||
substitution[typeParameterSymbol] = typeArgument.typeRef.coneType
|
||||
} else if (typeArgument is ConeKotlinType) {
|
||||
substitution[typeParameterSymbol] = typeArgument.type
|
||||
} else if (typeArgument is ConeTypeProjection) {
|
||||
val typeArgumentType = typeArgument.type
|
||||
if (typeArgumentType != null) {
|
||||
substitution[typeParameterSymbol] = typeArgumentType
|
||||
} else {
|
||||
substitution[typeParameterSymbol] =
|
||||
ConeStubTypeForTypeVariableInSubtyping(ConeTypeVariable("", typeParameterSymbol.toLookupTag()), ConeNullability.NOT_NULL)
|
||||
}
|
||||
val typeArgumentType = typeArgument.type
|
||||
if (typeArgumentType != null) {
|
||||
substitution[typeParameterSymbol] = typeArgumentType
|
||||
} else {
|
||||
substitution[typeParameterSymbol] =
|
||||
ConeStubTypeForTypeVariableInSubtyping(ConeTypeVariable("", typeParameterSymbol.toLookupTag()), ConeNullability.NOT_NULL)
|
||||
}
|
||||
}
|
||||
|
||||
val substitutor = substitutorByMap(substitution, context.session)
|
||||
val typeArgumentsWithSourceInfo = type.typeArguments.withIndex().map { (index, projection) ->
|
||||
val (argTypeRef, source) =
|
||||
if (!isAbbreviatedType)
|
||||
extractArgumentTypeRefAndSource(typeRef, index) ?: return
|
||||
else
|
||||
// For abbreviated arguments we use the whole typeRef as a place to report
|
||||
FirTypeRefSource(null, typeRef.source)
|
||||
|
||||
TypeArgumentWithSourceInfo(projection, argTypeRef, source)
|
||||
}
|
||||
|
||||
return checkUpperBoundViolated(
|
||||
context, reporter, typeParameterSymbols, typeArgumentsWithSourceInfo, substitutor,
|
||||
isAbbreviatedType,
|
||||
isIgnoreTypeParameters,
|
||||
)
|
||||
}
|
||||
|
||||
class TypeArgumentWithSourceInfo(
|
||||
val coneTypeProjection: ConeTypeProjection,
|
||||
val typeRef: FirTypeRef?,
|
||||
val source: KtSourceElement?,
|
||||
)
|
||||
|
||||
fun checkUpperBoundViolated(
|
||||
context: CheckerContext,
|
||||
reporter: DiagnosticReporter,
|
||||
typeParameters: List<FirTypeParameterSymbol>,
|
||||
arguments: List<TypeArgumentWithSourceInfo>,
|
||||
substitutor: ConeSubstitutor,
|
||||
isAbbreviatedType: Boolean = false,
|
||||
isIgnoreTypeParameters: Boolean = false
|
||||
) {
|
||||
val count = minOf(typeParameters.size, arguments.size)
|
||||
val typeSystemContext = context.session.typeContext
|
||||
|
||||
for (index in 0 until count) {
|
||||
var typeArgument: ConeKotlinType? = null
|
||||
var typeArgumentTypeRef: FirTypeRef? = null
|
||||
var typeArgumentSource: KtSourceElement? = null
|
||||
val argument = arguments.getOrNull(index) ?: continue
|
||||
val argumentType: ConeKotlinType? = argument.coneTypeProjection.type
|
||||
val argumentTypeRef = argument.typeRef
|
||||
val argumentSource = argument.source
|
||||
|
||||
if (typeArguments != null) {
|
||||
val localTypeArgument = typeArguments[index]
|
||||
if (localTypeArgument is FirTypeProjectionWithVariance) {
|
||||
typeArgumentTypeRef = localTypeArgument.typeRef
|
||||
typeArgument = typeArgumentTypeRef.coneType
|
||||
typeArgumentSource = localTypeArgument.source
|
||||
} else if (localTypeArgument is ConeKotlinType) {
|
||||
// Typealias case
|
||||
typeArgument = localTypeArgument
|
||||
typeArgumentSource = typeRef.source
|
||||
}
|
||||
} else {
|
||||
val localTypeArgument = type.typeArguments[index]
|
||||
if (localTypeArgument is ConeKotlinType) {
|
||||
typeArgument = localTypeArgument
|
||||
} else if (localTypeArgument is ConeKotlinTypeProjection) {
|
||||
typeArgument = localTypeArgument.type
|
||||
}
|
||||
val typeArgumentRefAndSource = typeArgumentRefsAndSources?.elementAtOrNull(index)
|
||||
if (typeArgumentRefAndSource != null) {
|
||||
typeArgumentTypeRef = typeArgumentRefAndSource.typeRef
|
||||
typeArgumentSource = typeArgumentRefAndSource.source
|
||||
} else {
|
||||
val extractedTypeArgumentRefAndSource = extractArgumentTypeRefAndSource(typeRef, index)
|
||||
if (extractedTypeArgumentRefAndSource != null) {
|
||||
typeArgumentTypeRef = extractedTypeArgumentRefAndSource.typeRef
|
||||
typeArgumentSource = extractedTypeArgumentRefAndSource.source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeArgument != null && typeArgumentSource != null) {
|
||||
if (!isIgnoreTypeParameters || (typeArgument.typeArguments.isEmpty() && typeArgument !is ConeTypeParameterType)) {
|
||||
if (argumentType != null && argumentSource != null) {
|
||||
if (!isIgnoreTypeParameters || (argumentType.typeArguments.isEmpty() && argumentType !is ConeTypeParameterType)) {
|
||||
val intersection =
|
||||
typeSystemContext.intersectTypes(typeParameterSymbols[index].resolvedBounds.map { it.coneType }) as? ConeKotlinType
|
||||
typeSystemContext.intersectTypes(typeParameters[index].resolvedBounds.map { it.coneType }) as? ConeKotlinType
|
||||
if (intersection != null) {
|
||||
val upperBound = substitutor.substituteOrSelf(intersection)
|
||||
if (!AbstractTypeChecker.isSubtypeOf(
|
||||
typeSystemContext,
|
||||
typeArgument.type,
|
||||
argumentType,
|
||||
upperBound,
|
||||
stubTypesEqualToAnything = true
|
||||
)
|
||||
) {
|
||||
val factory =
|
||||
if (isTypeAlias) FirErrors.UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION else FirErrors.UPPER_BOUND_VIOLATED
|
||||
reporter.reportOn(typeArgumentSource, factory, upperBound, typeArgument.type, context)
|
||||
if (isTypeAlias) {
|
||||
if (isAbbreviatedType) FirErrors.UPPER_BOUND_VIOLATED_IN_TYPEALIAS_EXPANSION else FirErrors.UPPER_BOUND_VIOLATED
|
||||
reporter.reportOn(argumentSource, factory, upperBound, argumentType.type, context)
|
||||
if (isAbbreviatedType) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkUpperBoundViolated(typeArgumentTypeRef, context, reporter, isIgnoreTypeParameters = isIgnoreTypeParameters)
|
||||
checkUpperBoundViolated(argumentTypeRef, context, reporter, isIgnoreTypeParameters = isIgnoreTypeParameters)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-17
@@ -5,22 +5,23 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.FirTypeRefSource
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.TypeArgumentWithSourceInfo
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.checkUpperBoundViolated
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.references.FirErrorNamedReference
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.resolve.diagnostics.ConeInapplicableWrongReceiver
|
||||
import org.jetbrains.kotlin.fir.resolve.fullyExpandedType
|
||||
import org.jetbrains.kotlin.fir.resolve.isTypeAliasedConstructor
|
||||
import org.jetbrains.kotlin.fir.resolve.substitution.substitutorByMap
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
|
||||
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeProjection
|
||||
import org.jetbrains.kotlin.fir.types.FirTypeProjectionWithVariance
|
||||
import org.jetbrains.kotlin.fir.types.toSymbol
|
||||
import org.jetbrains.kotlin.fir.types.*
|
||||
|
||||
object FirUpperBoundViolatedExpressionChecker : FirQualifiedAccessExpressionChecker() {
|
||||
override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
@@ -39,29 +40,46 @@ object FirUpperBoundViolatedExpressionChecker : FirQualifiedAccessExpressionChec
|
||||
calleeSymbol = calleReference.candidateSymbol as? FirCallableSymbol<*>
|
||||
}
|
||||
|
||||
val typeArguments: List<FirTypeProjection>?
|
||||
val typeArgumentRefsAndSources: List<FirTypeRefSource>?
|
||||
val typeParameters: List<FirTypeParameterSymbol>?
|
||||
val typeArguments: List<TypeArgumentWithSourceInfo>
|
||||
val typeParameters: List<FirTypeParameterSymbol>
|
||||
|
||||
if (calleeSymbol is FirConstructorSymbol && calleeSymbol.isTypeAliasedConstructor) {
|
||||
typeArguments = null
|
||||
typeArgumentRefsAndSources = expression.typeArguments.map {
|
||||
FirTypeRefSource((it as? FirTypeProjectionWithVariance)?.typeRef, it.source)
|
||||
val constructedType = expression.typeRef.coneType.fullyExpandedType(context.session)
|
||||
typeArguments = constructedType.typeArguments.map {
|
||||
TypeArgumentWithSourceInfo(it, typeRef = null, expression.source)
|
||||
}
|
||||
typeParameters = null
|
||||
|
||||
typeParameters = (constructedType.toSymbol(context.session) as? FirRegularClassSymbol)?.typeParameterSymbols ?: return
|
||||
} else {
|
||||
typeArguments = expression.typeArguments
|
||||
typeArgumentRefsAndSources = null
|
||||
typeParameters = calleeSymbol?.typeParameterSymbols
|
||||
typeArguments = expression.typeArguments.map { firTypeProjection ->
|
||||
TypeArgumentWithSourceInfo(
|
||||
firTypeProjection.toConeTypeProjection(),
|
||||
(firTypeProjection as? FirTypeProjectionWithVariance)?.typeRef,
|
||||
firTypeProjection.source
|
||||
)
|
||||
}
|
||||
typeParameters = calleeSymbol?.typeParameterSymbols ?: return
|
||||
}
|
||||
|
||||
// Neither common calls nor type alias constructor calls may contain projections
|
||||
// That should be checked somewhere else
|
||||
if (typeArguments.any { it.coneTypeProjection !is ConeKotlinType }) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeArguments.size != typeParameters.size) return
|
||||
|
||||
val substitutor = substitutorByMap(
|
||||
typeParameters.withIndex().associate { Pair(it.value, typeArguments[it.index].coneTypeProjection as ConeKotlinType) },
|
||||
context.session,
|
||||
)
|
||||
|
||||
checkUpperBoundViolated(
|
||||
expression.typeRef,
|
||||
context,
|
||||
reporter,
|
||||
typeParameters,
|
||||
typeArguments,
|
||||
typeArgumentRefsAndSources
|
||||
substitutor,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user