Skip to content

Commit 185333e

Browse files
committed
Big realizability refactoring
Move logic from TypeOps to new file CheckRealizable.scala. Also check realizable fields under strict mode. Check at phase PostTyper rather than Typer to avoid cycles. New tests for imports and deep paths.
1 parent 085e237 commit 185333e

File tree

12 files changed

+300
-168
lines changed

12 files changed

+300
-168
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
5+
import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
6+
import SymDenotations._, Denotations.SingleDenotation
7+
import config.Printers._
8+
import util.Positions._
9+
import Decorators._
10+
import StdNames._
11+
import Annotations._
12+
import collection.mutable
13+
import ast.tpd._
14+
15+
/** Realizability status */
16+
object CheckRealizable {
17+
18+
abstract class Realizability(val msg: String) {
19+
def andAlso(other: => Realizability) =
20+
if (this == Realizable) other else this
21+
def mapError(f: Realizability => Realizability) =
22+
if (this == Realizable) this else f(this)
23+
}
24+
25+
object Realizable extends Realizability("")
26+
27+
object NotConcrete extends Realizability(" is not a concrete type")
28+
29+
object NotStable extends Realizability(" is not a stable reference")
30+
31+
class NotFinal(sym: Symbol)(implicit ctx: Context)
32+
extends Realizability(i" refers to nonfinal $sym")
33+
34+
class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context)
35+
extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}")
36+
37+
class HasProblemField(fld: SingleDenotation, problem: Realizability)(implicit ctx: Context)
38+
extends Realizability(i" has a member $fld which is not a legal path\n since ${fld.symbol.name}: ${fld.info}${problem.msg}")
39+
40+
class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context)
41+
extends Realizability(i"s underlying type ${tp}${problem.msg}") {
42+
assert(problem != Realizable)
43+
}
44+
45+
def realizability(tp: Type)(implicit ctx: Context) =
46+
new CheckRealizable().realizability(tp)
47+
48+
def boundsRealizability(tp: Type)(implicit ctx: Context) =
49+
new CheckRealizable().boundsRealizability(tp)
50+
}
51+
52+
/** Compute realizability status */
53+
class CheckRealizable(implicit ctx: Context) {
54+
import CheckRealizable._
55+
56+
/** A set of all fields that have already been checked. Used
57+
* to avoid infinite recursions when analyzing recursive types.
58+
*/
59+
private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]()
60+
61+
/** Is this type a path with some part that is initialized on use? */
62+
private def isLateInitialized(tp: Type): Boolean = tp.dealias match {
63+
case tp: TermRef =>
64+
tp.symbol.isLateInitialized || isLateInitialized(tp.prefix)
65+
case _: SingletonType | NoPrefix =>
66+
false
67+
case tp: TypeRef =>
68+
true
69+
case tp: TypeProxy =>
70+
isLateInitialized(tp.underlying)
71+
case tp: AndOrType =>
72+
isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2)
73+
case _ =>
74+
true
75+
}
76+
77+
/** The realizability status of given type `tp`*/
78+
def realizability(tp: Type): Realizability = tp.dealias match {
79+
case tp: TermRef =>
80+
val sym = tp.symbol
81+
if (sym.is(Stable)) realizability(tp.prefix)
82+
else {
83+
val r =
84+
if (!sym.isStable) NotStable
85+
else if (!sym.isLateInitialized) realizability(tp.prefix)
86+
else if (!sym.isEffectivelyFinal) new NotFinal(sym)
87+
else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r))
88+
if (r == Realizable) sym.setFlag(Stable)
89+
r
90+
}
91+
case _: SingletonType | NoPrefix =>
92+
Realizable
93+
case tp =>
94+
def isConcrete(tp: Type): Boolean = tp.dealias match {
95+
case tp: TypeRef => tp.symbol.isClass
96+
case tp: TypeProxy => isConcrete(tp.underlying)
97+
case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
98+
case _ => false
99+
}
100+
if (!isConcrete(tp)) NotConcrete
101+
else boundsRealizability(tp).andAlso(memberRealizability(tp))
102+
}
103+
104+
/** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance
105+
* pointing to a bad bounds member otherwise.
106+
*/
107+
private def boundsRealizability(tp: Type) = {
108+
def hasBadBounds(mbr: SingleDenotation) = {
109+
val bounds = mbr.info.bounds
110+
!(bounds.lo <:< bounds.hi)
111+
}
112+
tp.nonClassTypeMembers.find(hasBadBounds) match {
113+
case Some(mbr) => new HasProblemBounds(mbr)
114+
case _ => Realizable
115+
}
116+
}
117+
118+
/** `Realizable` if `tp` all of `tp`'s non-struct fields have realizable types,
119+
* a `HasProblemField` instance pointing to a bad field otherwise.
120+
*/
121+
private def memberRealizability(tp: Type) = {
122+
def checkField(sofar: Realizability, fld: SingleDenotation): Realizability =
123+
sofar andAlso {
124+
if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy))
125+
Realizable
126+
else {
127+
checkedFields += fld.symbol
128+
realizability(fld.info).mapError(r => new HasProblemField(fld, r))
129+
}
130+
}
131+
if (ctx.settings.strict.value)
132+
// check fields only under strict mode for now.
133+
// Reason: We do track nulls, so an embedded field could well be nullable
134+
// which means it is not a path and need not be checked; but we cannot recognize
135+
// this situation until we have a typesystem that tracks nullability.
136+
((Realizable: Realizability) /: tp.fields)(checkField)
137+
else
138+
Realizable
139+
}
140+
}

src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import scala.reflect.io.AbstractFile
1212
import Decorators.SymbolIteratorDecorator
1313
import ast._
1414
import annotation.tailrec
15+
import CheckRealizable._
1516
import typer.Mode
1617
import util.SimpleMap
1718
import util.Stats
@@ -522,15 +523,6 @@ object SymDenotations {
522523
final def isStable(implicit ctx: Context) =
523524
isType || !is(UnstableValue, butNot = Stable)
524525

525-
/** Is this a denotation of a realizable term (or an arbitrary type)? */
526-
final def isRealizable(implicit ctx: Context) =
527-
is(Stable) || isType || {
528-
val isRealizable =
529-
!isLateInitialized ||
530-
isEffectivelyFinal && ctx.realizability(info) == TypeOps.Realizable
531-
isRealizable && { setFlag(Stable); true }
532-
}
533-
534526
/** Field is initialized on use, not on definition;
535527
* we do not count modules as fields here.
536528
*/

src/dotty/tools/dotc/core/TypeOps.scala

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import collection.mutable
1414
import ast.tpd._
1515

1616
trait TypeOps { this: Context => // TODO: Make standalone object.
17-
import TypeOps._
1817

1918
/** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec
2019
* for what this means. Called very often, so the code is optimized heavily.
@@ -301,73 +300,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
301300
}
302301
}
303302

304-
/** Is this type a path with some part that is initialized on use? */
305-
def isLateInitialized(tp: Type): Boolean = tp.dealias match {
306-
case tp: TermRef =>
307-
tp.symbol.isLateInitialized || isLateInitialized(tp.prefix)
308-
case _: SingletonType | NoPrefix =>
309-
false
310-
case tp: TypeRef =>
311-
true
312-
case tp: TypeProxy =>
313-
isLateInitialized(tp.underlying)
314-
case tp: AndOrType =>
315-
isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2)
316-
case _ =>
317-
true
318-
}
319-
320-
/** The realizability status of given type `tp`*/
321-
def realizability(tp: Type): Realizability = tp.dealias match {
322-
case tp: TermRef =>
323-
if (tp.symbol.isRealizable)
324-
if (tp.symbol.isLateInitialized || // we already checked realizability of info in that case
325-
!isLateInitialized(tp.prefix)) // symbol was definitely constructed in that case
326-
Realizable
327-
else
328-
realizability(tp.info)
329-
else if (!tp.symbol.isStable) NotStable
330-
else if (!tp.symbol.isEffectivelyFinal) new NotFinal(tp.symbol)
331-
else new ProblemInUnderlying(tp.info, realizability(tp.info))
332-
case _: SingletonType | NoPrefix =>
333-
Realizable
334-
case tp =>
335-
def isConcrete(tp: Type): Boolean = tp.dealias match {
336-
case tp: TypeRef => tp.symbol.isClass
337-
case tp: TypeProxy => isConcrete(tp.underlying)
338-
case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2)
339-
case _ => false
340-
}
341-
if (!isConcrete(tp)) NotConcrete
342-
else boundsRealizability(tp)
343-
}
344-
345-
/** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance
346-
* pointing to a bad bounds member otherwise.
347-
*/
348-
def boundsRealizability(tp: Type)(implicit ctx: Context) = {
349-
def hasBadBounds(mbr: SingleDenotation) = {
350-
val bounds = mbr.info.bounds
351-
!(bounds.lo <:< bounds.hi)
352-
}
353-
tp.nonClassTypeMembers.find(hasBadBounds) match {
354-
case Some(mbr) => new HasProblemBounds(mbr)
355-
case _ => Realizable
356-
}
357-
}
358-
359-
/* Might need at some point in the future
360-
def memberRealizability(tp: Type)(implicit ctx: Context) = {
361-
println(i"check member rel of $tp")
362-
def isUnrealizable(fld: SingleDenotation) =
363-
!fld.symbol.is(Lazy) && realizability(fld.info) != Realizable
364-
tp.fields.find(isUnrealizable) match {
365-
case Some(fld) => new HasProblemField(fld)
366-
case _ => Realizable
367-
}
368-
}
369-
*/
370-
371303
private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = {
372304
val lazyInfo = new LazyType { // needed so we do not force `formal`.
373305
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
@@ -584,30 +516,5 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
584516
}
585517

586518
object TypeOps {
587-
val emptyDNF = (Nil, Set[Name]()) :: Nil
588519
@sharable var track = false // !!!DEBUG
589-
590-
// ----- Realizibility Status -----------------------------------------------------
591-
592-
abstract class Realizability(val msg: String)
593-
594-
object Realizable extends Realizability("")
595-
596-
object NotConcrete extends Realizability(" is not a concrete type")
597-
598-
object NotStable extends Realizability(" is not a stable reference")
599-
600-
class NotFinal(sym: Symbol)(implicit ctx: Context)
601-
extends Realizability(i" refers to nonfinal $sym")
602-
603-
class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context)
604-
extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}")
605-
606-
/* Might need at some point in the future
607-
class HasProblemField(fld: SingleDenotation)(implicit ctx: Context)
608-
extends Realizability(i" has a member $fld which is uneligible as a path since ${fld.symbol.name}${ctx.realizability(fld.info)}")
609-
*/
610-
611-
class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context)
612-
extends Realizability(i"s underlying type ${tp}${problem.msg}")
613520
}

src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
101101
case tree: TypeTree => tree
102102
case _ =>
103103
if (tree.isType) {
104-
Checking.boundsChecker.traverse(tree)
104+
Checking.typeChecker.traverse(tree)
105105
TypeTree(tree.tpe).withPos(tree.pos)
106106
}
107107
else tree.tpe.widenTermRefExpr match {
@@ -180,7 +180,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran
180180
val tree1 =
181181
if (sym.isClass) tree
182182
else {
183-
Checking.boundsChecker.traverse(tree.rhs)
183+
Checking.typeChecker.traverse(tree.rhs)
184184
cpy.TypeDef(tree)(rhs = TypeTree(tree.symbol.info))
185185
}
186186
super.transform(tree1)

src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Trees._
1616
import ProtoTypes._
1717
import Constants._
1818
import Scopes._
19+
import CheckRealizable._
1920
import ErrorReporting.errorTree
2021
import annotation.unchecked
2122
import util.Positions._
@@ -49,14 +50,20 @@ object Checking {
4950
checkBounds(args, poly.paramBounds, _.substParams(poly, _))
5051

5152
/** Check all AppliedTypeTree nodes in this tree for legal bounds */
52-
val boundsChecker = new TreeTraverser {
53+
val typeChecker = new TreeTraverser {
5354
def traverse(tree: Tree)(implicit ctx: Context) = {
5455
tree match {
5556
case AppliedTypeTree(tycon, args) =>
5657
val tparams = tycon.tpe.typeSymbol.typeParams
5758
val bounds = tparams.map(tparam =>
5859
tparam.info.asSeenFrom(tycon.tpe.normalizedPrefix, tparam.owner.owner).bounds)
5960
checkBounds(args, bounds, _.substDealias(tparams, _))
61+
case Select(qual, name) if name.isTypeName =>
62+
checkRealizable(qual.tpe, qual.pos)
63+
case SelectFromTypeTree(qual, name) if name.isTypeName =>
64+
checkRealizable(qual.tpe, qual.pos)
65+
case SingletonTypeTree(ref) =>
66+
checkRealizable(ref.tpe, ref.pos)
6067
case _ =>
6168
}
6269
traverseChildren(tree)
@@ -83,6 +90,15 @@ object Checking {
8390
case _ =>
8491
}
8592

93+
/** Check that type `tp` is realizable. */
94+
def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = {
95+
val rstatus = realizability(tp)
96+
if (rstatus ne Realizable) {
97+
def msg = d"$tp is not a legal path\n since it${rstatus.msg}"
98+
if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos)
99+
}
100+
}
101+
86102
/** A type map which checks that the only cycles in a type are F-bounds
87103
* and that protects all F-bounded references by LazyRefs.
88104
*/
@@ -321,19 +337,10 @@ trait Checking {
321337
def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit =
322338
if (!tp.isStable) ctx.error(d"$tp is not stable", pos)
323339

324-
/** Check that type `tp` is realizable. */
325-
def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = {
326-
val rstatus = ctx.realizability(tp)
327-
if (rstatus ne TypeOps.Realizable) {
328-
def msg = d"$tp is not a legal path since it${rstatus.msg}"
329-
if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos)
330-
}
331-
}
332-
333340
/** Check that all type members of `tp` have realizable bounds */
334341
def checkRealizableBounds(tp: Type, pos: Position)(implicit ctx: Context): Unit = {
335-
val rstatus = ctx.boundsRealizability(tp)
336-
if (rstatus ne TypeOps.Realizable)
342+
val rstatus = boundsRealizability(tp)
343+
if (rstatus ne Realizable)
337344
ctx.error(i"$tp cannot be instantiated since it${rstatus.msg}", pos)
338345
}
339346

@@ -449,7 +456,6 @@ trait NoChecking extends Checking {
449456
override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info
450457
override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
451458
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
452-
override def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
453459
override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp
454460
override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
455461
override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp

0 commit comments

Comments
 (0)