From dca99c3fd289c7f42572fef34b66a2750af24957 Mon Sep 17 00:00:00 2001 From: Alexander Udalov Date: Thu, 22 Sep 2016 20:19:00 +0300 Subject: [PATCH] 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 --- .../kotlin/resolve/ClassifierUsageChecker.kt | 46 +++++++++++++++++++ .../kotlin/resolve/LazyTopDownAnalyzer.kt | 5 +- .../resolve/QualifiedExpressionResolveUtil.kt | 19 ++------ .../resolve/QualifiedExpressionResolver.kt | 12 +---- .../resolve/calls/CallExpressionResolver.kt | 9 ++-- .../calls/checkers/DeprecatedCallChecker.kt | 4 ++ .../tests/deprecated/companionObjectUsage.kt | 14 +++++- .../tests/deprecated/companionObjectUsage.txt | 16 +++++++ .../diagnostics/tests/regressions/kt13954.kt | 26 +++++++++++ .../diagnostics/tests/regressions/kt13954.txt | 40 ++++++++++++++++ .../checkers/DiagnosticsTestGenerated.java | 6 +++ 11 files changed, 166 insertions(+), 31 deletions(-) create mode 100644 compiler/testData/diagnostics/tests/regressions/kt13954.kt create mode 100644 compiler/testData/diagnostics/tests/regressions/kt13954.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ClassifierUsageChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ClassifierUsageChecker.kt index 16505d3f0d8..12e6a589323 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/ClassifierUsageChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/ClassifierUsageChecker.kt @@ -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, trace: BindingTrace, checkers: Iterable) { + 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) + } + } + } } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt index bc8c32872d6..e264730c2bd 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/LazyTopDownAnalyzer.kt @@ -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 ) { fun analyzeDeclarations(topDownAnalysisMode: TopDownAnalysisMode, declarations: Collection, outerDataFlowInfo: DataFlowInfo): TopDownAnalysisContext { @@ -204,6 +205,8 @@ class LazyTopDownAnalyzer( bodyResolver.resolveBodies(c) + ClassifierUsageChecker.check(declarations, trace, classifierUsageCheckers) + return c } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolveUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolveUtil.kt index 3407f59762f..66b74358aac 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolveUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolveUtil.kt @@ -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 + 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 + 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 + 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 } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt index 2157a9f9d44..6a27528de8e 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/QualifiedExpressionResolver.kt @@ -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) { +class QualifiedExpressionResolver { fun resolvePackageHeader( packageDirective: KtPackageDirective, module: ModuleDescriptor, @@ -376,9 +376,7 @@ class QualifiedExpressionResolver(val classifierUsageCheckers: Iterable + 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 { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/DeprecatedCallChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/DeprecatedCallChecker.kt index 80c6162fe6b..1259a15d201 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/DeprecatedCallChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/calls/checkers/DeprecatedCallChecker.kt @@ -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 diff --git a/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.kt b/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.kt index fce3cdad0d5..1d8dc3baece 100644 --- a/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.kt +++ b/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.kt @@ -17,4 +17,16 @@ fun useCompanion() { val x = Another.Companion Another.Companion.use() Another.use() -} \ No newline at end of file +} + +@Deprecated("Some") +class Some { + companion object { + fun use() {} + } +} + +fun some() { + Some.use() + Some.Companion.use() +} diff --git a/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.txt b/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.txt index 22f5d0b7da9..0e881eb043c 100644 --- a/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.txt +++ b/compiler/testData/diagnostics/tests/deprecated/companionObjectUsage.txt @@ -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 + } +} diff --git a/compiler/testData/diagnostics/tests/regressions/kt13954.kt b/compiler/testData/diagnostics/tests/regressions/kt13954.kt new file mode 100644 index 00000000000..d5a0c803542 --- /dev/null +++ b/compiler/testData/diagnostics/tests/regressions/kt13954.kt @@ -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 {} diff --git a/compiler/testData/diagnostics/tests/regressions/kt13954.txt b/compiler/testData/diagnostics/tests/regressions/kt13954.txt new file mode 100644 index 00000000000..ea7bb2f36ba --- /dev/null +++ b/compiler/testData/diagnostics/tests/regressions/kt13954.txt @@ -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 + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 9273c9e493d..25794246394 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -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");