Skip to content

Change is volatile #1051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Feb 9, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
633e2eb
No volatile check needed for strict vals.
odersky Jan 29, 2016
41f0567
Use isRealizable to identify stable prefixes
odersky Jan 29, 2016
1cc4d90
Move scala2Mode test from isStable to isRealizable
odersky Jan 30, 2016
1b7745e
Also consider type aliases when checking for realizability
odersky Jan 30, 2016
7ccd02c
Realizability refactoring
odersky Jan 30, 2016
322bcfe
Move realizability logic from Types to TypeOps.
odersky Jan 30, 2016
44c14b3
Cleanup of isEffectivelyFinal
odersky Jan 31, 2016
9a6f82b
Reorganize tests to account for new typing of projection
odersky Jan 31, 2016
5fd2028
Enforce rule that laziness is preserved when overriding.
odersky Jan 31, 2016
0480cb2
Only final lazy vals can be paths.
odersky Jan 31, 2016
e87dee2
Fix path error disgnastics
odersky Jan 31, 2016
a0fb068
Handle imports in path checks.
odersky Jan 31, 2016
3637e08
Fix isRealizableTest
odersky Jan 31, 2016
defba2a
Check that non-abstract classes have realizable bounds.
odersky Jan 31, 2016
1441622
Drop lines from test
odersky Jan 31, 2016
f44a1ed
Remove isVolatile and DNF methods
odersky Feb 1, 2016
20fc6bd
Consider by name parameters as lazily initialized
odersky Feb 1, 2016
d34256c
Handle paths of length > 1 for realizability checking
odersky Feb 1, 2016
ec4a3a0
Big realizability refactoring
odersky Feb 2, 2016
eaa1578
Perform typer realizability checks only during Typer.
odersky Feb 2, 2016
187c241
New test files from SI 7278.
odersky Feb 2, 2016
f6a5802
By-name parameters are not stable values.
odersky Feb 2, 2016
1511cb4
Fix commpilation error
odersky Feb 2, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/dotty/tools/backend/jvm/DottyBackendInterface.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{

val hashMethodSym: Symbol = NoSymbol // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase
val externalEqualsNumNum: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum)
lazy val externalEqualsNumChar: Symbol = ??? // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private
val externalEqualsNumChar: Symbol = NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private
val externalEqualsNumObject: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject)
val externalEquals: Symbol = defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol
val MaxFunctionArity: Int = Definitions.MaxFunctionArity
Expand Down
146 changes: 146 additions & 0 deletions src/dotty/tools/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package dotty.tools
package dotc
package core

import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
import SymDenotations._, Denotations.SingleDenotation
import config.Printers._
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)

/** Is this type a path with some part that is initialized on use?
*/
private def isLateInitialized(tp: Type): Boolean = tp.dealias match {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is never used.

case tp: TermRef =>
isLateInitialized(tp.symbol) || isLateInitialized(tp.prefix)
case _: SingletonType | NoPrefix =>
false
case tp: TypeRef =>
true
case tp: TypeProxy =>
isLateInitialized(tp.underlying)
case tp: AndOrType =>
isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2)
case _ =>
true
}

/** 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 `tp` all of `tp`'s non-struct fields have realizable types,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the first tp should not be there: "Realizable if all of ..."

* 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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you bypass the realizability checks if a field is private or mutable or lazy?

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
}
}
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ object Contexts {
}

@sharable object NoContext extends Context {
lazy val base = unsupported("base")
val base = null
override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this)
}

Expand Down
2 changes: 1 addition & 1 deletion src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ object Flags {
*/
final val Abstract = commonFlag(23, "abstract")

/** Method is assumed to be stable */
/** Lazy val or method is known or assumed to be stable and realizable */
final val Stable = termFlag(24, "<stable>")

/** A case parameter accessor */
Expand Down
23 changes: 7 additions & 16 deletions src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import scala.reflect.io.AbstractFile
import Decorators.SymbolIteratorDecorator
import ast._
import annotation.tailrec
import CheckRealizable._
import typer.Mode
import util.SimpleMap
import util.Stats
Expand Down Expand Up @@ -519,15 +520,8 @@ object SymDenotations {
)

/** Is this a denotation of a stable term (or an arbitrary type)? */
final def isStable(implicit ctx: Context) = {
val isUnstable =
(this is UnstableValue) ||
ctx.isVolatile(info) && !hasAnnotation(defn.UncheckedStableAnnot)
(this is Stable) || isType || {
if (isUnstable) false
else { setFlag(Stable); true }
}
}
final def isStable(implicit ctx: Context) =
isType || is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType])

/** Is this a "real" method? A real method is a method which is:
* - not an accessor
Expand Down Expand Up @@ -816,14 +810,11 @@ object SymDenotations {
enclClass(symbol, false)
}

final def isEffectivelyFinal(implicit ctx: Context): Boolean = {
(this.flags is Flags.PrivateOrFinal) || (!this.owner.isClass) ||
((this.owner.flags is (Flags.ModuleOrFinal)) && (!this.flags.is(Flags.MutableOrLazy))) ||
(this.owner.isAnonymousClass)
}
/** A symbol is effectively final if it cannot be overridden in a subclass */
final def isEffectivelyFinal(implicit ctx: Context): Boolean =
is(PrivateOrFinal) || !owner.isClass || owner.is(ModuleOrFinal) || owner.isAnonymousClass

/** The class containing this denotation which has the given effective name.
*/
/** The class containing this denotation which has the given effective name. */
final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = {
val cls = enclosingClass
if (cls.effectiveName == name || !cls.exists) cls else cls.owner.enclosingClassNamed(name)
Expand Down
94 changes: 1 addition & 93 deletions src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package dotc
package core

import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
import SymDenotations._, Denotations.Denotation
import SymDenotations._, Denotations.SingleDenotation
import config.Printers._
import util.Positions._
import Decorators._
Expand Down Expand Up @@ -341,96 +341,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
}
}

/** A type is volatile if its DNF contains an alternative of the form
* {P1, ..., Pn}, {N1, ..., Nk}, where the Pi are parent typerefs and the
* Nj are refinement names, and one the 4 following conditions is met:
*
* 1. At least two of the parents Pi are abstract types.
* 2. One of the parents Pi is an abstract type, and one other type Pj,
* j != i has an abstract member which has the same name as an
* abstract member of the whole type.
* 3. One of the parents Pi is an abstract type, and one of the refinement
* names Nj refers to an abstract member of the whole type.
* 4. One of the parents Pi is an an alias type with a volatile alias
* or an abstract type with a volatile upper bound.
*
* Lazy values are not allowed to have volatile type, as otherwise
* unsoundness can result.
*/
final def isVolatile(tp: Type): Boolean = {

/** Pre-filter to avoid expensive DNF computation
* If needsChecking returns false it is guaranteed that
* DNF does not contain intersections, or abstract types with upper
* bounds that themselves need checking.
*/
def needsChecking(tp: Type, isPart: Boolean): Boolean = tp match {
case tp: TypeRef =>
tp.info match {
case TypeAlias(alias) =>
needsChecking(alias, isPart)
case TypeBounds(lo, hi) =>
isPart || tp.controlled(isVolatile(hi))
case _ => false
}
case tp: RefinedType =>
needsChecking(tp.parent, true)
case tp: TypeProxy =>
needsChecking(tp.underlying, isPart)
case tp: AndType =>
true
case tp: OrType =>
isPart || needsChecking(tp.tp1, isPart) && needsChecking(tp.tp2, isPart)
case _ =>
false
}

needsChecking(tp, false) && {
DNF(tp) forall { case (parents, refinedNames) =>
val absParents = parents filter (_.symbol is Deferred)
absParents.nonEmpty && {
absParents.lengthCompare(2) >= 0 || {
val ap = absParents.head
((parents exists (p =>
(p ne ap)
|| p.memberNames(abstractTypeNameFilter, tp).nonEmpty
|| p.memberNames(abstractTermNameFilter, tp).nonEmpty))
|| (refinedNames & tp.memberNames(abstractTypeNameFilter, tp)).nonEmpty
|| (refinedNames & tp.memberNames(abstractTermNameFilter, tp)).nonEmpty
|| isVolatile(ap))
}
}
}
}
}

/** The disjunctive normal form of this type.
* This collects a set of alternatives, each alternative consisting
* of a set of typerefs and a set of refinement names. Both sets are represented
* as lists, to obtain a deterministic order. Collected are
* all type refs reachable by following aliases and type proxies, and
* collecting the elements of conjunctions (&) and disjunctions (|).
* The set of refinement names in each alternative
* are the set of names in refinement types encountered during the collection.
*/
final def DNF(tp: Type): List[(List[TypeRef], Set[Name])] = ctx.traceIndented(s"DNF($this)", checks) {
tp.dealias match {
case tp: TypeRef =>
(tp :: Nil, Set[Name]()) :: Nil
case RefinedType(parent, name) =>
for ((ps, rs) <- DNF(parent)) yield (ps, rs + name)
case tp: TypeProxy =>
DNF(tp.underlying)
case AndType(l, r) =>
for ((lps, lrs) <- DNF(l); (rps, rrs) <- DNF(r))
yield (lps | rps, lrs | rrs)
case OrType(l, r) =>
DNF(l) | DNF(r)
case tp =>
TypeOps.emptyDNF
}
}

private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = {
val lazyInfo = new LazyType { // needed so we do not force `formal`.
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
Expand Down Expand Up @@ -644,10 +554,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
if (scala2Mode) migrationWarning(msg, pos)
scala2Mode
}

}

object TypeOps {
val emptyDNF = (Nil, Set[Name]()) :: Nil
@sharable var track = false // !!!DEBUG
}
15 changes: 15 additions & 0 deletions src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,12 @@ object Types {
(name, buf) => buf += member(name).asSingleDenotation)
}

/** The set of abstract type members of this type. */
final def nonClassTypeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("nonClassTypeMembers") {
memberDenots(nonClassTypeNameFilter,
(name, buf) => buf += member(name).asSingleDenotation)
}

/** The set of type members of this type */
final def typeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("typeMembers") {
memberDenots(typeNameFilter,
Expand Down Expand Up @@ -3368,6 +3374,15 @@ object Types {
}
}

/** A filter for names of abstract types of a given type */
object nonClassTypeNameFilter extends NameFilter {
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
name.isTypeName && {
val mbr = pre.member(name)
mbr.symbol.isType && !mbr.symbol.isClass
}
}

/** A filter for names of deferred term definitions of a given type */
object abstractTermNameFilter extends NameFilter {
def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
Expand Down
4 changes: 2 additions & 2 deletions src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
case tree: TypeTree => tree
case _ =>
if (tree.isType) {
Checking.boundsChecker.traverse(tree)
Checking.typeChecker.traverse(tree)
TypeTree(tree.tpe).withPos(tree.pos)
}
else tree.tpe.widenTermRefExpr match {
Expand Down Expand Up @@ -180,7 +180,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
val tree1 =
if (sym.isClass) tree
else {
Checking.boundsChecker.traverse(tree.rhs)
Checking.typeChecker.traverse(tree.rhs)
cpy.TypeDef(tree)(rhs = TypeTree(tree.symbol.info))
}
super.transform(tree1)
Expand Down
Loading