aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
blob: 78ec685fc114d4df9a2fbad35194bbebdb510abd (plain) (tree)
1
2
3
4
5
6





                                                                 




















































                                                                                                                                     




                                                                                       







                                                                 
                                                                    































                                                                                        
                                                                            





                                                                                          



                                                                                                







                                                                            

                                                                                      





                                                                           
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
  }
}