Report cyclic scopes properly

This commit introduces proper handling of recursion in scopes, which
could occur when some of companion object supertypes are members of
that companion owner:

```
class Container {
  open class Base
  companion object : Base()
}
```

To resolve `Base`, we have to build member scope for `Container`.
In the member scope of `Container`, we see all classifiers from
companion and his supertypes
So, we have to resolve companion objects supertype, which happens to be
`Base` again - therefore, we encounter recursion here.

Previously, we created `ThrowingLexicalScope` for such recursive calls,
but didn't checked for loop explicitly, which lead to a wide variety of
bugs (see https://jetbrains.quip.com/dc5aABhZoaQY and KT-10532).

To report such cyclic declarations properly, we first change
`ThrowingLexicalScope` to `ErrorLexicalScope` -- the main difference is
that latter doesn't throws ISE when someone tries to resolve type in it,
allowing us to report error instead of crashing with exception.

Then, we add additional fake edge in supertypes graph (from
host-class to companion object) which allows us to piggyback on existing
supertypes loops detection mechanism, and report such cycles for user.
This commit is contained in:
Dmitry Savvinov
2017-11-29 16:44:57 +03:00
parent 33f9576dd1
commit 874267b79d
26 changed files with 741 additions and 45 deletions
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.storage.StorageManager;
import org.jetbrains.kotlin.utils.SmartList;
import java.util.Collection;
import java.util.Collections;
@@ -124,17 +125,39 @@ public abstract class AbstractClassTypeConstructor extends AbstractTypeConstruct
@NotNull
@Override
protected Collection<KotlinType> getAdditionalNeighboursInSupertypeGraph() {
protected Collection<KotlinType> getAdditionalNeighboursInSupertypeGraph(boolean useCompanions) {
DeclarationDescriptor containingDeclaration = getDeclarationDescriptor().getContainingDeclaration();
if (!(containingDeclaration instanceof ClassDescriptor)) {
return Collections.emptyList();
}
Collection<KotlinType> additionalNeighbours = new SmartList<KotlinType>();
// We suppose that there is an edge from C to A in graph when disconnecting loops in supertypes,
// because such cyclic declarations should be prohibited (see p.10.2.1 of Kotlin spec)
// class A : B {
// static class C {}
// }
// class B : A.C {}
DeclarationDescriptor containingDeclaration = getDeclarationDescriptor().getContainingDeclaration();
if (containingDeclaration instanceof ClassDescriptor) {
return Collections.<KotlinType>singleton(((ClassDescriptor) containingDeclaration).getDefaultType());
ClassDescriptor containingClassDescriptor = (ClassDescriptor) containingDeclaration;
additionalNeighbours.add(containingClassDescriptor.getDefaultType());
// Also we add edge from host-class to companion object. Together with previous edges
// (from nesteds to containing class), they can create visibility loops like in the
// following example:
//
// class ContainingClass {
// open class Nested {} // to create scope for resolving Nested, we have to resolve CO header
// companion object : Nested() {} // to resolve CO header, we have to resolve Nested
// }
//
// Relates to KT-21515
ClassDescriptor companion = containingClassDescriptor.getCompanionObjectDescriptor();
if (useCompanions && companion != null) {
additionalNeighbours.add(companion.getDefaultType());
}
return Collections.emptyList();
return additionalNeighbours;
}
}
@@ -41,27 +41,41 @@ abstract class AbstractTypeConstructor(storageManager: StorageManager) : TypeCon
var resultWithoutCycles =
supertypeLoopChecker.findLoopsInSupertypesAndDisconnect(
this, supertypes.allSupertypes,
{ it.computeNeighbours() },
{ reportSupertypeLoopError(it) })
{ it.computeNeighbours(useCompanions = false) },
{ reportSupertypeLoopError(it) }
)
if (resultWithoutCycles.isEmpty()) {
resultWithoutCycles = defaultSupertypeIfEmpty()?.let { listOf(it) }.orEmpty()
}
// We also check if there are a loop with additional edges going from owner of companion to
// the companion itself.
// Note that we use already disconnected types to not report two diagnostics on cyclic supertypes
supertypeLoopChecker.findLoopsInSupertypesAndDisconnect(
this, resultWithoutCycles,
{ it.computeNeighbours(useCompanions = true) },
{ reportScopesLoopError(it) }
)
supertypes.supertypesWithoutCycles = (resultWithoutCycles as? List<KotlinType>) ?: resultWithoutCycles.toList()
})
private fun TypeConstructor.computeNeighbours(): Collection<KotlinType> =
private fun TypeConstructor.computeNeighbours(useCompanions: Boolean): Collection<KotlinType> =
(this as? AbstractTypeConstructor)?.let {
abstractClassifierDescriptor ->
abstractClassifierDescriptor.supertypes().allSupertypes +
abstractClassifierDescriptor.getAdditionalNeighboursInSupertypeGraph()
abstractClassifierDescriptor.getAdditionalNeighboursInSupertypeGraph(useCompanions)
} ?: supertypes
protected abstract fun computeSupertypes(): Collection<KotlinType>
protected abstract val supertypeLoopChecker: SupertypeLoopChecker
protected open fun reportSupertypeLoopError(type: KotlinType) {}
protected open fun getAdditionalNeighboursInSupertypeGraph(): Collection<KotlinType> = emptyList()
// TODO: overload in AbstractTypeParameterDescriptor?
protected open fun reportScopesLoopError(type: KotlinType) {}
protected open fun getAdditionalNeighboursInSupertypeGraph(useCompanions: Boolean): Collection<KotlinType> = emptyList()
protected open fun defaultSupertypeIfEmpty(): KotlinType? = null
}