[FIR] Report proper resolution diagnostics from arguments checks

This commit is contained in:
Dmitriy Novozhilov
2021-03-12 11:08:21 +03:00
committed by TeamCityServer
parent 6cc3fff48d
commit 631b771251
8 changed files with 108 additions and 19 deletions
@@ -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)
}
}
@@ -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,
@@ -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)
} }
@@ -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<!>()