summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/dependencies/Changes.scala
blob: 80a068dcdf40b585846f655e31cdfd6ea7ea1ac8 (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package scala.tools.nsc
package dependencies

import symtab.Flags

import collection._

/** A component that describes the possible changes between successive
 *  compilations of a class.
 */
abstract class Changes {

  /** A compiler instance used to compile files on demand. */
  val compiler: Global

  import compiler._
  import symtab.Flags._

  abstract class Change

  /** Are the new modifiers more restrictive than the old ones? */
  private def moreRestrictive(from: Long, to: Long): Boolean =
    ((((to & PRIVATE) != 0L) && (from & PRIVATE) == 0L)
     || (((to & PROTECTED) != 0L) && (from & PROTECTED) == 0L))

  /** An entity in source code, either a class or a member definition.
   *  Name is fully-qualified.
   */
  abstract class Entity
  case class Class(name: String) extends Entity
  case class Definition(name: String) extends Entity

  case class Added(e: Entity) extends Change
  case class Removed(e: Entity) extends Change
  case class Changed(e: Entity)(implicit val reason: String) extends Change {
    override def toString = "Changed(" + e + ")[" + reason + "]"
  }

  private def sameSymbol(sym1: Symbol, sym2: Symbol): Boolean =
    sym1.fullNameString == sym2.fullNameString

  private def sameType(tp1: Type, tp2: Type) = {
    def typeOf(tp: Type): String = tp.toString + "[" + tp.getClass + "]"
    val res = sameType0(tp1, tp2)
//    if (!res) println("\t different types: " + typeOf(tp1) + " : " + typeOf(tp2))
    res
  }

  private def sameType0(tp1: Type, tp2: Type): Boolean = ((tp1, tp2) match {
    /*case (ErrorType, _) => false
    case (WildcardType, _) => false
    case (_, ErrorType) => false
    case (_, WildcardType) => false
    */
    case (NoType, _) => false
    case (NoPrefix, NoPrefix) => true
    case (_, NoType) => false
    case (_, NoPrefix) => false

    case (ThisType(sym1), ThisType(sym2))
      if sameSymbol(sym1, sym2) => true

    case (SingleType(pre1, sym1), SingleType(pre2, sym2))
      if sameType(pre1, pre2) && sameSymbol(sym1, sym2) => true
    case (ConstantType(value1), ConstantType(value2)) =>
      value1 == value2
    case (TypeRef(pre1, sym1, args1), TypeRef(pre2, sym2, args2)) =>
      sameType(pre1, pre2) && sameSymbol(sym1, sym2) &&
      ((tp1.isHigherKinded && tp2.isHigherKinded && tp1.normalize =:= tp2.normalize) ||
         sameTypes(args1, args2))
         // @M! normalize reduces higher-kinded case to PolyType's

    case (RefinedType(parents1, ref1), RefinedType(parents2, ref2)) =>
      def isSubScope(s1: Scope, s2: Scope): Boolean = s2.toList.forall {
        sym2 =>
          var e1 = s1.lookupEntry(sym2.name)
          (e1 ne null) && {
            var isEqual = false
            while (!isEqual && (e1 ne null)) {
              isEqual = sameType(e1.sym.info, sym2.info)
              e1 = s1.lookupNextEntry(e1)
            }
            isEqual
          }
      }
      sameTypes(parents1, parents2) && isSubScope(ref1, ref2) && isSubScope(ref2, ref1)

    case (MethodType(params1, res1), MethodType(params2, res2)) =>
      // new dependent types: probably fix this, use substSym as done for PolyType
      (sameTypes(tp1.paramTypes, tp2.paramTypes) &&
       sameType(res1, res2) &&
       tp1.isInstanceOf[ImplicitMethodType] == tp2.isInstanceOf[ImplicitMethodType])

    case (PolyType(tparams1, res1), PolyType(tparams2, res2)) =>
      (tparams1.length == tparams2.length &&
       List.forall2(tparams1, tparams2)
       ((p1, p2) => sameType(p1.info, p2.info)) &&
       sameType(res1, res2))
    case (ExistentialType(tparams1, res1), ExistentialType(tparams2, res2)) =>
      (tparams1.length == tparams2.length &&
       List.forall2(tparams1, tparams2)
       ((p1, p2) => sameType(p1.info, p2.info)) &&
       sameType(res1, res2))
    case (TypeBounds(lo1, hi1), TypeBounds(lo2, hi2)) =>
        sameType(lo1, lo2) && sameType(hi1, hi2)
    case (BoundedWildcardType(bounds), _) =>
      bounds containsType tp2
    case (_, BoundedWildcardType(bounds)) =>
      bounds containsType tp1

    case (AnnotatedType(_,_,_), _) =>
      annotationsConform(tp1, tp2) && annotationsConform(tp2, tp1) && tp1.withoutAnnotations =:= tp2.withoutAnnotations
    case (_, AnnotatedType(_,_,_)) =>
      annotationsConform(tp1, tp2) && annotationsConform(tp2, tp1) && tp1.withoutAnnotations =:= tp2.withoutAnnotations

    case (_: SingletonType, _: SingletonType) =>
      var origin1 = tp1
      while (origin1.underlying.isInstanceOf[SingletonType]) {
        assert(origin1 ne origin1.underlying, origin1)
        origin1 = origin1.underlying
      }
      var origin2 = tp2
      while (origin2.underlying.isInstanceOf[SingletonType]) {
        assert(origin2 ne origin2.underlying, origin2)
        origin2 = origin2.underlying
      }
      ((origin1 ne tp1) || (origin2 ne tp2)) && sameType(origin1, origin2)
    case _ =>
      false
    }) || {
      val tp1n = normalizePlus(tp1)
      val tp2n = normalizePlus(tp2)
      ((tp1n ne tp1) || (tp2n ne tp2)) && sameType(tp1n, tp2n)
    }

  def sameTypes(tps1: List[Type], tps2: List[Type]): Boolean =
    (tps1.length == tps2.length
     && List.forall2(tps1, tps2)(sameType))

  /** Return the list of changes between 'from' and 'to'.
   */
  def changeSet(from: Symbol, to: Symbol): List[Change] = {
    implicit val defaultReason = "types"
//     println("changeSet " + from + "(" + from.info + ")"
//             + " vs " + to + "(" + to.info + ")")
    val cs = new mutable.ListBuffer[Change]

    if ((from.info.parents zip to.info.parents) exists { case (t1, t2) => !sameType(t1, t2) })
      cs += Changed(toEntity(from))(from.info.parents.zip(to.info.parents).toString)
    if (from.typeParams != to.typeParams)
      cs += Changed(toEntity(from))(" tparams: " + from.typeParams.zip(to.typeParams))

    // new members not yet visited
    val newMembers = mutable.HashSet[Symbol]()
    newMembers ++= to.info.decls.iterator

    for (o <- from.info.decls.iterator;
         val n = to.info.decl(o.name)) {
      newMembers -= n

      if (!o.hasFlag(Flags.PRIVATE | Flags.LOCAL | Flags.LIFTED)) {
        if (o.isClass)
          cs ++= changeSet(o, n)
        else if (n == NoSymbol)
          cs += Removed(toEntity(o))
        else {
          val newSym = n.suchThat(ov => sameType(ov.tpe, o.tpe))
          if (newSym == NoSymbol || moreRestrictive(o.flags, newSym.flags)) {
            cs += Changed(toEntity(o))(n + " changed from " + o.tpe + " to " + n.tpe + " flags: " + Flags.flagsToString(o.flags))
          } else
            newMembers -= newSym
        }
      }
    }
    cs ++= (newMembers map (Added compose toEntity))

    cs.toList
  }
  def removeChangeSet(sym: Symbol): Change = Removed(toEntity(sym))

  private def toEntity(sym: Symbol): Entity =
    if (sym.isClass) Class(sym.fullNameString)
    else Definition(sym.fullNameString)
}