diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.fir.txt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.fir.txt index 57618327c93..eb0e058b6b8 100644 --- a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.fir.txt +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.fir.txt @@ -40,6 +40,18 @@ FILE: upperBoundViolated.kt super|>() } + } + public final class P : R|kotlin/Any| { + public constructor(): R|P| { + super() + } + + } + public final class P1 : R|kotlin/Any| { + public constructor(): R|P1| { + super() + } + } public final fun rest(): R|kotlin/Unit| { lval o1: R|S| = R|/S.S|() @@ -50,6 +62,7 @@ FILE: upperBoundViolated.kt lval o5: R|S, T>| = #|, R|T|>() lval o6: R|S, kotlin/String>>| = R|/S.S|, kotlin/String>|>() lval o7: R|S, kotlin/Nothing>>| = R|/S.S|, kotlin/Nothing>|>() + lval o8: R|P>| = R|/P.P||>() } public final class NumColl|> : R|kotlin/Any| { public constructor|>(): R|NumColl| { diff --git a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt index 7e330e16b9f..ccb68fdd696 100644 --- a/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt +++ b/compiler/fir/analysis-tests/testData/resolve/diagnostics/upperBoundViolated.kt @@ -24,6 +24,9 @@ fun test() { open class S class T : S() +class P +class P1 + fun rest() { val o1 = S() @@ -34,8 +37,9 @@ fun rest() { val o5 = S, T>() val o5 = S, T>() - val o6 = ST, String>>() + val o6 = S, String>>() val o7 = S, Nothing>>() + val o8 = PString, String>>() } class NumColl> 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 eca0be8ec11..5c346294d2f 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 @@ -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() ?: return - val count = minOf(calleeFir.typeParameters.size, expression.typeArguments.size) - - if (count == 0) { - return - } - - val parameterPairs = mutableMapOf() - - for (it in 0 until count) { - expression.typeArguments[it].safeAs() - ?.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, T>() - actual.coneType.safeAs()?.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> - // typealias A = B> - // val a = A() - 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 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() - ?.fir?.originalForSubstitutionOverride - ?: return - - // holds Collection bound. - // we need to do substitution here to get - // Collection - val actualConstructor = functionCall.calleeReference.safeAs() - ?.resolvedSymbol.safeAs() - ?.fir.safeAs() - ?.returnTypeRef?.coneType - ?.safeAs() - ?: return - - val count = minOf(protoConstructor.typeParameters.size, actualConstructor.typeArguments.size) - - if (count == 0) { - return - } - - val constructorsParameterPairs = mutableMapOf() - - for (it in 0 until count) { - actualConstructor.typeArguments[it].safeAs() - ?.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() ?: 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?, + typeArguments: List?, context: CheckerContext, - reporter: DiagnosticReporter, - typeSystemContext: ConeTypeContext, - reportTarget: FirSourceElement? - ): Boolean { - val prototypeClass = type.lookupTag.toSymbol(context.session) - ?.fir.safeAs() - ?: 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() + val typeArgumentsCount = typeArguments?.size ?: type.typeArguments.size + if (typeArgumentsCount == 0) { + return + } - for (it in 0 until count) { - type.typeArguments[it].safeAs() - ?.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() + ?: return + + prototypeClass.typeParameters.map { it.symbol } + } + + if (typeParameterSymbols.isEmpty()) { + return + } + + val count = minOf(typeParameterSymbols.size, typeArgumentsCount) + val substitution = mutableMapOf() + + 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) } } diff --git a/compiler/testData/diagnostics/tests/Bounds.fir.kt b/compiler/testData/diagnostics/tests/Bounds.fir.kt index 8930a684628..e722893a7b9 100644 --- a/compiler/testData/diagnostics/tests/Bounds.fir.kt +++ b/compiler/testData/diagnostics/tests/Bounds.fir.kt @@ -12,7 +12,7 @@ package boundsWithSubstitutors val b = X, C>>() val b0 = XAny?>() - val b1 = XX, String>>() + val b1 = X, String>>() // FILE: b.kt open class A {}