[FIR] Fix location of UPPER_BOUND_VIOLATED, fix detecting of several diagnostics, simplify FirUpperBoundViolatedChecker.kt

This commit is contained in:
Ivan Kochurkin
2021-06-08 17:52:02 +03:00
committed by teamcityserver
parent 1298ba431b
commit ada14413e0
4 changed files with 101 additions and 202 deletions
@@ -40,6 +40,18 @@ FILE: upperBoundViolated.kt
super<R|S<U, Y>|>()
}
}
public final class P<T> : R|kotlin/Any| {
public constructor<T>(): R|P<T>| {
super<R|kotlin/Any|>()
}
}
public final class P1<T1 : R|kotlin/Number|, T2 : R|kotlin/Number|> : R|kotlin/Any| {
public constructor<T1 : R|kotlin/Number|, T2 : R|kotlin/Number|>(): R|P1<T1, T2>| {
super<R|kotlin/Any|>()
}
}
public final fun <K, L : R|K|> rest(): R|kotlin/Unit| {
lval o1: R|S<K, L>| = R|/S.S|<R|K|, R|L|>()
@@ -50,6 +62,7 @@ FILE: upperBoundViolated.kt
lval o5: R|S<S<L, L>, T<K, L>>| = <CS errors: /S.S>#<R|S<L, L>|, R|T<K, L>|>()
lval o6: R|S<kotlin/Any, T<S<K, L>, kotlin/String>>| = R|/S.S|<R|kotlin/Any|, R|T<S<K, L>, kotlin/String>|>()
lval o7: R|S<kotlin/Any, T<S<K, L>, kotlin/Nothing>>| = R|/S.S|<R|kotlin/Any|, R|T<S<K, L>, kotlin/Nothing>|>()
lval o8: R|P<P1<kotlin/String, kotlin/String>>| = R|/P.P|<R|P1<kotlin/String, kotlin/String>|>()
}
public final class NumColl<T : R|kotlin/collections/Collection<kotlin/Number>|> : R|kotlin/Any| {
public constructor<T : R|kotlin/collections/Collection<kotlin/Number>|>(): R|NumColl<T>| {
@@ -24,6 +24,9 @@ fun test() {
open class S<F, G : F>
class T<U, Y : U> : S<U, Y>()
class P<T>
class P1<T1: Number, T2: Number>
fun <K, L : K> rest() {
val o1 = S<K, L>()
@@ -34,8 +37,9 @@ fun <K, L : K> rest() {
val o5 = S<S<K, L>, <!UPPER_BOUND_VIOLATED!>T<K, K><!>>()
val o5 = S<S<L, L>, <!UPPER_BOUND_VIOLATED!>T<K, L><!>>()
val o6 = S<Any, <!UPPER_BOUND_VIOLATED!>T<S<K, L>, String><!>>()
val o6 = S<Any, T<S<K, L>, <!UPPER_BOUND_VIOLATED!>String<!>>>()
val o7 = S<Any, T<S<K, L>, Nothing>>()
val o8 = P<P1<<!UPPER_BOUND_VIOLATED!>String<!>, <!UPPER_BOUND_VIOLATED!>String<!>>>()
}
class NumColl<T : Collection<Number>>
@@ -7,17 +7,17 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.extractTypeRefAndSourceFromTypeArgument
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.analysis.diagnostics.reportOn
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.declarations.FirRegularClass
import org.jetbrains.kotlin.fir.declarations.FirTypeParameterRefsOwner
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
import org.jetbrains.kotlin.fir.originalForSubstitutionOverride
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
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.FirConstructorSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirTypeParameterSymbol
import org.jetbrains.kotlin.fir.typeContext
import org.jetbrains.kotlin.fir.types.*
@@ -35,213 +35,99 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessExpressionChecker() {
?.fir.safeAs<FirTypeParameterRefsOwner>()
?: return
val count = minOf(calleeFir.typeParameters.size, expression.typeArguments.size)
if (count == 0) {
return
}
val parameterPairs = mutableMapOf<FirTypeParameterSymbol, FirTypeRef>()
for (it in 0 until count) {
expression.typeArguments[it].safeAs<FirTypeProjectionWithVariance>()
?.typeRef
?.let { that ->
if (that !is FirErrorTypeRef) {
parameterPairs[calleeFir.typeParameters[it].symbol] = that
} else {
return
}
}
}
// we substitute actual values to the
// type parameters from the declaration
val substitutor = substitutorByMap(
parameterPairs.mapValues { it.value.coneType },
context.session
)
parameterPairs.forEach { (proto, actual) ->
if (actual.source == null) {
// inferred types don't report INAPPLICABLE_CANDIDATE for type aliases!
return@forEach
}
val upperBound = getSubstitutedUpperBound(proto, substitutor, context.session.typeContext)
if (upperBound != null && !satisfiesBounds(upperBound, actual.coneType, context.session.typeContext)) {
reporter.reportOn(actual.source, upperBound, context)
return
}
// we must analyze nested things like
// S<S<K, L>, T<K, L>>()
actual.coneType.safeAs<ConeClassLikeType>()?.let {
val errorOccurred = analyzeTypeParameters(it, context, reporter, context.session.typeContext, actual.source)
if (errorOccurred) {
return
}
}
}
// if we're dealing with a constructor
// resolved from a typealias we need to
// check if our actual parameters satisfy
// this constructor parameters.
// e.g.
// class B<T : Collection<Number>>
// typealias A<G> = B<List<G>>
// val a = A<Int>()
when (calleeFir) {
is FirConstructor -> analyzeConstructorCall(expression, substitutor, context.session.typeContext, reporter, context)
}
}
private fun analyzeConstructorCall(
functionCall: FirQualifiedAccessExpression,
callSiteSubstitutor: ConeSubstitutor,
typeSystemContext: ConeTypeContext,
reporter: DiagnosticReporter,
context: CheckerContext
) {
// holds Collection<Number> bound.
// note that if B used another type parameter here,
// we'd get Collection<K>. So we need to do one more
// substitution here
val protoConstructor = functionCall.calleeReference.safeAs<FirResolvedNamedReference>()
?.resolvedSymbol.safeAs<FirConstructorSymbol>()
?.fir?.originalForSubstitutionOverride
?: return
// holds Collection<G> bound.
// we need to do substitution here to get
// Collection<Int>
val actualConstructor = functionCall.calleeReference.safeAs<FirResolvedNamedReference>()
?.resolvedSymbol.safeAs<FirConstructorSymbol>()
?.fir.safeAs<FirConstructor>()
?.returnTypeRef?.coneType
?.safeAs<ConeClassLikeType>()
?: return
val count = minOf(protoConstructor.typeParameters.size, actualConstructor.typeArguments.size)
if (count == 0) {
return
}
val constructorsParameterPairs = mutableMapOf<FirTypeParameterSymbol, ConeSimpleKotlinType>()
for (it in 0 until count) {
actualConstructor.typeArguments[it].safeAs<ConeSimpleKotlinType>()
?.let { that ->
if (that !is ConeClassErrorType) {
constructorsParameterPairs[protoConstructor.typeParameters[it].symbol] = that
} else {
return
}
}
}
// we substitute typealias declaration
// parameters to the ones used in the
// typealias target
val declarationSiteSubstitutor = substitutorByMap(
constructorsParameterPairs.toMap().mapValues { it.value.type },
context.session
)
constructorsParameterPairs.forEach { (proto, actual) ->
// just in case
var intersection = typeSystemContext.intersectTypes(
proto.fir.bounds.map { it.coneType }
).safeAs<ConeKotlinType>() ?: return@forEach
intersection = declarationSiteSubstitutor.substituteOrSelf(intersection)
intersection = callSiteSubstitutor.substituteOrSelf(intersection)
// substitute Int for G from
// the example above
val target = callSiteSubstitutor.substituteOrSelf(actual)
val satisfiesBounds = AbstractTypeChecker.isSubtypeOf(typeSystemContext, target, intersection)
if (!satisfiesBounds) {
reporter.reportOn(functionCall.source, intersection, context)
return
}
val coneType = expression.typeRef.coneType
if (coneType is ConeClassLikeType) {
analyzeTypeParameters(
coneType,
expression.typeRef,
calleeFir.typeParameters.map { it.symbol },
expression.typeArguments,
context,
reporter
)
}
}
/**
* Recursively analyzes type parameters
* and reports the diagnostic on the given
* reportTarget (because we can't report them
* on type parameters themselves now).
* Returns true if an error occured
* Recursively analyzes type parameters and reports the diagnostic on the given source calculated using typeRef
* Returns true if an error occurred
*/
private fun analyzeTypeParameters(
type: ConeClassLikeType,
typeRef: FirTypeRef?,
typeParameters: List<FirTypeParameterSymbol>?,
typeArguments: List<FirTypeProjection>?,
context: CheckerContext,
reporter: DiagnosticReporter,
typeSystemContext: ConeTypeContext,
reportTarget: FirSourceElement?
): Boolean {
val prototypeClass = type.lookupTag.toSymbol(context.session)
?.fir.safeAs<FirRegularClass>()
?: return false
val count = minOf(prototypeClass.typeParameters.size, type.typeArguments.size)
if (count == 0) {
return false
reporter: DiagnosticReporter
) {
fun getTypeArgument(index: Int): Any {
return typeArguments?.elementAt(index) ?: type.typeArguments[index]
}
val parameterPairs = mutableMapOf<FirTypeParameterSymbol, ConeClassLikeType>()
val typeArgumentsCount = typeArguments?.size ?: type.typeArguments.size
if (typeArgumentsCount == 0) {
return
}
for (it in 0 until count) {
type.typeArguments[it].safeAs<ConeClassLikeType>()
?.let { that ->
if (that !is ConeClassErrorType) {
parameterPairs[prototypeClass.typeParameters[it].symbol] = that
} else {
return true
}
val typeParameterSymbols = if (typeParameters != null) {
typeParameters
} else {
val prototypeClass = type.lookupTag.toSymbol(context.session)
?.fir.safeAs<FirRegularClass>()
?: return
prototypeClass.typeParameters.map { it.symbol }
}
if (typeParameterSymbols.isEmpty()) {
return
}
val count = minOf(typeParameterSymbols.size, typeArgumentsCount)
val substitution = mutableMapOf<FirTypeParameterSymbol, ConeKotlinType>()
for (index in 0 until count) {
val typeArgument = getTypeArgument(index)
val typeParameterSymbol = typeParameterSymbols[index]
if (typeArgument is FirTypeProjectionWithVariance) {
substitution[typeParameterSymbol] = typeArgument.typeRef.coneType
} else if (typeArgument is ConeClassLikeType) {
substitution[typeParameterSymbol] = typeArgument.type
}
}
val substitutor = substitutorByMap(substitution, context.session)
val typeSystemContext = context.session.typeContext
for (index in 0 until count) {
var typeArgument: ConeClassLikeType? = null
var typeArgumentTypeRef: FirTypeRef? = null
var typeArgumentSource: FirSourceElement? = null
if (typeArguments != null) {
val localTypeArgument = typeArguments[index]
if (localTypeArgument is FirTypeProjectionWithVariance) {
typeArgumentTypeRef = localTypeArgument.typeRef
typeArgument = typeArgumentTypeRef.coneType as? ConeClassLikeType
typeArgumentSource = localTypeArgument.source
}
}
val substitutor = substitutorByMap(
parameterPairs.toMap().mapValues { it.value.type },
context.session
)
parameterPairs.forEach { (proto, actual) ->
val upperBound = getSubstitutedUpperBound(proto, substitutor, typeSystemContext)
if (upperBound != null && !satisfiesBounds(upperBound, actual.type, typeSystemContext)) {
// should report on the parameter instead!
reporter.reportOn(reportTarget, upperBound, context)
return true
} else {
typeArgument = type.typeArguments[index] as? ConeClassLikeType
val argTypeRefSource = extractTypeRefAndSourceFromTypeArgument(typeRef, index)
typeArgumentTypeRef = argTypeRefSource?.first
typeArgumentSource = argTypeRefSource?.second
}
val errorOccurred = analyzeTypeParameters(actual, context, reporter, typeSystemContext, reportTarget)
if (errorOccurred) {
return true
if (typeArgument != null && typeArgumentSource != null) {
val upperBound = getSubstitutedUpperBound(typeParameterSymbols[index], substitutor, typeSystemContext)
if (upperBound != null && !satisfiesBounds(upperBound, typeArgument.type, typeSystemContext)) {
reporter.reportOn(typeArgumentSource, FirErrors.UPPER_BOUND_VIOLATED, upperBound, context)
} else {
analyzeTypeParameters(typeArgument, typeArgumentTypeRef, null, null, context, reporter)
}
}
}
return false
}
/**
* Returns true if target satisfies the
* bounds of the prototypeSymbol.
*/
private fun satisfiesBounds(
upperBound: ConeKotlinType,
target: ConeKotlinType,
typeSystemContext: ConeTypeContext
): Boolean {
return AbstractTypeChecker.isSubtypeOf(typeSystemContext, target, upperBound, stubTypesEqualToAnything = false)
}
private fun getSubstitutedUpperBound(
@@ -256,11 +142,7 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessExpressionChecker() {
return substitutor.substituteOrSelf(intersection)
}
private fun DiagnosticReporter.reportOn(
source: FirSourceElement?,
actual: ConeKotlinType,
context: CheckerContext
) {
reportOn(source, FirErrors.UPPER_BOUND_VIOLATED, actual, context)
private fun satisfiesBounds(upperBound: ConeKotlinType, target: ConeKotlinType, typeSystemContext: ConeTypeContext): Boolean {
return AbstractTypeChecker.isSubtypeOf(typeSystemContext, target, upperBound, stubTypesEqualToAnything = false)
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ package boundsWithSubstitutors
val b = X<Any, X<A<C>, C>>()
val b0 = X<Any, <!UPPER_BOUND_VIOLATED!>Any?<!>>()
val b1 = X<Any, <!UPPER_BOUND_VIOLATED!>X<A<C>, String><!>>()
val b1 = X<Any, X<A<C>, <!UPPER_BOUND_VIOLATED!>String<!>>>()
// FILE: b.kt
open class A {}