diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt new file mode 100644 index 00000000000..f23a547ef2f --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt @@ -0,0 +1,41 @@ +interface A + +class B where T : A +class C : A +typealias GGG = C +typealias HHH = GGG +typealias JJJ = B + +fun fest() { + +} + +fun test() { + val b1 = B<Int>() + val b2 = B() + fest<Boolean>() + fest() + fest() + fest<JJJ>() +} + +open class S +class T : S() + +fun rest() { + val o1 = S() + val o2 = S() + val o3 = S() + + val o4 = S, T>() + val o5 = S, T>() + val o5 = S, T>() + + val o6 = ST, String>>() + val o7 = S, Nothing>>() +} + +class NumColl> +typealias NL = NumColl> +val test7 = NL() +val test8 = NL() \ No newline at end of file diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.txt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.txt new file mode 100644 index 00000000000..ee33a27feb5 --- /dev/null +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.txt @@ -0,0 +1,61 @@ +FILE: upperBoundViolated.kt + public abstract interface A : R|kotlin/Any| { + } + public final class B : R|kotlin/Any| { + public constructor(): R|B| { + super() + } + + } + public final class C : R|A| { + public constructor(): R|C| { + super() + } + + } + public final typealias GGG = R|C| + public final typealias HHH = R|GGG| + public final typealias JJJ = R|B| + public final fun fest(): R|kotlin/Unit| { + } + public final fun test(): R|kotlin/Unit| { + lval b1: R|B| = R|/B.B|() + lval b2: R|B| = R|/B.B|() + R|/fest|() + R|/fest|() + R|/fest|() + R|/fest||>() + } + public open class S : R|kotlin/Any| { + public constructor(): R|S| { + super() + } + + } + public final class T : R|S| { + public constructor(): R|T| { + super|>() + } + + } + public final fun rest(): R|kotlin/Unit| { + lval o1: R|S| = R|/S.S|() + lval o2: R|S| = R|/S.S|() + lval o3: R|S| = R|/S.S|() + lval o4: R|S, T>| = R|/S.S||, R|T|>() + lval o5: R|S, T>| = R|/S.S||, R|T|>() + lval o5: R|S, T>| = R|/S.S||, R|T|>() + lval o6: R|S, kotlin/String>>| = R|/S.S|, kotlin/String>|>() + lval o7: R|S, kotlin/Nothing>>| = R|/S.S|, kotlin/Nothing>|>() + } + public final class NumColl|> : R|kotlin/Any| { + public constructor|>(): R|NumColl| { + super() + } + + } + public final typealias NL = R|NumColl>| + public final val test7: R|NumColl>| = R|/NumColl.NumColl|() + public get(): R|NumColl>| + public final val test8: R|NumColl>| = R|/NumColl.NumColl|() + public get(): R|NumColl>| diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java index e80f1edff85..a9f7a2f4a64 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsTestGenerated.java @@ -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"); diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java index 4a065812064..96d2b3867d2 100644 --- a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/FirDiagnosticsWithLightTreeTestGenerated.java @@ -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"); diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/DefaultExpressionCheckers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/DefaultExpressionCheckers.kt index 34e3ed7f3fc..2d6e04a8c92 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/DefaultExpressionCheckers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/DefaultExpressionCheckers.kt @@ -13,7 +13,8 @@ object CommonExpressionCheckers : ExpressionCheckers() { FirSuperclassNotAccessibleFromInterfaceChecker, FirAbstractSuperCallChecker, FirQualifiedSupertypeExtendedByOtherSupertypeChecker, - FirProjectionsOnNonClassTypeArgumentChecker + FirProjectionsOnNonClassTypeArgumentChecker, + FirUpperBoundViolatedChecker, ) override val functionCallCheckers: List = listOf() } \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUpperBoundViolatedChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUpperBoundViolatedChecker.kt new file mode 100644 index 00000000000..1d1bd53113b --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/expression/FirUpperBoundViolatedChecker.kt @@ -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() + ?.resolvedSymbol + ?.fir.safeAs() + ?: 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() + ?.typeRef.safeAs() + } + .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, T>() + actual.type.safeAs()?.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> + // typealias A = B> + // val a = A() + when (calleeFir) { + is FirConstructor -> analyzeConstructorCall(functionCall, substitutor, typeCheckerContext, reporter) + } + } + + private fun analyzeConstructorCall( + functionCall: FirQualifiedAccessExpression, + callSiteSubstitutor: ConeSubstitutor, + typeCheckerContext: AbstractTypeCheckerContext, + reporter: DiagnosticReporter, + ) { + // holds Collection bound. + // note that if B used another type parameter here, + // we'd get Collection. So we need to do one more + // substitution here + val protoConstructor = functionCall.calleeReference.safeAs() + ?.resolvedSymbol.safeAs() + ?.overriddenSymbol + ?.fir.safeAs() + ?: return + + // holds Collection bound. + // we need to do substitution here to get + // Collection + val actualConstructor = functionCall.calleeReference.safeAs() + ?.resolvedSymbol.safeAs() + ?.fir.safeAs() + ?.returnTypeRef.safeAs() + ?.type.safeAs() + ?: return + + val constructorsParameterPairs = protoConstructor.typeParameters + .zip(actualConstructor.typeArguments) + .map { (proto, actual) -> + proto.symbol to actual.safeAs() + } + .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().map { it.type } + ).safeAs() ?: 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() + ?: return + + val parameterPairs = prototypeClass.typeParameters.zip(type.typeArguments) + .map { (proto, actual) -> + proto.symbol to actual.safeAs() + } + .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().map { it.type } + ).safeAs() ?: 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. + */ + private fun List>.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)) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt index bb40fd27125..3e01665b474 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt @@ -76,8 +76,8 @@ object FirErrors { val DEPRECATED_MODIFIER_PAIR by error2() val INCOMPATIBLE_MODIFIERS by error2() - // projection val PROJECTION_ON_NON_CLASS_TYPE_ARGUMENT by error0() + val UPPER_BOUND_VIOLATED by error0() // Control flow diagnostics val UNINITIALIZED_VARIABLE by error1() diff --git a/compiler/testData/diagnostics/tests/Bounds.fir.kt b/compiler/testData/diagnostics/tests/Bounds.fir.kt index 7f4547bc671..15fc37429b1 100644 --- a/compiler/testData/diagnostics/tests/Bounds.fir.kt +++ b/compiler/testData/diagnostics/tests/Bounds.fir.kt @@ -7,13 +7,13 @@ package boundsWithSubstitutors class C : A() val a = B() - val a1 = B() + val a1 = B<Int>() class X() val b = X, C>>() - val b0 = X() - val b1 = X, String>>() + val b0 = XAny?>() + val b1 = XX, String>>() // FILE: b.kt open class A {} @@ -22,19 +22,19 @@ package boundsWithSubstitutors class Pair abstract class C, X : (B) -> Pair, B>>() : B() { // 2 errors - val a = B() // error + val a = B<Char>() // error abstract val x : (B) -> B } fun test() { - foo() + foo<Int?>() foo() bar() bar() - bar() - bar() + bar<Double?>() + bar<Double>() 1.buzz() } diff --git a/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt b/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt index 966c8561ea9..f614ee902c9 100644 --- a/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt +++ b/compiler/testData/diagnostics/tests/MultipleBounds.fir.kt @@ -36,8 +36,8 @@ class Test1() } fun test() { - Test1() - Test1() + Test1<B>() + Test1<A>() Test1() } diff --git a/compiler/testData/diagnostics/tests/inference/commonSuperTypeOfErrorTypes.fir.kt b/compiler/testData/diagnostics/tests/inference/commonSuperTypeOfErrorTypes.fir.kt index f338b2039e4..fd904f52bdd 100644 --- a/compiler/testData/diagnostics/tests/inference/commonSuperTypeOfErrorTypes.fir.kt +++ b/compiler/testData/diagnostics/tests/inference/commonSuperTypeOfErrorTypes.fir.kt @@ -9,13 +9,13 @@ fun materialize() = null as T fun test() { consume( - materialize>>(), - materialize>>() + materialize<Foo>>(), + materialize<Foo>>() ) consume( - materialize>>(), - materialize>() + materialize<Foo>>(), + materialize<Foo>() ) } diff --git a/compiler/testData/diagnostics/tests/nullableTypes/takingNullabilityFromExplicitTypeArgmentsInsteadOfUsingFlexibleTypes.fir.kt b/compiler/testData/diagnostics/tests/nullableTypes/takingNullabilityFromExplicitTypeArgmentsInsteadOfUsingFlexibleTypes.fir.kt index 2be852d310e..98b7bfe6764 100644 --- a/compiler/testData/diagnostics/tests/nullableTypes/takingNullabilityFromExplicitTypeArgmentsInsteadOfUsingFlexibleTypes.fir.kt +++ b/compiler/testData/diagnostics/tests/nullableTypes/takingNullabilityFromExplicitTypeArgmentsInsteadOfUsingFlexibleTypes.fir.kt @@ -31,8 +31,8 @@ class Bar { val foos14 = ArrayList?>() val foos15 = ArrayList>() - val foos16 = J() - val foos17 = J() - val foos18 = J() - val foos19 = J() + val foos16 = J<Foo>() + val foos17 = J<Foo?>() + val foos18 = J<T>() + val foos19 = J<T?>() } diff --git a/compiler/testData/diagnostics/tests/typealias/boundViolationInTypeAliasConstructor.fir.kt b/compiler/testData/diagnostics/tests/typealias/boundViolationInTypeAliasConstructor.fir.kt index 8b7607144f6..eb31c42f913 100644 --- a/compiler/testData/diagnostics/tests/typealias/boundViolationInTypeAliasConstructor.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/boundViolationInTypeAliasConstructor.fir.kt @@ -5,14 +5,14 @@ typealias N = Num typealias N2 = N val x1 = Num("") -val x2 = N("") -val x3 = N2("") +val x2 = N("") +val x3 = N2("") class TColl> typealias TC = TColl typealias TC2 = TC -val y1 = TColl() -val y2 = TC() -val y3 = TC2() +val y1 = TCollAny>() +val y2 = TC() +val y3 = TC2() diff --git a/compiler/testData/diagnostics/tests/typealias/boundsViolationInDeepTypeAliasExpansion.fir.kt b/compiler/testData/diagnostics/tests/typealias/boundsViolationInDeepTypeAliasExpansion.fir.kt index c868b92be74..51986247c74 100644 --- a/compiler/testData/diagnostics/tests/typealias/boundsViolationInDeepTypeAliasExpansion.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/boundsViolationInDeepTypeAliasExpansion.fir.kt @@ -14,4 +14,4 @@ fun test4(x: TC2>) {} val test5 = TC2>() val test6 = TC2>() val test7 = TC2>() -val test8 = TC2>() +val test8 = TC2>() diff --git a/compiler/testData/diagnostics/tests/typealias/boundsViolationInTypeAliasExpansion.fir.kt b/compiler/testData/diagnostics/tests/typealias/boundsViolationInTypeAliasExpansion.fir.kt index dd2a35d6211..d8133bb37be 100644 --- a/compiler/testData/diagnostics/tests/typealias/boundsViolationInTypeAliasExpansion.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/boundsViolationInTypeAliasExpansion.fir.kt @@ -6,7 +6,8 @@ class NumColl> class TColl> typealias NA = Num -typealias NL = NumColl> +typealias NL = NumColl> +typealias MMMM = NL typealias TC = TColl fun test1(x: NA) {} @@ -15,9 +16,10 @@ fun test3(x: NL) {} fun test4(x: NL) {} val test5 = NA() -val test6 = NA() +val test6 = NA() val test7 = NL() -val test8 = NL() +val test8 = MMMM() +val test9dwd = NL() fun test9(x: TC>) {} fun test10(x: TC>) {} @@ -27,4 +29,4 @@ fun test12(x: TC>) {} val test13 = TC>() val test14 = TC>() val test15 = TC>() -val test16 = TC>() +val test16 = TC>() diff --git a/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInference.fir.kt b/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInference.fir.kt index ec5a2863b61..b8ee1fd1f65 100644 --- a/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInference.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInference.fir.kt @@ -5,7 +5,7 @@ class Num(val x: Tn) typealias N = Num val test0 = N(1) -val test1 = N("1") +val test1 = N("1") class Cons(val head: T, val tail: Cons?) diff --git a/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInferenceWithNestedCalls.fir.kt b/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInferenceWithNestedCalls.fir.kt index 7f8a87e86f3..ed47e4b8a16 100644 --- a/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInferenceWithNestedCalls.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/typeAliasConstructorTypeArgumentsInferenceWithNestedCalls.fir.kt @@ -5,4 +5,4 @@ class Cons(val head: T, val tail: Cons?) typealias C = Cons val test1 = C(1, C(2, null)) -val test2 = C(1, C("", null)) +val test2 = C(1, C("", null)) diff --git a/compiler/testData/diagnostics/tests/typealias/wrongNumberOfArgumentsInTypeAliasConstructor.fir.kt b/compiler/testData/diagnostics/tests/typealias/wrongNumberOfArgumentsInTypeAliasConstructor.fir.kt index ec180d9b570..fa611a11a7c 100644 --- a/compiler/testData/diagnostics/tests/typealias/wrongNumberOfArgumentsInTypeAliasConstructor.fir.kt +++ b/compiler/testData/diagnostics/tests/typealias/wrongNumberOfArgumentsInTypeAliasConstructor.fir.kt @@ -29,9 +29,9 @@ val test3pr = P2(1, "") class Num(val x: T) typealias N = Num -val testN0 = N("") +val testN0 = N("") val testN1 = N(1) -val testN1a = N("") +val testN1a = N("") val testN2 = N(1) class MyPair(val string: T1, val number: T2) @@ -39,4 +39,4 @@ typealias MP = MyPair val testMP0 = MP("", 1) val testMP1 = MP(1, "") -val testMP2 = MP("", "") \ No newline at end of file +val testMP2 = MP("", "") \ No newline at end of file