From 224b2ac15ac59fd4c9268914096f29a1c47d3238 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 15:06:33 +0100 Subject: [PATCH 1/9] 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 6a5f9da07bca72e82c51ea50e313388b4d0c6be6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 28 Mar 2018 17:57:12 +0200 Subject: [PATCH 2/9] 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 d5d1d4233c3a5cd2d2123c7d25c4a9009e3c62a7 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 16:12:39 +0100 Subject: [PATCH 3/9] Drop stray comma --- 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 788b6d474999..e2ed33880995 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -49,7 +49,7 @@ object CheckRealizable { def boundsRealizability(tp: Type)(implicit ctx: Context): Realizability = new CheckRealizable().boundsRealizability(tp) - private val LateInitialized = Lazy | Erased, + private val LateInitialized = Lazy | Erased } /** Compute realizability status */ From 11fffe0b757a3e3707d82648271d75f0d5f1b112 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 17:37:44 +0200 Subject: [PATCH 4/9] 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 e2ed33880995..f8bf4f33fd62 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 8475876f282a83a86e5b4c3e37ea7ed6f6711816 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 15:11:10 +0100 Subject: [PATCH 5/9] 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 58aabb4deaaab8a62b9148f44e423b37e4221431 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 20 Mar 2018 13:59:43 +0100 Subject: [PATCH 6/9] Align isRealizable with isStable A TermRef is stable if its underlying type is stable. Realizability should behave the same way. Part 1 of 9125f580d44462fbb0f81ce8cdd7344e5cee51d1. --- .../tools/dotc/core/CheckRealizable.scala | 24 ++++++++++--------- .../src/dotty/tools/dotc/core/Types.scala | 7 ++++++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index f8bf4f33fd62..7c3b8303530a 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 576e69e790cc..43f5463ddeb6 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 isStableRealizable(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. From f4358503910b0abefe776e3475255fe49acbb3a3 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Wed, 30 May 2018 02:23:19 +0200 Subject: [PATCH 7/9] Use ProblemInUnderlying consistently --- 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 7c3b8303530a..baf64841ebc4 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -80,7 +80,7 @@ class CheckRealizable(implicit ctx: Context) { else realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) r andAlso { sym.setFlag(Stable) - realizability(tp.prefix) + realizability(tp.prefix).mapError(r => new ProblemInUnderlying(tp.prefix, r)) } } if (r == Realizable || tp.info.isStableRealizable) Realizable else r From 6d063a49b7cd7ce7cf77aa4d6f717f4349c6b3ca Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Tue, 10 Apr 2018 20:54:03 +0200 Subject: [PATCH 8/9] 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 +} From 72ea704680f5d7f91b5ad107fc19d3d125a6e9c5 Mon Sep 17 00:00:00 2001 From: "Paolo G. Giarrusso" Date: Sun, 2 Dec 2018 16:16:00 +0100 Subject: [PATCH 9/9] Refactor realizability to also cache result of isStableRealizable Also streamline logic significantly. --- .../tools/dotc/core/CheckRealizable.scala | 55 ++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala index baf64841ebc4..570e9c1a53e7 100644 --- a/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/compiler/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -66,24 +66,53 @@ class CheckRealizable(implicit ctx: Context) { */ private def isLateInitialized(sym: Symbol) = sym.is(LateInitialized, butNot = Module) - /** The realizability status of given type `tp`*/ + /** The realizability status of given type `tp`. Types can only project + * members from realizable types, that is, types having non-null values and + * not depending on mutable state. + * Beware that subtypes of realizable types might not be realizable; hence + * realizability checking restricts 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 + // and tp.prefix is a.b. val sym = tp.symbol - val r = - if (sym.is(Stable)) realizability(tp.prefix) + // tp is realizable if it selects a stable member on a realizable prefix. + + /** A member is always stable 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 _) + + def computeStableMember(): Realizability = { + // Reject fields that are mutable, by-name, and similar. + if (!sym.isStable) + patchRealizability(NotStable) + // Accept non-lazy symbols "lazy" + else if (!isLateInitialized(sym)) + patchRealizability(Realizable) + // Accept symbols that are lazy/erased, can't be overridden, and + // have a realizable type. We require finality because overrides + // of realizable fields might not be realizable. + else if (!sym.isEffectivelyFinal) + patchRealizability(new NotFinal(sym)) + else + // Since patchRealizability checks realizability(tp.info) through + // isStableRealizable, using patchRealizability wouldn't make + // a difference. Calling it here again might introduce a slowdown + // exponential in the prefix length, so we avoid it at the cost of + // code complexity. + realizability(tp.info).mapError(r => new ProblemInUnderlying(tp.info, r)) + } + + val stableMember = + // 1. the symbol is flagged as stable. + if (sym.is(Stable)) Realizable 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).mapError(r => new ProblemInUnderlying(tp.prefix, r)) - } + val r = computeStableMember() + if (r == Realizable) sym.setFlag(Stable) + r } - if (r == Realizable || tp.info.isStableRealizable) Realizable else r + stableMember andAlso realizability(tp.prefix).mapError(r => new ProblemInUnderlying(tp.prefix, r)) case _: SingletonType | NoPrefix => Realizable case tp =>