From 224b2ac15ac59fd4c9268914096f29a1c47d3238 Mon Sep 17 00:00:00 2001
From: "Paolo G. Giarrusso"
Date: Sun, 2 Dec 2018 15:06:33 +0100
Subject: [PATCH 01/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 6a5f9da07bca72e82c51ea50e313388b4d0c6be6 Mon Sep 17 00:00:00 2001
From: Martin Odersky
Date: Wed, 28 Mar 2018 17:57:12 +0200
Subject: [PATCH 02/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 d5d1d4233c3a5cd2d2123c7d25c4a9009e3c62a7 Mon Sep 17 00:00:00 2001
From: "Paolo G. Giarrusso"
Date: Sun, 2 Dec 2018 16:12:39 +0100
Subject: [PATCH 03/12] 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 04/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 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 05/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 58aabb4deaaab8a62b9148f44e423b37e4221431 Mon Sep 17 00:00:00 2001
From: Martin Odersky
Date: Tue, 20 Mar 2018 13:59:43 +0100
Subject: [PATCH 06/12] 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 07/12] 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 08/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
+}
From 72ea704680f5d7f91b5ad107fc19d3d125a6e9c5 Mon Sep 17 00:00:00 2001
From: "Paolo G. Giarrusso"
Date: Sun, 2 Dec 2018 16:16:00 +0100
Subject: [PATCH 09/12] 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 =>
From 61629e340572b793ac3c681e82f09f3c131b3c26 Mon Sep 17 00:00:00 2001
From: Martin Odersky
Date: Sat, 17 Mar 2018 19:15:46 +0100
Subject: [PATCH 10/12] Fix #4031: Check arguments of dependent methods for
realizability
The first attempt required changing z1720.scala; but that became unnecessary
after aligning isRealizable with isStable.
A TermRef is stable if its underlying type is stable. Realizability should
behave the same way.
---
.../src/dotty/tools/dotc/core/Types.scala | 6 ++--
.../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/pos/i4031.scala | 8 +++++
8 files changed, 97 insertions(+), 14 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/pos/i4031.scala
diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala
index 43f5463ddeb6..a5a371e877a3 100644
--- a/compiler/src/dotty/tools/dotc/core/Types.scala
+++ b/compiler/src/dotty/tools/dotc/core/Types.scala
@@ -4513,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 {
@@ -4586,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
}
@@ -4637,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..38b348227edc 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.isStableRealizable) 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/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
+}
From 75ba8578ed50aedb706977570357ac515819887c Mon Sep 17 00:00:00 2001
From: "Paolo G. Giarrusso"
Date: Sun, 2 Dec 2018 14:40:46 +0100
Subject: [PATCH 11/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 fed71287745203f05e06a5f5a2c774e604c75c66 Mon Sep 17 00:00:00 2001
From: Martin Odersky
Date: Tue, 20 Mar 2018 13:35:33 +0100
Subject: [PATCH 12/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.
---
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 a5a371e877a3..7de8e12cd9ad 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
}