From 8a00db1549d7871732e34d0c323a71a1f94ec4ac Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Mar 2018 11:05:31 +0100 Subject: [PATCH 01/13] Clean up logic for isStable --- compiler/src/dotty/tools/dotc/core/Flags.scala | 5 ++++- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 9f88fdbc0cf3..f4b04a47ec26 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -474,7 +474,10 @@ object Flags { assert(AfterLoadFlags.isTermFlags && AfterLoadFlags.isTypeFlags) /** A value that's unstable unless complemented with a Stable flag */ - final val UnstableValue = Mutable | Method + final val UnstableValue = + Mutable | Method | Erased + // TODO: Erased should be treated as stable just like final lazy is. + // Otherwise the usefulness of Erased is very much reduced. /** Flags that express the variance of a type parameter. */ final val VarianceFlags = Covariant | Contravariant diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index c190dfb7262c..059585926954 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -599,8 +599,10 @@ object SymDenotations { ) /** Is this a denotation of a stable term (or an arbitrary type)? */ - final def isStable(implicit ctx: Context) = - isType || !is(Erased) && (is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType])) + final def isStable(implicit ctx: Context) = { + def isUnstableValue = is(UnstableValue) || info.isInstanceOf[ExprType] + isType || is(Stable) || !isUnstableValue + } /** Is this a "real" method? A real method is a method which is: * - not an accessor From 99ab4367d1eea30bdfaa062c74358f86286f8af8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Mar 2018 19:15:46 +0100 Subject: [PATCH 02/13] Fix #4031: Check arguments of dependent methods for realizability --- .../src/dotty/tools/dotc/core/Types.scala | 13 ++++++-- .../dotty/tools/dotc/typer/Applications.scala | 5 +-- .../dotty/tools/dotc/typer/TypeAssigner.scala | 31 +++++++++++++++---- tests/neg/i4031-posttyper.scala | 7 +++++ tests/neg/i4031.scala | 17 ++++++++++ tests/neg/i50-volatile.scala | 15 ++++++--- tests/neg/realizability.scala | 22 +++++++++++++ tests/neg/z1720.scala | 16 ++++++++++ tests/pos/i4031.scala | 8 +++++ tests/pos/z1720.scala | 2 +- 10 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 tests/neg/i4031-posttyper.scala create mode 100644 tests/neg/i4031.scala create mode 100644 tests/neg/realizability.scala create mode 100644 tests/neg/z1720.scala create mode 100644 tests/pos/i4031.scala diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index c30f711c6096..38b9cf46321b 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -19,6 +19,7 @@ import Periods._ import util.Positions.{Position, NoPosition} import util.Stats._ import util.{DotClass, SimpleIdentitySet} +import CheckRealizable._ import reporting.diagnostic.Message import reporting.diagnostic.messages.CyclicReferenceInvolving import ast.tpd._ @@ -155,6 +156,12 @@ object Types { case _ => false } + /** Does this type denote a realizable stable reference? Much more expensive to checl + * than isStable, that's why some of the checks are done later in PostTyper. + */ + final def isRealizable(implicit ctx: Context): Boolean = + isStable && realizability(this) == Realizable + /** Is this type a (possibly refined or applied or aliased) type reference * to the given type symbol? * @sym The symbol to compare to. It must be a class symbol or abstract type. @@ -4104,6 +4111,8 @@ object Types { else if (variance < 0) lo else Range(lower(lo), upper(hi)) + protected def emptyRange = range(defn.NothingType, defn.AnyType) + protected def isRange(tp: Type) = tp.isInstanceOf[Range] protected def lower(tp: Type) = tp match { @@ -4171,7 +4180,7 @@ object Types { forwarded.orElse( range(super.derivedSelect(tp, preLo), super.derivedSelect(tp, preHi))) case _ => - super.derivedSelect(tp, pre) + if (pre == defn.AnyType) pre else super.derivedSelect(tp, pre) } override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) = @@ -4219,7 +4228,7 @@ object Types { else tp.derivedTypeBounds(lo, hi) override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type) = - if (isRange(thistp) || isRange(supertp)) range(defn.NothingType, defn.AnyType) + if (isRange(thistp) || isRange(supertp)) emptyRange else tp.derivedSuperType(thistp, supertp) override protected def derivedAppliedType(tp: AppliedType, tycon: Type, args: List[Type]): Type = diff --git a/compiler/src/dotty/tools/dotc/typer/Applications.scala b/compiler/src/dotty/tools/dotc/typer/Applications.scala index 83f8b88d86df..8afeb78948b1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -413,8 +413,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => def addTyped(arg: Arg, formal: Type): Type => Type = { addArg(typedArg(arg, formal), formal) if (methodType.isParamDependent) - safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg)) - else identity + safeSubstParam(_, methodType.paramRefs(n), typeOfArg(arg), initVariance = -1) + else + identity } def missingArg(n: Int): Unit = { diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 61502cddf482..527cc5fbcfb2 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -14,6 +14,7 @@ import collection.mutable import reporting.diagnostic.Message import reporting.diagnostic.messages._ import Checking.checkNoPrivateLeaks +import CheckRealizable._ trait TypeAssigner { import tpd._ @@ -106,7 +107,7 @@ trait TypeAssigner { case info: ClassInfo => range(defn.NothingType, apply(classBound(info))) case _ => - range(defn.NothingType, defn.AnyType) // should happen only in error cases + emptyRange // should happen only in error cases } case tp: ThisType if toAvoid(tp.cls) => range(defn.NothingType, apply(classBound(tp.cls.classInfo))) @@ -337,13 +338,31 @@ trait TypeAssigner { } } - /** Substitute argument type `argType` for parameter `pref` in type `tp`, - * skolemizing the argument type if it is not stable and `pref` occurs in `tp`. + /** Substitute argument type `argType` for parameter `pref` in type `tp`, but + * take special measures if the argument is not realizable: + * 1. If the widened argument type is known to have good bounds, + * substitute the skolemized argument type. + * 2. If the widened argument type is not known to have good bounds, eliminate all references + * to the parameter in `tp`. + * (2) is necessary since even with a skolemized type we might break subtyping if + * bounds are bad. This could lead to errors not being detected. A test case is the second + * failure in neg/i4031.scala */ - def safeSubstParam(tp: Type, pref: ParamRef, argType: Type)(implicit ctx: Context) = { + def safeSubstParam(tp: Type, pref: ParamRef, argType: Type, initVariance: Int = 1)(implicit ctx: Context) = { val tp1 = tp.substParam(pref, argType) - if ((tp1 eq tp) || argType.isStable) tp1 - else tp.substParam(pref, SkolemType(argType.widen)) + if ((tp1 eq tp) || argType.isRealizable) tp1 + else { + val widenedArgType = argType.widen + if (realizability(widenedArgType) == Realizable) + tp.substParam(pref, SkolemType(widenedArgType)) + else { + val avoidParam = new ApproximatingTypeMap { + variance = initVariance + def apply(t: Type) = if (t `eq` pref) emptyRange else mapOver(t) + } + avoidParam(tp) + } + } } /** Substitute types of all arguments `args` for corresponding `params` in `tp`. diff --git a/tests/neg/i4031-posttyper.scala b/tests/neg/i4031-posttyper.scala new file mode 100644 index 000000000000..b877867267da --- /dev/null +++ b/tests/neg/i4031-posttyper.scala @@ -0,0 +1,7 @@ +object App { + trait A { type L >: Any} + def upcast(a: A, x: Any): a.L = x + lazy val p: A { type L <: Nothing } = p + val q = new A { type L = Any } + def coerce2(x: Any): Int = upcast(p, x): p.L // error: not a legal path +} diff --git a/tests/neg/i4031.scala b/tests/neg/i4031.scala new file mode 100644 index 000000000000..eb553c117dbe --- /dev/null +++ b/tests/neg/i4031.scala @@ -0,0 +1,17 @@ +object App { + trait A { type L >: Any} + def upcast(a: A, x: Any): a.L = x + lazy val p: A { type L <: Nothing } = p + val q = new A { type L = Any } + def coerce(x: Any): Int = upcast(p, x) // error: not a legal path + + def compare(x: A, y: x.L) = assert(x == y) + def compare2(x: A)(y: x.type) = assert(x == y) + + + def main(args: Array[String]): Unit = { + println(coerce("Uh oh!")) + compare(p, p) // error: not a legal path + compare2(p)(p) // error: not a legal path + } +} diff --git a/tests/neg/i50-volatile.scala b/tests/neg/i50-volatile.scala index fcfc9592b9a6..fcf08101f679 100644 --- a/tests/neg/i50-volatile.scala +++ b/tests/neg/i50-volatile.scala @@ -12,14 +12,21 @@ class Test { class Client extends o.Inner // old-error // old-error - def xToString(x: o.X): String = x // old-error + def xToString(x: o.X): String = x // error def intToString(i: Int): String = xToString(i) } -object Test2 { - import Test.o._ // error +object Test2 { + trait A { + type X = String + } + trait B { + type X = Int + } + lazy val o: A & B = ??? - def xToString(x: X): String = x + def xToString(x: o.X): String = x // error + def intToString(i: Int): String = xToString(i) } diff --git a/tests/neg/realizability.scala b/tests/neg/realizability.scala new file mode 100644 index 000000000000..1730873e7692 --- /dev/null +++ b/tests/neg/realizability.scala @@ -0,0 +1,22 @@ +class C { type T } + +class Test { + + type D <: C + + lazy val a: C = ??? + final lazy val b: C = ??? + val c: D = ??? + final lazy val d: D = ??? + + val x1: a.T = ??? // error: not a legal path, since a is lazy & non-final + val x2: b.T = ??? // OK, b is lazy but concrete + val x3: c.T = ??? // OK, c is abstract but strict + val x4: d.T = ??? // error: not a legal path since d is abstract and lazy + + val y1: Singleton = a + val y2: Singleton = a + val y3: Singleton = a + val y4: Singleton = a + +} diff --git a/tests/neg/z1720.scala b/tests/neg/z1720.scala new file mode 100644 index 000000000000..19c1260f5392 --- /dev/null +++ b/tests/neg/z1720.scala @@ -0,0 +1,16 @@ +package test + +class Thing { + def info: Info[this.type] = InfoRepository.getInfo(this) + def info2: Info[this.type] = { + def self: this.type = this + InfoRepository.getInfo(self) // error: not a legal path + } +} + +trait Info[T] +case class InfoImpl[T](thing: T) extends Info[T] + +object InfoRepository { + def getInfo(t: Thing): Info[t.type] = InfoImpl(t) +} diff --git a/tests/pos/i4031.scala b/tests/pos/i4031.scala new file mode 100644 index 000000000000..faf8f9d7f256 --- /dev/null +++ b/tests/pos/i4031.scala @@ -0,0 +1,8 @@ +object App { + trait A { type L >: Any} + def upcast(a: A, x: Any): a.L = x + lazy val p: A { type L <: Nothing } = p + val q = new A { type L = Any } + def coerce1(x: Any): Any = upcast(q, x) // ok + def coerce3(x: Any): Any = upcast(p, x) // ok, since dependent result type is not needed +} diff --git a/tests/pos/z1720.scala b/tests/pos/z1720.scala index 7394d428c1f7..6eca2ac68866 100644 --- a/tests/pos/z1720.scala +++ b/tests/pos/z1720.scala @@ -3,7 +3,7 @@ package test class Thing { def info: Info[this.type] = InfoRepository.getInfo(this) def info2: Info[this.type] = { - def self: this.type = this + val self: this.type = this InfoRepository.getInfo(self) } } From d862da707ac85c08ad323cf63a13933c7f54d0a6 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Mon, 19 Mar 2018 14:23:26 +0100 Subject: [PATCH 03/13] Fix typo --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 38b9cf46321b..7f00900f0ff4 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -156,7 +156,7 @@ object Types { case _ => false } - /** Does this type denote a realizable stable reference? Much more expensive to checl + /** Does this type denote a realizable stable reference? Much more expensive to check * than isStable, that's why some of the checks are done later in PostTyper. */ final def isRealizable(implicit ctx: Context): Boolean = From 7d0fd02e5e74b45a53e6e59a6cb1518071872ff4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Mar 2018 13:35:33 +0100 Subject: [PATCH 04/13] Special case comparison with Any Add the rule T <: Any for any *-Type T. This was not include fully before. We did have the rule that T <: Any, if Any is in the base types of T. However, it could be that the base type wrt Any does not exist. Example: Any#L <: Any yielded false before, now yields true. This error manifested itself in i4031.scala. With the new rule, we can drop again the special case in derivedSelect. --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 14 +++++++------- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index e241a32e6a50..d7ddcde54ec2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -297,7 +297,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { thirdTry case tp1: TypeParamRef => def flagNothingBound = { - if (!frozenConstraint && tp2.isRef(defn.NothingClass) && state.isGlobalCommittable) { + if (!frozenConstraint && tp2.isRef(NothingClass) && state.isGlobalCommittable) { def msg = s"!!! instantiated to Nothing: $tp1, constraint = ${constraint.show}" if (Config.failOnInstantiationToNothing) assert(false, msg) else ctx.log(msg) @@ -379,8 +379,9 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { if (cls2.isClass) { if (cls2.typeParams.isEmpty) { if (cls2 eq AnyKindClass) return true - if (tp1.isRef(defn.NothingClass)) return true + if (tp1.isRef(NothingClass)) return true if (tp1.isLambdaSub) return false + if (cls2 eq AnyClass) return true // Note: We would like to replace this by `if (tp1.hasHigherKind)` // but right now we cannot since some parts of the standard library rely on the // idiom that e.g. `List <: Any`. We have to bootstrap without scalac first. @@ -394,7 +395,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { val base = tp1.baseType(cls2) if (base.typeSymbol == cls2) return true } - else if (tp1.isLambdaSub && !tp1.isRef(defn.AnyKindClass)) + else if (tp1.isLambdaSub && !tp1.isRef(AnyKindClass)) return recur(tp1, EtaExpansion(cls2.typeRef)) } fourthTry @@ -1257,7 +1258,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { // at run time. It would not work to replace that with `Nothing`. // However, maybe we can still apply the replacement to // types which are not explicitly written. - defn.NothingType + NothingType case _ => andType(tp1, tp2) } case _ => andType(tp1, tp2) @@ -1268,8 +1269,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { } /** The greatest lower bound of a list types */ - final def glb(tps: List[Type]): Type = - ((defn.AnyType: Type) /: tps)(glb) + final def glb(tps: List[Type]): Type = ((AnyType: Type) /: tps)(glb) /** The least upper bound of two types * @param canConstrain If true, new constraints might be added to simplify the lub. @@ -1299,7 +1299,7 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { /** The least upper bound of a list of types */ final def lub(tps: List[Type]): Type = - ((defn.NothingType: Type) /: tps)(lub(_,_, canConstrain = false)) + ((NothingType: Type) /: tps)(lub(_,_, canConstrain = false)) /** Try to produce joint arguments for a lub `A[T_1, ..., T_n] | A[T_1', ..., T_n']` using * the following strategies: diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7f00900f0ff4..d6a425ae5137 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4180,7 +4180,7 @@ object Types { forwarded.orElse( range(super.derivedSelect(tp, preLo), super.derivedSelect(tp, preHi))) case _ => - if (pre == defn.AnyType) pre else super.derivedSelect(tp, pre) + super.derivedSelect(tp, pre) } override protected def derivedRefinedType(tp: RefinedType, parent: Type, info: Type) = From d95554b918d530a26dd00192cd810d7607e67522 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Mar 2018 13:59:43 +0100 Subject: [PATCH 05/13] Align isRealizable with isStable A TermRef is stable if its underlying type is stable. Realizability should behave the same way. This obviates the change in z1720.scala. --- .../tools/dotc/core/CheckRealizable.scala | 22 ++++++++++--------- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotty/tools/dotc/typer/TypeAssigner.scala | 2 +- tests/neg/z1720.scala | 16 -------------- tests/pos/z1720.scala | 2 +- 5 files changed, 15 insertions(+), 29 deletions(-) delete mode 100644 tests/neg/z1720.scala diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index bac81942f8e3..68c04bf11466 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -72,16 +72,18 @@ class CheckRealizable(implicit ctx: Context) { 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 - } + val r = + 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 + } + if (r == Realizable || tp.info.isStableRealizable) Realizable else r case _: SingletonType | NoPrefix => Realizable case tp => diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index d6a425ae5137..73efd5bbcefd 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -159,7 +159,7 @@ object Types { /** Does this type denote a realizable stable reference? Much more expensive to check * than isStable, that's why some of the checks are done later in PostTyper. */ - final def isRealizable(implicit ctx: Context): Boolean = + final def isStableRealizable(implicit ctx: Context): Boolean = isStable && realizability(this) == Realizable /** Is this type a (possibly refined or applied or aliased) type reference diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 527cc5fbcfb2..e650b7d225a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -350,7 +350,7 @@ trait TypeAssigner { */ def safeSubstParam(tp: Type, pref: ParamRef, argType: Type, initVariance: Int = 1)(implicit ctx: Context) = { val tp1 = tp.substParam(pref, argType) - if ((tp1 eq tp) || argType.isRealizable) tp1 + if ((tp1 eq tp) || argType.isStableRealizable) tp1 else { val widenedArgType = argType.widen if (realizability(widenedArgType) == Realizable) diff --git a/tests/neg/z1720.scala b/tests/neg/z1720.scala deleted file mode 100644 index 19c1260f5392..000000000000 --- a/tests/neg/z1720.scala +++ /dev/null @@ -1,16 +0,0 @@ -package test - -class Thing { - def info: Info[this.type] = InfoRepository.getInfo(this) - def info2: Info[this.type] = { - def self: this.type = this - InfoRepository.getInfo(self) // error: not a legal path - } -} - -trait Info[T] -case class InfoImpl[T](thing: T) extends Info[T] - -object InfoRepository { - def getInfo(t: Thing): Info[t.type] = InfoImpl(t) -} diff --git a/tests/pos/z1720.scala b/tests/pos/z1720.scala index 6eca2ac68866..7394d428c1f7 100644 --- a/tests/pos/z1720.scala +++ b/tests/pos/z1720.scala @@ -3,7 +3,7 @@ package test class Thing { def info: Info[this.type] = InfoRepository.getInfo(this) def info2: Info[this.type] = { - val self: this.type = this + def self: this.type = this InfoRepository.getInfo(self) } } From ceaecdac5e3d2a34b7693baf769936f2d52627f2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Mar 2018 17:57:12 +0200 Subject: [PATCH 06/13] Harden RefChecks I had a TypeError crash in refchecks after screwing up a typeclass encoding in a particularly bad way. This commit reports an error instead. --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0864fde2ff81..af607c7f395c 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -960,7 +960,7 @@ class RefChecks extends MiniPhase { thisPhase => checkAllOverrides(cls) tree } catch { - case ex: MergeError => + case ex: TypeError => ctx.error(ex.getMessage, tree.pos) tree } From 3396862b008896976421544d3460aaa3388bbaca Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 5 Apr 2018 17:20:04 +0200 Subject: [PATCH 07/13] Refactor realizability to also cache result of isStableRealizable --- .../tools/dotc/core/CheckRealizable.scala | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 68c04bf11466..675ace682b35 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -68,22 +68,41 @@ class CheckRealizable(implicit ctx: Context) { */ private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) - /** The realizability status of given type `tp`*/ + /** The realizability status of given type `tp` */ def realizability(tp: Type): Realizability = tp.dealias match { case tp: TermRef => + // Suppose tp is a.b.c.type, where c is declared with type T, then sym is c, tp.info is T and + // and tp.prefix is a.b. val sym = tp.symbol - val r = - 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 - } - if (r == Realizable || tp.info.isStableRealizable) Realizable else r + // We know tp is realizable if either: + // 1. the symbol is stable and the prefix is realizable (so that, say, it contains no vars): + if (sym.is(Stable)) realizability(tp.prefix) + else { + // 2. if tp.info is a realizable singleton type. We check this last + // for performance, in all cases where some unrelated check might have failed. + def patchRealizability(r: Realizability) = + r.mapError(if (tp.info.isStableRealizable) Realizable else _) + val r = + if (!sym.isStable) + patchRealizability(NotStable) + // 3. If the symbol isn't "lazy" and its prefix is realizable + else if (!isLateInitialized(sym)) + // XXX: This is a bit fishy: we only cache that the symbol is + // stable if it appears under a realizable prefix. + // XXX: Add object DependsOnPrefix extends Realizability(""), but filter it out here. + patchRealizability(realizability(tp.prefix)) + // 4. If the symbol can't be overridden, and + else if (!sym.isEffectivelyFinal) + patchRealizability(new NotFinal(sym)) + else + // Since patchRealizability checks realizability(tp.info) through + // isStableRealizable, using patchRealizability wouldn't make + // a difference, and calling it here again might introduce + // a slowdown exponential in the prefix length. + realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) + if (r == Realizable) sym.setFlag(Stable) + r + } case _: SingletonType | NoPrefix => Realizable case tp => From 9e56db8c015e499b875e741d2a30d947eb1d3e95 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 16:42:27 +0200 Subject: [PATCH 08/13] Clarify documentation --- .../src/dotty/tools/dotc/core/CheckRealizable.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 675ace682b35..4ff0ab484b2c 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -68,7 +68,9 @@ class CheckRealizable(implicit ctx: Context) { */ private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) - /** The realizability status of given type `tp` */ + /** The realizability status of given type `tp`. We can only select members from realizable types. + * A type is realizable if it has non-null values. However, realizable types can + * have non-realizable subtypes, so we must restrict overriding. */ def realizability(tp: Type): Realizability = tp.dealias match { case tp: TermRef => // Suppose tp is a.b.c.type, where c is declared with type T, then sym is c, tp.info is T and @@ -83,6 +85,7 @@ class CheckRealizable(implicit ctx: Context) { def patchRealizability(r: Realizability) = r.mapError(if (tp.info.isStableRealizable) Realizable else _) val r = + // Reject fields that are mutable, by-name, and similar. if (!sym.isStable) patchRealizability(NotStable) // 3. If the symbol isn't "lazy" and its prefix is realizable @@ -91,10 +94,13 @@ class CheckRealizable(implicit ctx: Context) { // stable if it appears under a realizable prefix. // XXX: Add object DependsOnPrefix extends Realizability(""), but filter it out here. patchRealizability(realizability(tp.prefix)) - // 4. If the symbol can't be overridden, and else if (!sym.isEffectivelyFinal) patchRealizability(new NotFinal(sym)) else + // 4. If the symbol is effectively final, and a lazy or erased val + // and has a realizable type. We require finality because overrides + // of realizable fields might not be realizable. + // Since patchRealizability checks realizability(tp.info) through // isStableRealizable, using patchRealizability wouldn't make // a difference, and calling it here again might introduce From 4753947dc2e8c6bc99bf6ddb6d981adc9b16eb09 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 17:37:44 +0200 Subject: [PATCH 09/13] Seal Realizability --- compiler/src/dotty/tools/dotc/core/CheckRealizable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 4ff0ab484b2c..5e8a2db93809 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -14,7 +14,7 @@ import ast.tpd._ /** Realizability status */ object CheckRealizable { - abstract class Realizability(val msg: String) { + sealed abstract class Realizability(val msg: String) { def andAlso(other: => Realizability) = if (this == Realizable) other else this def mapError(f: Realizability => Realizability) = From b62f6db12c9777e1b4725e0428239a789210e18f Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 16:45:05 +0200 Subject: [PATCH 10/13] Cache stability of non-lazy stable symbols That is, set is(Stable) for symbols that satisfy isStable. --- .../src/dotty/tools/dotc/core/CheckRealizable.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 5e8a2db93809..788f76748e81 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -89,12 +89,12 @@ class CheckRealizable(implicit ctx: Context) { if (!sym.isStable) patchRealizability(NotStable) // 3. If the symbol isn't "lazy" and its prefix is realizable - else if (!isLateInitialized(sym)) - // XXX: This is a bit fishy: we only cache that the symbol is - // stable if it appears under a realizable prefix. - // XXX: Add object DependsOnPrefix extends Realizability(""), but filter it out here. + else if (!isLateInitialized(sym)) { + // The symbol itself is stable, cache this information: + sym.setFlag(Stable) + // Realizability now depends on the prefix: patchRealizability(realizability(tp.prefix)) - else if (!sym.isEffectivelyFinal) + } else if (!sym.isEffectivelyFinal) patchRealizability(new NotFinal(sym)) else // 4. If the symbol is effectively final, and a lazy or erased val From 7c8dc6e7ee9cf45d4887f814ba59f58eddeb7b8c Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 20:54:03 +0200 Subject: [PATCH 11/13] Add stress-test for realixability checking I wrote this because I feared (incorrectly) exponential slowdowns in realizability checking for this code. But debugging suggests that the complexity of realizability checking is constant in the size of these expressions (even after I disable caching of `Stable`). Beware 1: these expressions almost smash the stack by sheer size. Beware 2: this fails with `-Yno-deep-subtypes`, but simply because the checking heuristics assumes people don't try to do this. --- tests/pos-deep-subtype/i4036.scala | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/pos-deep-subtype/i4036.scala diff --git a/tests/pos-deep-subtype/i4036.scala b/tests/pos-deep-subtype/i4036.scala new file mode 100644 index 000000000000..df06ad9b3b3c --- /dev/null +++ b/tests/pos-deep-subtype/i4036.scala @@ -0,0 +1,64 @@ +trait A { def x: this.type = this; type T } +trait B { def y: this.type = this; def x: y.type = y; type T } +object A { + val v = new A { type T = Int } + v: v. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.type + + 1: v. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.T + + val u = new B { type T = Int } + u: u. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x. + x.type + + val z = new B { type T = this.type } + z: z.T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T# + T#T#T#T#T#T#T#T#T#T#T#T#T#T#T#T +} From bdfdc24f387eeaa6ef4d5c15c1d359d279dc01e1 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 21:12:45 +0200 Subject: [PATCH 12/13] Rename SymDenotation.isStable to SymDenotation.isStableMember This reduces confusion with `Type.isStable`. I'm tempted to call this `isPure`. --- compiler/src/dotty/tools/dotc/ast/TreeInfo.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/CheckRealizable.scala | 2 +- compiler/src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- compiler/src/dotty/tools/dotc/core/Types.scala | 4 ++-- compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala | 2 +- compiler/src/dotty/tools/dotc/transform/Getters.scala | 2 +- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala index d1e91799f31a..2570e1092a71 100644 --- a/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala +++ b/compiler/src/dotty/tools/dotc/ast/TreeInfo.scala @@ -399,7 +399,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => */ private def refPurity(tree: Tree)(implicit ctx: Context): PurityLevel = if (!tree.tpe.widen.isParameterless || tree.symbol.is(Erased)) SimplyPure - else if (!tree.symbol.isStable) Impure + else if (!tree.symbol.isStableMember) Impure else if (tree.symbol.is(Lazy)) Idempotent // TODO add Module flag, sinxce Module vals or not Lazy from the start. else SimplyPure @@ -462,7 +462,7 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] => case tpe: PolyType => maybeGetterType(tpe.resultType) case _ => false } - sym.owner.isClass && !sym.isStable && maybeGetterType(sym.info) + sym.owner.isClass && !sym.isStableMember && maybeGetterType(sym.info) } /** Is tree a reference to a mutable variable, or to a potential getter diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 788f76748e81..cd46cda2f23d 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -86,7 +86,7 @@ class CheckRealizable(implicit ctx: Context) { r.mapError(if (tp.info.isStableRealizable) Realizable else _) val r = // Reject fields that are mutable, by-name, and similar. - if (!sym.isStable) + if (!sym.isStableMember) patchRealizability(NotStable) // 3. If the symbol isn't "lazy" and its prefix is realizable else if (!isLateInitialized(sym)) { diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 059585926954..cd136022cf4c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -599,7 +599,7 @@ object SymDenotations { ) /** Is this a denotation of a stable term (or an arbitrary type)? */ - final def isStable(implicit ctx: Context) = { + final def isStableMember(implicit ctx: Context) = { def isUnstableValue = is(UnstableValue) || info.isInstanceOf[ExprType] isType || is(Stable) || !isUnstableValue } diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 73efd5bbcefd..91daa8a9c1e8 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -148,7 +148,7 @@ object Types { /** Does this type denote a stable reference (i.e. singleton type)? */ final def isStable(implicit ctx: Context): Boolean = stripTypeVar match { - case tp: TermRef => tp.termSymbol.isStable && tp.prefix.isStable || tp.info.isStable + case tp: TermRef => tp.termSymbol.isStableMember && tp.prefix.isStable || tp.info.isStable case _: SingletonType | NoPrefix => true case tp: RefinedOrRecType => tp.parent.isStable case tp: ExprType => tp.resultType.isStable @@ -960,7 +960,7 @@ object Types { /** Widen type if it is unstable (i.e. an ExprType, or TermRef to unstable symbol */ final def widenIfUnstable(implicit ctx: Context): Type = stripTypeVar match { case tp: ExprType => tp.resultType.widenIfUnstable - case tp: TermRef if !tp.symbol.isStable => tp.underlying.widenIfUnstable + case tp: TermRef if !tp.symbol.isStableMember => tp.underlying.widenIfUnstable case _ => this } diff --git a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala index 4be68b0879d4..24dc7e7c5cb5 100644 --- a/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala +++ b/compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala @@ -278,7 +278,7 @@ private class ExtractAPICollector(implicit val ctx: Context) extends ThunkHolder } else if (sym.is(Mutable, butNot = Accessor)) { new api.Var(apiType(sym.info), sym.name.toString, apiAccess(sym), apiModifiers(sym), apiAnnotations(sym).toArray) - } else if (sym.isStable) { + } else if (sym.isStableMember) { new api.Val(apiType(sym.info), sym.name.toString, apiAccess(sym), apiModifiers(sym), apiAnnotations(sym).toArray) } else { diff --git a/compiler/src/dotty/tools/dotc/transform/Getters.scala b/compiler/src/dotty/tools/dotc/transform/Getters.scala index 55e68d69ff11..88487a70b22d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Getters.scala +++ b/compiler/src/dotty/tools/dotc/transform/Getters.scala @@ -59,7 +59,7 @@ class Getters extends MiniPhase with SymTransformer { d.hasAnnotation(defn.ScalaStaticAnnot) || d.isSelfSym if (d.isTerm && (d.is(Lazy) || d.owner.isClass) && d.info.isValueType && !noGetterNeeded) { - val maybeStable = if (d.isStable) Stable else EmptyFlags + val maybeStable = if (d.isStableMember) Stable else EmptyFlags d.copySymDenotation( initFlags = d.flags | maybeStable | AccessorCreationFlags, info = ExprType(d.info)) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index af607c7f395c..4203f5de5897 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -367,7 +367,7 @@ object RefChecks { intersectionIsEmpty(member.extendedOverriddenSymbols, other.extendedOverriddenSymbols)) { overrideError("cannot override a concrete member without a third member that's overridden by both " + "(this rule is designed to prevent ``accidental overrides'')") - } else if (other.isStable && !member.isStable) { // (1.4) + } else if (other.isStableMember && !member.isStableMember) { // (1.4) overrideError("needs to be a stable, immutable value") } else if (member.is(ModuleVal) && !other.isRealMethod && !other.is(Deferred | Lazy)) { overrideError("may not override a concrete non-lazy value") From cccca682f1f4b1798090c9f0524b5f16e429f886 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 21:15:13 +0200 Subject: [PATCH 13/13] Treat erased members like lazy ones Partially address TODO on erased members by treating them like lazy values: they do not exist at runtime, and their initialization might fail, but they're realizable if their type is. I do not treat erased members as _final_ lazy ones because erased definitions are not automatically final, and it's not 100% obvious they should be (after discussion with Nicholas Stucki). Current situation: If `U <: T`, the current rules allow the following override ```scala trait A { erased def foo: T = ... } trait B extends A { erased def foo: U = ... } ``` And just like lazy values, `U` might be unrealizable even when `T` is, so it's unsafe to treat `(a: A).foo` as realizable. The above code might be used (questionably) through ```scala def takeErased(erased a: T): Any def takeBetterErased(erased a: U): String def f(a: A) = a match { case b: B => takeBetterErased(b.foo) case _ => takeErased(a.foo) } ``` It's very unclear whether that's desirable. Alternatively, concrete erased definitions might be made automatically final, and that would be picked up by `isEffectivelyFinal` without further changes. --- compiler/src/dotty/tools/dotc/core/CheckRealizable.scala | 4 ++-- compiler/src/dotty/tools/dotc/core/Flags.scala | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index cd46cda2f23d..42d6f65f1b19 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -88,8 +88,8 @@ class CheckRealizable(implicit ctx: Context) { // Reject fields that are mutable, by-name, and similar. if (!sym.isStableMember) patchRealizability(NotStable) - // 3. If the symbol isn't "lazy" and its prefix is realizable - else if (!isLateInitialized(sym)) { + // 3. If the symbol isn't "lazy" or erased, and its prefix is realizable + else if (!isLateInitialized(sym) && !sym.is(Erased)) { // The symbol itself is stable, cache this information: sym.setFlag(Stable) // Realizability now depends on the prefix: diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index f4b04a47ec26..b1084978a4e5 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -475,9 +475,7 @@ object Flags { /** A value that's unstable unless complemented with a Stable flag */ final val UnstableValue = - Mutable | Method | Erased - // TODO: Erased should be treated as stable just like final lazy is. - // Otherwise the usefulness of Erased is very much reduced. + Mutable | Method /** Flags that express the variance of a type parameter. */ final val VarianceFlags = Covariant | Contravariant