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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+5
-14
@@ -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 {
|
||||
|
||||
+4
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user