Move ClassifierUsageChecker invocation lower in LazyTopDownAnalyzer

Invoking checkers right after resolution of qualified expressions is too early:
DeprecatedClassifierUsageChecker tries to load the annotations of a referenced
classifier to report deprecation, and that may result in building light classes
for not yet analyzed declarations

 #KT-13954 Fixed
This commit is contained in:
Alexander Udalov
2016-09-22 20:19:00 +03:00
parent 35839695f7
commit dca99c3fd2
11 changed files with 166 additions and 31 deletions
@@ -17,8 +17,54 @@
package org.jetbrains.kotlin.resolve
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
interface ClassifierUsageChecker {
fun check(targetDescriptor: ClassifierDescriptor, trace: BindingTrace, element: PsiElement)
companion object {
fun check(declarations: Collection<PsiElement>, trace: BindingTrace, checkers: Iterable<ClassifierUsageChecker>) {
val visitor = object : KtTreeVisitorVoid() {
override fun visitReferenceExpression(expression: KtReferenceExpression) {
super.visitReferenceExpression(expression)
if (expression is KtNameReferenceExpression && expression.getReferencedNameElementType() == KtTokens.SUPER_KEYWORD) {
// Do not run checkers here because super expressions is not a proper expression. For example,
// it doesn't make sense to report deprecation on "super" in "super.foo()" if the super class is deprecated.
return
}
val target = trace.bindingContext.get(BindingContext.REFERENCE_TARGET, expression) as? ClassifierDescriptor ?: return
for (checker in checkers) {
checker.check(target, trace, expression)
}
if (isReferenceToCompanionViaOuterClass(expression, target)) {
val outerClass = target.containingDeclaration as ClassDescriptor
for (checker in checkers) {
checker.check(outerClass, trace, expression)
}
}
}
/**
* @return true iff [expression] references the companion of a class Foo via the name Foo, e.g. in `Foo.bar()`
*/
private fun isReferenceToCompanionViaOuterClass(expression: KtReferenceExpression, target: ClassifierDescriptor?): Boolean {
return DescriptorUtils.isCompanionObject(target) &&
trace.bindingContext[BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, expression] != null
}
}
for (declaration in declarations) {
declaration.accept(visitor)
}
}
}
}
@@ -47,7 +47,8 @@ class LazyTopDownAnalyzer(
private val declarationScopeProvider: DeclarationScopeProvider,
private val qualifiedExpressionResolver: QualifiedExpressionResolver,
private val identifierChecker: IdentifierChecker,
private val languageVersionSettings: LanguageVersionSettings
private val languageVersionSettings: LanguageVersionSettings,
private val classifierUsageCheckers: Iterable<ClassifierUsageChecker>
) {
fun analyzeDeclarations(topDownAnalysisMode: TopDownAnalysisMode, declarations: Collection<PsiElement>, outerDataFlowInfo: DataFlowInfo): TopDownAnalysisContext {
@@ -204,6 +205,8 @@ class LazyTopDownAnalyzer(
bodyResolver.resolveBodies(c)
ClassifierUsageChecker.check(declarations, trace, classifierUsageCheckers)
return c
}
@@ -25,12 +25,9 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.*
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
fun resolveQualifierAsReceiverInExpression(
qualifier: Qualifier,
selector: DeclarationDescriptor?,
context: ExpressionTypingContext,
classifierUsageCheckers: Iterable<ClassifierUsageChecker>
qualifier: Qualifier, selector: DeclarationDescriptor?, context: ExpressionTypingContext
): DeclarationDescriptor {
val referenceTarget = resolveQualifierReferenceTarget(qualifier, selector, context, classifierUsageCheckers)
val referenceTarget = resolveQualifierReferenceTarget(qualifier, selector, context)
if (referenceTarget is TypeParameterDescriptor) {
context.trace.report(Errors.TYPE_PARAMETER_ON_LHS_OF_DOT.on(qualifier.referenceExpression, referenceTarget))
@@ -40,11 +37,9 @@ fun resolveQualifierAsReceiverInExpression(
}
fun resolveQualifierAsStandaloneExpression(
qualifier: Qualifier,
context: ExpressionTypingContext,
classifierUsageCheckers: Iterable<ClassifierUsageChecker>
qualifier: Qualifier, context: ExpressionTypingContext
): DeclarationDescriptor {
val referenceTarget = resolveQualifierReferenceTarget(qualifier, null, context, classifierUsageCheckers)
val referenceTarget = resolveQualifierReferenceTarget(qualifier, null, context)
when (referenceTarget) {
is TypeAliasDescriptor -> {
@@ -73,8 +68,7 @@ fun resolveQualifierAsStandaloneExpression(
private fun resolveQualifierReferenceTarget(
qualifier: Qualifier,
selector: DeclarationDescriptor?,
context: ExpressionTypingContext,
classifierUsageCheckers: Iterable<ClassifierUsageChecker>
context: ExpressionTypingContext
): DeclarationDescriptor {
if (qualifier is TypeParameterQualifier) {
return qualifier.descriptor
@@ -110,9 +104,6 @@ private fun resolveQualifierReferenceTarget(
context.trace.recordType(qualifier.expression, classValueTypeDescriptor.defaultType)
if (classifier.hasCompanionObject) {
context.trace.record(BindingContext.SHORT_REFERENCE_TO_COMPANION_OBJECT, qualifier.referenceExpression, classifier)
for (checker in classifierUsageCheckers) {
checker.check(classValueDescriptor, context.trace, qualifier.referenceExpression)
}
}
return classValueTypeDescriptor
}
@@ -39,7 +39,7 @@ import org.jetbrains.kotlin.types.expressions.isWithoutValueArguments
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.check
class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable<ClassifierUsageChecker>) {
class QualifiedExpressionResolver {
fun resolvePackageHeader(
packageDirective: KtPackageDirective,
module: ModuleDescriptor,
@@ -376,9 +376,7 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable<Classifi
}
}
val classifierDescriptor = scopeForFirstPart?.let {
it.findClassifier(firstPart.name, firstPart.location)
}
val classifierDescriptor = scopeForFirstPart?.findClassifier(firstPart.name, firstPart.location)
if (classifierDescriptor != null) {
storeResult(trace, firstPart.expression, classifierDescriptor, shouldBeVisibleFrom, position)
@@ -626,12 +624,6 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable<Classifi
trace.record(BindingContext.REFERENCE_TARGET, referenceExpression, descriptor)
if (descriptor is ClassifierDescriptor) {
for (checker in classifierUsageCheckers) {
checker.check(descriptor, trace, referenceExpression)
}
}
if (descriptor is DeclarationDescriptorWithVisibility) {
val fromToCheck =
if (shouldBeVisibleFrom is PackageFragmentDescriptor && shouldBeVisibleFrom.source == SourceElement.NO_SOURCE && referenceExpression.containingFile !is DummyHolder) {
@@ -70,8 +70,7 @@ class CallExpressionResolver(
private val argumentTypeResolver: ArgumentTypeResolver,
private val dataFlowAnalyzer: DataFlowAnalyzer,
private val builtIns: KotlinBuiltIns,
private val qualifiedExpressionResolver: QualifiedExpressionResolver,
private val classifierUsageCheckers: Iterable<ClassifierUsageChecker>
private val qualifiedExpressionResolver: QualifiedExpressionResolver
) {
private lateinit var expressionTypingServices: ExpressionTypingServices
@@ -162,7 +161,7 @@ class CallExpressionResolver(
val temporaryForQualifier = TemporaryTraceAndCache.create(context, "trace to resolve as qualifier", nameExpression)
val contextForQualifier = context.replaceTraceAndCache(temporaryForQualifier)
qualifiedExpressionResolver.resolveNameExpressionAsQualifierForDiagnostics(nameExpression, receiver, contextForQualifier)?.let {
resolveQualifierAsStandaloneExpression(it, contextForQualifier, classifierUsageCheckers)
resolveQualifierAsStandaloneExpression(it, contextForQualifier)
temporaryForQualifier.commit()
} ?: temporaryForVariable.commit()
return noTypeInfo(context)
@@ -225,7 +224,7 @@ class CallExpressionResolver(
val type = functionDescriptor.returnType
// Extracting jump out possible and jump point flow info from arguments, if any
val arguments = callExpression.valueArguments
val resultFlowInfo = resolvedCall!!.dataFlowInfoForArguments.resultInfo
val resultFlowInfo = resolvedCall.dataFlowInfoForArguments.resultInfo
var jumpFlowInfo = resultFlowInfo
var jumpOutPossible = false
for (argument in arguments) {
@@ -449,7 +448,7 @@ class CallExpressionResolver(
context.trace.get(BindingContext.REFERENCE_TARGET, it)
}
resolveQualifierAsReceiverInExpression(qualifier, selectorDescriptor, context, classifierUsageCheckers)
resolveQualifierAsReceiverInExpression(qualifier, selectorDescriptor, context)
}
companion object {
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.psi.KtUnaryExpression
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
import org.jetbrains.kotlin.resolve.createDeprecationDiagnostic
import org.jetbrains.kotlin.resolve.getDeprecation
@@ -38,6 +39,9 @@ object DeprecatedCallChecker : CallChecker {
}
private fun check(targetDescriptor: CallableDescriptor, trace: BindingTrace, element: PsiElement) {
// Objects will be checked by DeprecatedClassifierUsageChecker
if (targetDescriptor is FakeCallableDescriptorForObject) return
val deprecation = targetDescriptor.getDeprecation()
// avoid duplicating diagnostic when deprecation for property effectively deprecates setter
@@ -17,4 +17,16 @@ fun useCompanion() {
val <!UNUSED_VARIABLE!>x<!> = Another.<!DEPRECATION!>Companion<!>
Another.<!DEPRECATION!>Companion<!>.use()
<!DEPRECATION!>Another<!>.use()
}
}
@Deprecated("Some")
class Some {
companion object {
fun use() {}
}
}
fun some() {
<!DEPRECATION!>Some<!>.use()
<!DEPRECATION!>Some<!>.Companion.use()
}
@@ -1,6 +1,7 @@
package
public fun first(): kotlin.Unit
public fun some(): kotlin.Unit
public fun useCompanion(): kotlin.Unit
public final class Another {
@@ -18,3 +19,18 @@ public final class Another {
public final fun use(): kotlin.Unit
}
}
@kotlin.Deprecated(message = "Some") public final class Some {
public constructor Some()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public companion object Companion {
private constructor Companion()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public final fun use(): kotlin.Unit
}
}
@@ -0,0 +1,26 @@
// FILE: foo/A.kt
package foo
class A(val c: C)
// FILE: foo/B.kt
package foo
class B {
interface D {
fun foo(): E
}
class E
}
// FILE: foo/C.java
package foo;
import static foo.B.D.*;
@SuppressWarnings("RedundantTypeArguments")
public class C {}
@@ -0,0 +1,40 @@
package
package foo {
public final class A {
public constructor A(/*0*/ c: foo.C)
public final val c: foo.C
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class B {
public constructor B()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public interface D {
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public abstract fun foo(): foo.B.E
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
public final class E {
public constructor E()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
@java.lang.SuppressWarnings(value = "RedundantTypeArguments") public open class C {
public constructor C()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
@@ -14826,6 +14826,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("kt13954.kt")
public void testKt13954() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/regressions/kt13954.kt");
doTest(fileName);
}
@TestMetadata("kt1489_1728.kt")
public void testKt1489_1728() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/regressions/kt1489_1728.kt");