Skip to content

Commit 6e7dd1b

Browse files
committed
Merge pull request #60 from odersky/fix/#50-volatile
Fix/#50 volatile
2 parents ac45e12 + c90044f commit 6e7dd1b

File tree

10 files changed

+113
-70
lines changed

10 files changed

+113
-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

+71-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 =>
@@ -74,8 +76,29 @@ trait TypeOps { this: Context =>
7476
def apply(tp: Type) = simplify(tp, this)
7577
}
7678

79+
/** A type is volatile if its DNF contains an alternative of the form
80+
* {P1, ..., Pn}, {N1, ..., Nk}, where the Pi are parent typerefs and the
81+
* Nj are refinement names, and one the 4 following conditions is met:
82+
*
83+
* 1. At least two of the parents Pi are abstract types.
84+
* 2. One of the parents Pi is an abstract type, and one other type Pj,
85+
* j != i has an abstract member which has the same name as an
86+
* abstract member of the whole type.
87+
* 3. One of the parents Pi is an abstract type, and one of the refinement
88+
* names Nj refers to an abstract member of the whole type.
89+
* 4. One of the parents Pi is an an alias type with a volatile alias
90+
* or an abstract type with a volatile upper bound.
91+
*
92+
* Lazy values are not allowed to have volatile type, as otherwise
93+
* unsoundness can result.
94+
*/
7795
final def isVolatile(tp: Type): Boolean = {
78-
/** Pre-filter to avoid expensive DNF computation */
96+
97+
/** Pre-filter to avoid expensive DNF computation
98+
* If needsChecking returns false it is guaranteed that
99+
* DNF does not contain intersections, or abstract types with upper
100+
* bounds that themselves need checking.
101+
*/
79102
def needsChecking(tp: Type, isPart: Boolean): Boolean = tp match {
80103
case tp: TypeRef =>
81104
tp.info match {
@@ -88,31 +111,62 @@ trait TypeOps { this: Context =>
88111
needsChecking(tp.parent, true)
89112
case tp: TypeProxy =>
90113
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)
114+
case tp: AndType =>
115+
true
116+
case tp: OrType =>
117+
isPart || needsChecking(tp.tp1, isPart) && needsChecking(tp.tp2, isPart)
95118
case _ =>
96119
false
97120
}
121+
98122
needsChecking(tp, false) && {
99-
tp.DNF forall { case (parents, refinedNames) =>
123+
DNF(tp) forall { case (parents, refinedNames) =>
100124
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-
)
125+
absParents.nonEmpty && {
126+
absParents.lengthCompare(2) >= 0 || {
127+
val ap = absParents.head
128+
((parents exists (p =>
129+
(p ne ap)
130+
|| p.memberNames(abstractTypeNameFilter, tp).nonEmpty
131+
|| p.memberNames(abstractTermNameFilter, tp).nonEmpty))
132+
|| (refinedNames & tp.memberNames(abstractTypeNameFilter, tp)).nonEmpty
133+
|| (refinedNames & tp.memberNames(abstractTermNameFilter, tp)).nonEmpty
134+
|| isVolatile(ap))
135+
}
111136
}
112137
}
113138
}
114139
}
115140

141+
/** The disjunctive normal form of this type.
142+
* This collects a set of alternatives, each alternative consisting
143+
* of a set of typerefs and a set of refinement names. Both sets are represented
144+
* as lists, to obtain a deterministic order. Collected are
145+
* all type refs reachable by following aliases and type proxies, and
146+
* collecting the elements of conjunctions (&) and disjunctions (|).
147+
* The set of refinement names in each alternative
148+
* are the set of names in refinement types encountered during the collection.
149+
*/
150+
final def DNF(tp: Type): List[(List[TypeRef], Set[Name])] = ctx.traceIndented(s"DNF($this)", checks) {
151+
tp.dealias match {
152+
case tp: TypeRef =>
153+
(tp :: Nil, Set[Name]()) :: Nil
154+
case RefinedType(parent, name) =>
155+
for ((ps, rs) <- DNF(parent)) yield (ps, rs + name)
156+
case tp: TypeProxy =>
157+
DNF(tp.underlying)
158+
case AndType(l, r) =>
159+
for ((lps, lrs) <- DNF(l); (rps, rrs) <- DNF(r))
160+
yield (lps | rps, lrs | rrs)
161+
case OrType(l, r) =>
162+
DNF(l) | DNF(r)
163+
case tp =>
164+
TypeOps.emptyDNF
165+
}
166+
}
167+
168+
169+
116170
private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = {
117171
val lazyInfo = new LazyType { // needed so we do not force `formal`.
118172
def complete(denot: SymDenotation)(implicit ctx: Context): Unit = {
@@ -215,6 +269,6 @@ trait TypeOps { this: Context =>
215269
}
216270

217271
object TypeOps {
218-
272+
val emptyDNF = (Nil, Set[Name]()) :: Nil
219273
var track = false // !!!DEBUG
220274
}

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
@@ -55,6 +55,7 @@ class tests extends CompilerTest {
5555
@Test def neg_rootImports = compileFile(negDir, "rootImplicits", xerrors = 2)
5656
@Test def neg_templateParents() = compileFile(negDir, "templateParents", xerrors = 3)
5757
@Test def neg_i39 = compileFile(negDir, "i39", xerrors = 1)
58+
@Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 4)
5859

5960
@Test def dotc = compileDir(dotcDir + "tools/dotc", twice)
6061
@Test def dotc_ast = compileDir(dotcDir + "tools/dotc/ast", twice)

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)