Skip to content

Commit af337f0

Browse files
committed
Fix of #50 - volatile
Volatile checking needs to take all intersections into account; previously these could be discarded through needsChecking. Plus several refactorings and additions. 1) Module vals now have Final and Stable flags set 2) All logic around isVolatile is now in TypeOps; some of it was moved from Types. 3) Added stability checking to Select and SelectFromType typings. Todo: We should find a better name for isVolatile. Maybe define the negation instead under the name "isRealizable"?.
1 parent d827b01 commit af337f0

File tree

10 files changed

+97
-70
lines changed

10 files changed

+97
-70
lines changed

Diff for: src/dotty/tools/dotc/config/Printers.scala

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ object Printers {
1414
val core: Printer = noPrinter
1515
val typr: Printer = noPrinter
1616
val constr: Printer = noPrinter
17+
val checks: Printer = noPrinter
1718
val overload: Printer = noPrinter
1819
val implicits: Printer = noPrinter
1920
val implicitsDetailed: Printer = noPrinter

Diff for: src/dotty/tools/dotc/core/Flags.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ object Flags {
422422
final val RetainedTypeArgFlags = VarianceFlags | ExpandedName | Protected | Local
423423

424424
/** Modules always have these flags set */
425-
final val ModuleCreationFlags = ModuleVal
425+
final val ModuleCreationFlags = ModuleVal | Final | Stable
426426

427427
/** Module classes always have these flags set */
428428
final val ModuleClassCreationFlags = ModuleClass | Final

Diff for: src/dotty/tools/dotc/core/SymDenotations.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ object SymDenotations {
355355
final def isStable(implicit ctx: Context) = {
356356
val isUnstable =
357357
(this is UnstableValue) ||
358-
info.isVolatile && !hasAnnotation(defn.uncheckedStableClass)
358+
ctx.isVolatile(info) && !hasAnnotation(defn.uncheckedStableClass)
359359
(this is Stable) || isType || {
360360
if (isUnstable) false
361361
else { setFlag(Stable); true }

Diff for: src/dotty/tools/dotc/core/TypeOps.scala

+55-17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package core
33

44
import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._
55
import SymDenotations._
6+
import config.Printers._
7+
import Decorators._
68
import util.SimpleMap
79

810
trait TypeOps { this: Context =>
@@ -75,7 +77,12 @@ trait TypeOps { this: Context =>
7577
}
7678

7779
final def isVolatile(tp: Type): Boolean = {
78-
/** Pre-filter to avoid expensive DNF computation */
80+
81+
/** Pre-filter to avoid expensive DNF computation
82+
* If needsChecking returns false it is guaranteed that
83+
* DNF does not contain intersections, or abstract types with upper
84+
* bounds that themselves need checking.
85+
*/
7986
def needsChecking(tp: Type, isPart: Boolean): Boolean = tp match {
8087
case tp: TypeRef =>
8188
tp.info match {
@@ -88,31 +95,62 @@ trait TypeOps { this: Context =>
8895
needsChecking(tp.parent, true)
8996
case tp: TypeProxy =>
9097
needsChecking(tp.underlying, isPart)
91-
case AndType(l, r) =>
92-
needsChecking(l, true) || needsChecking(r, true)
93-
case OrType(l, r) =>
94-
isPart || needsChecking(l, isPart) && needsChecking(r, isPart)
98+
case tp: AndType =>
99+
true
100+
case tp: OrType =>
101+
isPart || needsChecking(tp.tp1, isPart) && needsChecking(tp.tp2, isPart)
95102
case _ =>
96103
false
97104
}
105+
98106
needsChecking(tp, false) && {
99-
tp.DNF forall { case (parents, refinedNames) =>
107+
DNF(tp) forall { case (parents, refinedNames) =>
100108
val absParents = parents filter (_.symbol is Deferred)
101-
absParents.size >= 2 || {
102-
val ap = absParents.head
103-
((parents exists (p =>
104-
(p ne ap)
105-
|| p.memberNames(abstractTypeNameFilter, tp).nonEmpty
106-
|| p.memberNames(abstractTermNameFilter, tp).nonEmpty))
107-
|| (refinedNames & tp.memberNames(abstractTypeNameFilter, tp)).nonEmpty
108-
|| (refinedNames & tp.memberNames(abstractTermNameFilter, tp)).nonEmpty
109-
|| isVolatile(ap)
110-
)
109+
absParents.nonEmpty && {
110+
absParents.lengthCompare(2) >= 0 || {
111+
val ap = absParents.head
112+
((parents exists (p =>
113+
(p ne ap)
114+
|| p.memberNames(abstractTypeNameFilter, tp).nonEmpty
115+
|| p.memberNames(abstractTermNameFilter, tp).nonEmpty))
116+
|| (refinedNames & tp.memberNames(abstractTypeNameFilter, tp)).nonEmpty
117+
|| (refinedNames & tp.memberNames(abstractTermNameFilter, tp)).nonEmpty
118+
|| isVolatile(ap))
119+
}
111120
}
112121
}
113122
}
114123
}
115124

125+
/** The disjunctive normal form of this type.
126+
* This collects a set of alternatives, each alternative consisting
127+
* of a set of typerefs and a set of refinement names. Both sets are represented
128+
* as lists, to obtain a deterministic order. Collected are
129+
* all type refs reachable by following aliases and type proxies, and
130+
* collecting the elements of conjunctions (&) and disjunctions (|).
131+
* The set of refinement names in each alternative
132+
* are the set of names in refinement types encountered during the collection.
133+
*/
134+
final def DNF(tp: Type): List[(List[TypeRef], Set[Name])] = ctx.traceIndented(s"DNF($this)", checks) {
135+
tp.dealias match {
136+
case tp: TypeRef =>
137+
(tp :: Nil, Set[Name]()) :: Nil
138+
case RefinedType(parent, name) =>
139+
for ((ps, rs) <- DNF(parent)) yield (ps, rs + name)
140+
case tp: TypeProxy =>
141+
DNF(tp.underlying)
142+
case AndType(l, r) =>
143+
for ((lps, lrs) <- DNF(l); (rps, rrs) <- DNF(r))
144+
yield (lps | rps, lrs | rrs)
145+
case OrType(l, r) =>
146+
DNF(l) | DNF(r)
147+
case tp =>
148+
TypeOps.emptyDNF
149+
}
150+
}
151+
152+
153+
116154
private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = {
117155
val lazyInfo = new LazyType { // needed so we do not force `formal`.
118156
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
@@ -215,6 +253,6 @@ trait TypeOps { this: Context =>
215253
}
216254

217255
object TypeOps {
218-
256+
val emptyDNF = (Nil, Set[Name]()) :: Nil
219257
var track = false // !!!DEBUG
220258
}

Diff for: src/dotty/tools/dotc/core/Types.scala

+2-45
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ object Types {
106106
classSymbol.derivesFrom(cls)
107107

108108
/** A type T is a legal prefix in a type selection T#A if
109-
* T is stable or T contains no uninstantiated type variables.
109+
* T is stable or T contains no abstract types
110+
* !!! Todo: What about non-final vals that contain abstract types?
110111
*/
111112
final def isLegalPrefix(implicit ctx: Context): Boolean =
112113
isStable || memberNames(abstractTypeNameFilter).isEmpty
@@ -127,25 +128,6 @@ object Types {
127128
/** Is some part of this type produced as a repair for an error? */
128129
final def isErroneous(implicit ctx: Context): Boolean = existsPart(_.isError)
129130

130-
/** A type is volatile if its DNF contains an alternative of the form
131-
* {P1, ..., Pn}, {N1, ..., Nk}, where the Pi are parent typerefs and the
132-
* Nj are refinement names, and one the 4 following conditions is met:
133-
*
134-
* 1. At least two of the parents Pi are abstract types.
135-
* 2. One of the parents Pi is an abstract type, and one other type Pj,
136-
* j != i has an abstract member which has the same name as an
137-
* abstract member of the whole type.
138-
* 3. One of the parents Pi is an abstract type, and one of the refinement
139-
* names Nj refers to an abstract member of the whole type.
140-
* 4. One of the parents Pi is an abstract type with a volatile upper bound.
141-
*
142-
* Lazy values are not allowed to have volatile type, as otherwise
143-
* unsoundness can result.
144-
*/
145-
final def isVolatile(implicit ctx: Context): Boolean = track("isVolatile") {
146-
ctx.isVolatile(this)
147-
}
148-
149131
/** Does the type carry an annotation that is an instance of `cls`? */
150132
final def hasAnnotation(cls: ClassSymbol)(implicit ctx: Context): Boolean = stripTypeVar match {
151133
case AnnotatedType(annot, tp) => (annot matches cls) || (tp hasAnnotation cls)
@@ -741,31 +723,6 @@ object Types {
741723
def typeParamNamed(name: TypeName)(implicit ctx: Context): Symbol =
742724
classSymbol.decls.lookup(name) orElse member(name).symbol
743725

744-
/** The disjunctive normal form of this type.
745-
* This collects a set of alternatives, each alternative consisting
746-
* of a set of typerefs and a set of refinement names. Both sets are represented
747-
* as lists, to obtain a deterministic order. Collected are
748-
* all type refs reachable by following aliases and type proxies, and
749-
* collecting the elements of conjunctions (&) and disjunctions (|).
750-
* The set of refinement names in each alternative
751-
* are the set of names in refinement types encountered during the collection.
752-
*/
753-
final def DNF(implicit ctx: Context): List[(List[TypeRef], Set[Name])] = dealias match {
754-
case tp: TypeRef =>
755-
(tp :: Nil, Set[Name]()) :: Nil
756-
case RefinedType(parent, name) =>
757-
for ((ps, rs) <- parent.DNF) yield (ps, rs + name)
758-
case tp: TypeProxy =>
759-
tp.underlying.DNF
760-
case AndType(l, r) =>
761-
for ((lps, lrs) <- l.DNF; (rps, rrs) <- r.DNF)
762-
yield (lps | rps, lrs | rrs)
763-
case OrType(l, r) =>
764-
l.DNF | r.DNF
765-
case tp =>
766-
emptyDNF
767-
}
768-
769726
// ----- Substitutions -----------------------------------------------------
770727

771728
/** Substitute all types that refer in their symbol attribute to

Diff for: src/dotty/tools/dotc/typer/Checking.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ trait NoChecking {
2323
def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree
2424
def checkBounds(args: List[tpd.Tree], poly: PolyType, pos: Position)(implicit ctx: Context): Unit = ()
2525
def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
26+
def checkLegalPrefix(tp: Type, pos: Position)(implicit ctx: Context): Unit = ()
2627
def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp
2728
def checkImplicitTptNonEmpty(defTree: untpd.ValOrDefDef)(implicit ctx: Context): Unit = ()
2829
def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = ()
@@ -53,13 +54,17 @@ trait Checking extends NoChecking {
5354
if (!(bounds.lo <:< arg.tpe)) notConforms("lower", bounds.lo)
5455
}
5556

56-
/** Check that type `tp` is stable.
57+
/** Check that type `tp` is stable. */
58+
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit =
59+
if (!tp.isStable) ctx.error(i"$tp is not stable", pos)
60+
61+
/** Check that type `tp` is a legal prefix for '#'.
5762
* @return The type itself
5863
*/
59-
override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit =
60-
if (!tp.isStable) ctx.error(i"Prefix of type ${tp.widenIfUnstable} is not stable", pos)
64+
override def checkLegalPrefix(tp: Type, pos: Position)(implicit ctx: Context): Unit =
65+
if (!tp.isLegalPrefix) ctx.error(i"$tp is not a valid prefix for '#'", pos)
6166

62-
/** Check that `tp` is a class type with a stable prefix. Also, if `isFirst` is
67+
/** Check that `tp` is a class type with a stable prefix. Also, if `isFirst` is
6368
* false check that `tp` is a trait.
6469
* @return `tp` itself if it is a class or trait ref, ObjectClass.typeRef if not.
6570
*/

Diff for: src/dotty/tools/dotc/typer/Typer.scala

+2
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,13 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
265265

266266
def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") {
267267
val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this))
268+
if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos)
268269
checkValue(assignType(cpy.Select(tree, qual1, tree.name), qual1), pt)
269270
}
270271

271272
def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): SelectFromTypeTree = track("typedSelectFromTypeTree") {
272273
val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this))
274+
checkLegalPrefix(qual1.tpe, qual1.pos)
273275
assignType(cpy.SelectFromTypeTree(tree, qual1, tree.name), qual1)
274276
}
275277

Diff for: src/dotty/tools/dotc/util/common.scala

-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import core.Types.WildcardType
77
/** Common values hoisted out for performance */
88
object common {
99

10-
val emptyDNF = (Nil, Set[Name]()) :: Nil
11-
1210
val alwaysTrue = Function.const(true) _
1311
val alwaysZero = Function.const(0) _
1412
val alwaysWildcardType = Function.const(WildcardType) _

Diff for: test/dotc/tests.scala

+1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class tests extends CompilerTest {
5252
@Test def neg_rootImports = compileFile(negDir, "rootImplicits", xerrors = 2)
5353
@Test def neg_templateParents() = compileFile(negDir, "templateParents", xerrors = 3)
5454
@Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1)
55+
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4)
5556

5657
@Test def dotc = compileDir(dotcDir + "tools/dotc")
5758
@Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast")

Diff for: tests/neg/i50-volatile.scala

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
object Test {
2+
class Base {
3+
class Inner
4+
}
5+
type A <: Base {
6+
type X = String
7+
}
8+
type B <: {
9+
type X = Int
10+
}
11+
lazy val o: A & B = ???
12+
13+
class Client extends o.Inner
14+
15+
def xToString(x: o.X): String = x
16+
17+
def intToString(i: Int): String = xToString(i)
18+
}
19+
object Test2 {
20+
21+
import Test.o._
22+
23+
def xToString(x: X): String = x
24+
25+
}

0 commit comments

Comments
 (0)