Introduce deprecation of companion objects nested classes

Introdude deprecation as per KT-21515. Warning is reported on type
usage, that soon will became invisible. Quickfix by adding explicit
import is added.

Idea behind implementation is to mark scopes that are deprecated (see
ClassResolutionScopesSupport).

Then, during walk along hierarchy of scopes, look at deprecation status
of the scope that has provided this classifier.
Note that we also have to check if there are *some* non-deprecated
visibility paths (because we can see classifier by two paths, e.g. if
we've added explicit import) -- then this type reference shouldn't be
treated as deprecated.
This commit is contained in:
Dmitry Savvinov
2017-12-14 18:59:32 +03:00
parent acd8edaa9c
commit d570b863ce
117 changed files with 8027 additions and 113 deletions
@@ -106,6 +106,7 @@ public interface Errors {
DiagnosticFactory3<KtSimpleNameExpression, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_REFERENCE =
DiagnosticFactory3.create(ERROR);
DiagnosticFactory3<PsiElement, DeclarationDescriptor, Visibility, DeclarationDescriptor> INVISIBLE_MEMBER = DiagnosticFactory3.create(ERROR, CALL_ELEMENT);
DiagnosticFactory1<KtElement, DeclarationDescriptor> DEPRECATED_ACCESS_BY_SHORT_NAME = DiagnosticFactory1.create(WARNING);
DiagnosticFactory1<PsiElement, ConstructorDescriptor> PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL = DiagnosticFactory1.create(ERROR);
@@ -66,6 +66,7 @@ public class DefaultErrorMessages {
MAP.put(INVISIBLE_REFERENCE, "Cannot access ''{0}'': it is {1} in {2}", NAME, VISIBILITY, NAME_OF_CONTAINING_DECLARATION_OR_FILE);
MAP.put(INVISIBLE_MEMBER, "Cannot access ''{0}'': it is {1} in {2}", NAME, VISIBILITY, NAME_OF_CONTAINING_DECLARATION_OR_FILE);
MAP.put(DEPRECATED_ACCESS_BY_SHORT_NAME, "Access to this type by short name is deprecated, and soon is going to be removed. Please, add explicit qualifier or import", NAME);
MAP.put(PROTECTED_CONSTRUCTOR_NOT_IN_SUPER_CALL, "Protected constructor ''{0}'' from other classes can only be used in super-call", Renderers.SHORT_NAMES_IN_TYPES);
@@ -50,7 +50,7 @@ class SyntheticClassOrObjectDescriptor(
private val thisDescriptor: SyntheticClassOrObjectDescriptor get() = this // code readability
private val typeConstructor = SyntheticTypeConstructor(c.storageManager)
private val resolutionScopesSupport = ClassResolutionScopesSupport(thisDescriptor, c.storageManager, { outerScope })
private val resolutionScopesSupport = ClassResolutionScopesSupport(thisDescriptor, c.storageManager, c.languageVersionSettings, { outerScope })
private val syntheticSupertypes =
mutableListOf<KotlinType>().apply { c.syntheticResolveExtension.addSyntheticSupertypes(thisDescriptor, this) }
private val unsubstitutedMemberScope =
@@ -244,6 +244,8 @@ public interface BindingContext {
WritableSlice<PsiElement, PropertyDescriptor> PRIMARY_CONSTRUCTOR_PARAMETER = Slices.createSimpleSlice();
WritableSlice<PsiElement, TypeAliasDescriptor> TYPE_ALIAS = Slices.createSimpleSlice();
WritableSlice<PsiElement, Boolean> DEPRECATED_SHORT_NAME_ACCESS = Slices.createSimpleSlice();
WritableSlice[] DECLARATIONS_TO_DESCRIPTORS = new WritableSlice[] {
CLASS, TYPE_PARAMETER, FUNCTION, CONSTRUCTOR, VARIABLE, VALUE_PARAMETER, PROPERTY_ACCESSOR,
PRIMARY_CONSTRUCTOR_PARAMETER, SCRIPT, TYPE_ALIAS
@@ -33,8 +33,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.scopes.ImportingScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.receivers.*
import org.jetbrains.kotlin.resolve.scopes.utils.findClassifier
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
import org.jetbrains.kotlin.resolve.scopes.utils.*
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext
import org.jetbrains.kotlin.types.expressions.isWithoutValueArguments
@@ -62,6 +61,27 @@ class QualifiedExpressionResolver {
get() = qualifierParts.flatMap { it.typeArguments?.arguments.orEmpty() }
}
fun LexicalScope.findClassifierAndReportDeprecationIfNeeded(
name: Name,
lookupLocation: KotlinLookupLocation,
reportOn: KtExpression,
trace: BindingTrace
): ClassifierDescriptor? {
val (classifier, isDeprecated) = findFirstClassifierWithDeprecationStatus(name, lookupLocation) ?: return null
if (isDeprecated) {
trace.record(BindingContext.DEPRECATED_SHORT_NAME_ACCESS, reportOn) // For IDE
// slow-path: we know that closest classifier is imported by the deprecated path, but before reporting
// deprecation, we have to recheck if there's some other import path, which isn't deprecated (e.g. explicit import)
if (!classifier.canBeResolvedWithoutDeprecation(this, lookupLocation)) {
trace.report(Errors.DEPRECATED_ACCESS_BY_SHORT_NAME.on(reportOn, classifier))
}
}
return classifier
}
fun resolveDescriptorForType(
userType: KtUserType,
scope: LexicalScope,
@@ -71,7 +91,13 @@ class QualifiedExpressionResolver {
val ownerDescriptor = if (!isDebuggerContext) scope.ownerDescriptor else null
if (userType.qualifier == null) {
val descriptor = userType.referenceExpression?.let { expression ->
val classifier = scope.findClassifier(expression.getReferencedNameAsName(), KotlinLookupLocation(expression))
val classifier = scope.findClassifierAndReportDeprecationIfNeeded(
expression.getReferencedNameAsName(),
KotlinLookupLocation(expression),
expression,
trace
)
checkNotEnumEntry(classifier, trace, expression)
storeResult(trace, expression, classifier, ownerDescriptor, position = QualifierPosition.TYPE, isQualifier = false)
classifier
@@ -143,9 +169,14 @@ class QualifiedExpressionResolver {
}
if (qualifierPartList.size == 1) {
val (name, simpleName) = qualifierPartList.single()
val descriptor = scope.findClassifier(name, KotlinLookupLocation(simpleName))
storeResult(trace, simpleName, descriptor, ownerDescriptor, position = QualifierPosition.TYPE, isQualifier = true)
val (name, simpleNameExpression) = qualifierPartList.single()
val descriptor = scope.findClassifierAndReportDeprecationIfNeeded(
name,
KotlinLookupLocation(simpleNameExpression),
simpleNameExpression,
trace
)
storeResult(trace, simpleNameExpression, descriptor, ownerDescriptor, position = QualifierPosition.TYPE, isQualifier = true)
return TypeQualifierResolutionResult(qualifierPartList, descriptor)
}
@@ -18,10 +18,7 @@ package org.jetbrains.kotlin.resolve.calls.tower
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.name.Name
@@ -45,11 +42,14 @@ 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.*
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject
import org.jetbrains.kotlin.resolve.descriptorUtil.hasDynamicExtensionAnnotation
import org.jetbrains.kotlin.resolve.scopes.HierarchicalScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.SyntheticScopes
import org.jetbrains.kotlin.resolve.scopes.receivers.*
import org.jetbrains.kotlin.resolve.scopes.utils.canBeResolvedWithoutDeprecation
import org.jetbrains.kotlin.types.DeferredType
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.expressions.OperatorConventions
@@ -287,15 +287,54 @@ class NewResolutionOldInference(
error.message
)
)
is NestedClassViaInstanceReference -> tracing.nestedClassAccessViaInstanceReference(
resolvedCall.trace,
error.classDescriptor,
resolvedCall.explicitReceiverKind
)
is ErrorDescriptorDiagnostic -> {
// todo
// return@map null
}
is ResolvedUsingDeprecatedVisbility -> {
resolvedCall.trace.record(
BindingContext.DEPRECATED_SHORT_NAME_ACCESS,
resolvedCall.call.calleeExpression
)
val candidateDescriptor = resolvedCall.candidateDescriptor
val descriptorToLookup: DeclarationDescriptor = when (candidateDescriptor) {
is ClassConstructorDescriptor -> candidateDescriptor.containingDeclaration
is FakeCallableDescriptorForObject -> candidateDescriptor.classDescriptor
else -> error(
"Unexpected candidate descriptor of resolved call with " +
"ResolvedUsingDeprecatedVisibility-diagnostic: $candidateDescriptor\n" +
"Call context: ${resolvedCall.call.callElement.parent?.text}"
)
}
// If this descriptor was resolved from HierarchicalScope, then there can be another, non-deprecated path
// in parents of base scope
val sourceScope = error.baseSourceScope
val canBeResolvedWithoutDeprecation = if (sourceScope is HierarchicalScope) {
descriptorToLookup.canBeResolvedWithoutDeprecation(
sourceScope,
error.lookupLocation
)
} else {
// Normally, that should be unreachable, but instead of asserting that, we will report diagnostic
false
}
if (!canBeResolvedWithoutDeprecation) {
resolvedCall.trace.report(
Errors.DEPRECATED_ACCESS_BY_SHORT_NAME.on(resolvedCall.call.callElement, resolvedCall.resultingDescriptor)
)
}
}
}
}
}
@@ -16,9 +16,10 @@
package org.jetbrains.kotlin.resolve.lazy.descriptors
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
import org.jetbrains.kotlin.resolve.scopes.*
@@ -30,6 +31,7 @@ import java.util.*
class ClassResolutionScopesSupport(
private val classDescriptor: ClassDescriptor,
storageManager: StorageManager,
private val languageVersionSettings: LanguageVersionSettings,
private val getOuterScope: () -> LexicalScope
) {
private fun scopeWithGenerics(parent: LexicalScope): LexicalScopeImpl {
@@ -85,44 +87,50 @@ class ClassResolutionScopesSupport(
parent: LexicalScope,
ownerDescriptor: DeclarationDescriptor,
classDescriptor: ClassDescriptor,
withCompanionObject: Boolean = true
withCompanionObject: Boolean = true,
isDeprecated: Boolean = false
): LexicalScope {
val companionObjectDescriptor = classDescriptor.companionObjectDescriptor?.takeIf { withCompanionObject }
val staticScopes = ArrayList<MemberScope>(3)
// todo filter fake overrides
staticScopes.add(classDescriptor.staticScope)
staticScopes.add(classDescriptor.unsubstitutedInnerClassesScope)
staticScopes.addIfNotNull(companionObjectDescriptor?.getStaticScopeOfCompanionObject(classDescriptor))
val implicitReceiver: ReceiverParameterDescriptor?
val parentForNewScope = companionObjectDescriptor?.packScopesOfCompanionSupertypes(parent, ownerDescriptor) ?: parent
val parentForNewScope: LexicalScope
if (withCompanionObject) {
staticScopes.addIfNotNull(classDescriptor.companionObjectDescriptor?.unsubstitutedInnerClassesScope)
implicitReceiver = classDescriptor.companionObjectDescriptor?.thisAsReceiverParameter
parentForNewScope = classDescriptor.companionObjectDescriptor?.let {
it.getAllSuperclassesWithoutAny().asReversed().fold(parent) { scope, currentClass ->
createInheritanceScope(
parent = scope,
ownerDescriptor = ownerDescriptor,
classDescriptor = currentClass,
withCompanionObject = false
)
}
} ?: parent
} else {
implicitReceiver = null
parentForNewScope = parent
}
return LexicalChainedScope(
parentForNewScope, ownerDescriptor, false,
implicitReceiver,
LexicalScopeKind.CLASS_INHERITANCE,
memberScopes = staticScopes, isStaticScope = true
val lexicalChainedScope = LexicalChainedScope(
parentForNewScope, ownerDescriptor,
isOwnerDescriptorAccessibleByLabel = false,
implicitReceiver = companionObjectDescriptor?.thisAsReceiverParameter,
kind = LexicalScopeKind.CLASS_INHERITANCE,
memberScopes = staticScopes,
isStaticScope = true
)
return if (isDeprecated) DeprecatedLexicalScope(lexicalChainedScope) else lexicalChainedScope
}
private fun ClassDescriptor.getStaticScopeOfCompanionObject(companionOwner: ClassDescriptor): MemberScope? {
return when {
// We always see nesteds from our own companion
companionOwner == classDescriptor -> unsubstitutedInnerClassesScope
// We see nesteds from other companions in hierarchy only in legacy mode
languageVersionSettings.supportsFeature(LanguageFeature.ProhibitVisibilityOfNestedClassifiersFromSupertypesOfCompanion) -> null
else -> DeprecatedMemberScope(unsubstitutedInnerClassesScope)
}
}
private fun ClassDescriptor.packScopesOfCompanionSupertypes(
parent: LexicalScope,
ownerDescriptor: DeclarationDescriptor
): LexicalScope? {
if (languageVersionSettings.supportsFeature(LanguageFeature.ProhibitVisibilityOfNestedClassifiersFromSupertypesOfCompanion)) return null
return getAllSuperclassesWithoutAny().asReversed().fold(parent) { scope, currentClass ->
createInheritanceScope(scope, ownerDescriptor, currentClass, withCompanionObject = false, isDeprecated = true)
}
}
private fun <T : Any> StorageManager.createLazyValue(onRecursion: ((Boolean) -> T), compute: () -> T) =
@@ -13,6 +13,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.ReadOnly;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.config.LanguageFeature;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.ClassDescriptorBase;
@@ -221,7 +222,12 @@ public class LazyClassDescriptor extends ClassDescriptorBase implements ClassDes
return null;
}, null);
this.resolutionScopesSupport = new ClassResolutionScopesSupport(this, storageManager, this::getOuterScope);
this.resolutionScopesSupport = new ClassResolutionScopesSupport(
this,
storageManager,
c.getLanguageVersionSettings(),
this::getOuterScope
);
this.parameters = c.getStorageManager().createLazyValue(() -> {
KtClassLikeInfo classInfo = declarationProvider.getOwnerInfo();