Skip to content

Commit 459a9b3

Browse files
committed
fix #12634 - port sbt/zinc#979
Add sealedDescendants method to SymDenotation - recursive children of a symbol
1 parent a956774 commit 459a9b3

File tree

8 files changed

+105
-1
lines changed

8 files changed

+105
-1
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+64
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,70 @@ object SymDenotations {
16141614

16151615
annotations.collect { case Annotation.Child(child) => child }.reverse
16161616
end children
1617+
1618+
/** Recursively assemble all children of this symbol, Preserves order of insertion.
1619+
*/
1620+
final def sealedStrictDescendants(using Context): List[Symbol] =
1621+
1622+
@tailrec
1623+
def findLvlN(
1624+
explore: mutable.ArrayDeque[Symbol],
1625+
seen: util.HashSet[Symbol],
1626+
acc: mutable.ListBuffer[Symbol]
1627+
): List[Symbol] =
1628+
if explore.isEmpty then
1629+
acc.toList
1630+
else
1631+
val sym = explore.head
1632+
val explore1 = explore.dropInPlace(1)
1633+
val lvlN = sym.children
1634+
val notSeen = lvlN.filterConserve(!seen.contains(_))
1635+
if notSeen.isEmpty then
1636+
findLvlN(explore1, seen, acc)
1637+
else
1638+
findLvlN(explore1 ++= notSeen, {seen ++= notSeen; seen}, acc ++= notSeen)
1639+
end findLvlN
1640+
1641+
/** Scans through `explore` to see if there are recursive children.
1642+
* If a symbol in `explore` has children that are not contained in
1643+
* `lvl1`, fallback to `findLvlN`, or else return `lvl1`.
1644+
*
1645+
* Avoids allocating if there are no recursive children in `lvl1`.
1646+
* Allocates 1 HashSet if there are recursive children in `lvl1`, but we have seen them all.
1647+
* Unbounded if `lvl1` has recursive children that are unseen.
1648+
*/
1649+
@tailrec
1650+
def findLvl2(
1651+
lvl1: List[Symbol], explore: List[Symbol], seenOrNull: util.HashSet[Symbol] | Null
1652+
): List[Symbol] = explore match
1653+
case sym :: explore1 =>
1654+
val lvl2 = sym.children
1655+
if lvl2.isEmpty then // no children, scan rest of explore1
1656+
findLvl2(lvl1, explore1, seenOrNull)
1657+
else // check if we have seen the children before
1658+
val seen = // initialise the seen set if not already
1659+
if seenOrNull != null then seenOrNull
1660+
else util.HashSet.from(lvl1)
1661+
val notSeen = lvl2.filterConserve(!seen.contains(_))
1662+
if notSeen.isEmpty then // we found children, but we had already seen them, scan the rest of explore1
1663+
findLvl2(lvl1, explore1, seen)
1664+
else // found unseen recursive children, we should fallback to the loop
1665+
findLvlN(
1666+
explore = mutable.ArrayDeque.from(explore1).appendAll(notSeen),
1667+
seen = {seen ++= notSeen; seen},
1668+
acc = mutable.ListBuffer.from(lvl1).appendAll(notSeen)
1669+
)
1670+
case nil =>
1671+
lvl1
1672+
end findLvl2
1673+
1674+
val lvl1 = children
1675+
findLvl2(lvl1, lvl1, seenOrNull = null)
1676+
end sealedStrictDescendants
1677+
1678+
/** Same as `sealedStrictDescendants` but prepends this symbol as well.
1679+
*/
1680+
final def sealedDescendants(using Context): List[Symbol] = this.symbol :: sealedStrictDescendants
16171681
}
16181682

16191683
/** The contents of a class definition during a period

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
221221
val modifiers = apiModifiers(sym)
222222
val anns = apiAnnotations(sym).toArray
223223
val topLevel = sym.isTopLevelClass
224-
val childrenOfSealedClass = sym.children.sorted(classFirstSort).map(c =>
224+
val childrenOfSealedClass = sym.sealedDescendants.sorted(classFirstSort).map(c =>
225225
if (c.isClass)
226226
apiType(c.typeRef)
227227
else

compiler/src/dotty/tools/dotc/util/HashSet.scala

+5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ object HashSet:
77
*/
88
inline val DenseLimit = 8
99

10+
def from[T](xs: IterableOnce[T]): HashSet[T] =
11+
val set = new HashSet[T]()
12+
set ++= xs
13+
set
14+
1015
/** A hash set that allows some privileged protected access to its internals
1116
* @param initialCapacity Indicates the initial number of slots in the hash table.
1217
* The actual number of slots is always a power of 2, so the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
sealed trait Z
2+
sealed trait A extends Z
3+
class B extends A
4+
class C extends A
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
object App {
2+
def foo(z: Z) = z match {
3+
case _: B =>
4+
case _: C =>
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
sealed trait Z
2+
sealed trait A extends Z
3+
class B extends A
4+
class C extends A
5+
class D extends A
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import sbt._
2+
import Keys._
3+
4+
object DottyInjectedPlugin extends AutoPlugin {
5+
override def requires = plugins.JvmPlugin
6+
override def trigger = allRequirements
7+
8+
override val projectSettings = Seq(
9+
scalaVersion := sys.props("plugin.scalaVersion"),
10+
scalacOptions ++= Seq("-source:3.0-migration", "-Xfatal-warnings")
11+
)
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
> compile
2+
3+
# Introduce a new class C that also extends A
4+
$ copy-file changes/A.scala A.scala
5+
6+
# App.scala needs recompiling because the pattern match in it
7+
# is no longer exhaustive, which emits a warning
8+
-> compile

0 commit comments

Comments
 (0)