From 633e2ebfd42af65f8324aec87a2444bb9cec5eff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jan 2016 21:51:08 +0100 Subject: [PATCH 01/23] No volatile check needed for strict vals. --- src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- tests/neg/cycles.scala | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ac4c870a6ab6..ccbbbe1b9f1c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -522,7 +522,7 @@ object SymDenotations { final def isStable(implicit ctx: Context) = { val isUnstable = (this is UnstableValue) || - ctx.isVolatile(info) && !hasAnnotation(defn.UncheckedStableAnnot) + is(Lazy) && ctx.isVolatile(info) && !hasAnnotation(defn.UncheckedStableAnnot) (this is Stable) || isType || { if (isUnstable) false else { setFlag(Stable); true } diff --git a/tests/neg/cycles.scala b/tests/neg/cycles.scala index 0dd24c309ebc..a2b5e9691645 100644 --- a/tests/neg/cycles.scala +++ b/tests/neg/cycles.scala @@ -1,21 +1,21 @@ -class Foo[T <: U, U <: T] +class Foo[T <: U, U <: T] // error -class Bar[T >: T] +class Bar[T >: T] // error class A { val x: T = ??? - type T <: x.type + type T <: x.type // error } class B { - type T <: x.type + type T <: x.type // error val x: T = ??? } class C { val x: D#T = ??? class D { - type T <: x.type + type T <: x.type // error val z: x.type = ??? } } @@ -23,17 +23,17 @@ class C { class E { class F { type T <: x.type - val z: x.type = ??? + val z: x.type = ??? // error } - val x: F#T = ??? + lazy val x: F#T = ??? } class T1 { - type X = (U, U) // cycle + type X = (U, U) // error type U = X & Int } class T2 { - type X = (U, U) // cycle + type X = (U, U) // error type U = X | Int } object T12 { From 41f056750990a2e6391eec3436077715041d2b8a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 29 Jan 2016 23:46:00 +0100 Subject: [PATCH 02/23] Use isRealizable to identify stable prefixes Replaces isVolatile, which is too weak (and more complicated). Backwards compatibility with Scala2 is ensured by dropping the requirement in Scala2 mode. Fixes #1047, which now compiles without inifinite recursion. --- .../tools/dotc/core/SymDenotations.scala | 4 ++- src/dotty/tools/dotc/core/Types.scala | 17 ++++++++++ test/dotc/tests.scala | 2 +- tests/neg/cycles.scala | 18 +++++------ tests/pos/i1047.scala | 31 +++++++++++++++++++ 5 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 tests/pos/i1047.scala diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ccbbbe1b9f1c..9a432829bff2 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -522,7 +522,9 @@ object SymDenotations { final def isStable(implicit ctx: Context) = { val isUnstable = (this is UnstableValue) || - is(Lazy) && ctx.isVolatile(info) && !hasAnnotation(defn.UncheckedStableAnnot) + is(Lazy, butNot = Module) && + !(info.isRealizable || ctx.scala2Mode) && + !hasAnnotation(defn.UncheckedStableAnnot) (this is Stable) || isType || { if (isUnstable) false else { setFlag(Stable); true } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 47a4f088f754..1d4e80601cf5 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -127,6 +127,23 @@ object Types { false } + /** Is this type realizable in all contexts? */ + def isRealizable(implicit ctx: Context): Boolean = dealias match { + case tp: TermRef => tp.symbol.isStable + case tp: SingletonType => true + case tp => + def isConcrete(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isClass + case tp: TypeProxy => isConcrete(tp.underlying) + case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) + case _ => false + } + isConcrete(tp) && tp.abstractTypeMembers.forall { m => + val bounds = m.info.bounds + bounds.lo <:< bounds.hi + } + } + /** Does this type refer exactly to class symbol `sym`, instead of to a subclass of `sym`? * Implemented like `isRef`, but follows more types: all type proxies as well as and- and or-types */ diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 549dc582c7ce..13010323e5cb 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -146,7 +146,7 @@ class tests extends CompilerTest { @Test def neg_typetest = compileFile(negDir, "typetest", xerrors = 1) @Test def neg_t1569_failedAvoid = compileFile(negDir, "t1569-failedAvoid", xerrors = 1) @Test def neg_clashes = compileFile(negDir, "clashes", xerrors = 2) - @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 8) + @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 9) @Test def neg_boundspropagation = compileFile(negDir, "boundspropagation", xerrors = 5) @Test def neg_refinedSubtyping = compileFile(negDir, "refinedSubtyping", xerrors = 2) @Test def neg_hklower = compileFile(negDir, "hklower", xerrors = 3) diff --git a/tests/neg/cycles.scala b/tests/neg/cycles.scala index a2b5e9691645..77f902bf0deb 100644 --- a/tests/neg/cycles.scala +++ b/tests/neg/cycles.scala @@ -1,39 +1,39 @@ -class Foo[T <: U, U <: T] // error +class Foo[T <: U, U <: T] // error: cycle -class Bar[T >: T] // error +class Bar[T >: T] // error: cycle class A { val x: T = ??? - type T <: x.type // error + type T <: x.type // error: cycle } class B { - type T <: x.type // error + type T <: x.type // error: cycle val x: T = ??? } class C { val x: D#T = ??? class D { - type T <: x.type // error + type T <: x.type // error: cycle val z: x.type = ??? } } class E { class F { - type T <: x.type - val z: x.type = ??? // error + type T <: x.type // error: not stable + val z: x.type = ??? // error: not stable } lazy val x: F#T = ??? } class T1 { - type X = (U, U) // error + type X = (U, U) // error: cycle type U = X & Int } class T2 { - type X = (U, U) // error + type X = (U, U) // error: cycle type U = X | Int } object T12 { diff --git a/tests/pos/i1047.scala b/tests/pos/i1047.scala new file mode 100644 index 000000000000..0f9b54135f6e --- /dev/null +++ b/tests/pos/i1047.scala @@ -0,0 +1,31 @@ +package hello + +object world extends App { + println("hello dotty!") + + trait AnimalPackage { + type Animal <: AnimalU + type AnimalU = { val age: Int } + def newAnimal(a: AnimalU): Animal + def newSubAnimal[T](a: AnimalU & T): Animal & T + } + val p: AnimalPackage = new AnimalPackage { p => + type Animal = AnimalU + override def newAnimal(a: AnimalU): Animal = a + override def newSubAnimal[T](a: AnimalU & T): Animal & T = a + } + val lambda: p.Animal = p.newAnimal(new { val age = 1 }) + trait CatPackage { pc => + type Cat <: p.Animal & pc.CatDelta + type CatDelta = { val meow: Int } + type CatU = p.AnimalU & pc.CatDelta + def newCat(c: CatU): Cat + def newSubCat[T](c: CatU & T): Cat & T + } + val pc: CatPackage = new CatPackage { pc => + type Cat = p.Animal & pc.CatDelta + def newCat(c: CatU): Cat = p.newSubAnimal[pc.CatDelta](c) + def newSubCat[T](c: CatU & T): Cat & T = p.newSubAnimal[pc.CatDelta & T](c) + } + val felix: pc.Cat = pc.newCat(new { val age = 1; val meow = 2 }) +} From 1cc4d90627907a17172f7e8129003f9e9cb0cdf4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jan 2016 01:36:22 +0100 Subject: [PATCH 03/23] Move scala2Mode test from isStable to isRealizable This prepares the way for using isRealizable in different contexts. --- src/dotty/tools/dotc/core/SymDenotations.scala | 4 +--- src/dotty/tools/dotc/core/Types.scala | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 9a432829bff2..3a66e10d4145 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -522,9 +522,7 @@ object SymDenotations { final def isStable(implicit ctx: Context) = { val isUnstable = (this is UnstableValue) || - is(Lazy, butNot = Module) && - !(info.isRealizable || ctx.scala2Mode) && - !hasAnnotation(defn.UncheckedStableAnnot) + is(Lazy, butNot = Module) && !info.isRealizable && !hasAnnotation(defn.UncheckedStableAnnot) (this is Stable) || isType || { if (isUnstable) false else { setFlag(Stable); true } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 1d4e80601cf5..db0831ba09ef 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -138,10 +138,12 @@ object Types { case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) case _ => false } - isConcrete(tp) && tp.abstractTypeMembers.forall { m => + isConcrete(tp) && + tp.abstractTypeMembers.forall { m => val bounds = m.info.bounds bounds.lo <:< bounds.hi - } + } || + ctx.scala2Mode } /** Does this type refer exactly to class symbol `sym`, instead of to a subclass of `sym`? From 1b7745e5f9c0e251436b33247133f3810838cf12 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jan 2016 05:12:47 +0100 Subject: [PATCH 04/23] Also consider type aliases when checking for realizability Fixes #50. --- src/dotty/tools/dotc/core/Types.scala | 17 ++++++++++++++++- test/dotc/tests.scala | 1 + tests/neg/i1050.scala | 24 ++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/neg/i1050.scala diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index db0831ba09ef..7e8a11e5ba76 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -139,7 +139,7 @@ object Types { case _ => false } isConcrete(tp) && - tp.abstractTypeMembers.forall { m => + tp.nonClassTypeMembers.forall { m => val bounds = m.info.bounds bounds.lo <:< bounds.hi } || @@ -592,6 +592,12 @@ object Types { (name, buf) => buf += member(name).asSingleDenotation) } + /** The set of abstract type members of this type. */ + final def nonClassTypeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("nonClassTypeMembers") { + memberDenots(nonClassTypeNameFilter, + (name, buf) => buf += member(name).asSingleDenotation) + } + /** The set of type members of this type */ final def typeMembers(implicit ctx: Context): Seq[SingleDenotation] = track("typeMembers") { memberDenots(typeNameFilter, @@ -3387,6 +3393,15 @@ object Types { } } + /** A filter for names of abstract types of a given type */ + object nonClassTypeNameFilter extends NameFilter { + def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = + name.isTypeName && { + val mbr = pre.member(name) + mbr.symbol.isType && !mbr.symbol.isClass + } + } + /** A filter for names of deferred term definitions of a given type */ object abstractTermNameFilter extends NameFilter { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 13010323e5cb..85b1db139196 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -161,6 +161,7 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala new file mode 100644 index 000000000000..48e2b5f2e119 --- /dev/null +++ b/tests/neg/i1050.scala @@ -0,0 +1,24 @@ +trait A { type L <: Nothing } +trait B { type L >: Any} +object Test { + lazy val x: A & B = ??? + val y: x.L = 1 + val z: String = y +} +object Test50 { + trait A { + type X = String + } + trait B { + type X = Int + } + lazy val o: A & B = ??? + + def xToString(x: o.X): String = x + + def intToString(i: Int): String = xToString(i) + + def main(args: Array[String]) = { + val s: String = intToString(1) + } +} From 7ccd02c2cd23e4187f3e3a378973704cecd6459a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jan 2016 20:01:14 +0100 Subject: [PATCH 05/23] Realizability refactoring Distinguish between isStable and isRealizable. Issue migration warnings for realizibility failures. Provide error diagnostics why something is not realizable. --- src/dotty/tools/dotc/core/Flags.scala | 2 +- .../tools/dotc/core/SymDenotations.scala | 16 ++++---- src/dotty/tools/dotc/core/Types.scala | 40 ++++++++++++++----- src/dotty/tools/dotc/typer/Checking.scala | 20 +++++++--- src/dotty/tools/dotc/typer/Typer.scala | 7 ++-- 5 files changed, 58 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/core/Flags.scala b/src/dotty/tools/dotc/core/Flags.scala index 42d06c2ab0f8..8c9db3a5c965 100644 --- a/src/dotty/tools/dotc/core/Flags.scala +++ b/src/dotty/tools/dotc/core/Flags.scala @@ -300,7 +300,7 @@ object Flags { */ final val Abstract = commonFlag(23, "abstract") - /** Method is assumed to be stable */ + /** Lazy val or method is known or assumed to be stable and realizable */ final val Stable = termFlag(24, "") /** A case parameter accessor */ diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 3a66e10d4145..fa199ab53abc 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -519,15 +519,15 @@ object SymDenotations { ) /** Is this a denotation of a stable term (or an arbitrary type)? */ - final def isStable(implicit ctx: Context) = { - val isUnstable = - (this is UnstableValue) || - is(Lazy, butNot = Module) && !info.isRealizable && !hasAnnotation(defn.UncheckedStableAnnot) - (this is Stable) || isType || { - if (isUnstable) false - else { setFlag(Stable); true } + final def isStable(implicit ctx: Context) = + isType || !is(UnstableValue, butNot = Stable) + + /** Is this a denotation of a realizable term (or an arbitrary type)? */ + final def isRealizable(implicit ctx: Context) = + is(Stable) || isType || { + val isRealizable = !is(Lazy, butNot = Module) || info.realizability == Realizable + isRealizable && { setFlag(Stable); true } } - } /** Is this a "real" method? A real method is a method which is: * - not an accessor diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 7e8a11e5ba76..0a5050148503 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -127,10 +127,12 @@ object Types { false } - /** Is this type realizable in all contexts? */ - def isRealizable(implicit ctx: Context): Boolean = dealias match { - case tp: TermRef => tp.symbol.isStable - case tp: SingletonType => true + /** The realizibility status of this type */ + def realizability(implicit ctx: Context): Realizability = dealias match { + case tp: TermRef => + if (tp.symbol.isRealizable) Realizable else NotStable + case _: SingletonType | NoPrefix => + Realizable case tp => def isConcrete(tp: Type): Boolean = tp.dealias match { case tp: TypeRef => tp.symbol.isClass @@ -138,12 +140,17 @@ object Types { case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) case _ => false } - isConcrete(tp) && - tp.nonClassTypeMembers.forall { m => - val bounds = m.info.bounds - bounds.lo <:< bounds.hi - } || - ctx.scala2Mode + if (!isConcrete(tp)) NotConcrete + else { + def hasBadBounds(mbr: SingleDenotation) = { + val bounds = mbr.info.bounds + !(bounds.lo <:< bounds.hi) + } + tp.nonClassTypeMembers.find(hasBadBounds) match { + case Some(mbr) => new HasProblemBounds(mbr) + case _ => Realizable + } + } } /** Does this type refer exactly to class symbol `sym`, instead of to a subclass of `sym`? @@ -3429,6 +3436,19 @@ object Types { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true } + // ----- Realizibility Status ----------------------------------------------------- + + abstract class Realizability(val msg: String) + + object Realizable extends Realizability("") + + object NotConcrete extends Realizability("is not a concrete type") + + object NotStable extends Realizability("is not a stable reference") + + class HasProblemBounds(mbr: SingleDenotation)(implicit ctx: Context) + extends Realizability(i"has a member $mbr with possibly empty bounds ${mbr.info.bounds.lo} .. ${mbr.info.bounds.hi}") + // ----- Exceptions ------------------------------------------------------------- class TypeError(msg: String) extends Exception(msg) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 0b4787d15905..3a55352fd208 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -318,9 +318,18 @@ trait Checking { } /** Check that type `tp` is stable. */ - def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = - if (!tp.isStable && !tp.isErroneous) - ctx.error(d"$tp is not stable", pos) + def checkStableAndRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = + if (!tp.isStable) ctx.error(d"$tp is not stable", pos) + else checkRealizable(tp, pos) + + /** Check that type `tp` is realizable. */ + def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = tp.realizability + if (rstatus ne Realizable) { + def msg = d"$tp is not a legal path since it ${rstatus.msg}" + if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) + } + } /** Check that `tp` is a class type with a stable prefix. Also, if `traitReq` is * true check that `tp` is a trait. @@ -330,7 +339,7 @@ trait Checking { def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp.underlyingClassRef(refinementOK = false) match { case tref: TypeRef => - if (ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos) + if (ctx.phase <= ctx.refchecksPhase) checkStableAndRealizable(tref.prefix, pos) if (traitReq && !(tref.symbol is Trait)) ctx.error(d"$tref is not a trait", pos) tp case _ => @@ -433,7 +442,8 @@ trait NoChecking extends Checking { import tpd._ override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree - override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def checkStableAndRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index c94c90d1de4a..79035108eee4 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -308,7 +308,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { def asSelect(implicit ctx: Context): Tree = { val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) - if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) + if (tree.name.isTypeName) checkStableAndRealizable(qual1.tpe, qual1.pos) typedSelect(tree, pt, qual1) } @@ -342,6 +342,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): Tree = track("typedSelectFromTypeTree") { val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) + //checkRealizable(qual1.tpe, qual1.pos) assignType(cpy.SelectFromTypeTree(tree)(qual1, tree.name), qual1) } @@ -822,7 +823,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(implicit ctx: Context): SingletonTypeTree = track("typedSingletonTypeTree") { val ref1 = typedExpr(tree.ref) - checkStable(ref1.tpe, tree.pos) + checkStableAndRealizable(ref1.tpe, tree.pos) assignType(cpy.SingletonTypeTree(tree)(ref1), ref1) } @@ -1056,7 +1057,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) - checkStable(expr1.tpe, imp.expr.pos) + checkStableAndRealizable(expr1.tpe, imp.expr.pos) assignType(cpy.Import(imp)(expr1, imp.selectors), sym) } From 322bcfef33834eb1e57c874c5a745faf65e1b8bc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 30 Jan 2016 20:22:41 +0100 Subject: [PATCH 06/23] Move realizability logic from Types to TypeOps. Types is already big enough. --- .../tools/dotc/core/SymDenotations.scala | 2 +- src/dotty/tools/dotc/core/TypeOps.scala | 44 ++++++++++++++++++- src/dotty/tools/dotc/core/Types.scala | 39 ---------------- src/dotty/tools/dotc/typer/Checking.scala | 4 +- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index fa199ab53abc..ba8dde62d19f 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -525,7 +525,7 @@ object SymDenotations { /** Is this a denotation of a realizable term (or an arbitrary type)? */ final def isRealizable(implicit ctx: Context) = is(Stable) || isType || { - val isRealizable = !is(Lazy, butNot = Module) || info.realizability == Realizable + val isRealizable = !is(Lazy, butNot = Module) || ctx.realizability(info) == TypeOps.Realizable isRealizable && { setFlag(Stable); true } } diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 34febf3be689..26ce4ebf8bd8 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -3,7 +3,7 @@ package dotc package core import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ -import SymDenotations._, Denotations.Denotation +import SymDenotations._, Denotations.SingleDenotation import config.Printers._ import util.Positions._ import Decorators._ @@ -14,6 +14,7 @@ import collection.mutable import ast.tpd._ trait TypeOps { this: Context => // TODO: Make standalone object. + import TypeOps._ /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec * for what this means. Called very often, so the code is optimized heavily. @@ -427,10 +428,36 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case OrType(l, r) => DNF(l) | DNF(r) case tp => - TypeOps.emptyDNF + emptyDNF } } + /** The realizability status of given type `tp`*/ + def realizability(tp: Type): Realizability = tp.dealias match { + case tp: TermRef => + if (tp.symbol.isRealizable) Realizable else NotStable + case _: SingletonType | NoPrefix => + Realizable + case tp => + def isConcrete(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isClass + case tp: TypeProxy => isConcrete(tp.underlying) + case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) + case _ => false + } + if (!isConcrete(tp)) NotConcrete + else { + def hasBadBounds(mbr: SingleDenotation) = { + val bounds = mbr.info.bounds + !(bounds.lo <:< bounds.hi) + } + tp.nonClassTypeMembers.find(hasBadBounds) match { + case Some(mbr) => new HasProblemBounds(mbr) + case _ => Realizable + } + } + } + private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = { val lazyInfo = new LazyType { // needed so we do not force `formal`. def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { @@ -650,4 +677,17 @@ trait TypeOps { this: Context => // TODO: Make standalone object. object TypeOps { val emptyDNF = (Nil, Set[Name]()) :: Nil @sharable var track = false // !!!DEBUG + + // ----- Realizibility Status ----------------------------------------------------- + + abstract class Realizability(val msg: String) + + object Realizable extends Realizability("") + + object NotConcrete extends Realizability("is not a concrete type") + + object NotStable extends Realizability("is not a stable reference") + + class HasProblemBounds(mbr: SingleDenotation)(implicit ctx: Context) + extends Realizability(i"has a member $mbr with possibly empty bounds ${mbr.info.bounds.lo} .. ${mbr.info.bounds.hi}") } diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 0a5050148503..8595da6405a9 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -127,32 +127,6 @@ object Types { false } - /** The realizibility status of this type */ - def realizability(implicit ctx: Context): Realizability = dealias match { - case tp: TermRef => - if (tp.symbol.isRealizable) Realizable else NotStable - case _: SingletonType | NoPrefix => - Realizable - case tp => - def isConcrete(tp: Type): Boolean = tp.dealias match { - case tp: TypeRef => tp.symbol.isClass - case tp: TypeProxy => isConcrete(tp.underlying) - case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) - case _ => false - } - if (!isConcrete(tp)) NotConcrete - else { - def hasBadBounds(mbr: SingleDenotation) = { - val bounds = mbr.info.bounds - !(bounds.lo <:< bounds.hi) - } - tp.nonClassTypeMembers.find(hasBadBounds) match { - case Some(mbr) => new HasProblemBounds(mbr) - case _ => Realizable - } - } - } - /** Does this type refer exactly to class symbol `sym`, instead of to a subclass of `sym`? * Implemented like `isRef`, but follows more types: all type proxies as well as and- and or-types */ @@ -3436,19 +3410,6 @@ object Types { def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean = true } - // ----- Realizibility Status ----------------------------------------------------- - - abstract class Realizability(val msg: String) - - object Realizable extends Realizability("") - - object NotConcrete extends Realizability("is not a concrete type") - - object NotStable extends Realizability("is not a stable reference") - - class HasProblemBounds(mbr: SingleDenotation)(implicit ctx: Context) - extends Realizability(i"has a member $mbr with possibly empty bounds ${mbr.info.bounds.lo} .. ${mbr.info.bounds.hi}") - // ----- Exceptions ------------------------------------------------------------- class TypeError(msg: String) extends Exception(msg) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 3a55352fd208..d333efa335f1 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -324,8 +324,8 @@ trait Checking { /** Check that type `tp` is realizable. */ def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { - val rstatus = tp.realizability - if (rstatus ne Realizable) { + val rstatus = ctx.realizability(tp) + if (rstatus ne TypeOps.Realizable) { def msg = d"$tp is not a legal path since it ${rstatus.msg}" if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) } From 44c14b3fb6e5eb6f2b9734f092eef1d85f6b4d18 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jan 2016 03:19:12 +0100 Subject: [PATCH 07/23] Cleanup of isEffectivelyFinal EffectivelyFinal came without documentation, so it was not clear what is was supposed to compute. I looked at the use sites, and it seems that all they need is "impossible to override". So I changed the code to do that and dropped the additional condition that members of modules or final classes were not allowed to be lazy or mutable. It was not clear to me what that had to do with finality. --- src/dotty/tools/dotc/core/SymDenotations.scala | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index ba8dde62d19f..98def30716ca 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -816,14 +816,11 @@ object SymDenotations { enclClass(symbol, false) } - final def isEffectivelyFinal(implicit ctx: Context): Boolean = { - (this.flags is Flags.PrivateOrFinal) || (!this.owner.isClass) || - ((this.owner.flags is (Flags.ModuleOrFinal)) && (!this.flags.is(Flags.MutableOrLazy))) || - (this.owner.isAnonymousClass) - } + /** A symbol is effectively final if it cannot be overridden in a subclass */ + final def isEffectivelyFinal(implicit ctx: Context): Boolean = + is(PrivateOrFinal) || !owner.isClass || owner.is(ModuleOrFinal) || owner.isAnonymousClass - /** The class containing this denotation which has the given effective name. - */ + /** The class containing this denotation which has the given effective name. */ final def enclosingClassNamed(name: Name)(implicit ctx: Context): Symbol = { val cls = enclosingClass if (cls.effectiveName == name || !cls.exists) cls else cls.owner.enclosingClassNamed(name) From 9a6f82b2ecfd7462d0a1f4e0464878fd58231277 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jan 2016 14:03:26 +0100 Subject: [PATCH 08/23] Reorganize tests to account for new typing of projection Tests with failed projections are moved to pos-scala2, which was renamed from pos-special. Files in pos-scala2 are compiled with -language:Scala2 option. --- src/dotty/tools/dotc/core/TypeOps.scala | 17 +++-- src/dotty/tools/dotc/typer/Checking.scala | 9 ++- src/dotty/tools/dotc/typer/Typer.scala | 16 +++-- test/dotc/tests.scala | 11 ++-- tests/neg/cycles.scala | 10 +-- tests/neg/i1050.scala | 62 ++++++++++++++++++- tests/neg/ski.scala | 22 +++---- tests/neg/t2994.scala | 6 +- tests/{pos-special => pos-scala2}/i871.scala | 0 tests/{pos => pos-scala2}/t1292.scala | 0 tests/{pos => pos-scala2}/t2994.scala | 0 tests/{pos => pos-scala2}/t3568.scala | 0 tests/{pos => pos-scala2}/t3731.scala | 0 tests/{pos => pos-scala2}/t3833.scala | 0 tests/{pos => pos-scala2}/t5070.scala | 0 tests/{pos => pos-scala2}/t5541.scala | 0 .../variances-constr.scala | 0 tests/pos-special/i871.flags | 1 - 18 files changed, 112 insertions(+), 42 deletions(-) rename tests/{pos-special => pos-scala2}/i871.scala (100%) rename tests/{pos => pos-scala2}/t1292.scala (100%) rename tests/{pos => pos-scala2}/t2994.scala (100%) rename tests/{pos => pos-scala2}/t3568.scala (100%) rename tests/{pos => pos-scala2}/t3731.scala (100%) rename tests/{pos => pos-scala2}/t3833.scala (100%) rename tests/{pos => pos-scala2}/t5070.scala (100%) rename tests/{pos => pos-scala2}/t5541.scala (100%) rename tests/{pos-special => pos-scala2}/variances-constr.scala (100%) delete mode 100644 tests/pos-special/i871.flags diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 26ce4ebf8bd8..9fcc6abbcfa6 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -435,7 +435,10 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** The realizability status of given type `tp`*/ def realizability(tp: Type): Realizability = tp.dealias match { case tp: TermRef => - if (tp.symbol.isRealizable) Realizable else NotStable + if (tp.symbol.isRealizable) Realizable + else if (!tp.symbol.isStable) NotStable + else if (!tp.symbol.isEffectivelyFinal) new NotFinal(tp.symbol) + else new ProblemInUnderlying(tp.info, realizability(tp.info)) case _: SingletonType | NoPrefix => Realizable case tp => @@ -684,10 +687,16 @@ object TypeOps { object Realizable extends Realizability("") - object NotConcrete extends Realizability("is not a concrete type") + object NotConcrete extends Realizability("it is not a concrete type") - object NotStable extends Realizability("is not a stable reference") + object NotStable extends Realizability("it is not a stable reference") + + class NotFinal(sym: Symbol)(implicit ctx: Context) + extends Realizability(i"it refers to nonfinal $sym") class HasProblemBounds(mbr: SingleDenotation)(implicit ctx: Context) - extends Realizability(i"has a member $mbr with possibly empty bounds ${mbr.info.bounds.lo} .. ${mbr.info.bounds.hi}") + extends Realizability(i"it has a member $mbr with possibly conflicting bounds ${mbr.info.bounds.lo} <: ... <: ${mbr.info.bounds.hi}") + + class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) + extends Realizability(i"its underlying type ${tp} ${problem.msg}") } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index d333efa335f1..ee1c2a06155c 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -318,15 +318,14 @@ trait Checking { } /** Check that type `tp` is stable. */ - def checkStableAndRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = + def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = if (!tp.isStable) ctx.error(d"$tp is not stable", pos) - else checkRealizable(tp, pos) /** Check that type `tp` is realizable. */ def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { val rstatus = ctx.realizability(tp) if (rstatus ne TypeOps.Realizable) { - def msg = d"$tp is not a legal path since it ${rstatus.msg}" + def msg = d"$tp is not a legal path since ${rstatus.msg}" if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) } } @@ -339,7 +338,7 @@ trait Checking { def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp.underlyingClassRef(refinementOK = false) match { case tref: TypeRef => - if (ctx.phase <= ctx.refchecksPhase) checkStableAndRealizable(tref.prefix, pos) + if (ctx.phase <= ctx.refchecksPhase) checkStable(tref.prefix, pos) if (traitReq && !(tref.symbol is Trait)) ctx.error(d"$tref is not a trait", pos) tp case _ => @@ -442,7 +441,7 @@ trait NoChecking extends Checking { import tpd._ override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree - override def checkStableAndRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () + override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 79035108eee4..ce0a5c0f2714 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -308,7 +308,10 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { def asSelect(implicit ctx: Context): Tree = { val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) - if (tree.name.isTypeName) checkStableAndRealizable(qual1.tpe, qual1.pos) + if (tree.name.isTypeName) { + checkStable(qual1.tpe, qual1.pos) + checkRealizable(qual1.tpe, qual1.pos) + } typedSelect(tree, pt, qual1) } @@ -342,7 +345,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): Tree = track("typedSelectFromTypeTree") { val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) - //checkRealizable(qual1.tpe, qual1.pos) + checkRealizable(qual1.tpe, qual1.pos) assignType(cpy.SelectFromTypeTree(tree)(qual1, tree.name), qual1) } @@ -823,7 +826,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(implicit ctx: Context): SingletonTypeTree = track("typedSingletonTypeTree") { val ref1 = typedExpr(tree.ref) - checkStableAndRealizable(ref1.tpe, tree.pos) + checkStable(ref1.tpe, tree.pos) + checkRealizable(ref1.tpe, tree.pos) assignType(cpy.SingletonTypeTree(tree)(ref1), ref1) } @@ -920,8 +924,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def completeAnnotations(mdef: untpd.MemberDef, sym: Symbol)(implicit ctx: Context): Unit = { // necessary to force annotation trees to be computed. sym.annotations.foreach(_.tree) - // necessary in order to mark the typed ahead annotations as definitiely typed: - untpd.modsDeco(mdef).mods.annotations.mapconserve(typedAnnotation) + // necessary in order to mark the typed ahead annotations as definitely typed: + untpd.modsDeco(mdef).mods.annotations.foreach(typedAnnotation) } def typedAnnotation(annot: untpd.Tree)(implicit ctx: Context): Tree = track("typedAnnotation") { @@ -1057,7 +1061,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) - checkStableAndRealizable(expr1.tpe, imp.expr.pos) + checkStable(expr1.tpe, imp.expr.pos) assignType(cpy.Import(imp)(expr1, imp.selectors), sym) } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 85b1db139196..3414f197741b 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -42,6 +42,7 @@ class tests extends CompilerTest { val testsDir = "./tests/" val posDir = testsDir + "pos/" val posSpecialDir = testsDir + "pos-special/" + val posScala2Dir = testsDir + "pos-scala2/" val negDir = testsDir + "neg/" val runDir = testsDir + "run/" val newDir = testsDir + "new/" @@ -101,8 +102,8 @@ class tests extends CompilerTest { @Test def pos_all = compileFiles(posDir) // twice omitted to make tests run faster - @Test def pos_i871 = compileFile(posSpecialDir, "i871", scala2mode) - @Test def pos_variancesConstr = compileFile(posSpecialDir, "variances-constr", scala2mode) + @Test def pos_scala2_all = compileFiles(posScala2Dir, scala2mode) + @Test def pos_859 = compileFile(posSpecialDir, "i859", scala2mode)(allowDeepSubtypes) @Test def new_all = compileFiles(newDir, twice) @@ -137,7 +138,7 @@ class tests extends CompilerTest { @Test def neg_t1843_variances = compileFile(negDir, "t1843-variances", xerrors = 1) @Test def neg_t2660_ambi = compileFile(negDir, "t2660", xerrors = 2) - @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2) + @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 5) @Test def neg_subtyping = compileFile(negDir, "subtyping", xerrors = 5) @Test def neg_variances = compileFile(negDir, "variances", xerrors = 2) @Test def neg_variancesConstr = compileFile(negDir, "variances-constr", xerrors = 2) @@ -161,7 +162,7 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 2) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 3) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) @@ -170,7 +171,7 @@ class tests extends CompilerTest { @Test def neg_selfreq = compileFile(negDir, "selfreq", xerrors = 2) @Test def neg_singletons = compileFile(negDir, "singletons", xerrors = 8) @Test def neg_shadowedImplicits = compileFile(negDir, "arrayclone-new", xerrors = 2) - @Test def neg_ski = compileFile(negDir, "ski", xerrors = 2) + @Test def neg_ski = compileFile(negDir, "ski", xerrors = 10) @Test def neg_traitParamsTyper = compileFile(negDir, "traitParamsTyper", xerrors = 5) @Test def neg_traitParamsMixin = compileFile(negDir, "traitParamsMixin", xerrors = 2) @Test def neg_firstError = compileFile(negDir, "firstError", xerrors = 3) diff --git a/tests/neg/cycles.scala b/tests/neg/cycles.scala index 77f902bf0deb..550bc98ead56 100644 --- a/tests/neg/cycles.scala +++ b/tests/neg/cycles.scala @@ -9,11 +9,11 @@ class A { class B { type T <: x.type // error: cycle - val x: T = ??? + final val x: T = ??? } class C { - val x: D#T = ??? + final val x: D#T = ??? // error: conflicting bounds class D { type T <: x.type // error: cycle val z: x.type = ??? @@ -25,7 +25,7 @@ class E { type T <: x.type // error: not stable val z: x.type = ??? // error: not stable } - lazy val x: F#T = ??? + lazy val x: F#T = ??? // error: conflicting bounds } class T1 { @@ -37,6 +37,6 @@ class T2 { type U = X | Int } object T12 { - ??? : (T1 {})#U - ??? : (T2 {})#U + ??? : (T1 {})#U // error: conflicting bounds + ??? : (T2 {})#U // error: conflicting bounds } diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index 48e2b5f2e119..bc906f4f4e70 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -2,7 +2,7 @@ trait A { type L <: Nothing } trait B { type L >: Any} object Test { lazy val x: A & B = ??? - val y: x.L = 1 + val y: x.L = 1 // error: underlying conflicting bounds val z: String = y } object Test50 { @@ -14,7 +14,7 @@ object Test50 { } lazy val o: A & B = ??? - def xToString(x: o.X): String = x + def xToString(x: o.X): String = x // error: underlying conflicting bounds def intToString(i: Int): String = xToString(i) @@ -22,3 +22,61 @@ object Test50 { val s: String = intToString(1) } } +object Test2 { + + trait C { type A } + + type T = C { type A = Any } + type U = C { type A = Nothing } + type X = T & U + + def main(args: Array[String]) = { + val y: X#A = 1 // error: conflicting bounds + val z: String = y + } +} +object Tiark1 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + val p: B + def brand(x: Any): p.L = x // error: not final + } + trait V extends U { + lazy val p: A & B = ??? + } + val v = new V {} + v.brand("boom!") +} +object Tiark2 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + type X <: B + val p: X + def brand(x: Any): p.L = x // error: not final + } + trait V extends U { + type X = B & A + lazy val p: X = ??? + } + val v = new V {} + v.brand("boom!"): Nothing +} +/* +object Import { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + val p: B + def brand(x: Any): p.L = x // error: not final + locally { import p._ + } + } + trait V extends U { + lazy val p: A & B = ??? + } + val v = new V {} + v.brand("boom!") +} +*/ diff --git a/tests/neg/ski.scala b/tests/neg/ski.scala index 6510e66aeefe..8fb35c7f2ba1 100644 --- a/tests/neg/ski.scala +++ b/tests/neg/ski.scala @@ -17,8 +17,8 @@ trait S2[x <: Term, y <: Term] extends Term { type eval = S2[x, y] } trait S3[x <: Term, y <: Term, z <: Term] extends Term { - type ap[v <: Term] = eval#ap[v] - type eval = x#ap[z]#ap[y#ap[z]]#eval + type ap[v <: Term] = eval#ap[v] // error + type eval = x#ap[z]#ap[y#ap[z]]#eval // error // error } // The K combinator @@ -31,8 +31,8 @@ trait K1[x <: Term] extends Term { type eval = K1[x] } trait K2[x <: Term, y <: Term] extends Term { - type ap[z <: Term] = eval#ap[z] - type eval = x#eval + type ap[z <: Term] = eval#ap[z] // error + type eval = x#eval // error } // The I combinator @@ -41,8 +41,8 @@ trait I extends Term { type eval = I } trait I1[x <: Term] extends Term { - type ap[y <: Term] = eval#ap[y] - type eval = x#eval + type ap[y <: Term] = eval#ap[y] // error + type eval = x#eval // error } // Constants @@ -64,9 +64,9 @@ case class Equals[A >: B <:B , B]() object Test { type T1 = Equals[Int, Int] // compiles fine - type T2 = Equals[String, Int] // error + type T2 = Equals[String, Int] // was error, now masked type T3 = Equals[I#ap[c]#eval, c] - type T3a = Equals[I#ap[c]#eval, d]// error + type T3a = Equals[I#ap[c]#eval, d] // was error, now masked // Ic -> c type T4 = Equals[I#ap[c]#eval, c] @@ -106,11 +106,11 @@ object Test { type eval = A0 } trait A1 extends Term { - type ap[x <: Term] = x#ap[A0]#eval + type ap[x <: Term] = x#ap[A0]#eval // error type eval = A1 } trait A2 extends Term { - type ap[x <: Term] = x#ap[A1]#eval + type ap[x <: Term] = x#ap[A1]#eval // error type eval = A2 } @@ -126,7 +126,7 @@ object Test { type T15 = Equals[NN3#eval, c] trait An extends Term { - type ap[x <: Term] = x#ap[An]#eval + type ap[x <: Term] = x#ap[An]#eval // error type eval = An } diff --git a/tests/neg/t2994.scala b/tests/neg/t2994.scala index 9e9c4ec087b0..9827b19896f6 100644 --- a/tests/neg/t2994.scala +++ b/tests/neg/t2994.scala @@ -7,7 +7,7 @@ object Naturals { type a[s[_ <: NAT] <: NAT, z <: NAT] = z } final class SUCC[n <: NAT] extends NAT { - type a[s[_ <: NAT] <: NAT, z <: NAT] = s[n#a[s, z]] + type a[s[_ <: NAT] <: NAT, z <: NAT] = s[n#a[s, z]] // error: not a legal path } type _0 = ZERO type _1 = SUCC[_0] @@ -21,8 +21,8 @@ object Naturals { // crashes scala-2.8.0 beta1 trait MUL[n <: NAT, m <: NAT] extends NAT { trait curry[n[_[_], _], s[_]] { type f[z <: NAT] = n[s, z] } // can't do double param lists: - // error: `]' expected but `[` found. - type a[s[_ <: NAT] <: NAT, z <: NAT] = n#a[curry[m#a, s]#f, z] + // error: `]' expected but `[` found. // error: wrong number of type arguments + type a[s[_ <: NAT] <: NAT, z <: NAT] = n#a[curry[m#a, s]#f, z] // error: not a legal path // error: not a legal path } } diff --git a/tests/pos-special/i871.scala b/tests/pos-scala2/i871.scala similarity index 100% rename from tests/pos-special/i871.scala rename to tests/pos-scala2/i871.scala diff --git a/tests/pos/t1292.scala b/tests/pos-scala2/t1292.scala similarity index 100% rename from tests/pos/t1292.scala rename to tests/pos-scala2/t1292.scala diff --git a/tests/pos/t2994.scala b/tests/pos-scala2/t2994.scala similarity index 100% rename from tests/pos/t2994.scala rename to tests/pos-scala2/t2994.scala diff --git a/tests/pos/t3568.scala b/tests/pos-scala2/t3568.scala similarity index 100% rename from tests/pos/t3568.scala rename to tests/pos-scala2/t3568.scala diff --git a/tests/pos/t3731.scala b/tests/pos-scala2/t3731.scala similarity index 100% rename from tests/pos/t3731.scala rename to tests/pos-scala2/t3731.scala diff --git a/tests/pos/t3833.scala b/tests/pos-scala2/t3833.scala similarity index 100% rename from tests/pos/t3833.scala rename to tests/pos-scala2/t3833.scala diff --git a/tests/pos/t5070.scala b/tests/pos-scala2/t5070.scala similarity index 100% rename from tests/pos/t5070.scala rename to tests/pos-scala2/t5070.scala diff --git a/tests/pos/t5541.scala b/tests/pos-scala2/t5541.scala similarity index 100% rename from tests/pos/t5541.scala rename to tests/pos-scala2/t5541.scala diff --git a/tests/pos-special/variances-constr.scala b/tests/pos-scala2/variances-constr.scala similarity index 100% rename from tests/pos-special/variances-constr.scala rename to tests/pos-scala2/variances-constr.scala diff --git a/tests/pos-special/i871.flags b/tests/pos-special/i871.flags deleted file mode 100644 index a5c112f5a7e7..000000000000 --- a/tests/pos-special/i871.flags +++ /dev/null @@ -1 +0,0 @@ --language:Scala2 \ No newline at end of file From 5fd2028931874291b3cf1b7efef4fed7119d9316 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jan 2016 16:37:10 +0100 Subject: [PATCH 09/23] Enforce rule that laziness is preserved when overriding. --- .../tools/backend/jvm/DottyBackendInterface.scala | 2 +- src/dotty/tools/dotc/core/Contexts.scala | 2 +- src/dotty/tools/dotc/typer/RefChecks.scala | 12 +++++++----- test/dotc/tests.scala | 4 ++-- tests/neg/overrides.scala | 8 +++++++- tests/pos-scala2/pos-special/i871.scala | 4 ++++ tests/run/t429.scala | 2 +- 7 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 tests/pos-scala2/pos-special/i871.scala diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index f701438884dd..b6adba85a28b 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -131,7 +131,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{ val hashMethodSym: Symbol = NoSymbol // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase val externalEqualsNumNum: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumNum) - lazy val externalEqualsNumChar: Symbol = ??? // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private + val externalEqualsNumChar: Symbol = NoSymbol // ctx.requiredMethod(BoxesRunTimeTypeRef, nme.equalsNumChar) // this method is private val externalEqualsNumObject: Symbol = defn.BoxesRunTimeModule.requiredMethod(nme.equalsNumObject) val externalEquals: Symbol = defn.BoxesRunTimeClass.info.decl(nme.equals_).suchThat(toDenot(_).info.firstParamTypes.size == 2).symbol val MaxFunctionArity: Int = Definitions.MaxFunctionArity diff --git a/src/dotty/tools/dotc/core/Contexts.scala b/src/dotty/tools/dotc/core/Contexts.scala index 9ccb03b89d47..c1132ce4b34c 100644 --- a/src/dotty/tools/dotc/core/Contexts.scala +++ b/src/dotty/tools/dotc/core/Contexts.scala @@ -493,7 +493,7 @@ object Contexts { } @sharable object NoContext extends Context { - lazy val base = unsupported("base") + val base = null override val implicits: ContextualImplicits = new ContextualImplicits(Nil, null)(this) } diff --git a/src/dotty/tools/dotc/typer/RefChecks.scala b/src/dotty/tools/dotc/typer/RefChecks.scala index 7ccb3d10398e..067694bfdfa4 100644 --- a/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/src/dotty/tools/dotc/typer/RefChecks.scala @@ -211,7 +211,7 @@ object RefChecks { if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { // suppress errors relating toi synthetic companion objects if other override // errors (e.g. relating to the companion class) have already been reported. - if (member.owner == clazz) ctx.error(fullmsg+", member = $member", member.pos) + if (member.owner == clazz) ctx.error(fullmsg, member.pos) else mixinOverrideErrors += new MixinOverrideError(member, fullmsg) hasErrors = true } @@ -330,10 +330,12 @@ object RefChecks { "(this rule is designed to prevent ``accidental overrides'')") } else if (other.isStable && !member.isStable) { // (1.4) overrideError("needs to be a stable, immutable value") - } else if (member.is(Lazy) && !other.isRealMethod && !other.is(Deferred | Lazy)) { - overrideError("cannot override a concrete non-lazy value") - } else if (other.is(Lazy, butNot = Deferred) && !other.isRealMethod && !member.is(Lazy)) { - overrideError("must be declared lazy to override a concrete lazy value") + } else if (member.is(ModuleVal) && !other.isRealMethod && !other.is(Deferred | Lazy)) { + overrideError("may not override a concrete non-lazy value") + } else if (member.is(Lazy, butNot = Module) && !other.isRealMethod && !other.is(Lazy)) { + overrideError("may not override a non-lazy value") + } else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) { + overrideError("must be declared lazy to override a lazy value") } else if (other.is(Deferred) && member.is(Macro) && member.extendedOverriddenSymbols.forall(_.is(Deferred))) { // (1.9) overrideError("cannot be used here - term macros cannot override abstract methods") } else if (other.is(Macro) && !member.is(Macro)) { // (1.10) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 3414f197741b..1c62ed96d5bc 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -122,10 +122,10 @@ class tests extends CompilerTest { @Test def neg_autoTupling2 = compileFile(negDir, "autoTuplingTest", xerrors = 3) @Test def neg_companions = compileFile(negDir, "companions", xerrors = 1) @Test def neg_over = compileFile(negDir, "over", xerrors = 3) - @Test def neg_overrides = compileFile(negDir, "overrides", xerrors = 12) + @Test def neg_overrides = compileFile(negDir, "overrides", xerrors = 14) @Test def neg_overrideClass = compileFile(negDir, "overrideClass", List("-language:Scala2"), xerrors = 1) @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 2) - @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 6) + @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 5) @Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12) val negTailcallDir = negDir + "tailcall/" diff --git a/tests/neg/overrides.scala b/tests/neg/overrides.scala index fe14f91efa3e..81a93a7a2833 100644 --- a/tests/neg/overrides.scala +++ b/tests/neg/overrides.scala @@ -3,10 +3,16 @@ class Foo { type B >: Int <: Int def get: A = 42 } -class Bar extends Foo { +trait T { + lazy val x: Int + val y: Int +} +class Bar extends Foo with T { override type A = Any // error type B >: String <: Any // error override def get: A = "bar" + val x = 2 // error + lazy val y = 3 // error } object Test { def main(args: Array[String]): Unit = { diff --git a/tests/pos-scala2/pos-special/i871.scala b/tests/pos-scala2/pos-special/i871.scala new file mode 100644 index 000000000000..2e5f100d8772 --- /dev/null +++ b/tests/pos-scala2/pos-special/i871.scala @@ -0,0 +1,4 @@ +trait Message { + def first(x: Int) + def second +} diff --git a/tests/run/t429.scala b/tests/run/t429.scala index e62a6b307bc9..eeed4b080a15 100644 --- a/tests/run/t429.scala +++ b/tests/run/t429.scala @@ -1,7 +1,7 @@ object Test { abstract class A { Console.print("A"); - val x: Int; + lazy val x: Int; val y: Int = {Console.print("y"); x + 1} } class B extends A { From 0480cb2a5902b733145f54fdc238aba7b831396b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jan 2016 17:04:02 +0100 Subject: [PATCH 10/23] Only final lazy vals can be paths. Reason: They might be overridden by other lazy vals that are not realizable, and therefore risk creating bad bounds. --- .../tools/dotc/core/SymDenotations.scala | 4 ++- test/dotc/tests.scala | 3 +- tests/neg/i1050.scala | 4 +-- tests/neg/i1050a.scala | 29 +++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 tests/neg/i1050a.scala diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 98def30716ca..0b18a29c4d2c 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -525,7 +525,9 @@ object SymDenotations { /** Is this a denotation of a realizable term (or an arbitrary type)? */ final def isRealizable(implicit ctx: Context) = is(Stable) || isType || { - val isRealizable = !is(Lazy, butNot = Module) || ctx.realizability(info) == TypeOps.Realizable + val isRealizable = + !is(Lazy, butNot = Module) || + is(Final) && ctx.realizability(info) == TypeOps.Realizable isRealizable && { setFlag(Stable); true } } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 1c62ed96d5bc..689f92b04347 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -162,7 +162,8 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 3) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 5) + @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index bc906f4f4e70..99d81c317ac2 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -39,7 +39,7 @@ object Tiark1 { trait A { type L <: Nothing } trait B { type L >: Any} trait U { - val p: B + lazy val p: B def brand(x: Any): p.L = x // error: not final } trait V extends U { @@ -53,7 +53,7 @@ object Tiark2 { trait B { type L >: Any} trait U { type X <: B - val p: X + lazy val p: X def brand(x: Any): p.L = x // error: not final } trait V extends U { diff --git a/tests/neg/i1050a.scala b/tests/neg/i1050a.scala new file mode 100644 index 000000000000..47e2f0c599b3 --- /dev/null +++ b/tests/neg/i1050a.scala @@ -0,0 +1,29 @@ +object Tiark1 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + val p: B + def brand(x: Any): p.L = x // error: not final + } + trait V extends U { + lazy val p: A & B = ??? + } + val v = new V {} + v.brand("boom!") +} +object Tiark2 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + type X <: B + val p: X + def brand(x: Any): p.L = x // error: not final + } + trait V extends U { + type X = B & A + lazy val p: X = ??? + } + val v = new V {} + v.brand("boom!"): Nothing +} + From e87dee212351aa7acb15760814cdd1c30c4de019 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jan 2016 22:22:54 +0100 Subject: [PATCH 11/23] Fix path error disgnastics Fix wording so that it works for nested errors as well. Incorparte Tiark's latest example. --- src/dotty/tools/dotc/core/TypeOps.scala | 11 +++++------ src/dotty/tools/dotc/typer/Checking.scala | 2 +- test/dotc/tests.scala | 2 +- tests/neg/i1050.scala | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 9fcc6abbcfa6..882ab9f10e0f 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -674,7 +674,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. if (scala2Mode) migrationWarning(msg, pos) scala2Mode } - } object TypeOps { @@ -687,16 +686,16 @@ object TypeOps { object Realizable extends Realizability("") - object NotConcrete extends Realizability("it is not a concrete type") + object NotConcrete extends Realizability(" is not a concrete type") - object NotStable extends Realizability("it is not a stable reference") + object NotStable extends Realizability(" is not a stable reference") class NotFinal(sym: Symbol)(implicit ctx: Context) - extends Realizability(i"it refers to nonfinal $sym") + extends Realizability(i" refers to nonfinal $sym") class HasProblemBounds(mbr: SingleDenotation)(implicit ctx: Context) - extends Realizability(i"it has a member $mbr with possibly conflicting bounds ${mbr.info.bounds.lo} <: ... <: ${mbr.info.bounds.hi}") + extends Realizability(i" has a member $mbr with possibly conflicting bounds ${mbr.info.bounds.lo} <: ... <: ${mbr.info.bounds.hi}") class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) - extends Realizability(i"its underlying type ${tp} ${problem.msg}") + extends Realizability(i"s underlying type ${tp}${problem.msg}") } diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index ee1c2a06155c..ca7c5c5ab68d 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -325,7 +325,7 @@ trait Checking { def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { val rstatus = ctx.realizability(tp) if (rstatus ne TypeOps.Realizable) { - def msg = d"$tp is not a legal path since ${rstatus.msg}" + def msg = d"$tp is not a legal path since it${rstatus.msg}" if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) } } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 689f92b04347..81fc277fa77e 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -162,7 +162,7 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 5) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 6) @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index 99d81c317ac2..047d9f776501 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -63,6 +63,22 @@ object Tiark2 { val v = new V {} v.brand("boom!"): Nothing } +object Tiark3 { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + type X <: B + def p2: X + final lazy val p: X = p2 + def brand(x: Any): p.L = x + } + trait V extends U { + type X = B with A + def p2: X = ??? + } + val v = new V {} + v.brand("boom!"): Nothing +} /* object Import { trait A { type L <: Nothing } From a0fb0685fcfc5e988e0d033af26c1055269488e5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 31 Jan 2016 23:55:27 +0100 Subject: [PATCH 12/23] Handle imports in path checks. If `T` is a member of `p` then { import p._; ... T ... } should be checked in the same way as { ... p.T ... } --- src/dotty/tools/dotc/typer/Typer.scala | 2 ++ test/dotc/tests.scala | 2 +- tests/neg/i1050.scala | 20 +++++++++----------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index ce0a5c0f2714..7894a5b5fdc9 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -274,6 +274,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val tree1 = ownType match { case ownType: NamedType if !prefixIsElidable(ownType) => + checkRealizable(ownType.prefix, tree.pos) ref(ownType).withPos(tree.pos) case _ => tree.withType(ownType) @@ -991,6 +992,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) checkVariance(impl1) + if (!cls.is(AbstractOrTrait)) checkRealizableBounds(cls.typeRef, cdef.pos) assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls) // todo later: check that diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 81fc277fa77e..e1a10a6e28fe 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -162,7 +162,7 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 6) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 8) @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index 047d9f776501..25e647a57bc0 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -40,7 +40,7 @@ object Tiark1 { trait B { type L >: Any} trait U { lazy val p: B - def brand(x: Any): p.L = x // error: not final + def brand(x: Any): p.L = x // error: nonfinal lazy } trait V extends U { lazy val p: A & B = ??? @@ -54,7 +54,7 @@ object Tiark2 { trait U { type X <: B lazy val p: X - def brand(x: Any): p.L = x // error: not final + def brand(x: Any): p.L = x // error: nonfinal lazy } trait V extends U { type X = B & A @@ -70,7 +70,7 @@ object Tiark3 { type X <: B def p2: X final lazy val p: X = p2 - def brand(x: Any): p.L = x + def brand(x: Any): p.L = x // error: underlying not concrete } trait V extends U { type X = B with A @@ -79,20 +79,18 @@ object Tiark3 { val v = new V {} v.brand("boom!"): Nothing } -/* object Import { trait A { type L <: Nothing } trait B { type L >: Any} trait U { - val p: B - def brand(x: Any): p.L = x // error: not final - locally { import p._ + lazy val p: B + locally { val x: p.L = ??? } // error: nonfinal lazy + locally { + import p._ + val x: L = ??? // error: nonfinal lazy } } trait V extends U { lazy val p: A & B = ??? - } - val v = new V {} - v.brand("boom!") } -*/ + From 3637e08033c857e19862ae1fc9730d96cdba3fe2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Feb 2016 00:07:56 +0100 Subject: [PATCH 13/23] Fix isRealizableTest Need to demand "effecively final" instead of `is(Final)`. --- src/dotty/tools/dotc/core/SymDenotations.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index 0b18a29c4d2c..b76752cc0400 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -527,7 +527,7 @@ object SymDenotations { is(Stable) || isType || { val isRealizable = !is(Lazy, butNot = Module) || - is(Final) && ctx.realizability(info) == TypeOps.Realizable + isEffectivelyFinal && ctx.realizability(info) == TypeOps.Realizable isRealizable && { setFlag(Stable); true } } From defba2af7c3b49231962a6f882173bcaff914fcc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Feb 2016 00:08:26 +0100 Subject: [PATCH 14/23] Check that non-abstract classes have realizable bounds. --- src/dotty/tools/dotc/core/TypeOps.scala | 25 ++++++++++++++--------- src/dotty/tools/dotc/typer/Checking.scala | 7 +++++++ test/dotc/tests.scala | 2 +- tests/neg/i1050.scala | 21 ++++++++++++++++--- 4 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 882ab9f10e0f..227dbb90f91a 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -449,16 +449,21 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case _ => false } if (!isConcrete(tp)) NotConcrete - else { - def hasBadBounds(mbr: SingleDenotation) = { - val bounds = mbr.info.bounds - !(bounds.lo <:< bounds.hi) - } - tp.nonClassTypeMembers.find(hasBadBounds) match { - case Some(mbr) => new HasProblemBounds(mbr) - case _ => Realizable - } - } + else boundsRealizability(tp) + } + + /** `Realizable` is `tp` has good bounds, a `HasProblemBounds` instance + * pointing to a bad bounds member otherwise. + */ + def boundsRealizability(tp: Type)(implicit ctx: Context) = { + def hasBadBounds(mbr: SingleDenotation) = { + val bounds = mbr.info.bounds + !(bounds.lo <:< bounds.hi) + } + tp.nonClassTypeMembers.find(hasBadBounds) match { + case Some(mbr) => new HasProblemBounds(mbr) + case _ => Realizable + } } private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = { diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index ca7c5c5ab68d..7e90d755b54d 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -330,6 +330,13 @@ trait Checking { } } + /** Check that all type members of `tp` have realizable bounds */ + def checkRealizableBounds(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = ctx.boundsRealizability(tp) + if (rstatus ne TypeOps.Realizable) + ctx.error(i"$tp cannot be instantiated since it${rstatus.msg}", pos) + } + /** Check that `tp` is a class type with a stable prefix. Also, if `traitReq` is * true check that `tp` is a trait. * Stability checking is disabled in phases after RefChecks. diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index e1a10a6e28fe..47ac00c6f4d0 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -162,7 +162,7 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 8) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 11) @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index 25e647a57bc0..5808011fa6f2 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -79,6 +79,21 @@ object Tiark3 { val v = new V {} v.brand("boom!"): Nothing } +object Tiark4 { + trait U { + type Y + trait X { type L = Y } + def compute: X + final lazy val p: X = compute + def brand(x: Y): p.L = x + } + trait V extends U { + type Y >: Any <: Nothing + def compute: X = ??? + } + val v = new V {} // error: cannot be instantiated + v.brand("boom!") +} object Import { trait A { type L <: Nothing } trait B { type L >: Any} @@ -90,7 +105,7 @@ object Import { val x: L = ??? // error: nonfinal lazy } } - trait V extends U { - lazy val p: A & B = ??? } - +object V { // error: cannot be instantiated + type Y >: Any <: Nothing // error: only classes can have declared but undefined members +} From 1441622f094e6b149fb5e166fcc51c299f116913 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Feb 2016 00:17:07 +0100 Subject: [PATCH 15/23] Drop lines from test The lines in question now cause an error ("cannot be instantiated...") which masks the real tests at phase PostTyper. Also adapt bugcount of hklower test --- test/dotc/tests.scala | 4 ++-- tests/neg/bounds.scala | 2 -- tests/neg/hklower.scala | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 47ac00c6f4d0..8c61f56b7092 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -110,7 +110,7 @@ class tests extends CompilerTest { @Test def neg_abstractOverride() = compileFile(negDir, "abstract-override", xerrors = 2) @Test def neg_blockescapes() = compileFile(negDir, "blockescapesNeg", xerrors = 1) - @Test def neg_bounds() = compileFile(negDir, "bounds", xerrors = 3) + @Test def neg_bounds() = compileFile(negDir, "bounds", xerrors = 2) @Test def neg_typedapply() = compileFile(negDir, "typedapply", xerrors = 3) @Test def neg_typedIdents() = compileDir(negDir, "typedIdents", xerrors = 2) @Test def neg_assignments() = compileFile(negDir, "assignments", xerrors = 3) @@ -150,7 +150,7 @@ class tests extends CompilerTest { @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 9) @Test def neg_boundspropagation = compileFile(negDir, "boundspropagation", xerrors = 5) @Test def neg_refinedSubtyping = compileFile(negDir, "refinedSubtyping", xerrors = 2) - @Test def neg_hklower = compileFile(negDir, "hklower", xerrors = 3) + @Test def neg_hklower = compileFile(negDir, "hklower", xerrors = 4) @Test def neg_Iter2 = compileFile(negDir, "Iter2", xerrors = 2) @Test def neg_i0091_infpaths = compileFile(negDir, "i0091-infpaths", xerrors = 3) @Test def neg_i0248_inherit_refined = compileFile(negDir, "i0248-inherit-refined", xerrors = 4) diff --git a/tests/neg/bounds.scala b/tests/neg/bounds.scala index 55eec6941b10..cd6b8d05c9cd 100644 --- a/tests/neg/bounds.scala +++ b/tests/neg/bounds.scala @@ -1,9 +1,7 @@ object Test { - class C[B >: String <: Int](x: B) def g[B >: String <: Int](x: B): Int = x def main(args: Array[String]): Unit = { g("foo") - new C("bar") } def baz[X >: Y, Y <: String](x: X, y: Y) = (x, y) diff --git a/tests/neg/hklower.scala b/tests/neg/hklower.scala index e29a1545e8d1..5c1ba27ba28f 100644 --- a/tests/neg/hklower.scala +++ b/tests/neg/hklower.scala @@ -1,4 +1,4 @@ -class Test { +class Test { // error conflicting bounds type T[X] // OK type U[X] = T[X] // OK From f44a1ed47d8fc68d9dae321fd28433b70abf7bfe Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Feb 2016 01:12:54 +0100 Subject: [PATCH 16/23] Remove isVolatile and DNF methods These are replaced by the realizibility logic. --- src/dotty/tools/dotc/core/TypeOps.scala | 92 +------------------------ 1 file changed, 1 insertion(+), 91 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 227dbb90f91a..37245efa54d1 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -342,96 +342,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } - /** A type is volatile if its DNF contains an alternative of the form - * {P1, ..., Pn}, {N1, ..., Nk}, where the Pi are parent typerefs and the - * Nj are refinement names, and one the 4 following conditions is met: - * - * 1. At least two of the parents Pi are abstract types. - * 2. One of the parents Pi is an abstract type, and one other type Pj, - * j != i has an abstract member which has the same name as an - * abstract member of the whole type. - * 3. One of the parents Pi is an abstract type, and one of the refinement - * names Nj refers to an abstract member of the whole type. - * 4. One of the parents Pi is an an alias type with a volatile alias - * or an abstract type with a volatile upper bound. - * - * Lazy values are not allowed to have volatile type, as otherwise - * unsoundness can result. - */ - final def isVolatile(tp: Type): Boolean = { - - /** Pre-filter to avoid expensive DNF computation - * If needsChecking returns false it is guaranteed that - * DNF does not contain intersections, or abstract types with upper - * bounds that themselves need checking. - */ - def needsChecking(tp: Type, isPart: Boolean): Boolean = tp match { - case tp: TypeRef => - tp.info match { - case TypeAlias(alias) => - needsChecking(alias, isPart) - case TypeBounds(lo, hi) => - isPart || tp.controlled(isVolatile(hi)) - case _ => false - } - case tp: RefinedType => - needsChecking(tp.parent, true) - case tp: TypeProxy => - needsChecking(tp.underlying, isPart) - case tp: AndType => - true - case tp: OrType => - isPart || needsChecking(tp.tp1, isPart) && needsChecking(tp.tp2, isPart) - case _ => - false - } - - needsChecking(tp, false) && { - DNF(tp) forall { case (parents, refinedNames) => - val absParents = parents filter (_.symbol is Deferred) - absParents.nonEmpty && { - absParents.lengthCompare(2) >= 0 || { - val ap = absParents.head - ((parents exists (p => - (p ne ap) - || p.memberNames(abstractTypeNameFilter, tp).nonEmpty - || p.memberNames(abstractTermNameFilter, tp).nonEmpty)) - || (refinedNames & tp.memberNames(abstractTypeNameFilter, tp)).nonEmpty - || (refinedNames & tp.memberNames(abstractTermNameFilter, tp)).nonEmpty - || isVolatile(ap)) - } - } - } - } - } - - /** The disjunctive normal form of this type. - * This collects a set of alternatives, each alternative consisting - * of a set of typerefs and a set of refinement names. Both sets are represented - * as lists, to obtain a deterministic order. Collected are - * all type refs reachable by following aliases and type proxies, and - * collecting the elements of conjunctions (&) and disjunctions (|). - * The set of refinement names in each alternative - * are the set of names in refinement types encountered during the collection. - */ - final def DNF(tp: Type): List[(List[TypeRef], Set[Name])] = ctx.traceIndented(s"DNF($this)", checks) { - tp.dealias match { - case tp: TypeRef => - (tp :: Nil, Set[Name]()) :: Nil - case RefinedType(parent, name) => - for ((ps, rs) <- DNF(parent)) yield (ps, rs + name) - case tp: TypeProxy => - DNF(tp.underlying) - case AndType(l, r) => - for ((lps, lrs) <- DNF(l); (rps, rrs) <- DNF(r)) - yield (lps | rps, lrs | rrs) - case OrType(l, r) => - DNF(l) | DNF(r) - case tp => - emptyDNF - } - } - /** The realizability status of given type `tp`*/ def realizability(tp: Type): Realizability = tp.dealias match { case tp: TermRef => @@ -452,7 +362,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. else boundsRealizability(tp) } - /** `Realizable` is `tp` has good bounds, a `HasProblemBounds` instance + /** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance * pointing to a bad bounds member otherwise. */ def boundsRealizability(tp: Type)(implicit ctx: Context) = { From 20fc6bd93c543f98da39a165437234670505f860 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Feb 2016 12:13:41 +0100 Subject: [PATCH 17/23] Consider by name parameters as lazily initialized should be terated analogous to lazy vals for realizability checking. --- src/dotty/tools/dotc/core/SymDenotations.scala | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index b76752cc0400..fb59cae590f2 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -526,11 +526,17 @@ object SymDenotations { final def isRealizable(implicit ctx: Context) = is(Stable) || isType || { val isRealizable = - !is(Lazy, butNot = Module) || + !isLateInitialized || isEffectivelyFinal && ctx.realizability(info) == TypeOps.Realizable isRealizable && { setFlag(Stable); true } } + /** Field is initialized on use, not on definition; + * we do not count modules as fields here. + */ + final def isLateInitialized(implicit ctx: Context) = + is(Lazy, butNot = Module) || is(Param) && info.isInstanceOf[ExprType] + /** Is this a "real" method? A real method is a method which is: * - not an accessor * - not a label From d34256c14a507dbdaea10bd83e8006cdafb9c799 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 1 Feb 2016 13:03:15 +0100 Subject: [PATCH 18/23] Handle paths of length > 1 for realizability checking --- src/dotty/tools/dotc/core/TypeOps.scala | 44 +++++++++++++++++++++++-- test/dotc/tests.scala | 2 +- tests/neg/i1050.scala | 27 +++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 37245efa54d1..925ebcbcd8be 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -342,10 +342,31 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } + /** Is this type a path with some part that is initialized on use? */ + def isLateInitialized(tp: Type): Boolean = tp.dealias match { + case tp: TermRef => + tp.symbol.isLateInitialized || isLateInitialized(tp.prefix) + case _: SingletonType | NoPrefix => + false + case tp: TypeRef => + true + case tp: TypeProxy => + isLateInitialized(tp.underlying) + case tp: AndOrType => + isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2) + case _ => + true + } + /** The realizability status of given type `tp`*/ def realizability(tp: Type): Realizability = tp.dealias match { case tp: TermRef => - if (tp.symbol.isRealizable) Realizable + if (tp.symbol.isRealizable) + if (tp.symbol.isLateInitialized || // we already checked realizability of info in that case + !isLateInitialized(tp.prefix)) // symbol was definitely constructed in that case + Realizable + else + realizability(tp.info) else if (!tp.symbol.isStable) NotStable else if (!tp.symbol.isEffectivelyFinal) new NotFinal(tp.symbol) else new ProblemInUnderlying(tp.info, realizability(tp.info)) @@ -376,6 +397,18 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } + /* Might need at some point in the future + def memberRealizability(tp: Type)(implicit ctx: Context) = { + println(i"check member rel of $tp") + def isUnrealizable(fld: SingleDenotation) = + !fld.symbol.is(Lazy) && realizability(fld.info) != Realizable + tp.fields.find(isUnrealizable) match { + case Some(fld) => new HasProblemField(fld) + case _ => Realizable + } + } + */ + private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = { val lazyInfo = new LazyType { // needed so we do not force `formal`. def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { @@ -608,9 +641,14 @@ object TypeOps { class NotFinal(sym: Symbol)(implicit ctx: Context) extends Realizability(i" refers to nonfinal $sym") - class HasProblemBounds(mbr: SingleDenotation)(implicit ctx: Context) - extends Realizability(i" has a member $mbr with possibly conflicting bounds ${mbr.info.bounds.lo} <: ... <: ${mbr.info.bounds.hi}") + class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context) + extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}") + /* Might need at some point in the future + class HasProblemField(fld: SingleDenotation)(implicit ctx: Context) + extends Realizability(i" has a member $fld which is uneligible as a path since ${fld.symbol.name}${ctx.realizability(fld.info)}") + */ + class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) extends Realizability(i"s underlying type ${tp}${problem.msg}") } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 8c61f56b7092..be457d916ab6 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -162,7 +162,7 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 11) + @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 14) @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index 5808011fa6f2..f2c237af2822 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -109,3 +109,30 @@ object Import { object V { // error: cannot be instantiated type Y >: Any <: Nothing // error: only classes can have declared but undefined members } +object Tiark5 { + trait A { type L <: Nothing } + trait B { type L >: Any } + def f(x: => A & B)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds + f(???)("boom!") +} +object Tiark5Inherited { + trait A { type L <: Nothing } + trait B { type L >: Any } + trait A2 extends A + trait B2 extends B + def f(x: => A2 & B2)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds + f(???)("boom!") +} +object Tiark6 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait X { + val q: A & B = ??? + } + final lazy val p: X = ??? + def brand(x: Any): p.q.L = x // error: conflicting bounds + } + val v = new U {} + v.brand("boom!"): Nothing +} From ec4a3a0f4d0b8cccf636d1608896e7cafba9dec0 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Feb 2016 10:21:12 +0100 Subject: [PATCH 19/23] Big realizability refactoring Move logic from TypeOps to new file CheckRealizable.scala. Also check realizable fields under strict mode. Check at phase PostTyper rather than Typer to avoid cycles. New tests for imports and deep paths. --- .../tools/dotc/core/CheckRealizable.scala | 140 ++++++++++++++++++ .../tools/dotc/core/SymDenotations.scala | 10 +- src/dotty/tools/dotc/core/TypeOps.scala | 93 ------------ .../tools/dotc/transform/PostTyper.scala | 4 +- src/dotty/tools/dotc/typer/Checking.scala | 32 ++-- src/dotty/tools/dotc/typer/Typer.scala | 9 +- test/dotc/tests.scala | 13 +- tests/neg/cycles.scala | 4 +- tests/neg/i1050.scala | 119 +++++++++++---- tests/neg/i1050a.scala | 9 +- tests/neg/i1050c.scala | 31 ++++ tests/neg/ski.scala | 4 +- 12 files changed, 300 insertions(+), 168 deletions(-) create mode 100644 src/dotty/tools/dotc/core/CheckRealizable.scala create mode 100644 tests/neg/i1050c.scala diff --git a/src/dotty/tools/dotc/core/CheckRealizable.scala b/src/dotty/tools/dotc/core/CheckRealizable.scala new file mode 100644 index 000000000000..ce922635b17b --- /dev/null +++ b/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -0,0 +1,140 @@ +package dotty.tools +package dotc +package core + +import Contexts._, Types._, Symbols._, Names._, Flags._, Scopes._ +import SymDenotations._, Denotations.SingleDenotation +import config.Printers._ +import util.Positions._ +import Decorators._ +import StdNames._ +import Annotations._ +import collection.mutable +import ast.tpd._ + +/** Realizability status */ +object CheckRealizable { + + abstract class Realizability(val msg: String) { + def andAlso(other: => Realizability) = + if (this == Realizable) other else this + def mapError(f: Realizability => Realizability) = + if (this == Realizable) this else f(this) + } + + object Realizable extends Realizability("") + + object NotConcrete extends Realizability(" is not a concrete type") + + object NotStable extends Realizability(" is not a stable reference") + + class NotFinal(sym: Symbol)(implicit ctx: Context) + extends Realizability(i" refers to nonfinal $sym") + + class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context) + extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}") + + class HasProblemField(fld: SingleDenotation, problem: Realizability)(implicit ctx: Context) + extends Realizability(i" has a member $fld which is not a legal path\n since ${fld.symbol.name}: ${fld.info}${problem.msg}") + + class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) + extends Realizability(i"s underlying type ${tp}${problem.msg}") { + assert(problem != Realizable) + } + + def realizability(tp: Type)(implicit ctx: Context) = + new CheckRealizable().realizability(tp) + + def boundsRealizability(tp: Type)(implicit ctx: Context) = + new CheckRealizable().boundsRealizability(tp) +} + +/** Compute realizability status */ +class CheckRealizable(implicit ctx: Context) { + import CheckRealizable._ + + /** A set of all fields that have already been checked. Used + * to avoid infinite recursions when analyzing recursive types. + */ + private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]() + + /** Is this type a path with some part that is initialized on use? */ + private def isLateInitialized(tp: Type): Boolean = tp.dealias match { + case tp: TermRef => + tp.symbol.isLateInitialized || isLateInitialized(tp.prefix) + case _: SingletonType | NoPrefix => + false + case tp: TypeRef => + true + case tp: TypeProxy => + isLateInitialized(tp.underlying) + case tp: AndOrType => + isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2) + case _ => + true + } + + /** The realizability status of given type `tp`*/ + 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 (!sym.isLateInitialized) 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 + } + case _: SingletonType | NoPrefix => + Realizable + case tp => + def isConcrete(tp: Type): Boolean = tp.dealias match { + case tp: TypeRef => tp.symbol.isClass + case tp: TypeProxy => isConcrete(tp.underlying) + case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) + case _ => false + } + if (!isConcrete(tp)) NotConcrete + else boundsRealizability(tp).andAlso(memberRealizability(tp)) + } + + /** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance + * pointing to a bad bounds member otherwise. + */ + private def boundsRealizability(tp: Type) = { + def hasBadBounds(mbr: SingleDenotation) = { + val bounds = mbr.info.bounds + !(bounds.lo <:< bounds.hi) + } + tp.nonClassTypeMembers.find(hasBadBounds) match { + case Some(mbr) => new HasProblemBounds(mbr) + case _ => Realizable + } + } + + /** `Realizable` if `tp` all of `tp`'s non-struct fields have realizable types, + * a `HasProblemField` instance pointing to a bad field otherwise. + */ + private def memberRealizability(tp: Type) = { + def checkField(sofar: Realizability, fld: SingleDenotation): Realizability = + sofar andAlso { + if (checkedFields.contains(fld.symbol) || fld.symbol.is(Private | Mutable | Lazy)) + Realizable + else { + checkedFields += fld.symbol + realizability(fld.info).mapError(r => new HasProblemField(fld, r)) + } + } + if (ctx.settings.strict.value) + // check fields only under strict mode for now. + // Reason: We do track nulls, so an embedded field could well be nullable + // which means it is not a path and need not be checked; but we cannot recognize + // this situation until we have a typesystem that tracks nullability. + ((Realizable: Realizability) /: tp.fields)(checkField) + else + Realizable + } +} diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index fb59cae590f2..a4082607c7fd 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -12,6 +12,7 @@ import scala.reflect.io.AbstractFile import Decorators.SymbolIteratorDecorator import ast._ import annotation.tailrec +import CheckRealizable._ import typer.Mode import util.SimpleMap import util.Stats @@ -522,15 +523,6 @@ object SymDenotations { final def isStable(implicit ctx: Context) = isType || !is(UnstableValue, butNot = Stable) - /** Is this a denotation of a realizable term (or an arbitrary type)? */ - final def isRealizable(implicit ctx: Context) = - is(Stable) || isType || { - val isRealizable = - !isLateInitialized || - isEffectivelyFinal && ctx.realizability(info) == TypeOps.Realizable - isRealizable && { setFlag(Stable); true } - } - /** Field is initialized on use, not on definition; * we do not count modules as fields here. */ diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 925ebcbcd8be..4251648a307b 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -14,7 +14,6 @@ import collection.mutable import ast.tpd._ trait TypeOps { this: Context => // TODO: Make standalone object. - import TypeOps._ /** The type `tp` as seen from prefix `pre` and owner `cls`. See the spec * for what this means. Called very often, so the code is optimized heavily. @@ -342,73 +341,6 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } } - /** Is this type a path with some part that is initialized on use? */ - def isLateInitialized(tp: Type): Boolean = tp.dealias match { - case tp: TermRef => - tp.symbol.isLateInitialized || isLateInitialized(tp.prefix) - case _: SingletonType | NoPrefix => - false - case tp: TypeRef => - true - case tp: TypeProxy => - isLateInitialized(tp.underlying) - case tp: AndOrType => - isLateInitialized(tp.tp1) || isLateInitialized(tp.tp2) - case _ => - true - } - - /** The realizability status of given type `tp`*/ - def realizability(tp: Type): Realizability = tp.dealias match { - case tp: TermRef => - if (tp.symbol.isRealizable) - if (tp.symbol.isLateInitialized || // we already checked realizability of info in that case - !isLateInitialized(tp.prefix)) // symbol was definitely constructed in that case - Realizable - else - realizability(tp.info) - else if (!tp.symbol.isStable) NotStable - else if (!tp.symbol.isEffectivelyFinal) new NotFinal(tp.symbol) - else new ProblemInUnderlying(tp.info, realizability(tp.info)) - case _: SingletonType | NoPrefix => - Realizable - case tp => - def isConcrete(tp: Type): Boolean = tp.dealias match { - case tp: TypeRef => tp.symbol.isClass - case tp: TypeProxy => isConcrete(tp.underlying) - case tp: AndOrType => isConcrete(tp.tp1) && isConcrete(tp.tp2) - case _ => false - } - if (!isConcrete(tp)) NotConcrete - else boundsRealizability(tp) - } - - /** `Realizable` if `tp` has good bounds, a `HasProblemBounds` instance - * pointing to a bad bounds member otherwise. - */ - def boundsRealizability(tp: Type)(implicit ctx: Context) = { - def hasBadBounds(mbr: SingleDenotation) = { - val bounds = mbr.info.bounds - !(bounds.lo <:< bounds.hi) - } - tp.nonClassTypeMembers.find(hasBadBounds) match { - case Some(mbr) => new HasProblemBounds(mbr) - case _ => Realizable - } - } - - /* Might need at some point in the future - def memberRealizability(tp: Type)(implicit ctx: Context) = { - println(i"check member rel of $tp") - def isUnrealizable(fld: SingleDenotation) = - !fld.symbol.is(Lazy) && realizability(fld.info) != Realizable - tp.fields.find(isUnrealizable) match { - case Some(fld) => new HasProblemField(fld) - case _ => Realizable - } - } - */ - private def enterArgBinding(formal: Symbol, info: Type, cls: ClassSymbol, decls: Scope) = { val lazyInfo = new LazyType { // needed so we do not force `formal`. def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { @@ -625,30 +557,5 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } object TypeOps { - val emptyDNF = (Nil, Set[Name]()) :: Nil @sharable var track = false // !!!DEBUG - - // ----- Realizibility Status ----------------------------------------------------- - - abstract class Realizability(val msg: String) - - object Realizable extends Realizability("") - - object NotConcrete extends Realizability(" is not a concrete type") - - object NotStable extends Realizability(" is not a stable reference") - - class NotFinal(sym: Symbol)(implicit ctx: Context) - extends Realizability(i" refers to nonfinal $sym") - - class HasProblemBounds(typ: SingleDenotation)(implicit ctx: Context) - extends Realizability(i" has a member $typ with possibly conflicting bounds ${typ.info.bounds.lo} <: ... <: ${typ.info.bounds.hi}") - - /* Might need at some point in the future - class HasProblemField(fld: SingleDenotation)(implicit ctx: Context) - extends Realizability(i" has a member $fld which is uneligible as a path since ${fld.symbol.name}${ctx.realizability(fld.info)}") - */ - - class ProblemInUnderlying(tp: Type, problem: Realizability)(implicit ctx: Context) - extends Realizability(i"s underlying type ${tp}${problem.msg}") } diff --git a/src/dotty/tools/dotc/transform/PostTyper.scala b/src/dotty/tools/dotc/transform/PostTyper.scala index f9862bb95115..14edaa7b5c70 100644 --- a/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/src/dotty/tools/dotc/transform/PostTyper.scala @@ -101,7 +101,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran case tree: TypeTree => tree case _ => if (tree.isType) { - Checking.boundsChecker.traverse(tree) + Checking.typeChecker.traverse(tree) TypeTree(tree.tpe).withPos(tree.pos) } else tree.tpe.widenTermRefExpr match { @@ -180,7 +180,7 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisTran val tree1 = if (sym.isClass) tree else { - Checking.boundsChecker.traverse(tree.rhs) + Checking.typeChecker.traverse(tree.rhs) cpy.TypeDef(tree)(rhs = TypeTree(tree.symbol.info)) } super.transform(tree1) diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 7e90d755b54d..437902d0527e 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -16,6 +16,7 @@ import Trees._ import ProtoTypes._ import Constants._ import Scopes._ +import CheckRealizable._ import ErrorReporting.errorTree import annotation.unchecked import util.Positions._ @@ -49,7 +50,7 @@ object Checking { checkBounds(args, poly.paramBounds, _.substParams(poly, _)) /** Check all AppliedTypeTree nodes in this tree for legal bounds */ - val boundsChecker = new TreeTraverser { + val typeChecker = new TreeTraverser { def traverse(tree: Tree)(implicit ctx: Context) = { tree match { case AppliedTypeTree(tycon, args) => @@ -57,6 +58,12 @@ object Checking { val bounds = tparams.map(tparam => tparam.info.asSeenFrom(tycon.tpe.normalizedPrefix, tparam.owner.owner).bounds) checkBounds(args, bounds, _.substDealias(tparams, _)) + case Select(qual, name) if name.isTypeName => + checkRealizable(qual.tpe, qual.pos) + case SelectFromTypeTree(qual, name) if name.isTypeName => + checkRealizable(qual.tpe, qual.pos) + case SingletonTypeTree(ref) => + checkRealizable(ref.tpe, ref.pos) case _ => } traverseChildren(tree) @@ -83,6 +90,15 @@ object Checking { case _ => } + /** Check that type `tp` is realizable. */ + def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { + val rstatus = realizability(tp) + if (rstatus ne Realizable) { + def msg = d"$tp is not a legal path\n since it${rstatus.msg}" + if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) + } + } + /** A type map which checks that the only cycles in a type are F-bounds * and that protects all F-bounded references by LazyRefs. */ @@ -321,19 +337,10 @@ trait Checking { def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = if (!tp.isStable) ctx.error(d"$tp is not stable", pos) - /** Check that type `tp` is realizable. */ - def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = { - val rstatus = ctx.realizability(tp) - if (rstatus ne TypeOps.Realizable) { - def msg = d"$tp is not a legal path since it${rstatus.msg}" - if (ctx.scala2Mode) ctx.migrationWarning(msg, pos) else ctx.error(msg, pos) - } - } - /** Check that all type members of `tp` have realizable bounds */ def checkRealizableBounds(tp: Type, pos: Position)(implicit ctx: Context): Unit = { - val rstatus = ctx.boundsRealizability(tp) - if (rstatus ne TypeOps.Realizable) + val rstatus = boundsRealizability(tp) + if (rstatus ne Realizable) ctx.error(i"$tp cannot be instantiated since it${rstatus.msg}", pos) } @@ -449,7 +456,6 @@ trait NoChecking extends Checking { override def checkNonCyclic(sym: Symbol, info: TypeBounds, reportErrors: Boolean)(implicit ctx: Context): Type = info override def checkValue(tree: Tree, proto: Type)(implicit ctx: Context): tree.type = tree override def checkStable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () - override def checkRealizable(tp: Type, pos: Position)(implicit ctx: Context): Unit = () override def checkClassTypeWithStablePrefix(tp: Type, pos: Position, traitReq: Boolean)(implicit ctx: Context): Type = tp override def checkImplicitParamsNotSingletons(vparamss: List[List[ValDef]])(implicit ctx: Context): Unit = () override def checkFeasible(tp: Type, pos: Position, where: => String = "")(implicit ctx: Context): Type = tp diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 7894a5b5fdc9..542f78f94726 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -274,7 +274,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val tree1 = ownType match { case ownType: NamedType if !prefixIsElidable(ownType) => - checkRealizable(ownType.prefix, tree.pos) ref(ownType).withPos(tree.pos) case _ => tree.withType(ownType) @@ -309,10 +308,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = track("typedSelect") { def asSelect(implicit ctx: Context): Tree = { val qual1 = typedExpr(tree.qualifier, selectionProto(tree.name, pt, this)) - if (tree.name.isTypeName) { - checkStable(qual1.tpe, qual1.pos) - checkRealizable(qual1.tpe, qual1.pos) - } + if (tree.name.isTypeName) checkStable(qual1.tpe, qual1.pos) typedSelect(tree, pt, qual1) } @@ -346,7 +342,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSelectFromTypeTree(tree: untpd.SelectFromTypeTree, pt: Type)(implicit ctx: Context): Tree = track("typedSelectFromTypeTree") { val qual1 = typedType(tree.qualifier, selectionProto(tree.name, pt, this)) - checkRealizable(qual1.tpe, qual1.pos) assignType(cpy.SelectFromTypeTree(tree)(qual1, tree.name), qual1) } @@ -828,7 +823,6 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedSingletonTypeTree(tree: untpd.SingletonTypeTree)(implicit ctx: Context): SingletonTypeTree = track("typedSingletonTypeTree") { val ref1 = typedExpr(tree.ref) checkStable(ref1.tpe, tree.pos) - checkRealizable(ref1.tpe, tree.pos) assignType(cpy.SingletonTypeTree(tree)(ref1), ref1) } @@ -1064,6 +1058,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) checkStable(expr1.tpe, imp.expr.pos) + checkRealizable(expr1.tpe, imp.expr.pos) assignType(cpy.Import(imp)(expr1, imp.selectors), sym) } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index be457d916ab6..d96fe54887a8 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -125,7 +125,7 @@ class tests extends CompilerTest { @Test def neg_overrides = compileFile(negDir, "overrides", xerrors = 14) @Test def neg_overrideClass = compileFile(negDir, "overrideClass", List("-language:Scala2"), xerrors = 1) @Test def neg_i39 = compileFile(negDir, "i39", xerrors = 2) - @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 5) + @Test def neg_i50_volatile = compileFile(negDir, "i50-volatile", xerrors = 3) @Test def neg_zoo = compileFile(negDir, "zoo", xerrors = 12) val negTailcallDir = negDir + "tailcall/" @@ -138,7 +138,7 @@ class tests extends CompilerTest { @Test def neg_t1843_variances = compileFile(negDir, "t1843-variances", xerrors = 1) @Test def neg_t2660_ambi = compileFile(negDir, "t2660", xerrors = 2) - @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 5) + @Test def neg_t2994 = compileFile(negDir, "t2994", xerrors = 2) @Test def neg_subtyping = compileFile(negDir, "subtyping", xerrors = 5) @Test def neg_variances = compileFile(negDir, "variances", xerrors = 2) @Test def neg_variancesConstr = compileFile(negDir, "variances-constr", xerrors = 2) @@ -147,7 +147,7 @@ class tests extends CompilerTest { @Test def neg_typetest = compileFile(negDir, "typetest", xerrors = 1) @Test def neg_t1569_failedAvoid = compileFile(negDir, "t1569-failedAvoid", xerrors = 1) @Test def neg_clashes = compileFile(negDir, "clashes", xerrors = 2) - @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 9) + @Test def neg_cycles = compileFile(negDir, "cycles", xerrors = 7) @Test def neg_boundspropagation = compileFile(negDir, "boundspropagation", xerrors = 5) @Test def neg_refinedSubtyping = compileFile(negDir, "refinedSubtyping", xerrors = 2) @Test def neg_hklower = compileFile(negDir, "hklower", xerrors = 4) @@ -162,8 +162,9 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", xerrors = 14) + @Test def neg_i1050 = compileFile(negDir, "i1050", List("-strict"), xerrors = 13) @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) + @Test def neg_i1050c = compileFile(negDir, "i1050c", xerrors = 4) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) @@ -172,7 +173,7 @@ class tests extends CompilerTest { @Test def neg_selfreq = compileFile(negDir, "selfreq", xerrors = 2) @Test def neg_singletons = compileFile(negDir, "singletons", xerrors = 8) @Test def neg_shadowedImplicits = compileFile(negDir, "arrayclone-new", xerrors = 2) - @Test def neg_ski = compileFile(negDir, "ski", xerrors = 10) + @Test def neg_ski = compileFile(negDir, "ski", xerrors = 12) @Test def neg_traitParamsTyper = compileFile(negDir, "traitParamsTyper", xerrors = 5) @Test def neg_traitParamsMixin = compileFile(negDir, "traitParamsMixin", xerrors = 2) @Test def neg_firstError = compileFile(negDir, "firstError", xerrors = 3) @@ -202,7 +203,7 @@ class tests extends CompilerTest { |./scala-scala/src/library/scala/collection/generic/GenSeqFactory.scala""".stripMargin) @Test def compileIndexedSeq = compileLine("./scala-scala/src/library/scala/collection/immutable/IndexedSeq.scala") - @Test def dotty = compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant"))(allowDeepSubtypes) // note the -deep argument + @Test def dotty = compileDir(dottyDir, ".", List("-deep", "-Ycheck-reentrant", "-strict"))(allowDeepSubtypes) // note the -deep argument @Test def dotc_ast = compileDir(dotcDir, "ast") @Test def dotc_config = compileDir(dotcDir, "config") diff --git a/tests/neg/cycles.scala b/tests/neg/cycles.scala index 550bc98ead56..ced6f56b529d 100644 --- a/tests/neg/cycles.scala +++ b/tests/neg/cycles.scala @@ -13,7 +13,7 @@ class B { } class C { - final val x: D#T = ??? // error: conflicting bounds + final val x: D#T = ??? class D { type T <: x.type // error: cycle val z: x.type = ??? @@ -25,7 +25,7 @@ class E { type T <: x.type // error: not stable val z: x.type = ??? // error: not stable } - lazy val x: F#T = ??? // error: conflicting bounds + lazy val x: F#T = ??? } class T1 { diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index f2c237af2822..fb416060613e 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -1,3 +1,4 @@ +// i1050 checks failing at posttyper trait A { type L <: Nothing } trait B { type L >: Any} object Test { @@ -79,36 +80,6 @@ object Tiark3 { val v = new V {} v.brand("boom!"): Nothing } -object Tiark4 { - trait U { - type Y - trait X { type L = Y } - def compute: X - final lazy val p: X = compute - def brand(x: Y): p.L = x - } - trait V extends U { - type Y >: Any <: Nothing - def compute: X = ??? - } - val v = new V {} // error: cannot be instantiated - v.brand("boom!") -} -object Import { - trait A { type L <: Nothing } - trait B { type L >: Any} - trait U { - lazy val p: B - locally { val x: p.L = ??? } // error: nonfinal lazy - locally { - import p._ - val x: L = ??? // error: nonfinal lazy - } - } -} -object V { // error: cannot be instantiated - type Y >: Any <: Nothing // error: only classes can have declared but undefined members -} object Tiark5 { trait A { type L <: Nothing } trait B { type L >: Any } @@ -136,3 +107,91 @@ object Tiark6 { val v = new U {} v.brand("boom!"): Nothing } + +object Indirect { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait X { + val q: A & B = ??? + type M = q.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} +object Indirect2 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: A & B = ??? + } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} +object Rec1 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + type L = Int + val r: Y + } + trait X { + val q: Y = ??? + type M = q.r.L // if we are not careful we get a stackoverflow here + } + } +} +object Rec2 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: A & B & Y + } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} +object Indirect3 { + trait B { type L >: Any } + trait A { type L <: Nothing } + trait U { + trait Y { + val r: Y & A & B = ??? + } + trait X { + val q: Y = ??? + type M = q.r.L + } + final lazy val p: X = ??? + def brand(x: Any): p.M = x // error: conflicting bounds + } + def main(args: Array[String]): Unit = { + val v = new U {} + v.brand("boom!"): Nothing + } +} diff --git a/tests/neg/i1050a.scala b/tests/neg/i1050a.scala index 47e2f0c599b3..4fea5e3b1426 100644 --- a/tests/neg/i1050a.scala +++ b/tests/neg/i1050a.scala @@ -1,12 +1,13 @@ +// i1050 checks failing at refchecks object Tiark1 { trait A { type L <: Nothing } trait B { type L >: Any} trait U { val p: B - def brand(x: Any): p.L = x // error: not final + def brand(x: Any): p.L = x } trait V extends U { - lazy val p: A & B = ??? + lazy val p: A & B = ??? // error: may not override } val v = new V {} v.brand("boom!") @@ -17,11 +18,11 @@ object Tiark2 { trait U { type X <: B val p: X - def brand(x: Any): p.L = x // error: not final + def brand(x: Any): p.L = x } trait V extends U { type X = B & A - lazy val p: X = ??? + lazy val p: X = ??? // error: may not override } val v = new V {} v.brand("boom!"): Nothing diff --git a/tests/neg/i1050c.scala b/tests/neg/i1050c.scala new file mode 100644 index 000000000000..ece1f99867de --- /dev/null +++ b/tests/neg/i1050c.scala @@ -0,0 +1,31 @@ +// i1050 checks failing at typer +object Import { + trait A { type L <: Nothing } + trait B { type L >: Any} + trait U { + lazy val p: B + locally { val x: p.L = ??? } // error: nonfinal lazy + locally { + import p._ + val x: L = ??? // error: nonfinal lazy + } + } +} +object Tiark4 { + trait U { + type Y + trait X { type L = Y } + def compute: X + final lazy val p: X = compute + def brand(x: Y): p.L = x + } + trait V extends U { + type Y >: Any <: Nothing + def compute: X = ??? + } + val v = new V {} // error: cannot be instantiated + v.brand("boom!") +} +object V { // error: cannot be instantiated + type Y >: Any <: Nothing // error: only classes can have declared but undefined members +} diff --git a/tests/neg/ski.scala b/tests/neg/ski.scala index 8fb35c7f2ba1..b192dc9e2e14 100644 --- a/tests/neg/ski.scala +++ b/tests/neg/ski.scala @@ -64,9 +64,9 @@ case class Equals[A >: B <:B , B]() object Test { type T1 = Equals[Int, Int] // compiles fine - type T2 = Equals[String, Int] // was error, now masked + type T2 = Equals[String, Int] // error type T3 = Equals[I#ap[c]#eval, c] - type T3a = Equals[I#ap[c]#eval, d] // was error, now masked + type T3a = Equals[I#ap[c]#eval, d] // error // Ic -> c type T4 = Equals[I#ap[c]#eval, c] From eaa157860fd278f7d1404bb2aa495547277fd311 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Feb 2016 11:26:09 +0100 Subject: [PATCH 20/23] Perform typer realizability checks only during Typer. --- src/dotty/tools/dotc/typer/Typer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index 542f78f94726..dd75e960e5ef 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -986,7 +986,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit val impl1 = cpy.Template(impl)(constr1, parents1, self1, body1) .withType(dummy.nonMemberTermRef) checkVariance(impl1) - if (!cls.is(AbstractOrTrait)) checkRealizableBounds(cls.typeRef, cdef.pos) + if (!cls.is(AbstractOrTrait) && !ctx.isAfterTyper) checkRealizableBounds(cls.typeRef, cdef.pos) assignType(cpy.TypeDef(cdef)(name, impl1, Nil), cls) // todo later: check that @@ -1058,7 +1058,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit def typedImport(imp: untpd.Import, sym: Symbol)(implicit ctx: Context): Import = track("typedImport") { val expr1 = typedExpr(imp.expr, AnySelectionProto) checkStable(expr1.tpe, imp.expr.pos) - checkRealizable(expr1.tpe, imp.expr.pos) + if (!ctx.isAfterTyper) checkRealizable(expr1.tpe, imp.expr.pos) assignType(cpy.Import(imp)(expr1, imp.selectors), sym) } From 187c241d7b2b3698a5773463c17fd26c8294d0f7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Feb 2016 12:34:14 +0100 Subject: [PATCH 21/23] New test files from SI 7278. --- tests/neg/t7278.scala | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/run/t7278.scala | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/neg/t7278.scala create mode 100644 tests/run/t7278.scala diff --git a/tests/neg/t7278.scala b/tests/neg/t7278.scala new file mode 100644 index 000000000000..9a8292409ee1 --- /dev/null +++ b/tests/neg/t7278.scala @@ -0,0 +1,42 @@ +class A { class E } +class B extends A { class E } +trait C { type E = Int } +trait D { type E = String } +trait EC { type E } + +object Test { + // should not compile (?) + // martin says "I'd argue about that" + // martin retracts his statement: this should not compile + type EE[+X <: EC] = X#E + type EE2[+X <: EC] = X#E // repeat to get error count to 2 + + def fail1(): Unit = { + val b = new B + var x1: EE[A] = null + var x2: EE[B] = new b.E // error: found: B#E, required: A#E +// x1 = x2 // gives a prior type error: B#E, required: A#E, masked to get at the real thing. + } + +/* Not representable in dotty as there are no existential types + def fail2(): Unit = { + val b = new B + var x1: p.E forSome { val p: A } = new b.E // should not compile + var x2: p.E forSome { val p: B } = new b.E + x1 = x2 // should not compile + } +*/ + def fail3(): Unit = { + var x1: EE[C] = 5 + var x2: EE[C & D] = "" + x1 = x2 + } + + def wrap(label: String)(op: => Unit): Unit = + try { op } catch { case x: ClassCastException => println("%30s %s".format(label, x)) } + + def main(args: Array[String]): Unit = { + wrap("Variance and inner classes")(fail1()) + wrap("Linearization and type aliases")(fail3()) + } +} diff --git a/tests/run/t7278.scala b/tests/run/t7278.scala new file mode 100644 index 000000000000..d408ed550e64 --- /dev/null +++ b/tests/run/t7278.scala @@ -0,0 +1,32 @@ +class Foo { + class Bar { + def foo = 1 + } + } + + class SubFoo extends Foo { + class SubBar { + def foo = 42 + } + } + +object Test { + def main(args: Array[String]): Unit = { + +// Let's create some instances: + val foo = new Foo + val fooBar = new foo.Bar + assert(fooBar.foo == 1) //> res0: Int = 1 + // ok + + val subFoo = new SubFoo + val subFooBar = new subFoo.SubBar + assert(subFooBar.foo == 42) //> res1: Int = 42 + // ok + + val superFoo: Foo = subFoo + val superFooBar = new superFoo.Bar + assert(superFooBar.foo == 1) //> res2: Int = 1 + // NOT OK! + } +} From f6a5802b7185525f7e221b745ec283f8ea206161 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Feb 2016 21:53:49 +0100 Subject: [PATCH 22/23] By-name parameters are not stable values. --- .../tools/dotc/core/CheckRealizable.scala | 10 +++++---- .../tools/dotc/core/SymDenotations.scala | 8 +------ test/dotc/tests.scala | 4 ++-- tests/neg/i1050.scala | 14 ------------- tests/neg/i1050c.scala | 21 +++++++++++++++++++ 5 files changed, 30 insertions(+), 27 deletions(-) diff --git a/src/dotty/tools/dotc/core/CheckRealizable.scala b/src/dotty/tools/dotc/core/CheckRealizable.scala index ce922635b17b..9c4915ee7d59 100644 --- a/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -58,10 +58,12 @@ class CheckRealizable(implicit ctx: Context) { */ private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]() - /** Is this type a path with some part that is initialized on use? */ + /** Is this type a path with some part that is initialized on use? + * (note we exclude modules here, because their realizability is ensured separately). + */ private def isLateInitialized(tp: Type): Boolean = tp.dealias match { case tp: TermRef => - tp.symbol.isLateInitialized || isLateInitialized(tp.prefix) + tp.symbol.is(Lazy, butNot = Module) || isLateInitialized(tp.prefix) case _: SingletonType | NoPrefix => false case tp: TypeRef => @@ -130,8 +132,8 @@ class CheckRealizable(implicit ctx: Context) { } if (ctx.settings.strict.value) // check fields only under strict mode for now. - // Reason: We do track nulls, so an embedded field could well be nullable - // which means it is not a path and need not be checked; but we cannot recognize + // Reason: An embedded field could well be nullable, which means it + // should not be part of a path and need not be checked; but we cannot recognize // this situation until we have a typesystem that tracks nullability. ((Realizable: Realizability) /: tp.fields)(checkField) else diff --git a/src/dotty/tools/dotc/core/SymDenotations.scala b/src/dotty/tools/dotc/core/SymDenotations.scala index a4082607c7fd..4495b40961af 100644 --- a/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/src/dotty/tools/dotc/core/SymDenotations.scala @@ -521,13 +521,7 @@ object SymDenotations { /** Is this a denotation of a stable term (or an arbitrary type)? */ final def isStable(implicit ctx: Context) = - isType || !is(UnstableValue, butNot = Stable) - - /** Field is initialized on use, not on definition; - * we do not count modules as fields here. - */ - final def isLateInitialized(implicit ctx: Context) = - is(Lazy, butNot = Module) || is(Param) && info.isInstanceOf[ExprType] + isType || is(Stable) || !(is(UnstableValue) || info.isInstanceOf[ExprType]) /** Is this a "real" method? A real method is a method which is: * - not an accessor diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index d96fe54887a8..3610482364ea 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -162,9 +162,9 @@ class tests extends CompilerTest { @Test def neg_i803 = compileFile(negDir, "i803", xerrors = 2) @Test def neg_i866 = compileFile(negDir, "i866", xerrors = 2) @Test def neg_i974 = compileFile(negDir, "i974", xerrors = 2) - @Test def neg_i1050 = compileFile(negDir, "i1050", List("-strict"), xerrors = 13) + @Test def neg_i1050 = compileFile(negDir, "i1050", List("-strict"), xerrors = 11) @Test def neg_i1050a = compileFile(negDir, "i1050a", xerrors = 2) - @Test def neg_i1050c = compileFile(negDir, "i1050c", xerrors = 4) + @Test def neg_i1050c = compileFile(negDir, "i1050c", xerrors = 8) @Test def neg_moduleSubtyping = compileFile(negDir, "moduleSubtyping", xerrors = 4) @Test def neg_escapingRefs = compileFile(negDir, "escapingRefs", xerrors = 2) @Test def neg_instantiateAbstract = compileFile(negDir, "instantiateAbstract", xerrors = 8) diff --git a/tests/neg/i1050.scala b/tests/neg/i1050.scala index fb416060613e..1ade1366a9b8 100644 --- a/tests/neg/i1050.scala +++ b/tests/neg/i1050.scala @@ -80,20 +80,6 @@ object Tiark3 { val v = new V {} v.brand("boom!"): Nothing } -object Tiark5 { - trait A { type L <: Nothing } - trait B { type L >: Any } - def f(x: => A & B)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds - f(???)("boom!") -} -object Tiark5Inherited { - trait A { type L <: Nothing } - trait B { type L >: Any } - trait A2 extends A - trait B2 extends B - def f(x: => A2 & B2)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds - f(???)("boom!") -} object Tiark6 { trait B { type L >: Any } trait A { type L <: Nothing } diff --git a/tests/neg/i1050c.scala b/tests/neg/i1050c.scala index ece1f99867de..7998d864da24 100644 --- a/tests/neg/i1050c.scala +++ b/tests/neg/i1050c.scala @@ -29,3 +29,24 @@ object Tiark4 { object V { // error: cannot be instantiated type Y >: Any <: Nothing // error: only classes can have declared but undefined members } +object Tiark5 { + trait A { type L <: Nothing } + trait B { type L >: Any } + def f(x: => A & B)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds + f(???)("boom!") +} +object Tiark5Inherited { + trait A { type L <: Nothing } + trait B { type L >: Any } + trait A2 extends A + trait B2 extends B + def f(x: => A2 & B2)(y: Any):Nothing = (y:x.L) // error: underlying conflicting bounds + f(???)("boom!") +} +object Tiark7 { + trait A { type L <: Nothing } + trait B { type L >: Any } + def f(x: => B)(y: Any):x.L = y // error: x is not stable + def f1(x: => A & B)(y: Any):Nothing = f(x)(y) // error: type mismatch + f1(???)("boom!"): Nothing +} From 1511cb49b990e14bd085a26bbcaa77cd827f151b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 2 Feb 2016 23:50:40 +0100 Subject: [PATCH 23/23] Fix commpilation error --- src/dotty/tools/dotc/core/CheckRealizable.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/CheckRealizable.scala b/src/dotty/tools/dotc/core/CheckRealizable.scala index 9c4915ee7d59..6e6efa549471 100644 --- a/src/dotty/tools/dotc/core/CheckRealizable.scala +++ b/src/dotty/tools/dotc/core/CheckRealizable.scala @@ -58,12 +58,16 @@ class CheckRealizable(implicit ctx: Context) { */ private val checkedFields: mutable.Set[Symbol] = mutable.LinkedHashSet[Symbol]() + /** Is symbol's definitition a lazy val? + * (note we exclude modules here, because their realizability is ensured separately) + */ + private def isLateInitialized(sym: Symbol) = sym.is(Lazy, butNot = Module) + /** Is this type a path with some part that is initialized on use? - * (note we exclude modules here, because their realizability is ensured separately). */ private def isLateInitialized(tp: Type): Boolean = tp.dealias match { case tp: TermRef => - tp.symbol.is(Lazy, butNot = Module) || isLateInitialized(tp.prefix) + isLateInitialized(tp.symbol) || isLateInitialized(tp.prefix) case _: SingletonType | NoPrefix => false case tp: TypeRef => @@ -84,7 +88,7 @@ class CheckRealizable(implicit ctx: Context) { else { val r = if (!sym.isStable) NotStable - else if (!sym.isLateInitialized) realizability(tp.prefix) + 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)