[FIR] Report proper resolution diagnostics from arguments checks
This commit is contained in:
committed by
TeamCityServer
parent
6cc3fff48d
commit
631b771251
+2
-2
@@ -11,7 +11,7 @@ FILE: nullableIntegerLiteralType.kt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<Inapplicable(INAPPLICABLE): /takeInt>#(R|<local>/x|)
|
<Inapplicable(MAY_THROW_RUNTIME_ERROR): /takeInt>#(R|<local>/x|)
|
||||||
}
|
}
|
||||||
public final fun test_2(b: R|kotlin/Boolean|, y: R|kotlin/Int|): R|kotlin/Unit| {
|
public final fun test_2(b: R|kotlin/Boolean|, y: R|kotlin/Int|): R|kotlin/Unit| {
|
||||||
lval x: R|kotlin/Int?| = when () {
|
lval x: R|kotlin/Int?| = when () {
|
||||||
@@ -23,5 +23,5 @@ FILE: nullableIntegerLiteralType.kt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
<Inapplicable(INAPPLICABLE): /takeInt>#(R|<local>/x|)
|
<Inapplicable(MAY_THROW_RUNTIME_ERROR): /takeInt>#(R|<local>/x|)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ FILE: main.kt
|
|||||||
}
|
}
|
||||||
public final fun test_1(b: R|B<kotlin/Int>|, x: R|kotlin/Int|, inv: R|Inv<kotlin/Int>|): R|kotlin/Unit| {
|
public final fun test_1(b: R|B<kotlin/Int>|, x: R|kotlin/Int|, inv: R|Inv<kotlin/Int>|): R|kotlin/Unit| {
|
||||||
R|<local>/b|.R|SubstitutionOverride</B.take: R|kotlin/String|>|(R|<local>/x|)
|
R|<local>/b|.R|SubstitutionOverride</B.take: R|kotlin/String|>|(R|<local>/x|)
|
||||||
R|<local>/b|.<Inapplicable(INAPPLICABLE): /B.take>#(Null(null))
|
R|<local>/b|.<Inapplicable(MAY_THROW_RUNTIME_ERROR): /B.take>#(Null(null))
|
||||||
R|<local>/b|.R|SubstitutionOverride</B.takeInv: R|kotlin/String|>|(R|<local>/inv|)
|
R|<local>/b|.R|SubstitutionOverride</B.takeInv: R|kotlin/String|>|(R|<local>/inv|)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.fir.types.*
|
|||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
|
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilder
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
|
import org.jetbrains.kotlin.resolve.calls.inference.addSubtypeConstraintIfCompatible
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintPosition
|
||||||
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
|
import org.jetbrains.kotlin.resolve.calls.inference.model.SimpleConstraintSystemConstraintPosition
|
||||||
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
import org.jetbrains.kotlin.types.AbstractTypeChecker
|
||||||
import org.jetbrains.kotlin.types.model.CaptureStatus
|
import org.jetbrains.kotlin.types.model.CaptureStatus
|
||||||
@@ -71,6 +72,7 @@ fun Candidate.resolveArgumentExpression(
|
|||||||
// Assignment
|
// Assignment
|
||||||
checkApplicabilityForArgumentType(
|
checkApplicabilityForArgumentType(
|
||||||
csBuilder,
|
csBuilder,
|
||||||
|
argument,
|
||||||
StandardClassIds.Unit.constructClassLikeType(emptyArray(), isNullable = false),
|
StandardClassIds.Unit.constructClassLikeType(emptyArray(), isNullable = false),
|
||||||
expectedType?.type,
|
expectedType?.type,
|
||||||
SimpleConstraintSystemConstraintPosition,
|
SimpleConstraintSystemConstraintPosition,
|
||||||
@@ -136,6 +138,7 @@ private fun Candidate.resolveBlockArgument(
|
|||||||
if (returnArguments.isEmpty()) {
|
if (returnArguments.isEmpty()) {
|
||||||
checkApplicabilityForArgumentType(
|
checkApplicabilityForArgumentType(
|
||||||
csBuilder,
|
csBuilder,
|
||||||
|
block,
|
||||||
block.typeRef.coneType,
|
block.typeRef.coneType,
|
||||||
expectedType?.type,
|
expectedType?.type,
|
||||||
SimpleConstraintSystemConstraintPosition,
|
SimpleConstraintSystemConstraintPosition,
|
||||||
@@ -170,9 +173,10 @@ fun Candidate.resolveSubCallArgument(
|
|||||||
isDispatch: Boolean,
|
isDispatch: Boolean,
|
||||||
useNullableArgumentType: Boolean = false
|
useNullableArgumentType: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
require(argument is FirExpression)
|
||||||
val candidate = argument.candidate() ?: return resolvePlainExpressionArgument(
|
val candidate = argument.candidate() ?: return resolvePlainExpressionArgument(
|
||||||
csBuilder,
|
csBuilder,
|
||||||
argument as FirExpression,
|
argument,
|
||||||
expectedType,
|
expectedType,
|
||||||
sink,
|
sink,
|
||||||
context,
|
context,
|
||||||
@@ -186,7 +190,17 @@ fun Candidate.resolveSubCallArgument(
|
|||||||
*/
|
*/
|
||||||
val type: ConeKotlinType = context.returnTypeCalculator.tryCalculateReturnType(candidate.symbol.firUnsafe()).type
|
val type: ConeKotlinType = context.returnTypeCalculator.tryCalculateReturnType(candidate.symbol.firUnsafe()).type
|
||||||
val argumentType = candidate.substitutor.substituteOrSelf(type)
|
val argumentType = candidate.substitutor.substituteOrSelf(type)
|
||||||
resolvePlainArgumentType(csBuilder, argumentType, expectedType, sink, context, isReceiver, isDispatch, useNullableArgumentType)
|
resolvePlainArgumentType(
|
||||||
|
csBuilder,
|
||||||
|
argument,
|
||||||
|
argumentType,
|
||||||
|
expectedType,
|
||||||
|
sink,
|
||||||
|
context,
|
||||||
|
isReceiver,
|
||||||
|
isDispatch,
|
||||||
|
useNullableArgumentType
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Candidate.resolvePlainExpressionArgument(
|
fun Candidate.resolvePlainExpressionArgument(
|
||||||
@@ -201,11 +215,22 @@ fun Candidate.resolvePlainExpressionArgument(
|
|||||||
) {
|
) {
|
||||||
if (expectedType == null) return
|
if (expectedType == null) return
|
||||||
val argumentType = argument.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
|
val argumentType = argument.typeRef.coneTypeSafe<ConeKotlinType>() ?: return
|
||||||
resolvePlainArgumentType(csBuilder, argumentType, expectedType, sink, context, isReceiver, isDispatch, useNullableArgumentType)
|
resolvePlainArgumentType(
|
||||||
|
csBuilder,
|
||||||
|
argument,
|
||||||
|
argumentType,
|
||||||
|
expectedType,
|
||||||
|
sink,
|
||||||
|
context,
|
||||||
|
isReceiver,
|
||||||
|
isDispatch,
|
||||||
|
useNullableArgumentType
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Candidate.resolvePlainArgumentType(
|
fun Candidate.resolvePlainArgumentType(
|
||||||
csBuilder: ConstraintSystemBuilder,
|
csBuilder: ConstraintSystemBuilder,
|
||||||
|
argument: FirExpression,
|
||||||
argumentType: ConeKotlinType,
|
argumentType: ConeKotlinType,
|
||||||
expectedType: ConeKotlinType?,
|
expectedType: ConeKotlinType?,
|
||||||
sink: CheckerSink,
|
sink: CheckerSink,
|
||||||
@@ -237,7 +262,7 @@ fun Candidate.resolvePlainArgumentType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkApplicabilityForArgumentType(
|
checkApplicabilityForArgumentType(
|
||||||
csBuilder, argumentTypeForApplicabilityCheck, expectedType, position, isReceiver, isDispatch, sink, context
|
csBuilder, argument, argumentTypeForApplicabilityCheck, expectedType, position, isReceiver, isDispatch, sink, context
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,6 +324,7 @@ private fun Candidate.captureTypeFromExpressionOrNull(argumentType: ConeKotlinTy
|
|||||||
|
|
||||||
private fun checkApplicabilityForArgumentType(
|
private fun checkApplicabilityForArgumentType(
|
||||||
csBuilder: ConstraintSystemBuilder,
|
csBuilder: ConstraintSystemBuilder,
|
||||||
|
argument: FirExpression,
|
||||||
argumentType: ConeKotlinType,
|
argumentType: ConeKotlinType,
|
||||||
expectedType: ConeKotlinType?,
|
expectedType: ConeKotlinType?,
|
||||||
position: SimpleConstraintSystemConstraintPosition,
|
position: SimpleConstraintSystemConstraintPosition,
|
||||||
@@ -308,15 +334,48 @@ private fun checkApplicabilityForArgumentType(
|
|||||||
context: ResolutionContext
|
context: ResolutionContext
|
||||||
) {
|
) {
|
||||||
if (expectedType == null) return
|
if (expectedType == null) return
|
||||||
|
|
||||||
|
fun unstableSmartCastOrSubtypeError(
|
||||||
|
unstableType: ConeKotlinType?,
|
||||||
|
actualExpectedType: ConeKotlinType,
|
||||||
|
position: ConstraintPosition
|
||||||
|
): ResolutionDiagnostic? {
|
||||||
|
if (unstableType != null) {
|
||||||
|
if (csBuilder.addSubtypeConstraintIfCompatible(unstableType, actualExpectedType, position)) {
|
||||||
|
return UnstableSmartCast.ResolutionError(argument, unstableType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (argumentType.isMarkedNullable) {
|
||||||
|
if (csBuilder.addSubtypeConstraintIfCompatible(argumentType, actualExpectedType, position)) return null
|
||||||
|
if (csBuilder.addSubtypeConstraintIfCompatible(
|
||||||
|
argumentType.withNullability(ConeNullability.NOT_NULL),
|
||||||
|
actualExpectedType,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
) return ArgumentTypeMismatch(actualExpectedType, argumentType, argument)
|
||||||
|
}
|
||||||
|
|
||||||
|
csBuilder.addSubtypeConstraint(argumentType, actualExpectedType, position)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (isReceiver && isDispatch) {
|
if (isReceiver && isDispatch) {
|
||||||
if (!expectedType.isNullable && argumentType.isMarkedNullable) {
|
if (!expectedType.isNullable && argumentType.isMarkedNullable) {
|
||||||
sink.reportDiagnostic(InapplicableWrongReceiver(expectedType, argumentType))
|
sink.reportDiagnostic(InapplicableWrongReceiver(expectedType, argumentType))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!csBuilder.addSubtypeConstraintIfCompatible(argumentType, expectedType, position)) {
|
if (!csBuilder.addSubtypeConstraintIfCompatible(argumentType, expectedType, position)) {
|
||||||
if (!isReceiver) {
|
if (!isReceiver) {
|
||||||
csBuilder.addSubtypeConstraint(argumentType, expectedType, position)
|
sink.reportDiagnosticIfNotNull(
|
||||||
|
unstableSmartCastOrSubtypeError(
|
||||||
|
unstableType = null, // TODO: handle unstable smartcasts
|
||||||
|
expectedType,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,3 +49,9 @@ class CheckerSinkImpl(
|
|||||||
override val needYielding: Boolean
|
override val needYielding: Boolean
|
||||||
get() = stopOnFirstError && !candidate.isSuccessful
|
get() = stopOnFirstError && !candidate.isSuccessful
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun CheckerSink.reportDiagnosticIfNotNull(diagnostic: ResolutionDiagnostic?) {
|
||||||
|
if (diagnostic != null) {
|
||||||
|
reportDiagnostic(diagnostic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+32
-9
@@ -11,10 +11,11 @@ import org.jetbrains.kotlin.fir.declarations.FirValueParameter
|
|||||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||||
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
import org.jetbrains.kotlin.fir.types.ConeKotlinType
|
||||||
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
|
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability.*
|
||||||
|
|
||||||
abstract class ResolutionDiagnostic(val applicability: CandidateApplicability)
|
abstract class ResolutionDiagnostic(val applicability: CandidateApplicability)
|
||||||
|
|
||||||
abstract class InapplicableArgumentDiagnostic : ResolutionDiagnostic(CandidateApplicability.INAPPLICABLE) {
|
abstract class InapplicableArgumentDiagnostic : ResolutionDiagnostic(INAPPLICABLE) {
|
||||||
abstract val argument: FirExpression
|
abstract val argument: FirExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ class MixingNamedAndPositionArguments(override val argument: FirExpression) : In
|
|||||||
class TooManyArguments(
|
class TooManyArguments(
|
||||||
val argument: FirExpression,
|
val argument: FirExpression,
|
||||||
val function: FirFunction<*>
|
val function: FirFunction<*>
|
||||||
) : ResolutionDiagnostic(CandidateApplicability.INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
|
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
|
||||||
|
|
||||||
class NamedArgumentNotAllowed(
|
class NamedArgumentNotAllowed(
|
||||||
override val argument: FirExpression,
|
override val argument: FirExpression,
|
||||||
@@ -47,24 +48,46 @@ class NonVarargSpread(override val argument: FirExpression) : InapplicableArgume
|
|||||||
class NoValueForParameter(
|
class NoValueForParameter(
|
||||||
val valueParameter: FirValueParameter,
|
val valueParameter: FirValueParameter,
|
||||||
val function: FirFunction<*>
|
val function: FirFunction<*>
|
||||||
) : ResolutionDiagnostic(CandidateApplicability.INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
|
) : ResolutionDiagnostic(INAPPLICABLE_ARGUMENTS_MAPPING_ERROR)
|
||||||
|
|
||||||
class NameNotFound(
|
class NameNotFound(
|
||||||
override val argument: FirExpression,
|
override val argument: FirExpression,
|
||||||
val function: FirFunction<*>
|
val function: FirFunction<*>
|
||||||
) : InapplicableArgumentDiagnostic()
|
) : InapplicableArgumentDiagnostic()
|
||||||
|
|
||||||
object InapplicableCandidate : ResolutionDiagnostic(CandidateApplicability.INAPPLICABLE)
|
object InapplicableCandidate : ResolutionDiagnostic(INAPPLICABLE)
|
||||||
|
|
||||||
object HiddenCandidate : ResolutionDiagnostic(CandidateApplicability.HIDDEN)
|
object HiddenCandidate : ResolutionDiagnostic(HIDDEN)
|
||||||
|
|
||||||
object ResolvedWithLowPriority : ResolutionDiagnostic(CandidateApplicability.RESOLVED_LOW_PRIORITY)
|
object ResolvedWithLowPriority : ResolutionDiagnostic(RESOLVED_LOW_PRIORITY)
|
||||||
|
|
||||||
class InapplicableWrongReceiver(
|
class InapplicableWrongReceiver(
|
||||||
val expectedType: ConeKotlinType? = null,
|
val expectedType: ConeKotlinType? = null,
|
||||||
val actualType: ConeKotlinType? = null,
|
val actualType: ConeKotlinType? = null,
|
||||||
) : ResolutionDiagnostic(CandidateApplicability.INAPPLICABLE_WRONG_RECEIVER)
|
) : ResolutionDiagnostic(INAPPLICABLE_WRONG_RECEIVER)
|
||||||
|
|
||||||
object LowerPriorityToPreserveCompatibilityDiagnostic : ResolutionDiagnostic(CandidateApplicability.RESOLVED_NEED_PRESERVE_COMPATIBILITY)
|
object LowerPriorityToPreserveCompatibilityDiagnostic : ResolutionDiagnostic(RESOLVED_NEED_PRESERVE_COMPATIBILITY)
|
||||||
|
|
||||||
object CandidateChosenUsingOverloadResolutionByLambdaAnnotation : ResolutionDiagnostic(CandidateApplicability.RESOLVED)
|
object CandidateChosenUsingOverloadResolutionByLambdaAnnotation : ResolutionDiagnostic(RESOLVED)
|
||||||
|
|
||||||
|
sealed class UnstableSmartCast(
|
||||||
|
val argument: FirExpression,
|
||||||
|
val targetType: ConeKotlinType,
|
||||||
|
applicability: CandidateApplicability
|
||||||
|
) : ResolutionDiagnostic(applicability) {
|
||||||
|
class ResolutionError(
|
||||||
|
argument: FirExpression,
|
||||||
|
targetType: ConeKotlinType,
|
||||||
|
) : UnstableSmartCast(argument, targetType, MAY_THROW_RUNTIME_ERROR)
|
||||||
|
|
||||||
|
class DiagnosticError(
|
||||||
|
argument: FirExpression,
|
||||||
|
targetType: ConeKotlinType,
|
||||||
|
) : UnstableSmartCast(argument, targetType, RESOLVED_WITH_ERROR)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArgumentTypeMismatch(
|
||||||
|
val expectedType: ConeKotlinType,
|
||||||
|
val actualType: ConeKotlinType,
|
||||||
|
val argument: FirExpression
|
||||||
|
) : ResolutionDiagnostic(MAY_THROW_RUNTIME_ERROR)
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ object CheckExtensionReceiver : ResolutionStage() {
|
|||||||
)
|
)
|
||||||
candidate.resolvePlainArgumentType(
|
candidate.resolvePlainArgumentType(
|
||||||
candidate.csBuilder,
|
candidate.csBuilder,
|
||||||
|
argumentExtensionReceiverValue.receiverExpression,
|
||||||
argumentType = argumentType,
|
argumentType = argumentType,
|
||||||
expectedType = expectedType,
|
expectedType = expectedType,
|
||||||
sink = sink,
|
sink = sink,
|
||||||
|
|||||||
Vendored
+1
-1
@@ -13,6 +13,6 @@ public class J {
|
|||||||
|
|
||||||
fun test(j: J, nullStr: String?, nullByte: Byte?, nullDouble: Double?) {
|
fun test(j: J, nullStr: String?, nullByte: Byte?, nullDouble: Double?) {
|
||||||
j.foo(nullStr)
|
j.foo(nullStr)
|
||||||
j.<!NONE_APPLICABLE!>foo<!>(nullDouble)
|
j.<!INAPPLICABLE_CANDIDATE!>foo<!>(nullDouble)
|
||||||
j.foo(nullByte)
|
j.foo(nullByte)
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -6,7 +6,7 @@ fun foo(x: Int) {}
|
|||||||
fun foo(x: Int, y: String) {}
|
fun foo(x: Int, y: String) {}
|
||||||
|
|
||||||
fun bar(nullX: Int?, nullY: String?, notNullY: String) {
|
fun bar(nullX: Int?, nullY: String?, notNullY: String) {
|
||||||
<!NONE_APPLICABLE!>foo<!>(nullX)
|
<!INAPPLICABLE_CANDIDATE!>foo<!>(nullX)
|
||||||
<!INAPPLICABLE_CANDIDATE!>foo<!>(nullX, notNullY)
|
<!INAPPLICABLE_CANDIDATE!>foo<!>(nullX, notNullY)
|
||||||
<!INAPPLICABLE_CANDIDATE!>foo<!>(nullX, nullY)
|
<!INAPPLICABLE_CANDIDATE!>foo<!>(nullX, nullY)
|
||||||
<!NONE_APPLICABLE!>foo<!>()
|
<!NONE_APPLICABLE!>foo<!>()
|
||||||
|
|||||||
Reference in New Issue
Block a user