aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
blob: 78ec685fc114d4df9a2fbad35194bbebdb510abd (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
package dotty.tools
package dotc
package core

import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
import SymDenotations._, Denotations.SingleDenotation
import util.Positions._
import Decorators._
import StdNames._
import Annotations._
import collection.mutable
import ast.tpd._

/** Realizability status */
object CheckRealizable {

  abstract class Realizability(val msg: String) {
    def andAlso(other: => Realizability) =
      if (this == Realizable) other else this
    def mapError(f: Realizability => Realizability) =
      if (this == Realizable) this else f(this)
  }

  object Realizable extends Realizability("")

  object NotConcrete extends Realizability(" is not a concrete type")

  object NotStable extends Realizability(" is not a stable reference")

  class NotFinal(sym: Symbol)(implicit ctx: Context)
  extends Realizability(i" refers to nonfinal $sym")

  class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context)
  extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}")

  class HasProblemField(fld: SingleDenotation, problem: Realizability)(implicit ctx: Context)
  extends Realizability(i" has a member $fld which is not a legal path\n since ${fld.symbol.name}: ${fld.info}${problem.msg}")

  class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context)
  extends Realizability(i"s underlying type ${tp}${problem.msg}") {
    assert(problem != Realizable)
  }

  def realizability(tp: Type)(implicit ctx: Context) =
    new CheckRealizable().realizability(tp)

  def boundsRealizability(tp: Type)(implicit ctx: Context) =
    new CheckRealizable().boundsRealizability(tp)
}

/** Compute realizability status */
class CheckRealizable(implicit ctx: Context) {
  import CheckRealizable._

  /** A set of all fields that have already been checked. Used
   *  to avoid infinite recursions when analyzing recursive types.
   */
  private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]()

  /** Is symbol's definitition a lazy val?
   *  (note we exclude modules here, because their realizability is ensured separately)
   */
  private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module)

  /** The realizability status of given type `tp`*/
  def realizability(tp: Type): Realizability = tp.dealias match {
    case tp: TermRef =>
      val sym = tp.symbol
      if (sym.is(Stable)) realizability(tp.prefix)
      else {
        val r =
          if (!sym.isStable) NotStable
          else if (!isLateInitialized(sym)) realizability(tp.prefix)
          else if (!sym.isEffectivelyFinal) new NotFinal(sym)
          else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r))
        if (r == Realizable) sym.setFlag(Stable)
        r
      }
    case _: SingletonType | NoPrefix =>
      Realizable
    case tp =>
      def isConcrete(tp: Type): Boolean = tp.dealias match {
        case tp: TypeRef => tp.symbol.isClass
        case tp: TypeProxy => isConcrete(tp.underlying)
        case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
        case _ => false
      }
      if (!isConcrete(tp)) NotConcrete
      else boundsRealizability(tp).andAlso(memberRealizability(tp))
  }

  /** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance
   *  pointing to a bad bounds member otherwise.
   */
  private def boundsRealizability(tp: Type) = {
    def hasBadBounds(mbr: SingleDenotation) = {
      val bounds = mbr.info.bounds
      !(bounds.lo <:< bounds.hi)
    }
    tp.nonClassTypeMembers.find(hasBadBounds) match {
      case Some(mbr) => new HasProblemBounds(mbr)
      case _ => Realizable
    }
  }

  /** `Realizable` if all of `tp`'s non-struct fields have realizable types,
   *  a `HasProblemField` instance pointing to a bad field otherwise.
   */
  private def memberRealizability(tp: Type) = {
    def checkField(sofar: Realizability, fld: SingleDenotation): Realizability =
      sofar andAlso {
        if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy))
          // if field is private it cannot be part of a visible path
          // if field is mutable it cannot be part of a path
          // if field is lazy it does not need to be initialized when the owning object is
          // so in all cases the field does not influence realizability of the enclosing object.
          Realizable
        else {
          checkedFields += fld.symbol
          realizability(fld.info).mapError(r => new HasProblemField(fld, r))
        }
      }
    if (ctx.settings.strict.value)
      // check fields only under strict mode for now.
      // Reason: An embedded field could well be nullable, which means it
      // should not be part of a path and need not be checked; but we cannot recognize
      // this situation until we have a typesystem that tracks nullability.
      ((Realizable: Realizability) /: tp.fields)(checkField)
    else
      Realizable
  }
}