[FIR] Add diagnostic UPPER_BOUND_VIOLATED

This commit is contained in:
Nick
2020-07-09 13:46:16 +03:00
committed by Mikhail Glukhikh
parent b9a220c624
commit 41a0cfe002
17 changed files with 361 additions and 34 deletions
@@ -0,0 +1,41 @@
interface A
class B<T> where T : A
class C : A
typealias GGG = C
typealias HHH = GGG
typealias JJJ = B<C>
fun <T : A> fest() {
}
fun test() {
val b1 = B<<!UPPER_BOUND_VIOLATED!>Int<!>>()
val b2 = B<C>()
fest<<!UPPER_BOUND_VIOLATED!>Boolean<!>>()
fest<C>()
fest<HHH>()
fest<<!UPPER_BOUND_VIOLATED!>JJJ<!>>()
}
open class S<F, G : F>
class T<U, Y : U> : S<U, Y>()
fun <K, L : K> rest() {
val o1 = S<K, L>()
val o2 = S<K, K>()
val o3 = S<L, L>()
val o4 = S<S<K, L>, T<K, L>>()
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 o7 = S<Any, T<S<K, L>, Nothing>>()
}
class NumColl<T : Collection<Number>>
typealias NL<K> = NumColl<List<K>>
val test7 = NL<Int>()
val test8 = <!UPPER_BOUND_VIOLATED!>NL<String>()<!>
@@ -0,0 +1,61 @@
FILE: upperBoundViolated.kt
public abstract interface A : R|kotlin/Any| {
}
public final class B<T : R|A|> : R|kotlin/Any| {
public constructor<T : R|A|>(): R|B<T>| {
super<R|kotlin/Any|>()
}
}
public final class C : R|A| {
public constructor(): R|C| {
super<R|kotlin/Any|>()
}
}
public final typealias GGG = R|C|
public final typealias HHH = R|GGG|
public final typealias JJJ = R|B<C>|
public final fun <T : R|A|> fest(): R|kotlin/Unit| {
}
public final fun test(): R|kotlin/Unit| {
lval b1: R|B<kotlin/Int>| = R|/B.B|<R|kotlin/Int|>()
lval b2: R|B<C>| = R|/B.B|<R|C|>()
R|/fest|<R|kotlin/Boolean|>()
R|/fest|<R|C|>()
R|/fest|<R|C|>()
R|/fest|<R|B<C>|>()
}
public open class S<F, G : R|F|> : R|kotlin/Any| {
public constructor<F, G : R|F|>(): R|S<F, G>| {
super<R|kotlin/Any|>()
}
}
public final class T<U, Y : R|U|> : R|S<U, Y>| {
public constructor<U, Y : R|U|>(): R|T<U, Y>| {
super<R|S<U, Y>|>()
}
}
public final fun <K, L : R|K|> rest(): R|kotlin/Unit| {
lval o1: R|S<K, L>| = R|/S.S|<R|K|, R|L|>()
lval o2: R|S<K, K>| = R|/S.S|<R|K|, R|K|>()
lval o3: R|S<L, L>| = R|/S.S|<R|L|, R|L|>()
lval o4: R|S<S<K, L>, T<K, L>>| = R|/S.S|<R|S<K, L>|, R|T<K, L>|>()
lval o5: R|S<S<K, L>, T<K, K>>| = R|/S.S|<R|S<K, L>|, R|T<K, K>|>()
lval o5: R|S<S<L, L>, T<K, L>>| = R|/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>|>()
}
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>| {
super<R|kotlin/Any|>()
}
}
public final typealias NL<K> = R|NumColl<kotlin/collections/List<K>>|
public final val test7: R|NumColl<kotlin/collections/List<kotlin/Int>>| = R|/NumColl.NumColl|<R|kotlin/Int|>()
public get(): R|NumColl<kotlin/collections/List<kotlin/Int>>|
public final val test8: R|NumColl<kotlin/collections/List<kotlin/String>>| = R|/NumColl.NumColl|<R|kotlin/String|>()
public get(): R|NumColl<kotlin/collections/List<kotlin/String>>|
@@ -946,6 +946,11 @@ public class FirDiagnosticsTestGenerated extends AbstractFirDiagnosticsTest {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt");
}
@TestMetadata("upperBoundViolated.kt")
public void testUpperBoundViolated() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt");
}
@TestMetadata("valOnAnnotationParameter.kt")
public void testValOnAnnotationParameter() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
@@ -946,6 +946,11 @@ public class FirDiagnosticsWithLightTreeTestGenerated extends AbstractFirDiagnos
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/typeOfAnnotationMember.kt");
}
@TestMetadata("upperBoundViolated.kt")
public void testUpperBoundViolated() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt");
}
@TestMetadata("valOnAnnotationParameter.kt")
public void testValOnAnnotationParameter() throws Exception {
runTest("compiler/fir/analysis-tests/testData/resolve/diagnostics/valOnAnnotationParameter.kt");
@@ -13,7 +13,8 @@ object CommonExpressionCheckers : ExpressionCheckers() {
FirSuperclassNotAccessibleFromInterfaceChecker,
FirAbstractSuperCallChecker,
FirQualifiedSupertypeExtendedByOtherSupertypeChecker,
FirProjectionsOnNonClassTypeArgumentChecker
FirProjectionsOnNonClassTypeArgumentChecker,
FirUpperBoundViolatedChecker,
)
override val functionCallCheckers: List<FirFunctionCallChecker> = listOf()
}
@@ -0,0 +1,212 @@
/*
* Copyright 2010-2020 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.expression
import org.jetbrains.kotlin.fir.FirSourceElement
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
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.*
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.AbstractTypeCheckerContext
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() {
override fun check(functionCall: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) {
// something that contains the type parameters
// declarations with their declared bounds.
// it may be the called function declaration
// or the class declaration
val calleeFir = functionCall.calleeReference.safeAs<FirResolvedNamedReference>()
?.resolvedSymbol
?.fir.safeAs<FirTypeParameterRefsOwner>()
?: return
val typeCheckerContext = context.session.typeContext.newBaseTypeCheckerContext(
errorTypesEqualToAnything = false,
stubTypesEqualToAnything = false
)
val parameterPairs = calleeFir.typeParameters.zip(functionCall.typeArguments)
.map { (proto, actual) ->
proto.symbol to actual.safeAs<FirTypeProjectionWithVariance>()
?.typeRef.safeAs<FirResolvedTypeRef>()
}
.toMapWithoutNulls()
// we substitute actual values to the
// type parameters from the declaration
val substitutor = substitutorByMap(
parameterPairs.mapValues { it.value.type }
)
parameterPairs.forEach { proto, actual ->
if (!satisfiesBounds(proto, actual.type, substitutor, typeCheckerContext)) {
reporter.report(actual.source)
return@forEach
}
// we must analyze nested things like
// S<S<K, L>, T<K, L>>()
actual.type.safeAs<ConeClassLikeType>()?.let {
analyzeTypeParameters(it, context, reporter, typeCheckerContext, actual.source)
}
}
// 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(functionCall, substitutor, typeCheckerContext, reporter)
}
}
private fun analyzeConstructorCall(
functionCall: FirQualifiedAccessExpression,
callSiteSubstitutor: ConeSubstitutor,
typeCheckerContext: AbstractTypeCheckerContext,
reporter: DiagnosticReporter,
) {
// 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>()
?.overriddenSymbol
?.fir.safeAs<FirConstructor>()
?: 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.safeAs<FirResolvedTypeRef>()
?.type.safeAs<ConeClassLikeType>()
?: return
val constructorsParameterPairs = protoConstructor.typeParameters
.zip(actualConstructor.typeArguments)
.map { (proto, actual) ->
proto.symbol to actual.safeAs<ConeSimpleKotlinType>()
}
.toMapWithoutNulls()
// we substitute typealias declaration
// parameters to the ones used in the
// typealias target
val declarationSiteSubstitutor = substitutorByMap(
constructorsParameterPairs.mapValues { it.value.type }
)
constructorsParameterPairs.forEach { proto, actual ->
// just in case
var intersection = typeCheckerContext.intersectTypes(
proto.fir.bounds.filterIsInstance<FirResolvedTypeRef>().map { it.type }
).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(typeCheckerContext, target, intersection)
if (!satisfiesBounds) {
reporter.report(functionCall.source)
return@forEach
}
}
}
/**
* Recursively analyzes type parameters
* and reports the diagnostic on the given
* reportTarget (because we can't report them
* on type parameters themselves now).
*/
private fun analyzeTypeParameters(
type: ConeClassLikeType,
context: CheckerContext,
reporter: DiagnosticReporter,
typeCheckerContext: AbstractTypeCheckerContext,
reportTarget: FirSourceElement?
) {
val prototypeClass = type.lookupTag.toSymbol(context.session)
?.fir.safeAs<FirRegularClass>()
?: return
val parameterPairs = prototypeClass.typeParameters.zip(type.typeArguments)
.map { (proto, actual) ->
proto.symbol to actual.safeAs<ConeClassLikeType>()
}
.toMapWithoutNulls()
val substitutor = substitutorByMap(
parameterPairs.mapValues { it.value.type }
)
parameterPairs.forEach { proto, actual ->
if (!satisfiesBounds(proto, actual.type, substitutor, typeCheckerContext)) {
// should report on the parameter instead!
reporter.report(reportTarget)
return@forEach
}
analyzeTypeParameters(actual, context, reporter, typeCheckerContext, reportTarget)
}
}
/**
* Returns true if target satisfies the
* bounds of the prototypeSymbol.
*/
private fun satisfiesBounds(
prototypeSymbol: FirTypeParameterSymbol,
target: ConeKotlinType,
substitutor: ConeSubstitutor,
typeCheckerContext: AbstractTypeCheckerContext
): Boolean {
var intersection = typeCheckerContext.intersectTypes(
prototypeSymbol.fir.bounds.filterIsInstance<FirResolvedTypeRef>().map { it.type }
).safeAs<ConeKotlinType>() ?: return true
intersection = substitutor.substituteOrSelf(intersection)
return AbstractTypeChecker.isSubtypeOf(typeCheckerContext, target, intersection)
}
/**
* Removes the entries where either A? or B?
* is null and constructs a Map<A, B>.
*/
private fun <A, B> List<Pair<A?, B?>>.toMapWithoutNulls() = this
.filter { it.first != null && it.second != null }
.map { it.first!! to it.second!! }
.toMap()
private fun DiagnosticReporter.report(source: FirSourceElement?) {
source?.let {
report(FirErrors.UPPER_BOUND_VIOLATED.on(it))
}
}
}
@@ -76,8 +76,8 @@ object FirErrors {
val DEPRECATED_MODIFIER_PAIR by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
val INCOMPATIBLE_MODIFIERS by error2<FirSourceElement, PsiElement, KtModifierKeywordToken, KtModifierKeywordToken>()
// projection
val PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT by error0<FirSourceElement, PsiElement>()
val UPPER_BOUND_VIOLATED by error0<FirSourceElement, PsiElement>()
// Control flow diagnostics
val UNINITIALIZED_VARIABLE by error1<FirSourceElement, PsiElement, FirPropertySymbol>()