[FE 1.0] Implement checker to detect changed resolve for progressions and ranges due to the start of implementing Collection<T>

^KT-49276 Fixed
This commit is contained in:
Victor Petukhov
2021-11-30 14:47:17 +03:00
committed by teamcity
parent 4363667bc1
commit 7475d26902
25 changed files with 588 additions and 18 deletions
@@ -208,6 +208,8 @@ public interface Errors {
DiagnosticFactory0<KtModifierList> MODIFIER_LIST_NOT_ALLOWED = DiagnosticFactory0.create(ERROR);
DiagnosticFactoryForDeprecation1<PsiElement, CallableDescriptor> PROGRESSIONS_CHANGING_RESOLVE = DiagnosticFactoryForDeprecation1.create(LanguageFeature.ProgressionsChangingResolve);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Errors in declarations
@@ -612,6 +612,10 @@ public class DefaultErrorMessages {
MAP.put(DEPRECATED_SYNTAX_WITH_DEFINITELY_NOT_NULL, "Applying ''!!'' to the whole as/is expression without parentheses is deprecated. Please, put parentheses explicitly");
MAP.put(MODIFIER_LIST_NOT_ALLOWED, "Modifiers and annotations are not allowed here, because there are other modifiers or annotations outside of parenthesis");
MAP.put(PROGRESSIONS_CHANGING_RESOLVE, "Progressions and ranges types will start implementing Collection interface soon. " +
"This call will resolve to another declaration: {0}. " +
"See https://youtrack.jetbrains.com/issue/KT-49276 for more details. " +
"Please specify a progrssion type of argument explicitly through explicit cast to resolve to a proper declaration.", COMPACT);
MAP.put(TOO_MANY_ARGUMENTS, "Too many arguments for {0}", FQ_NAMES_IN_TYPES);
@@ -235,7 +235,7 @@ private fun getIdForSimpleNameExpression(
if (implicitReceiver == null) {
selectorInfo
} else {
val receiverInfo = getIdForImplicitReceiver(implicitReceiver, simpleNameExpression)
val receiverInfo = getIdForImplicitReceiver(implicitReceiver)
if (receiverInfo == null) {
selectorInfo
@@ -261,15 +261,10 @@ private fun getIdForSimpleNameExpression(
}
}
private fun getIdForImplicitReceiver(receiverValue: ReceiverValue?, expression: KtExpression?) =
private fun getIdForImplicitReceiver(receiverValue: ReceiverValue?): IdentifierInfo? =
when (receiverValue) {
is ContextReceiver -> IdentifierInfo.Receiver(receiverValue)
is ImplicitReceiver -> getIdForThisReceiver(receiverValue.declarationDescriptor)
is TransientReceiver ->
throw AssertionError("Transient receiver is implicit for an explicit expression: $expression. Receiver: $receiverValue")
else -> null
}
@@ -28,9 +28,13 @@ import org.jetbrains.kotlin.resolve.calls.components.candidate.SimpleResolutionC
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
import org.jetbrains.kotlin.resolve.calls.inference.BuilderInferenceSession
import org.jetbrains.kotlin.resolve.calls.inference.NewConstraintSystem
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter
import org.jetbrains.kotlin.resolve.calls.inference.components.ResultTypeResolver
import org.jetbrains.kotlin.resolve.calls.inference.components.TypeVariableDirectionCalculator
import org.jetbrains.kotlin.resolve.calls.inference.model.ConstraintStorage
import org.jetbrains.kotlin.resolve.calls.inference.model.NewTypeVariable
import org.jetbrains.kotlin.resolve.calls.inference.model.TypeVariableTypeConstructor
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
@@ -79,6 +83,7 @@ class KotlinResolutionCallbacksImpl(
private val topLevelCallContext: BasicCallResolutionContext,
private val missingSupertypesResolver: MissingSupertypesResolver,
private val kotlinCallResolver: KotlinCallResolver,
private val resultTypeResolver: ResultTypeResolver,
) : KotlinResolutionCallbacks {
class LambdaInfo(val expectedType: UnwrappedType, val contextDependency: ContextDependency) {
val returnStatements = ArrayList<Pair<KtReturnExpression, LambdaContextInfo?>>()
@@ -89,6 +94,15 @@ class KotlinResolutionCallbacksImpl(
}
}
override fun findResultType(constraintSystem: NewConstraintSystem, typeVariable: TypeVariableTypeConstructor): KotlinType? {
val variableWithConstraints = constraintSystem.getBuilder().currentStorage().notFixedTypeVariables[typeVariable] ?: return null
return resultTypeResolver.findResultType(
constraintSystem.asConstraintSystemCompleterContext(),
variableWithConstraints,
TypeVariableDirectionCalculator.ResolveDirection.UNKNOWN
) as KotlinType
}
override fun resolveCallableReferenceArgument(
argument: CallableReferenceKotlinCallArgument,
expectedType: UnwrappedType?,
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.resolve.calls.CallTransformer
import org.jetbrains.kotlin.resolve.calls.KotlinCallResolver
import org.jetbrains.kotlin.resolve.calls.SPECIAL_FUNCTION_NAMES
import org.jetbrains.kotlin.resolve.calls.components.InferenceSession
import org.jetbrains.kotlin.resolve.calls.components.KotlinResolutionCallbacks
import org.jetbrains.kotlin.resolve.calls.components.PostponedArgumentsAnalyzer
import org.jetbrains.kotlin.resolve.calls.components.candidate.ResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
@@ -28,6 +29,7 @@ import org.jetbrains.kotlin.resolve.calls.context.CallPosition
import org.jetbrains.kotlin.resolve.calls.context.ContextDependency
import org.jetbrains.kotlin.resolve.calls.inference.buildResultingSubstitutor
import org.jetbrains.kotlin.resolve.calls.inference.components.KotlinConstraintSystemCompleter
import org.jetbrains.kotlin.resolve.calls.inference.components.ResultTypeResolver
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.results.*
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
@@ -36,6 +38,7 @@ import org.jetbrains.kotlin.resolve.calls.tasks.DynamicCallableDescriptors
import org.jetbrains.kotlin.resolve.calls.tasks.OldResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.resolve.calls.util.*
import org.jetbrains.kotlin.resolve.checkers.PassingProgressionAsCollectionCallChecker
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
@@ -72,8 +75,11 @@ class PSICallResolver(
private val deprecationResolver: DeprecationResolver,
private val moduleDescriptor: ModuleDescriptor,
private val candidateInterceptor: CandidateInterceptor,
private val missingSupertypesResolver: MissingSupertypesResolver
private val missingSupertypesResolver: MissingSupertypesResolver,
private val resultTypeResolver: ResultTypeResolver,
) {
private val callCheckersWithAdditionalResolve = listOf(PassingProgressionAsCollectionCallChecker(kotlinCallResolver))
private val givenCandidatesName = Name.special("<given candidates>")
private val arePartiallySpecifiedTypeArgumentsEnabled = languageVersionSettings.supportsFeature(LanguageFeature.PartiallySpecifiedTypeArguments)
@@ -114,8 +120,22 @@ class PSICallResolver(
}
val overloadResolutionResults = convertToOverloadResolutionResults<D>(context, result, tracingStrategy)
return overloadResolutionResults.also {
clearCacheForApproximationResults()
checkCallWithAdditionalResolve(it, scopeTower, resolutionCallbacks, expectedType, context)
}
}
private fun <D : CallableDescriptor> checkCallWithAdditionalResolve(
overloadResolutionResults: OverloadResolutionResults<D>,
scopeTower: ImplicitScopeTower,
resolutionCallbacks: KotlinResolutionCallbacks,
expectedType: UnwrappedType?,
context: BasicCallResolutionContext,
) {
for (callChecker in callCheckersWithAdditionalResolve) {
callChecker.check(overloadResolutionResults, scopeTower, resolutionCallbacks, expectedType, context)
}
}
@@ -189,7 +209,8 @@ class PSICallResolver(
argumentTypeResolver, languageVersionSettings, kotlinToResolvedCallTransformer,
dataFlowValueFactory, inferenceSession, constantExpressionEvaluator, typeResolver,
this, postponedArgumentsAnalyzer, kotlinConstraintSystemCompleter, callComponents,
doubleColonExpressionResolver, deprecationResolver, moduleDescriptor, context, missingSupertypesResolver, kotlinCallResolver
doubleColonExpressionResolver, deprecationResolver, moduleDescriptor, context, missingSupertypesResolver, kotlinCallResolver,
resultTypeResolver
)
private fun calculateExpectedType(context: BasicCallResolutionContext): UnwrappedType? {
@@ -0,0 +1,184 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.checkers
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.diagnostics.Errors.PROGRESSIONS_CHANGING_RESOLVE
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.KotlinCallResolver
import org.jetbrains.kotlin.resolve.calls.components.KotlinResolutionCallbacks
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.KotlinCallArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.SimpleKotlinCallArgument
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.tower.*
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValueWithSmartCastInfo
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.ClassicTypeCheckerState
import org.jetbrains.kotlin.types.checker.ClassicTypeCheckerStateInternals
import org.jetbrains.kotlin.types.checker.intersectTypes
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
/*
NB: this checker is exceptionally temporary added for stdlib migration purposes (see KT-49276).
Please don't use similar logic for other checkers
*/
@OptIn(ClassicTypeCheckerStateInternals::class)
class PassingProgressionAsCollectionCallChecker(private val kotlinCallResolver: KotlinCallResolver) {
private val typeCheckerState = ClassicTypeCheckerState(isErrorTypeEqualsToAnything = false)
private val iterableProgressions = listOf(
CHAR_RANGE_FQN, CHAR_PROGRESSION_FQN,
INT_RANGE_FQN, INT_PROGRESSION_FQN,
LONG_RANGE_FQN, LONG_PROGRESSION_FQN,
UINT_RANGE_FQN, UINT_PROGRESSION_FQN,
ULONG_RANGE_FQN, ULONG_PROGRESSION_FQN
)
private fun check(
resolvedCall: ResolvedCall<*>,
scopeTower: ImplicitScopeTower,
resolutionCallbacks: KotlinResolutionCallbacks,
expectedType: UnwrappedType?,
context: BasicCallResolutionContext,
) {
// The stdlib migration is going to be finished in 1.8, checks aren't needed there (DisableCheckingChangedProgressionsResolve has 1.8 since version)
val isCheckingDisabled = context.languageVersionSettings.supportsFeature(LanguageFeature.DisableCheckingChangedProgressionsResolve)
if (isCheckingDisabled || resolvedCall !is NewResolvedCallImpl<*>) return
val kotlinCall = resolvedCall.psiKotlinCall
val valueArguments = kotlinCall.argumentsInParenthesis.takeIf { it.isNotEmpty() } ?: return
val progressionOrRangeArgumentTypes = valueArguments.map {
if (it !is SimpleKotlinCallArgument) return@map null
getRangeOrProgressionElementType(it.receiver.receiverValue.type, iterableProgressions)
}
if (progressionOrRangeArgumentTypes.all { it == null }) return
val builtIns = resolvedCall.candidateDescriptor.builtIns
val newArguments = replaceArgumentsWithCollectionIfNeeded(valueArguments, progressionOrRangeArgumentTypes, context.trace, builtIns)
val newCall = PSIKotlinCallImpl(
kotlinCall.callKind, kotlinCall.psiCall, kotlinCall.tracingStrategy, kotlinCall.explicitReceiver,
kotlinCall.dispatchReceiverForInvokeExtension, kotlinCall.name, kotlinCall.typeArguments, newArguments,
kotlinCall.externalArgument, kotlinCall.startingDataFlowInfo, kotlinCall.resultDataFlowInfo,
kotlinCall.dataFlowInfoForArguments, kotlinCall.isForImplicitInvoke
)
val candidateForCollectionReplacedArgument = kotlinCallResolver.resolveCall(
scopeTower, resolutionCallbacks, newCall, expectedType, context.collectAllCandidates
).singleOrNull() ?: return
// Resolve wasn't changed or inapplicable
if (
candidateForCollectionReplacedArgument.descriptor == resolvedCall.candidateDescriptor ||
!candidateForCollectionReplacedArgument.isSuccessful
) return
val collectionOfAnyType = makeCollectionOfAnyType(builtIns)
for ((i, argument) in newCall.argumentsInParenthesis.withIndex()) {
// Skip if the argument wasn't a Range/Progression
if (progressionOrRangeArgumentTypes.getOrNull(i) == null) continue
val resolvedCallForCollectionReplacedArgument = candidateForCollectionReplacedArgument.resolvedCall
val alternativeParameterType =
resolvedCallForCollectionReplacedArgument.argumentToCandidateParameter[argument]?.type?.let { type ->
if (type.isTypeParameter()) {
val typeVariable = resolvedCallForCollectionReplacedArgument.freshVariablesSubstitutor.freshVariables.find {
it.originalTypeParameter == type.constructor.declarationDescriptor
}?.freshTypeConstructor ?: return@let null
resolutionCallbacks.findResultType(candidateForCollectionReplacedArgument.getSystem(), typeVariable)
} else type
} ?: continue
val cons = alternativeParameterType.constructor
if (cons.declarationDescriptor != builtIns.collection && (cons !is IntersectionTypeConstructor || cons.supertypes.none { it.constructor.declarationDescriptor == builtIns.collection })) continue
val argumentExpression = argument.psiExpression ?: continue
val initialArgumentType = resolvedCall.candidateDescriptor.valueParameters.getOrNull(i)?.type ?: continue
// Iterable initial type is an exception, considered as similar to Collection passing candidate
if (initialArgumentType.constructor.declarationDescriptor == builtIns.iterable) continue
// The initial type should be wider than Collection
if (
AbstractTypeChecker.isSubtypeOf(typeCheckerState, collectionOfAnyType, initialArgumentType)
|| initialArgumentType.isTypeParameter()
) {
context.trace.report(
PROGRESSIONS_CHANGING_RESOLVE.on(
candidateForCollectionReplacedArgument.callComponents.languageVersionSettings,
argumentExpression,
resolvedCallForCollectionReplacedArgument.candidateDescriptor
)
)
}
}
}
fun check(
overloadResolutionResults: OverloadResolutionResults<*>,
scopeTower: ImplicitScopeTower,
resolutionCallbacks: KotlinResolutionCallbacks,
expectedType: UnwrappedType?,
context: BasicCallResolutionContext,
) {
if (!overloadResolutionResults.isSingleResult) return
val resolvedCall = overloadResolutionResults.resultingCall as? NewAbstractResolvedCall<*> ?: return
check(resolvedCall, scopeTower, resolutionCallbacks, expectedType, context)
}
private fun makeCollectionOfAnyType(builtIns: KotlinBuiltIns): KotlinType =
KotlinTypeFactory.simpleNotNullType(
Annotations.EMPTY,
builtIns.collection,
listOf(TypeProjectionImpl(builtIns.nullableAnyType))
)
private fun replaceArgumentsWithCollectionIfNeeded(
valueArguments: List<KotlinCallArgument>,
progressionOrRangeArgumentTypes: List<KotlinType?>,
trace: BindingTrace,
builtIns: KotlinBuiltIns
): List<KotlinCallArgument> = valueArguments.mapIndexed { i, argument ->
if (argument !is ExpressionKotlinCallArgumentImpl) return@mapIndexed argument
val progressionOrRangeElementType = progressionOrRangeArgumentTypes[i] ?: return@mapIndexed argument
val psiExpression = argument.psiExpression ?: return@mapIndexed argument
val newType = intersectTypes(
listOf(
KotlinTypeFactory.simpleNotNullType(
Annotations.EMPTY,
builtIns.collection,
listOf(TypeProjectionImpl(progressionOrRangeElementType))
),
argument.receiver.receiverValue.type.unwrap()
)
)
ExpressionKotlinCallArgumentImpl(
argument.psiCallArgument.valueArgument,
DataFlowInfo.EMPTY,
DataFlowInfo.EMPTY,
ReceiverValueWithSmartCastInfo(
ExpressionReceiver.create(psiExpression, newType, trace.bindingContext),
emptySet(),
true
)
)
}
}
@@ -281,10 +281,5 @@ fun isCharSequenceIterator(descriptor: CallableDescriptor) =
}
fun isPrimitiveNumberClassDescriptor(descriptor: DeclarationDescriptor?): Boolean {
return if (descriptor !is ClassDescriptor) {
false
} else KotlinBuiltIns.isPrimitiveClass((descriptor as ClassDescriptor?)!!) && !KotlinBuiltIns.isBoolean(
(descriptor as ClassDescriptor?)!!
)
}
fun isPrimitiveNumberClassDescriptor(descriptor: DeclarationDescriptor?): Boolean =
descriptor is ClassDescriptor && KotlinBuiltIns.isPrimitiveClass(descriptor) && !KotlinBuiltIns.isBoolean(descriptor)