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 =>