From 1ea5678932f1a525873147bf285838536f21c478 Mon Sep 17 00:00:00 2001 From: Nick Date: Mon, 13 Jul 2020 10:11:58 +0300 Subject: [PATCH] [FIR] UPPER_BOUND_VIOLATED optimizations --- .../resolve/diagnostics/upperBoundViolated.kt | 9 +- .../diagnostics/upperBoundViolated.txt | 13 +++ .../FirUpperBoundViolatedChecker.kt | 94 +++++++++++++------ 3 files changed, 85 insertions(+), 31 deletions(-) diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt index f23a547ef2f..5ab1247d452 100644 --- a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt @@ -13,6 +13,9 @@ fun fest() { fun test() { val b1 = B<Int>() val b2 = B() + val b3 = B<Any?>() + val b4 = B() + val b5 = B<B>() fest<Boolean>() fest() fest() @@ -38,4 +41,8 @@ fun rest() { class NumColl> typealias NL = NumColl> val test7 = NL() -val test8 = NL() \ No newline at end of file +val test8 = NL() + +class NumberPhile(x: T) +val np1 = NumberPhile(10) +val np2 = NumberPhile("Test") \ 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 index ee33a27feb5..65813044e15 100644 --- a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.txt +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.txt @@ -21,6 +21,9 @@ FILE: upperBoundViolated.kt public final fun test(): R|kotlin/Unit| { lval b1: R|B| = R|/B.B|() lval b2: R|B| = R|/B.B|() + lval b3: R|B| = R|/B.B|() + lval b4: R|B| = R|/B.B|() + lval b5: R|B>| = R|/B.B||>() R|/fest|() R|/fest|() R|/fest|() @@ -59,3 +62,13 @@ FILE: upperBoundViolated.kt public get(): R|NumColl>| public final val test8: R|NumColl>| = R|/NumColl.NumColl|() public get(): R|NumColl>| + public final class NumberPhile : R|kotlin/Any| { + public constructor(x: R|T|): R|NumberPhile| { + super() + } + + } + public final val np1: R|NumberPhile| = R|/NumberPhile.NumberPhile|(Int(10)) + public get(): R|NumberPhile| + public final val np2: = #(String(Test)) + public get(): 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 index 1d1bd53113b..c182376b050 100644 --- 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 @@ -21,6 +21,7 @@ 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.min import org.jetbrains.kotlin.utils.addToStdlib.safeAs object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { @@ -39,12 +40,16 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { stubTypesEqualToAnything = false ) - val parameterPairs = calleeFir.typeParameters.zip(functionCall.typeArguments) - .map { (proto, actual) -> - proto.symbol to actual.safeAs() - ?.typeRef.safeAs() - } - .toMapWithoutNulls() + val parameterPairs = mutableMapOf() + val count = min(calleeFir.typeParameters.size, functionCall.typeArguments.size) + + for (it in 0 until count) { + functionCall.typeArguments[it].safeAs() + ?.typeRef.safeAs() + ?.let { that -> + parameterPairs[calleeFir.typeParameters[it].symbol] = that + } + } // we substitute actual values to the // type parameters from the declaration @@ -52,16 +57,28 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { parameterPairs.mapValues { it.value.type } ) - parameterPairs.forEach { proto, actual -> + val canBeSkip = mutableSetOf() + + parameterPairs.forEach { (proto, actual) -> + if (actual.source == null) { +// canBeSkip.add(proto) // inferred types report INAPPLICABLE_CANDIDATE in case of an error + // inferred types don't report INAPPLICABLE_CANDIDATE for typealiases! + return@forEach + } + if (!satisfiesBounds(proto, actual.type, substitutor, typeCheckerContext)) { reporter.report(actual.source) - return@forEach + return } // we must analyze nested things like // S, T>() actual.type.safeAs()?.let { - analyzeTypeParameters(it, context, reporter, typeCheckerContext, actual.source) + val errorOccurred = analyzeTypeParameters(it, context, reporter, typeCheckerContext, actual.source) + + if (errorOccurred) { + return + } } } @@ -74,7 +91,7 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { // typealias A = B> // val a = A() when (calleeFir) { - is FirConstructor -> analyzeConstructorCall(functionCall, substitutor, typeCheckerContext, reporter) + is FirConstructor -> analyzeConstructorCall(functionCall, substitutor, typeCheckerContext, reporter, canBeSkip) } } @@ -83,6 +100,7 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { callSiteSubstitutor: ConeSubstitutor, typeCheckerContext: AbstractTypeCheckerContext, reporter: DiagnosticReporter, + canBeSkip: MutableSet ) { // holds Collection bound. // note that if B used another type parameter here, @@ -104,21 +122,26 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { ?.type.safeAs() ?: return - val constructorsParameterPairs = protoConstructor.typeParameters - .zip(actualConstructor.typeArguments) - .map { (proto, actual) -> - proto.symbol to actual.safeAs() - } - .toMapWithoutNulls() + val constructorsParameterPairs = mutableMapOf() + val count = min(protoConstructor.typeParameters.size, actualConstructor.typeArguments.size) + + for (it in 0 until count) { + actualConstructor.typeArguments[it].safeAs() + ?.let { that -> +// if (!canBeSkip.contains(protoConstructor.typeParameters[it].symbol)) { + constructorsParameterPairs[protoConstructor.typeParameters[it].symbol] = that +// } + } + } // we substitute typealias declaration // parameters to the ones used in the // typealias target val declarationSiteSubstitutor = substitutorByMap( - constructorsParameterPairs.mapValues { it.value.type } + constructorsParameterPairs.toMap().mapValues { it.value.type } ) - constructorsParameterPairs.forEach { proto, actual -> + constructorsParameterPairs.forEach { (proto, actual) -> // just in case var intersection = typeCheckerContext.intersectTypes( proto.fir.bounds.filterIsInstance().map { it.type } @@ -134,7 +157,7 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { if (!satisfiesBounds) { reporter.report(functionCall.source) - return@forEach + return } } } @@ -144,6 +167,7 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { * 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 */ private fun analyzeTypeParameters( type: ConeClassLikeType, @@ -151,30 +175,40 @@ object FirUpperBoundViolatedChecker : FirQualifiedAccessChecker() { reporter: DiagnosticReporter, typeCheckerContext: AbstractTypeCheckerContext, reportTarget: FirSourceElement? - ) { + ): Boolean { val prototypeClass = type.lookupTag.toSymbol(context.session) ?.fir.safeAs() - ?: return + ?: return false - val parameterPairs = prototypeClass.typeParameters.zip(type.typeArguments) - .map { (proto, actual) -> - proto.symbol to actual.safeAs() - } - .toMapWithoutNulls() + val parameterPairs = mutableMapOf() + val count = min(prototypeClass.typeParameters.size, type.typeArguments.size) + + for (it in 0 until count) { + type.typeArguments[it].safeAs() + ?.let { that -> + parameterPairs[prototypeClass.typeParameters[it].symbol] = that + } + } val substitutor = substitutorByMap( - parameterPairs.mapValues { it.value.type } + parameterPairs.toMap().mapValues { it.value.type } ) - parameterPairs.forEach { proto, actual -> + parameterPairs.forEach { (proto, actual) -> if (!satisfiesBounds(proto, actual.type, substitutor, typeCheckerContext)) { // should report on the parameter instead! reporter.report(reportTarget) - return@forEach + return true } - analyzeTypeParameters(actual, context, reporter, typeCheckerContext, reportTarget) + val errorOccurred = analyzeTypeParameters(actual, context, reporter, typeCheckerContext, reportTarget) + + if (errorOccurred) { + return true + } } + + return false } /**