aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala
blob: 1f1f371a6ec846eec494613096db8f38cac47a3e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package dotty.tools.dotc
package transform

import core._
import Flags._, Symbols._, Contexts._, Types._, Scopes._, Decorators._
import util.HashSet
import collection.mutable
import collection.immutable.BitSet
import typer.ErrorReporting.cyclicErrorMsg
import scala.annotation.tailrec

/** A module that can produce a kind of iterator (`Cursor`),
 *  which yields all pairs of overriding/overridden symbols
 *  that are visible in some baseclass, unless there's a parent class
 *  that already contains the same pairs.
 *
 *  Adapted from the 2.9 version of OverridingPairs. The 2.10 version is IMO
 *  way too unwieldy to be maintained.
 */
object OverridingPairs {

  /** The cursor class
   *  @param base   the base class that contains the overriding pairs
   */
  class Cursor(base: Symbol)(implicit ctx: Context) {

    private val self = base.thisType

    /** Symbols to exclude: Here these are constructors and private locals.
     *  But it may be refined in subclasses.
     */
    protected def exclude(sym: Symbol): Boolean = !sym.memberCanMatchInheritedSymbols

    /** The parents of base (may also be refined).
     */
    protected def parents: Array[Symbol] = base.info.parents.toArray map (_.typeSymbol)

    /** Does `sym1` match `sym2` so that it qualifies as overriding.
     *  Types always match. Term symbols match if their membertypes
     *  relative to <base>.this do
     */
    protected def matches(sym1: Symbol, sym2: Symbol): Boolean =
      sym1.isType || self.memberInfo(sym1).matches(self.memberInfo(sym2))

    /** The symbols that can take part in an overriding pair */
    private val decls = {
      val decls = newScope
      // fill `decls` with overriding shadowing overridden */
      def fillDecls(bcs: List[Symbol], deferred: Boolean): Unit = bcs match {
        case bc :: bcs1 =>
          fillDecls(bcs1, deferred)
          var e = bc.info.decls.lastEntry
          while (e != null) {
            if (e.sym.is(Deferred) == deferred && !exclude(e.sym))
              decls.enter(e.sym)
            e = e.prev
          }
        case nil =>
      }
      // first, deferred (this will need to change if we change lookup rules!
      fillDecls(base.info.baseClasses, deferred = true)
      // then, concrete.
      fillDecls(base.info.baseClasses, deferred = false)
      decls
    }

    private val subParents = {
      val subParents = new mutable.HashMap[Symbol, BitSet]
      for (bc <- base.info.baseClasses)
        subParents(bc) = BitSet(parents.indices.filter(parents(_).derivesFrom(bc)): _*)
      subParents
    }

    private def hasCommonParentAsSubclass(cls1: Symbol, cls2: Symbol): Boolean =
      (subParents(cls1) intersect subParents(cls2)).nonEmpty

    /** The scope entries that have already been visited as overridden
     *  (maybe excluded because of hasCommonParentAsSubclass).
     *  These will not appear as overriding
     */
    private val visited = new mutable.HashSet[Symbol]

    /** The current entry candidate for overriding
     */
    private var curEntry = decls.lastEntry

    /** The current entry candidate for overridden */
    private var nextEntry = curEntry

    /** The current candidate symbol for overriding */
    var overriding: Symbol = _

    /** If not null: The symbol overridden by overriding */
    var overridden: Symbol = _

    //@M: note that next is called once during object initialization
    final def hasNext: Boolean = nextEntry ne null

    /**  @post
     *     curEntry   = the next candidate that may override something else
     *     nextEntry  = curEntry
     *     overriding = curEntry.sym
     */
    private def nextOverriding(): Unit = {
      @tailrec def loop(): Unit =
        if (curEntry ne null) {
          overriding = curEntry.sym
          if (visited.contains(overriding)) {
            curEntry = curEntry.prev
            loop()
          }
        }
      loop()
      nextEntry = curEntry
    }

    /** @post
     *    hasNext    = there is another overriding pair
     *    overriding = overriding member of the pair, provided hasNext is true
     *    overridden = overridden member of the pair, provided hasNext is true
     */
    @tailrec final def next(): Unit =
      if (nextEntry ne null) {
        nextEntry = decls.lookupNextEntry(nextEntry)
        if (nextEntry ne null) {
          try {
            overridden = nextEntry.sym
            if (overriding.owner != overridden.owner && matches(overriding, overridden)) {
              visited += overridden
              if (!hasCommonParentAsSubclass(overriding.owner, overridden.owner)) return
            }
          }
          catch {
            case ex: CyclicReference =>
              // See neg/i1750a for an example where a cyclic error can arise.
              // The root cause in this example is an illegal "override" of an inner trait
              ctx.error(cyclicErrorMsg(ex), base.pos)
          }
        } else {
          curEntry = curEntry.prev
          nextOverriding()
        }
        next()
      }

    nextOverriding()
    next()
  }
}