[NI] Make model more robust: remove diagnostics from resolution atoms
Currently there are two major phases in NI that report diagnostics: resolution parts and completion. They connected in method `KotlinCallCompleter.runCompletion` and previously diagnostics were collected in several places, from resolution atoms, partly from constraint system and partly from `CallResolutionResult`, some of them were lost. To mitigate this problem, now diagnostics are not bind to the intermediate candidate, only to the result resolution candidate (overloaded or usual one). And all diagnostics are now collected in method `runCompletion`
This commit is contained in:
+16
-9
@@ -31,9 +31,10 @@ class AdditionalDiagnosticReporter(private val languageVersionSettings: Language
|
||||
fun reportAdditionalDiagnostics(
|
||||
candidate: ResolvedCallAtom,
|
||||
resultingDescriptor: CallableDescriptor,
|
||||
kotlinDiagnosticsHolder: KotlinDiagnosticsHolder
|
||||
kotlinDiagnosticsHolder: KotlinDiagnosticsHolder,
|
||||
diagnostics: Collection<KotlinCallDiagnostic>
|
||||
) {
|
||||
reportSmartCasts(candidate, resultingDescriptor, kotlinDiagnosticsHolder)
|
||||
reportSmartCasts(candidate, resultingDescriptor, kotlinDiagnosticsHolder, diagnostics)
|
||||
}
|
||||
|
||||
private fun createSmartCastDiagnostic(
|
||||
@@ -51,7 +52,8 @@ class AdditionalDiagnosticReporter(private val languageVersionSettings: Language
|
||||
private fun reportSmartCastOnReceiver(
|
||||
candidate: ResolvedCallAtom,
|
||||
receiver: SimpleKotlinCallArgument?,
|
||||
parameter: ReceiverParameterDescriptor?
|
||||
parameter: ReceiverParameterDescriptor?,
|
||||
diagnostics: Collection<KotlinCallDiagnostic>
|
||||
): SmartCastDiagnostic? {
|
||||
if (receiver == null || parameter == null) return null
|
||||
val expectedType = parameter.type.unwrap().let { if (receiver.isSafeCall) it.makeNullableAsSpecified(true) else it }
|
||||
@@ -60,11 +62,11 @@ class AdditionalDiagnosticReporter(private val languageVersionSettings: Language
|
||||
|
||||
// todo may be we have smart cast to Int?
|
||||
return smartCastDiagnostic.takeIf {
|
||||
candidate.diagnostics.filterIsInstance<UnsafeCallError>().none {
|
||||
diagnostics.filterIsInstance<UnsafeCallError>().none {
|
||||
it.receiver == receiver
|
||||
}
|
||||
&&
|
||||
candidate.diagnostics.filterIsInstance<UnstableSmartCast>().none {
|
||||
diagnostics.filterIsInstance<UnstableSmartCast>().none {
|
||||
it.argument == receiver
|
||||
}
|
||||
}
|
||||
@@ -73,17 +75,22 @@ class AdditionalDiagnosticReporter(private val languageVersionSettings: Language
|
||||
private fun reportSmartCasts(
|
||||
candidate: ResolvedCallAtom,
|
||||
resultingDescriptor: CallableDescriptor,
|
||||
kotlinDiagnosticsHolder: KotlinDiagnosticsHolder
|
||||
kotlinDiagnosticsHolder: KotlinDiagnosticsHolder,
|
||||
diagnostics: Collection<KotlinCallDiagnostic>
|
||||
) {
|
||||
kotlinDiagnosticsHolder.addDiagnosticIfNotNull(reportSmartCastOnReceiver(candidate, candidate.extensionReceiverArgument, resultingDescriptor.extensionReceiverParameter))
|
||||
kotlinDiagnosticsHolder.addDiagnosticIfNotNull(reportSmartCastOnReceiver(candidate, candidate.dispatchReceiverArgument, resultingDescriptor.dispatchReceiverParameter))
|
||||
kotlinDiagnosticsHolder.addDiagnosticIfNotNull(
|
||||
reportSmartCastOnReceiver(candidate, candidate.extensionReceiverArgument, resultingDescriptor.extensionReceiverParameter, diagnostics)
|
||||
)
|
||||
kotlinDiagnosticsHolder.addDiagnosticIfNotNull(
|
||||
reportSmartCastOnReceiver(candidate, candidate.dispatchReceiverArgument, resultingDescriptor.dispatchReceiverParameter, diagnostics)
|
||||
)
|
||||
|
||||
for (parameter in resultingDescriptor.valueParameters) {
|
||||
for (argument in candidate.argumentMappingByOriginal[parameter.original]?.arguments ?: continue) {
|
||||
val effectiveExpectedType = argument.getExpectedType(parameter, languageVersionSettings)
|
||||
val smartCastDiagnostic = createSmartCastDiagnostic(candidate, argument, effectiveExpectedType) ?: continue
|
||||
|
||||
val thereIsUnstableSmartCastError = candidate.diagnostics.filterIsInstance<UnstableSmartCast>().any {
|
||||
val thereIsUnstableSmartCastError = diagnostics.filterIsInstance<UnstableSmartCast>().any {
|
||||
it.argument == argument
|
||||
}
|
||||
|
||||
|
||||
+6
-9
@@ -29,8 +29,6 @@ import org.jetbrains.kotlin.resolve.calls.results.TypeSpecificityComparator
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.ImplicitScopeTower
|
||||
import org.jetbrains.kotlin.resolve.calls.tower.TowerResolver
|
||||
import org.jetbrains.kotlin.types.UnwrappedType
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
|
||||
class CallableReferenceOverloadConflictResolver(
|
||||
@@ -62,7 +60,8 @@ class CallableReferenceResolver(
|
||||
|
||||
fun processCallableReferenceArgument(
|
||||
csBuilder: ConstraintSystemBuilder,
|
||||
resolvedAtom: ResolvedCallableReferenceAtom
|
||||
resolvedAtom: ResolvedCallableReferenceAtom,
|
||||
diagnosticsHolder: KotlinDiagnosticsHolder
|
||||
) {
|
||||
val argument = resolvedAtom.atom
|
||||
val expectedType = resolvedAtom.expectedType?.let { csBuilder.buildCurrentSubstitutor().safeSubstitute(it) }
|
||||
@@ -71,30 +70,28 @@ class CallableReferenceResolver(
|
||||
val candidates = runRHSResolution(scopeTower, argument, expectedType) { checkCallableReference ->
|
||||
csBuilder.runTransaction { checkCallableReference(this); false }
|
||||
}
|
||||
val diagnostics = SmartList<KotlinCallDiagnostic>()
|
||||
|
||||
val chosenCandidate = candidates.singleOrNull()
|
||||
if (chosenCandidate != null) {
|
||||
val (toFreshSubstitutor, diagnostic) = with(chosenCandidate) {
|
||||
csBuilder.checkCallableReference(argument, dispatchReceiver, extensionReceiver, candidate,
|
||||
reflectionCandidateType, expectedType, scopeTower.lexicalScope.ownerDescriptor)
|
||||
}
|
||||
diagnostics.addIfNotNull(diagnostic)
|
||||
diagnosticsHolder.addDiagnosticIfNotNull(diagnostic)
|
||||
chosenCandidate.freshSubstitutor = toFreshSubstitutor
|
||||
}
|
||||
else {
|
||||
if (candidates.isEmpty()) {
|
||||
diagnostics.add(NoneCallableReferenceCandidates(argument))
|
||||
diagnosticsHolder.addDiagnostic(NoneCallableReferenceCandidates(argument))
|
||||
}
|
||||
else {
|
||||
diagnostics.add(CallableReferenceCandidatesAmbiguity(argument, candidates))
|
||||
diagnosticsHolder.addDiagnostic(CallableReferenceCandidatesAmbiguity(argument, candidates))
|
||||
}
|
||||
}
|
||||
|
||||
// todo -- create this inside CallableReferencesCandidateFactory
|
||||
val subKtArguments = listOfNotNull(buildResolvedKtArgument(argument.lhsResult))
|
||||
|
||||
resolvedAtom.setAnalyzedResults(chosenCandidate, subKtArguments, diagnostics)
|
||||
resolvedAtom.setAnalyzedResults(chosenCandidate, subKtArguments)
|
||||
}
|
||||
|
||||
private fun buildResolvedKtArgument(lhsResult: LHSResult): ResolvedAtom? {
|
||||
|
||||
+26
-4
@@ -49,13 +49,20 @@ class KotlinCallCompleter(
|
||||
// this is needed at least for non-local return checker, because when we analyze lambda we should already bind descriptor for outer call
|
||||
candidate?.resolvedCall?.let { resolutionCallbacks.bindStubResolvedCallForCandidate(it) }
|
||||
|
||||
val diagnosticsFromResolutionParts = candidate?.diagnosticsFromResolutionParts ?: emptyList<KotlinCallDiagnostic>()
|
||||
|
||||
if (candidate == null || candidate.csBuilder.hasContradiction) {
|
||||
val candidateForCompletion = candidate ?: factory.createErrorCandidate().forceResolution()
|
||||
candidateForCompletion.prepareForCompletion(expectedType, resolutionCallbacks)
|
||||
runCompletion(candidateForCompletion.resolvedCall, ConstraintSystemCompletionMode.FULL, diagnosticHolder, candidateForCompletion.getSystem(), resolutionCallbacks)
|
||||
|
||||
val systemStorage = candidate?.getSystem()?.asReadOnlyStorage() ?: ConstraintStorage.Empty
|
||||
return CallResolutionResult(CallResolutionResult.Type.ERROR, candidate?.resolvedCall, diagnosticHolder.getDiagnostics(), systemStorage)
|
||||
return CallResolutionResult(
|
||||
CallResolutionResult.Type.ERROR,
|
||||
candidate?.resolvedCall,
|
||||
diagnosticHolder.getDiagnostics() + diagnosticsFromResolutionParts,
|
||||
systemStorage
|
||||
)
|
||||
}
|
||||
|
||||
val completionType = candidate.prepareForCompletion(expectedType, resolutionCallbacks)
|
||||
@@ -63,10 +70,20 @@ class KotlinCallCompleter(
|
||||
runCompletion(candidate.resolvedCall, completionType, diagnosticHolder, constraintSystem, resolutionCallbacks)
|
||||
|
||||
return if (completionType == ConstraintSystemCompletionMode.FULL) {
|
||||
CallResolutionResult(CallResolutionResult.Type.COMPLETED, candidate.resolvedCall, diagnosticHolder.getDiagnostics(), constraintSystem.asReadOnlyStorage())
|
||||
CallResolutionResult(
|
||||
CallResolutionResult.Type.COMPLETED,
|
||||
candidate.resolvedCall,
|
||||
diagnosticHolder.getDiagnostics() + diagnosticsFromResolutionParts,
|
||||
constraintSystem.asReadOnlyStorage()
|
||||
)
|
||||
}
|
||||
else {
|
||||
CallResolutionResult(CallResolutionResult.Type.PARTIAL, candidate.resolvedCall, diagnosticHolder.getDiagnostics(), constraintSystem.asReadOnlyStorage())
|
||||
CallResolutionResult(
|
||||
CallResolutionResult.Type.PARTIAL,
|
||||
candidate.resolvedCall,
|
||||
diagnosticHolder.getDiagnostics() + diagnosticsFromResolutionParts,
|
||||
constraintSystem.asReadOnlyStorage()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +117,12 @@ class KotlinCallCompleter(
|
||||
val returnType = resolvedCallAtom.freshReturnType ?: constraintSystem.builtIns.unitType
|
||||
kotlinConstraintSystemCompleter.runCompletion(constraintSystem.asConstraintSystemCompleterContext(), completionMode, resolvedCallAtom, returnType) {
|
||||
if (!skipPostponedArguments) {
|
||||
postponedArgumentsAnalyzer.analyze(constraintSystem.asPostponedArgumentsAnalyzerContext(), resolutionCallbacks, it)
|
||||
postponedArgumentsAnalyzer.analyze(
|
||||
constraintSystem.asPostponedArgumentsAnalyzerContext(),
|
||||
resolutionCallbacks,
|
||||
it,
|
||||
diagnosticsHolder
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-8
@@ -39,17 +39,24 @@ class PostponedArgumentsAnalyzer(
|
||||
fun getBuilder(): ConstraintSystemBuilder
|
||||
}
|
||||
|
||||
fun analyze(c: Context, resolutionCallbacks: KotlinResolutionCallbacks, argument: ResolvedAtom) {
|
||||
fun analyze(c: Context, resolutionCallbacks: KotlinResolutionCallbacks, argument: ResolvedAtom, diagnosticsHolder: KotlinDiagnosticsHolder) {
|
||||
when (argument) {
|
||||
is ResolvedLambdaAtom -> analyzeLambda(c, resolutionCallbacks, argument)
|
||||
is LambdaWithTypeVariableAsExpectedTypeAtom -> analyzeLambda(c, resolutionCallbacks, argument.transformToResolvedLambda(c.getBuilder()))
|
||||
is ResolvedCallableReferenceAtom -> callableReferenceResolver.processCallableReferenceArgument(c.getBuilder(), argument)
|
||||
is ResolvedLambdaAtom ->
|
||||
analyzeLambda(c, resolutionCallbacks, argument, diagnosticsHolder)
|
||||
|
||||
is LambdaWithTypeVariableAsExpectedTypeAtom ->
|
||||
analyzeLambda(c, resolutionCallbacks, argument.transformToResolvedLambda(c.getBuilder()), diagnosticsHolder)
|
||||
|
||||
is ResolvedCallableReferenceAtom ->
|
||||
callableReferenceResolver.processCallableReferenceArgument(c.getBuilder(), argument, diagnosticsHolder)
|
||||
|
||||
is ResolvedCollectionLiteralAtom -> TODO("Not supported")
|
||||
|
||||
else -> error("Unexpected resolved primitive: ${argument.javaClass.canonicalName}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun analyzeLambda(c: Context, resolutionCallbacks: KotlinResolutionCallbacks, lambda: ResolvedLambdaAtom) {
|
||||
private fun analyzeLambda(c: Context, resolutionCallbacks: KotlinResolutionCallbacks, lambda: ResolvedLambdaAtom, diagnosticHolder: KotlinDiagnosticsHolder) {
|
||||
val currentSubstitutor = c.buildCurrentSubstitutor()
|
||||
fun substitute(type: UnwrappedType) = currentSubstitutor.safeSubstitute(type)
|
||||
|
||||
@@ -61,8 +68,6 @@ class PostponedArgumentsAnalyzer(
|
||||
|
||||
resultArguments.forEach { c.addSubsystemForArgument(it) }
|
||||
|
||||
val diagnosticHolder = KotlinDiagnosticsHolder.SimpleHolder()
|
||||
|
||||
val subResolvedKtPrimitives = resultArguments.map {
|
||||
checkSimpleArgument(c.getBuilder(), it, lambda.returnType.let(::substitute), diagnosticHolder, isReceiver = false)
|
||||
}
|
||||
@@ -72,6 +77,6 @@ class PostponedArgumentsAnalyzer(
|
||||
c.getBuilder().addSubtypeConstraint(lambda.returnType.let(::substitute), unitType, LambdaArgumentConstraintPosition(lambda))
|
||||
}
|
||||
|
||||
lambda.setAnalyzedResults(resultArguments, subResolvedKtPrimitives, diagnosticHolder.getDiagnostics())
|
||||
lambda.setAnalyzedResults(resultArguments, subResolvedKtPrimitives)
|
||||
}
|
||||
}
|
||||
+10
-15
@@ -46,10 +46,8 @@ sealed class ResolvedAtom {
|
||||
|
||||
lateinit var subResolvedAtoms: List<ResolvedAtom>
|
||||
private set
|
||||
lateinit var diagnostics: Collection<KotlinCallDiagnostic>
|
||||
private set
|
||||
|
||||
protected open fun setAnalyzedResults(subResolvedAtoms: List<ResolvedAtom>, diagnostics: Collection<KotlinCallDiagnostic>) {
|
||||
protected open fun setAnalyzedResults(subResolvedAtoms: List<ResolvedAtom>) {
|
||||
assert(!analyzed) {
|
||||
"Already analyzed: $this"
|
||||
}
|
||||
@@ -57,7 +55,6 @@ sealed class ResolvedAtom {
|
||||
analyzed = true
|
||||
|
||||
this.subResolvedAtoms = subResolvedAtoms
|
||||
this.diagnostics = diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +71,7 @@ abstract class ResolvedCallAtom : ResolvedAtom() {
|
||||
|
||||
class ResolvedExpressionAtom(override val atom: ExpressionKotlinCallArgument) : ResolvedAtom() {
|
||||
init {
|
||||
setAnalyzedResults(listOf(), listOf())
|
||||
setAnalyzedResults(listOf())
|
||||
}
|
||||
}
|
||||
sealed class PostponedResolvedAtom : ResolvedAtom() {
|
||||
@@ -90,7 +87,7 @@ class LambdaWithTypeVariableAsExpectedTypeAtom(
|
||||
override val outputType: UnwrappedType? get() = null
|
||||
|
||||
fun setAnalyzed(resolvedLambdaAtom: ResolvedLambdaAtom) {
|
||||
setAnalyzedResults(listOf(resolvedLambdaAtom), listOf())
|
||||
setAnalyzedResults(listOf(resolvedLambdaAtom))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,11 +104,10 @@ class ResolvedLambdaAtom(
|
||||
|
||||
fun setAnalyzedResults(
|
||||
resultArguments: List<KotlinCallArgument>,
|
||||
subResolvedAtoms: List<ResolvedAtom>,
|
||||
diagnostics: Collection<KotlinCallDiagnostic>
|
||||
subResolvedAtoms: List<ResolvedAtom>
|
||||
) {
|
||||
this.resultArguments = resultArguments
|
||||
setAnalyzedResults(subResolvedAtoms, diagnostics)
|
||||
setAnalyzedResults(subResolvedAtoms)
|
||||
}
|
||||
|
||||
override val inputTypes: Collection<UnwrappedType> get() = receiver?.let { parameters + it } ?: parameters
|
||||
@@ -127,11 +123,10 @@ class ResolvedCallableReferenceAtom(
|
||||
|
||||
fun setAnalyzedResults(
|
||||
candidate: CallableReferenceCandidate?,
|
||||
subResolvedAtoms: List<ResolvedAtom>,
|
||||
diagnostics: Collection<KotlinCallDiagnostic>
|
||||
subResolvedAtoms: List<ResolvedAtom>
|
||||
) {
|
||||
this.candidate = candidate
|
||||
setAnalyzedResults(subResolvedAtoms, diagnostics)
|
||||
setAnalyzedResults(subResolvedAtoms)
|
||||
}
|
||||
|
||||
override val inputTypes: Collection<UnwrappedType>
|
||||
@@ -154,14 +149,14 @@ class ResolvedCollectionLiteralAtom(
|
||||
val expectedType: UnwrappedType?
|
||||
) : ResolvedAtom() {
|
||||
init {
|
||||
setAnalyzedResults(listOf(), listOf())
|
||||
setAnalyzedResults(listOf())
|
||||
}
|
||||
}
|
||||
|
||||
class CallResolutionResult(
|
||||
val type: Type,
|
||||
val resultCallAtom: ResolvedCallAtom?,
|
||||
diagnostics: List<KotlinCallDiagnostic>,
|
||||
val diagnostics: List<KotlinCallDiagnostic>,
|
||||
val constraintSystem: ConstraintStorage,
|
||||
val allCandidates: Collection<KotlinResolutionCandidate>? = null
|
||||
) : ResolvedAtom() {
|
||||
@@ -175,7 +170,7 @@ class CallResolutionResult(
|
||||
}
|
||||
|
||||
init {
|
||||
setAnalyzedResults(listOfNotNull(resultCallAtom), diagnostics)
|
||||
setAnalyzedResults(listOfNotNull(resultCallAtom))
|
||||
}
|
||||
|
||||
override fun toString() = "$type, resultCallAtom = $resultCallAtom, (${diagnostics.joinToString()})"
|
||||
|
||||
+5
-5
@@ -68,8 +68,8 @@ class KotlinResolutionCandidate(
|
||||
val knownTypeParametersResultingSubstitutor: TypeSubstitutor? = null,
|
||||
private val resolutionSequence: List<ResolutionPart> = resolvedCall.atom.callKind.resolutionSequence
|
||||
) : Candidate, KotlinDiagnosticsHolder {
|
||||
val diagnosticsFromResolutionParts = arrayListOf<KotlinCallDiagnostic>() // TODO: this is mutable list, take diagnostics only once!
|
||||
private var newSystem: NewConstraintSystemImpl? = null
|
||||
private val diagnostics = arrayListOf<KotlinCallDiagnostic>()
|
||||
private var currentApplicability = ResolutionCandidateApplicability.RESOLVED
|
||||
private var subResolvedAtoms: MutableList<ResolvedAtom> = arrayListOf()
|
||||
|
||||
@@ -87,7 +87,7 @@ class KotlinResolutionCandidate(
|
||||
internal val csBuilder get() = getSystem().getBuilder()
|
||||
|
||||
override fun addDiagnostic(diagnostic: KotlinCallDiagnostic) {
|
||||
diagnostics.add(diagnostic)
|
||||
diagnosticsFromResolutionParts.add(diagnostic)
|
||||
currentApplicability = maxOf(diagnostic.candidateApplicability, currentApplicability)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ class KotlinResolutionCandidate(
|
||||
partIndex++
|
||||
}
|
||||
if (step == stepCount) {
|
||||
resolvedCall.setAnalyzedResults(subResolvedAtoms, diagnostics + getSystem().diagnostics)
|
||||
resolvedCall.setAnalyzedResults(subResolvedAtoms)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,8 +176,8 @@ class MutableResolvedCallAtom(
|
||||
override lateinit var substitutor: FreshVariableNewTypeSubstitutor
|
||||
lateinit var argumentToCandidateParameter: Map<KotlinCallArgument, ValueParameterDescriptor>
|
||||
|
||||
override public fun setAnalyzedResults(subResolvedAtoms: List<ResolvedAtom>, diagnostics: Collection<KotlinCallDiagnostic>) {
|
||||
super.setAnalyzedResults(subResolvedAtoms, diagnostics)
|
||||
override public fun setAnalyzedResults(subResolvedAtoms: List<ResolvedAtom>) {
|
||||
super.setAnalyzedResults(subResolvedAtoms)
|
||||
}
|
||||
|
||||
override fun toString(): String = "$atom, candidate = $candidateDescriptor"
|
||||
|
||||
Reference in New Issue
Block a user