[FIR] Implement ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE diagnostic

#KT-59401 Fixed
This commit is contained in:
Kirill Rakhman
2023-11-03 15:27:36 +01:00
committed by Space Team
parent d91000e39c
commit 789f886d3c
18 changed files with 104 additions and 92 deletions
@@ -2597,6 +2597,12 @@ internal val KT_DIAGNOSTIC_CONVERTER = KtDiagnosticConverterBuilder.buildConvert
token,
)
}
add(FirErrors.ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE) { firDiagnostic ->
AdaptedCallableReferenceAgainstReflectionTypeImpl(
firDiagnostic as KtPsiDiagnostic,
token,
)
}
add(FirErrors.CLASS_LITERAL_LHS_NOT_A_CLASS) { firDiagnostic ->
ClassLiteralLhsNotAClassImpl(
firDiagnostic as KtPsiDiagnostic,
@@ -1841,6 +1841,10 @@ sealed interface KtFirDiagnostic<PSI : PsiElement> : KtDiagnosticWithPsi<PSI> {
override val diagnosticClass get() = CallableReferenceToAnnotationConstructor::class
}
interface AdaptedCallableReferenceAgainstReflectionType : KtFirDiagnostic<KtExpression> {
override val diagnosticClass get() = AdaptedCallableReferenceAgainstReflectionType::class
}
interface ClassLiteralLhsNotAClass : KtFirDiagnostic<KtExpression> {
override val diagnosticClass get() = ClassLiteralLhsNotAClass::class
}
@@ -2212,6 +2212,11 @@ internal class CallableReferenceToAnnotationConstructorImpl(
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtExpression>(firDiagnostic, token), KtFirDiagnostic.CallableReferenceToAnnotationConstructor
internal class AdaptedCallableReferenceAgainstReflectionTypeImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<KtExpression>(firDiagnostic, token), KtFirDiagnostic.AdaptedCallableReferenceAgainstReflectionType
internal class ClassLiteralLhsNotAClassImpl(
firDiagnostic: KtPsiDiagnostic,
token: KtLifetimeToken,
@@ -872,6 +872,7 @@ object DIAGNOSTICS_LIST : DiagnosticList("FirErrors") {
}
val CALLABLE_REFERENCE_LHS_NOT_A_CLASS by error<KtExpression>()
val CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR by error<KtExpression>(PositioningStrategy.REFERENCE_BY_QUALIFIED)
val ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE by error<KtExpression>()
val CLASS_LITERAL_LHS_NOT_A_CLASS by error<KtExpression>()
val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error<KtExpression>()
@@ -488,6 +488,7 @@ object FirErrors {
val EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED by error1<KtExpression, FirCallableSymbol<*>>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val CALLABLE_REFERENCE_LHS_NOT_A_CLASS by error0<KtExpression>()
val CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR by error0<KtExpression>(SourceElementPositioningStrategies.REFERENCE_BY_QUALIFIED)
val ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE by error0<KtExpression>()
val CLASS_LITERAL_LHS_NOT_A_CLASS by error0<KtExpression>()
val NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error0<KtExpression>()
val EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS by error1<PsiElement, ConeKotlinType>()
@@ -319,6 +319,7 @@ val FIR_NON_SUPPRESSIBLE_ERROR_NAMES: Set<String> = setOf(
"EXTENSION_IN_CLASS_REFERENCE_NOT_ALLOWED",
"CALLABLE_REFERENCE_LHS_NOT_A_CLASS",
"CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR",
"ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE",
"CLASS_LITERAL_LHS_NOT_A_CLASS",
"NULLABLE_TYPE_IN_CLASS_LITERAL_LHS",
"EXPRESSION_OF_NULLABLE_TYPE_IN_CLASS_LITERAL_LHS",
@@ -72,6 +72,7 @@ import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS_WITH_COMPLEX_SUBSTITUTION
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_TYPE_ALIAS_WITH_USE_SITE_VARIANCE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ACTUAL_WITHOUT_EXPECT
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ACTUALS
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ALTERED_ASSIGN
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.AMBIGUOUS_ANNOTATION_ARGUMENT
@@ -1474,6 +1475,10 @@ object FirErrorsDefaultMessages : BaseDiagnosticRendererFactory() {
)
map.put(CALLABLE_REFERENCE_LHS_NOT_A_CLASS, "Left-hand side of callable reference cannot be a type parameter.")
map.put(CALLABLE_REFERENCE_TO_ANNOTATION_CONSTRUCTOR, "Annotation class cannot be instantiated.")
map.put(
ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE,
"Adapted callable reference cannot be resolved against reflective types."
)
map.put(CLASS_LITERAL_LHS_NOT_A_CLASS, "Only classes are allowed on the left-hand side of a class literal.")
map.put(NULLABLE_TYPE_IN_CLASS_LITERAL_LHS, "Type in a class literal must not be nullable.")
@@ -358,6 +358,8 @@ private fun mapInapplicableCandidateError(
isError = rootCause.isError
)
is AdaptedCallableReferenceIsUsedWithReflection -> FirErrors.ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE.createOn(source)
else -> genericDiagnostic
}
}.distinct()
@@ -400,8 +400,9 @@ class FirCallResolver(
manager = TowerResolveManager(localCollector),
)
}
val isSuccess = result.currentApplicability.isSuccess
val (reducedCandidates, newApplicability) = reduceCandidates(result, callableReferenceAccess.explicitReceiver)
val isSuccess = reducedCandidates.isNotEmpty() && reducedCandidates.all { it.isSuccessful }
val applicability = newApplicability ?: result.currentApplicability
(callableReferenceAccess.explicitReceiver as? FirResolvedQualifier)?.replaceResolvedToCompanionObject(
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.name.StandardClassIds
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemOperation
import org.jetbrains.kotlin.resolve.calls.inference.runTransaction
import org.jetbrains.kotlin.resolve.calls.tower.isSuccess
import org.jetbrains.kotlin.types.AbstractTypeChecker
import org.jetbrains.kotlin.types.expressions.CoercionStrategy
import org.jetbrains.kotlin.utils.addToStdlib.shouldNotBeCalled
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
@@ -47,13 +48,31 @@ internal object CheckCallableReferenceExpectedType : CheckerStage() {
val fir: FirCallableDeclaration = candidate.symbol.fir as FirCallableDeclaration
val (rawResultingType, callableReferenceAdaptation) = buildReflectionType(fir, resultingReceiverType, candidate, context)
val isExpectedTypeReflectionType = callInfo.expectedType?.isReflectFunctionType(callInfo.session) == true
val (rawResultingType, callableReferenceAdaptation) = buildResultingTypeAndAdaptation(
fir,
resultingReceiverType,
candidate,
context,
// If the input and output types match the expected type but the expected type is a reflection type, and we need an adaptation,
// we want to report AdaptedCallableReferenceIsUsedWithReflection but *not* InapplicableCandidate because
// AdaptedCallableReferenceIsUsedWithReflection has the higher applicability.
// Therefore, we force a reflection type whenever the expected type is a reflection type.
//
// If the input and output types end up not matching, we'll report InapplicableCandidate, regardless of whether the
// expected/actual type is a reflection type.
forceReflectionType = isExpectedTypeReflectionType
)
val resultingType = candidate.substitutor.substituteOrSelf(rawResultingType)
if (callableReferenceAdaptation.needCompatibilityResolveForCallableReference()) {
if (callableReferenceAdaptation != null) {
if (!context.session.languageVersionSettings.supportsFeature(LanguageFeature.DisableCompatibilityModeForNewInference)) {
sink.reportDiagnostic(LowerPriorityToPreserveCompatibilityDiagnostic)
}
if (isExpectedTypeReflectionType) {
sink.reportDiagnostic(AdaptedCallableReferenceIsUsedWithReflection)
}
}
candidate.resultingTypeForCallableReference = resultingType
@@ -88,11 +107,17 @@ internal object CheckCallableReferenceExpectedType : CheckerStage() {
}
}
private fun buildReflectionType(
/**
* The resulting type is a reflection type ([FunctionTypeKind.reflectKind])
* iff the adaptation is `null` or [forceReflectionType]` == true`.
* Otherwise, it's a non-reflection type ([FunctionTypeKind.nonReflectKind]).
*/
private fun buildResultingTypeAndAdaptation(
fir: FirCallableDeclaration,
receiverType: ConeKotlinType?,
candidate: Candidate,
context: ResolutionContext
context: ResolutionContext,
forceReflectionType: Boolean,
): Pair<ConeKotlinType, CallableReferenceAdaptation?> {
val returnTypeRef = context.bodyResolveComponents.returnTypeCalculator.tryCalculateReturnType(fir)
return when (fir) {
@@ -128,7 +153,7 @@ private fun buildReflectionType(
?: FunctionTypeKind.Function
return createFunctionType(
if (callableReferenceAdaptation == null) baseFunctionTypeKind.reflectKind() else baseFunctionTypeKind.nonReflectKind(),
if (callableReferenceAdaptation == null || forceReflectionType) baseFunctionTypeKind.reflectKind() else baseFunctionTypeKind.nonReflectKind(),
parameters,
receiverType = receiverType.takeIf { fir.receiverParameter != null },
rawReturnType = returnType,
@@ -145,16 +170,20 @@ internal class CallableReferenceAdaptation(
val coercionStrategy: CoercionStrategy,
val defaults: Int,
val mappedArguments: CallableReferenceMappedArguments,
val suspendConversionStrategy: CallableReferenceConversionStrategy
)
private fun CallableReferenceAdaptation?.needCompatibilityResolveForCallableReference(): Boolean {
// KT-13934: check containing declaration for companion object
if (this == null) return false
return defaults != 0 ||
suspendConversionStrategy != CallableReferenceConversionStrategy.NoConversion ||
coercionStrategy != CoercionStrategy.NO_COERCION ||
mappedArguments.values.any { it is ResolvedCallArgument.VarargArgument }
val suspendConversionStrategy: CallableReferenceConversionStrategy,
) {
init {
if (AbstractTypeChecker.RUN_SLOW_ASSERTIONS) {
require(
defaults != 0 ||
suspendConversionStrategy != CallableReferenceConversionStrategy.NoConversion ||
coercionStrategy != CoercionStrategy.NO_COERCION ||
mappedArguments.values.any { it is ResolvedCallArgument.VarargArgument }
) {
"Adaptation must be non-trivial."
}
}
}
}
private fun BodyResolveComponents.getCallableReferenceAdaptation(
@@ -146,3 +146,5 @@ class AmbiguousValuesForContextReceiverParameter(
) : ResolutionDiagnostic(INAPPLICABLE)
object ResolutionResultOverridesOtherToPreserveCompatibility : ResolutionDiagnostic(RESOLVED)
object AdaptedCallableReferenceIsUsedWithReflection : ResolutionDiagnostic(RESOLVED_WITH_ERROR)
@@ -3,12 +3,12 @@ fun foo(x: String = "O"): String = x
fun bar(x: String = "K"): String = x
fun dump(dumpStrategy: String) {
val k0: kotlin.reflect.KFunction0<String> = returnAdapter(::<!INAPPLICABLE_CANDIDATE!>foo<!>) // Error: ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE
val k0: kotlin.reflect.KFunction0<String> = returnAdapter(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>::foo<!>) // Error: ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE
val k1: kotlin.reflect.KFunction0<String> = <!INITIALIZER_TYPE_MISMATCH!>::foo<!>
// Should be error here, too
val k2: kotlin.reflect.KFunction0<String> = <!INITIALIZER_TYPE_MISMATCH, TYPE_MISMATCH!>if (dumpStrategy == "KotlinLike") ::foo else ::bar<!>
val f0: Function0<String> = returnAdapter(::<!INAPPLICABLE_CANDIDATE!>foo<!>)
val f0: Function0<String> = returnAdapter(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>::foo<!>)
val f1: Function0<String> = ::foo
val f2: Function0<String> = if (dumpStrategy == "KotlinLike") ::foo else ::bar
}
@@ -1,27 +0,0 @@
// !LANGUAGE: -AdaptedCallableReferenceAgainstReflectiveType +DisableCompatibilityModeForNewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER
import kotlin.reflect.KFunction1
fun foo(x: Int): Unit {} // (1)
fun bar(f: KFunction1<Int, Unit>) {}
fun test() {
bar(::foo) // OK, foo resolved to (1)
}
object Scope {
fun foo(x: Int, y: Int = 0): Int = 0 // (2)
fun test() {
bar(::foo) // Error and foo should be resolved to (2)
}
}
object Local {
fun baz(x: Int, y: Int = 0): Int = 0
fun test() {
bar(::<!INAPPLICABLE_CANDIDATE!>baz<!>)
}
}
@@ -1,3 +1,4 @@
// FIR_IDENTICAL
// !LANGUAGE: -AdaptedCallableReferenceAgainstReflectiveType +DisableCompatibilityModeForNewInference
// !DIAGNOSTICS: -UNUSED_PARAMETER
@@ -15,6 +16,7 @@ object Scope {
fun test() {
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>::foo<!>) // Error and foo should be resolved to (2)
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>Scope::foo<!>) // Error and foo should be resolved to (2)
}
}
@@ -25,3 +27,11 @@ object Local {
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>::baz<!>)
}
}
object WrongType {
fun foo(x: String, y: Int = 0) {} // (3)
fun test() {
bar(::foo) // Should resolve to (1) because (3) has wrong type on top of being adapted
}
}
@@ -1,23 +0,0 @@
package
public fun bar(/*0*/ f: kotlin.reflect.KFunction1<kotlin.Int, kotlin.Unit>): kotlin.Unit
public fun foo(/*0*/ x: kotlin.Int): kotlin.Unit
public fun test(): kotlin.Unit
public object Local {
private constructor Local()
public final fun baz(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.Int = ...): kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object Scope {
private constructor Scope()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.Int = ...): kotlin.Int
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -15,6 +15,7 @@ object Scope {
fun test() {
bar(::foo)
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>Scope::foo<!>)
}
}
@@ -22,6 +23,14 @@ object Local {
fun baz(x: Int, y: Int = 0): Int = 0
fun test() {
bar(::<!INAPPLICABLE_CANDIDATE!>baz<!>)
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>::baz<!>)
}
}
object WrongType {
fun foo(x: String, y: Int = 0) {} // (3)
fun test() {
bar(::foo) // Should resolve to (1) because (3) has wrong type on top of being adapted
}
}
@@ -15,6 +15,7 @@ object Scope {
fun test() {
bar(<!COMPATIBILITY_WARNING!>::foo<!>)
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>Scope::foo<!>)
}
}
@@ -25,3 +26,11 @@ object Local {
bar(<!ADAPTED_CALLABLE_REFERENCE_AGAINST_REFLECTION_TYPE!>::baz<!>)
}
}
object WrongType {
fun foo(x: String, y: Int = 0) {} // (3)
fun test() {
bar(::foo) // Should resolve to (1) because (3) has wrong type on top of being adapted
}
}
@@ -1,23 +0,0 @@
package
public fun bar(/*0*/ f: kotlin.reflect.KFunction1<kotlin.Int, kotlin.Unit>): kotlin.Unit
public fun foo(/*0*/ x: kotlin.Int): kotlin.Unit
public fun test(): kotlin.Unit
public object Local {
private constructor Local()
public final fun baz(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.Int = ...): kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public object Scope {
private constructor Scope()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(/*0*/ x: kotlin.Int, /*1*/ y: kotlin.Int = ...): kotlin.Int
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public final fun test(): kotlin.Unit
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}