From a24218a1434d368f8a3a68f4162a8c24cae91bc3 Mon Sep 17 00:00:00 2001 From: Abel Nieto Date: Mon, 26 Nov 2018 17:17:48 -0500 Subject: [PATCH 01/12] Fix #5521: prefix not checked in realizability check We were forgetting to check the prefix in the uncached case. --- .../src/dotty/tools/dotc/core/CheckRealizable.scala | 7 +++++-- tests/neg/i5521.scala | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tests/neg/i5521.scala diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 774cc233bff8..13ed730d5729 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -77,8 +77,11 @@ class CheckRealizable(implicit ctx: Context) { 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 r1 = if (r == Realizable) { + sym.setFlag(Stable) + realizability(tp.prefix) + } else r + r1 } case _: SingletonType | NoPrefix => Realizable diff --git a/tests/neg/i5521.scala b/tests/neg/i5521.scala new file mode 100644 index 000000000000..9edb43605036 --- /dev/null +++ b/tests/neg/i5521.scala @@ -0,0 +1,12 @@ +class Hello { + class Foo { + class Bar + final lazy val s: Bar = ??? + } + + lazy val foo: Foo = ??? + + val x: foo.s.type = ??? // error: `foo` must be final since it's a lazy val + val x2: foo.s.type = ??? // error: `foo` must be final since it's a lazy val +} + From b66bcfe519325098d55012bc3ed549114a46f40a Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Thu, 29 Nov 2018 23:56:26 +0100 Subject: [PATCH 02/12] Don't check prefix twice This change also caches stability for the affected code path. --- 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 13ed730d5729..57ad3bb08351 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -74,7 +74,7 @@ class CheckRealizable(implicit ctx: Context) { else { val r = if (!sym.isStable) NotStable - else if (!isLateInitialized(sym)) realizability(tp.prefix) + else if (!isLateInitialized(sym)) Realizable else if (!sym.isEffectivelyFinal) new NotFinal(sym) else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) val r1 = if (r == Realizable) { From 0eea5b3f7c96dc905fc861bac337236495e76d7f Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Fri, 30 Nov 2018 00:00:16 +0100 Subject: [PATCH 03/12] Clean up with andAlso combinator --- compiler/src/dotty/tools/dotc/core/CheckRealizable.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index 57ad3bb08351..788b6d474999 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -77,11 +77,10 @@ class CheckRealizable(implicit ctx: Context) { else if (!isLateInitialized(sym)) Realizable else if (!sym.isEffectivelyFinal) new NotFinal(sym) else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) - val r1 = if (r == Realizable) { + r andAlso { sym.setFlag(Stable) realizability(tp.prefix) - } else r - r1 + } } case _: SingletonType | NoPrefix => Realizable From dbbc0b55edfe41e4b7b34c994b0c7be86a95f909 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 17 Mar 2018 19:15:46 +0100 Subject: [PATCH 04/12] 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 576e69e790cc..e05e53bc3447 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -18,6 +18,7 @@ import Denotations._ import Periods._ import util.Stats._ import util.SimpleIdentitySet +import CheckRealizable._ import reporting.diagnostic.Message import ast.tpd._ import ast.TreeTypeMap @@ -157,6 +158,12 @@ object Types { case _ => false } + /** 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 = + 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. @@ -4506,6 +4513,8 @@ object Types { else if (lo `eq` hi) lo else Range(lower(lo), upper(hi)) + protected def emptyRange: Type = range(defn.NothingType, defn.AnyType) + protected def isRange(tp: Type): Boolean = tp.isInstanceOf[Range] protected def lower(tp: Type): Type = tp match { @@ -4579,7 +4588,7 @@ object Types { forwarded.orElse( range(super.derivedSelect(tp, preLo).loBound, super.derivedSelect(tp, preHi).hiBound)) case _ => - super.derivedSelect(tp, pre) match { + if (pre == defn.AnyType) pre else super.derivedSelect(tp, pre) match { case TypeBounds(lo, hi) => range(lo, hi) case tp => tp } @@ -4630,7 +4639,7 @@ object Types { else tp.derivedTypeBounds(lo, hi) override protected def derivedSuperType(tp: SuperType, thistp: Type, supertp: Type): 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 eea06175ecb0..693cb8a95274 100644 --- a/compiler/src/dotty/tools/dotc/typer/Applications.scala +++ b/compiler/src/dotty/tools/dotc/typer/Applications.scala @@ -478,8 +478,9 @@ trait Applications extends Compatibility { self: Typer with Dynamic => addArg(typedArg(arg, formal), formal) if (methodType.isParamDependent && typeOfArg(arg).exists) // `typeOfArg(arg)` could be missing because the evaluation of `arg` produced type errors - 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 4ca25185d907..a3f7f7f0c920 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -13,6 +13,7 @@ import NameOps._ import collection.mutable import reporting.diagnostic.messages._ import Checking.checkNoPrivateLeaks +import CheckRealizable._ trait TypeAssigner { import tpd._ @@ -105,7 +106,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))) @@ -340,13 +341,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): Type = { + def safeSubstParam(tp: Type, pref: ParamRef, argType: Type, initVariance: Int = 1)(implicit ctx: Context): Type = { 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 f0f5625d8954777814561da4979573a51c3e5adc Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 14:40:46 +0100 Subject: [PATCH 05/12] XXX accept new error report in test After porting the commit from #4036, this line newly gives an error. Might be OK, investigate. --- tests/neg/i50-volatile.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i50-volatile.scala b/tests/neg/i50-volatile.scala index fcf08101f679..c62513d90949 100644 --- a/tests/neg/i50-volatile.scala +++ b/tests/neg/i50-volatile.scala @@ -10,7 +10,7 @@ class Test { } lazy val o: A & B = ??? - class Client extends o.Inner // old-error // old-error + class Client extends o.Inner // error // old-error def xToString(x: o.X): String = x // error From d655af206e14a82c268cddf33670d902236c419a Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 15:06:33 +0100 Subject: [PATCH 06/12] Show that Any#L is still rejected when written by the user --- tests/neg/i4031-anysel.scala | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/neg/i4031-anysel.scala diff --git a/tests/neg/i4031-anysel.scala b/tests/neg/i4031-anysel.scala new file mode 100644 index 000000000000..c97209733375 --- /dev/null +++ b/tests/neg/i4031-anysel.scala @@ -0,0 +1,3 @@ +object Test { + val v: Any = 1: Any#L // error +} From 1644303d2b86997ef31797c4ebf3dfe8927fa4e3 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 15:11:10 +0100 Subject: [PATCH 07/12] Cleanup: don't refer to members of defn directly Extracted from 7659c110cccaaeec6f59cd987ee117e885386762. --- .../src/dotty/tools/dotc/core/TypeComparer.scala | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 60ad1963ee29..3e0d9ae110a2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -319,7 +319,7 @@ class TypeComparer(initctx: Context) extends 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) @@ -404,7 +404,7 @@ class TypeComparer(initctx: Context) extends 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 // 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 @@ -417,7 +417,7 @@ class TypeComparer(initctx: Context) extends 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 @@ -1382,7 +1382,7 @@ class TypeComparer(initctx: Context) extends 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) @@ -1393,8 +1393,7 @@ class TypeComparer(initctx: Context) extends 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. @@ -1424,7 +1423,7 @@ class TypeComparer(initctx: Context) extends 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: From db6aaa3131a7e645f72875a426a3bead3b8cece0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Mar 2018 13:35:33 +0100 Subject: [PATCH 08/12] 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. TODO: in the context of #4108, this seems questionable. Also, `Any#L` seems an invalid type (even tho it is sound to consider it empty), and we'd probably not want to accept it if the user writes it; here it is only OK because it's introduced by avoidance. Or are user-written types checked elsewhere? --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 1 + compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 3e0d9ae110a2..c24ecdfaa667 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -406,6 +406,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling { if (cls2 eq AnyKindClass) 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. diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e05e53bc3447..868dab5575fe 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -4588,7 +4588,7 @@ object Types { forwarded.orElse( range(super.derivedSelect(tp, preLo).loBound, super.derivedSelect(tp, preHi).hiBound)) case _ => - if (pre == defn.AnyType) pre else super.derivedSelect(tp, pre) match { + super.derivedSelect(tp, pre) match { case TypeBounds(lo, hi) => range(lo, hi) case tp => tp } From 89ff456056f48c8fa6b55f3455b9f97b65a3aa70 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Mar 2018 13:59:43 +0100 Subject: [PATCH 09/12] 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 | 24 ++++++++++--------- .../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, 16 insertions(+), 30 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 788b6d474999..2ee6870119ff 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -70,18 +70,20 @@ 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)) Realizable - else if (!sym.isEffectivelyFinal) new NotFinal(sym) - else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) - r andAlso { - sym.setFlag(Stable) - realizability(tp.prefix) + val r = + if (sym.is(Stable)) realizability(tp.prefix) + else { + val r = + if (!sym.isStable) NotStable + else if (!isLateInitialized(sym)) Realizable + else if (!sym.isEffectivelyFinal) new NotFinal(sym) + else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) + r andAlso { + sym.setFlag(Stable) + realizability(tp.prefix) + } } - } + 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 868dab5575fe..7de8e12cd9ad 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -161,7 +161,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 a3f7f7f0c920..38b348227edc 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -353,7 +353,7 @@ trait TypeAssigner { */ def safeSubstParam(tp: Type, pref: ParamRef, argType: Type, initVariance: Int = 1)(implicit ctx: Context): Type = { 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 b23c2c985edff55a7098c17f8ec9865c28d7faaf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Mar 2018 17:57:12 +0200 Subject: [PATCH 10/12] 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 e36a6817fb91..1d84c3478d33 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -979,7 +979,7 @@ class RefChecks extends MiniPhase { thisPhase => checkAllOverrides(cls) tree } catch { - case ex: MergeError => + case ex: TypeError => ctx.error(ex.getMessage, tree.pos) tree } From f2b04c260295070fb0814eab4682008c70da1cb1 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 17:37:44 +0200 Subject: [PATCH 11/12] 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 2ee6870119ff..9089254ae9ad 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -10,7 +10,7 @@ import collection.mutable /** Realizability status */ object CheckRealizable { - abstract class Realizability(val msg: String) { + sealed abstract class Realizability(val msg: String) { def andAlso(other: => Realizability): Realizability = if (this == Realizable) other else this def mapError(f: Realizability => Realizability): Realizability = From 2c2e85f11244f859e4cdbcfc0073e26a6df6522d Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 20:54:03 +0200 Subject: [PATCH 12/12] Add stress-test for realizability 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 +}