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:
Denis.Zharkov
2021-12-20 12:55:28 +03:00
committed by teamcity
parent c2f03b4d28
commit b193e708d3
10 changed files with 131 additions and 127 deletions
@@ -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)
}
}
}
@@ -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,
)
}
}