Skip to content

Commit e5413b1

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

File tree

8 files changed

+104
-1
lines changed

8 files changed

+104
-1
lines changed

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,69 @@ 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+
def findLvl2(
1650+
lvl1: List[Symbol], explore: List[Symbol], seenOrNull: util.HashSet[Symbol] | Null
1651+
): List[Symbol] = explore match
1652+
case sym :: explore1 =>
1653+
val lvl2 = sym.children
1654+
if lvl2.isEmpty then // no children, scan rest of explore1
1655+
findLvl2(lvl1, explore1, seenOrNull)
1656+
else // check if we have seen the children before
1657+
val seen = // initialise the seen set if not already
1658+
if seenOrNull != null then seenOrNull
1659+
else util.HashSet.from(lvl1)
1660+
val notSeen = lvl2.filterConserve(!seen.contains(_))
1661+
if notSeen.isEmpty then // we found children, but we had already seen them, scan the rest of explore1
1662+
findLvl2(lvl1, explore1, seen)
1663+
else // found unseen recursive children, we should fallback to the loop
1664+
findLvlN(
1665+
explore = mutable.ArrayDeque.from(explore1).appendAll(notSeen),
1666+
seen = {seen ++= notSeen; seen},
1667+
acc = mutable.ListBuffer.from(lvl1).appendAll(notSeen)
1668+
)
1669+
case nil =>
1670+
lvl1
1671+
end findLvl2
1672+
1673+
val lvl1 = children
1674+
findLvl2(lvl1, lvl1, seenOrNull = null)
1675+
end sealedStrictDescendants
1676+
1677+
/** Same as `sealedStrictDescendants` but prepends this symbol as well.
1678+
*/
1679+
final def sealedDescendants(using Context): List[Symbol] = this.symbol :: sealedStrictDescendants
16171680
}
16181681

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

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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 5 additions & 0 deletions
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
Lines changed: 4 additions & 0 deletions
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
Lines changed: 6 additions & 0 deletions
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+
}
Lines changed: 5 additions & 0 deletions
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
Lines changed: 12 additions & 0 deletions
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+
}
Lines changed: 8 additions & 0 deletions
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)