Add frontend checks for missing dependency supertypes

Call checker and declaration checker are used in order to preserve backward compatibility.
Attempt to use classifier usage checker was not good enouth,
since not all errors found with it would actually be reported before.
For example types and constructor calls don't cause supertypes to resolve,
so missing supertypes would not lead to errors in case they are the only use of class name.

Updated tests failing due to missing Java dependencies in superclasses.
This commit is contained in:
Pavel Kirpichenkov
2019-11-05 12:03:53 +03:00
parent 388cd53105
commit 8c52bb4212
49 changed files with 423 additions and 1373 deletions
@@ -100,6 +100,7 @@ public interface Errors {
DiagnosticFactory2<PsiElement, String, String> API_NOT_AVAILABLE = DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<PsiElement, FqName> MISSING_DEPENDENCY_CLASS = DiagnosticFactory1.create(ERROR);
DiagnosticFactory2<PsiElement, FqName, FqName> MISSING_DEPENDENCY_SUPERCLASS = DiagnosticFactory2.create(ERROR);
DiagnosticFactory1<PsiElement, FqName> MISSING_BUILT_IN_DECLARATION = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_BASE_CLASS = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, String> MISSING_SCRIPT_STANDARD_TEMPLATE = DiagnosticFactory1.create(ERROR);
@@ -375,6 +375,7 @@ public class DefaultErrorMessages {
MAP.put(API_NOT_AVAILABLE, "This declaration is only available since Kotlin {0} and cannot be used with the specified API version {1}", STRING, STRING);
MAP.put(MISSING_DEPENDENCY_CLASS, "Cannot access class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
MAP.put(MISSING_DEPENDENCY_SUPERCLASS, "Cannot access ''{0}'' which is a supertype of ''{1}''. Check your module classpath for missing or conflicting dependencies", TO_STRING, TO_STRING);
MAP.put(MISSING_BUILT_IN_DECLARATION, "Cannot access built-in declaration ''{0}''. Ensure that you have a dependency on the Kotlin standard library", TO_STRING);
MAP.put(MISSING_SCRIPT_BASE_CLASS, "Cannot access script base class ''{0}''. Check your module classpath for missing or conflicting dependencies", TO_STRING);
MAP.put(MISSING_SCRIPT_STANDARD_TEMPLATE, "No script runtime was found in the classpath: class ''{0}'' not found. Please add kotlin-script-runtime.jar to the module dependencies.", TO_STRING);
@@ -34,7 +34,8 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
LocalVariableTypeParametersChecker(),
ExplicitApiDeclarationChecker(),
TailrecFunctionChecker,
TrailingCommaDeclarationChecker
TrailingCommaDeclarationChecker,
MissingDependencySupertypeChecker.ForDeclarations
)
private val DEFAULT_CALL_CHECKERS = listOf(
@@ -46,7 +47,8 @@ private val DEFAULT_CALL_CHECKERS = listOf(
UnderscoreUsageChecker, AssigningNamedArgumentToVarargChecker(), ImplicitNothingAsTypeParameterCallChecker,
PrimitiveNumericComparisonCallChecker, LambdaWithSuspendModifierCallChecker,
UselessElvisCallChecker(), ResultTypeWithNullableOperatorsChecker(), NullableVarargArgumentCallChecker,
NamedFunAsExpressionChecker, ContractNotAllowedCallChecker, ReifiedTypeParameterSubstitutionChecker(), TypeOfChecker
NamedFunAsExpressionChecker, ContractNotAllowedCallChecker, ReifiedTypeParameterSubstitutionChecker(), TypeOfChecker,
MissingDependencySupertypeChecker.ForCalls
)
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_CLASSIFIER_USAGE_CHECKERS = listOf(
@@ -9,18 +9,12 @@ import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
import org.jetbrains.kotlin.builtins.isFunctionOrSuspendFunctionType
import org.jetbrains.kotlin.contracts.EffectSystem
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.contracts.description.LazyContractProvider
import org.jetbrains.kotlin.contracts.parsing.isContractCallDescriptor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck
import org.jetbrains.kotlin.psi.psiUtil.isFirstStatement
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.BindingContext.CONSTRAINT_SYSTEM_COMPLETER
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.ResolveArgumentsMode.RESOLVE_FUNCTION_ARGUMENTS
@@ -46,10 +40,12 @@ import org.jetbrains.kotlin.resolve.calls.results.ResolutionStatus
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.resolve.checkers.MissingDependencySupertypeChecker
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstant
import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.expressions.DataFlowAnalyzer
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
@@ -80,21 +76,25 @@ class CallCompleter(
completeAllCandidates(context, results)
}
if (resolvedCall != null && context.trace.wantsDiagnostics()) {
val calleeExpression = if (resolvedCall is VariableAsFunctionResolvedCall)
resolvedCall.variableCall.call.calleeExpression
else
resolvedCall.call.calleeExpression
val reportOn =
if (calleeExpression != null && !calleeExpression.isFakeElement) calleeExpression
else resolvedCall.call.callElement
if (context.trace.wantsDiagnostics()) {
if (resolvedCall == null) {
checkMissingSupertypes(context, moduleDescriptor)
} else {
val calleeExpression = if (resolvedCall is VariableAsFunctionResolvedCall)
resolvedCall.variableCall.call.calleeExpression
else
resolvedCall.call.calleeExpression
val reportOn =
if (calleeExpression != null && !calleeExpression.isFakeElement) calleeExpression
else resolvedCall.call.callElement
val callCheckerContext = CallCheckerContext(context, deprecationResolver, moduleDescriptor)
for (callChecker in callCheckers) {
callChecker.check(resolvedCall, reportOn, callCheckerContext)
val callCheckerContext = CallCheckerContext(context, deprecationResolver, moduleDescriptor)
for (callChecker in callCheckers) {
callChecker.check(resolvedCall, reportOn, callCheckerContext)
if (resolvedCall is VariableAsFunctionResolvedCall) {
callChecker.check(resolvedCall.variableCall, reportOn, callCheckerContext)
if (resolvedCall is VariableAsFunctionResolvedCall) {
callChecker.check(resolvedCall.variableCall, reportOn, callCheckerContext)
}
}
}
}
@@ -105,6 +105,15 @@ class CallCompleter(
return results
}
private fun checkMissingSupertypes(context: BasicCallResolutionContext, moduleDescriptor: ModuleDescriptor) {
val call = context.call
val explicitReceiver = call.explicitReceiver.safeAs<ReceiverValue>()
?: return
MissingDependencySupertypeChecker.checkSupertypes(
explicitReceiver.type, call.callElement, context.trace, moduleDescriptor
)
}
private fun <D : CallableDescriptor> completeAllCandidates(
context: BasicCallResolutionContext,
results: OverloadResolutionResultsImpl<D>
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.resolve.calls.tower
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.createFunctionType
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl
import org.jetbrains.kotlin.descriptors.impl.ReceiverParameterDescriptorImpl
import org.jetbrains.kotlin.psi.KtElement
@@ -31,6 +28,7 @@ import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategyImpl
import org.jetbrains.kotlin.resolve.calls.util.CallMaker
import org.jetbrains.kotlin.resolve.checkers.MissingDependencySupertypeChecker
import org.jetbrains.kotlin.resolve.deprecation.DeprecationResolver
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.expressions.DoubleColonExpressionResolver
@@ -92,6 +90,7 @@ class ResolvedAtomCompleter(
val lastCall = if (resolvedCall is VariableAsFunctionResolvedCall) resolvedCall.functionCall else resolvedCall
if (ErrorUtils.isError(resolvedCall.candidateDescriptor)) {
kotlinToResolvedCallTransformer.runArgumentsChecks(topLevelCallContext, topLevelTrace, lastCall as NewResolvedCallImpl<*>)
checkMissingReceiverSupertypes(resolvedCall, moduleDescriptor, topLevelTrace)
return resolvedCall
}
@@ -118,6 +117,22 @@ class ResolvedAtomCompleter(
return resolvedCall
}
private fun checkMissingReceiverSupertypes(
resolvedCall: ResolvedCall<CallableDescriptor>,
moduleDescriptor: ModuleDescriptor,
trace: BindingTrace
) {
val receiverValue = resolvedCall.dispatchReceiver ?: resolvedCall.extensionReceiver
receiverValue?.type?.let { receiverType ->
MissingDependencySupertypeChecker.checkSupertypes(
receiverType,
resolvedCall.call.callElement,
trace,
moduleDescriptor
)
}
}
private fun clearPartiallyResolvedCall(resolvedCallAtom: ResolvedCallAtom) {
val psiCall = KotlinToResolvedCallTransformer.keyForPartiallyResolvedCall(resolvedCallAtom)
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2019 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 com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtTypeParameterListOwner
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.supertypes
object MissingDependencySupertypeChecker {
object ForDeclarations : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
val trace = context.trace
val module = context.moduleDescriptor
if (descriptor is ClassDescriptor) {
checkSupertypes(descriptor.defaultType, declaration, trace, module)
}
if (declaration is KtTypeParameterListOwner) {
for (ktTypeParameter in declaration.typeParameters) {
val typeParameterDescriptor = trace.bindingContext.get(BindingContext.TYPE_PARAMETER, ktTypeParameter) ?: continue
for (upperBound in typeParameterDescriptor.upperBounds) {
checkSupertypes(upperBound, ktTypeParameter, trace, module)
}
}
}
}
}
object ForCalls : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val descriptor = resolvedCall.resultingDescriptor
// Constructor call leads to resolution of supertypes of enclosing class if it's an inner class constructor
checkHierarchy(descriptor.dispatchReceiverParameter?.declaration, reportOn, context)
// The constructed class' own supertypes are not resolved after constructor call,
// so its containing declaration should not be checked.
if (descriptor !is ConstructorDescriptor) {
checkHierarchy(descriptor.containingDeclaration, reportOn, context)
checkHierarchy(descriptor.extensionReceiverParameter?.declaration, reportOn, context)
}
}
private val ReceiverParameterDescriptor.declaration
get() = value.type.constructor.declarationDescriptor
private fun checkHierarchy(declaration: DeclarationDescriptor?, reportOn: PsiElement, context: CallCheckerContext) {
if (declaration !is ClassifierDescriptor) return
checkSupertypes(declaration.defaultType, reportOn, context.trace, context.moduleDescriptor)
}
}
fun checkSupertypes(
classifierType: KotlinType,
reportOn: PsiElement,
trace: BindingTrace,
moduleDescriptor: ModuleDescriptor
) {
val classifierDescriptor = classifierType.constructor.declarationDescriptor ?: return
for (supertype in classifierType.supertypes()) {
val supertypeDeclaration = supertype.constructor.declarationDescriptor
/*
* TODO: expects are not checked, because findClassAcrossModuleDependencies does not work with actualization via type alias
* Type parameters are skipped here, bounds of type parameters are checked in declaration checker separately
* Local declarations are ignored for optimization
*/
if (supertypeDeclaration !is ClassDescriptor || supertypeDeclaration.isExpect) continue
if (supertypeDeclaration.visibility == Visibilities.LOCAL) continue
val superTypeClassId = supertypeDeclaration.classId ?: continue
val dependency = moduleDescriptor.findClassAcrossModuleDependencies(superTypeClassId)
if (dependency == null || dependency is NotFoundClasses.MockClassDescriptor) {
trace.report(
Errors.MISSING_DEPENDENCY_SUPERCLASS.on(
reportOn,
supertypeDeclaration.fqNameSafe,
classifierDescriptor.fqNameSafe
)
)
}
}
}
}