From 79045e2c0e2830818a9be1dfaa2a7f7f83f9c0de Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:23:05 +0200 Subject: [PATCH 01/15] Fix non-sensical code Replacing or types by their dominators and implicit conversions caused the code to do the right thing anyway, but with the arrival of true or-types, this became a static error. --- src/dotty/tools/dotc/transform/LambdaLift.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/LambdaLift.scala b/src/dotty/tools/dotc/transform/LambdaLift.scala index 18b030913ef0..19fb3dd0c8e4 100644 --- a/src/dotty/tools/dotc/transform/LambdaLift.scala +++ b/src/dotty/tools/dotc/transform/LambdaLift.scala @@ -121,7 +121,10 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisTransform private def symSet(f: LinkedHashMap[Symbol, SymSet], sym: Symbol): SymSet = f.getOrElseUpdate(sym, newSymSet) - def freeVars(sym: Symbol): List[Symbol] = free.getOrElse(sym, Nil).toList + def freeVars(sym: Symbol): List[Symbol] = free get sym match { + case Some(set) => set.toList + case None => Nil + } def proxyOf(sym: Symbol, fv: Symbol) = proxyMap.getOrElse(sym, Map.empty)(fv) From bc791ed3390e954b7338594b771ad09c5150d591 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:26:38 +0200 Subject: [PATCH 02/15] Reformatting to avoid a long line --- src/dotty/tools/dotc/transform/TailRec.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/transform/TailRec.scala b/src/dotty/tools/dotc/transform/TailRec.scala index 065bcb397690..d99a48af3975 100644 --- a/src/dotty/tools/dotc/transform/TailRec.scala +++ b/src/dotty/tools/dotc/transform/TailRec.scala @@ -252,7 +252,10 @@ class TailRec extends MiniPhaseTransform with DenotTransformer with FullParamete } else targs val method = if (callTargs.nonEmpty) TypeApply(Ident(label.termRef), callTargs) else Ident(label.termRef) - val thisPassed = if(this.method.owner.isClass) method appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) else method + val thisPassed = + if (this.method.owner.isClass) + method.appliedTo(receiver.ensureConforms(method.tpe.widen.firstParamTypes.head)) + else method val res = if (thisPassed.tpe.widen.isParameterless) thisPassed From f6291e42082708e164ebc3456d84e69f4f29cf59 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:29:20 +0200 Subject: [PATCH 03/15] Change default of unsafe Config option --- src/dotty/tools/dotc/config/Config.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/config/Config.scala b/src/dotty/tools/dotc/config/Config.scala index c188bfab4620..7744a5479b4b 100644 --- a/src/dotty/tools/dotc/config/Config.scala +++ b/src/dotty/tools/dotc/config/Config.scala @@ -87,8 +87,12 @@ object Config { */ final val checkLambdaVariance = false - /** Check that certain types cannot be created in erasedTypes phases */ - final val checkUnerased = true + /** Check that certain types cannot be created in erasedTypes phases. + * Note: Turning this option on will get some false negatives, since it is + * possible that And/Or types are still created during erasure as the result + * of some operation on an existing type. + */ + final val checkUnerased = false /** In `derivedSelect`, rewrite * From 98a69d90b16f0cc997255f097d3d5d9f2a60301b Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:35:58 +0200 Subject: [PATCH 04/15] Keep or types Don't replace them by their dominators, unless one of the following holds: - language:Scala2 mode is on - we are at the point of findMember selection - we compare with a higher-kinded application This means approximateUnion is now split into harmonizeUnion and orDominator which each implement one of the former's two functionalities. --- .../tools/dotc/core/ConstraintHandling.scala | 15 +- src/dotty/tools/dotc/core/StdNames.scala | 1 - src/dotty/tools/dotc/core/TypeOps.scala | 128 +++++++++++------- src/dotty/tools/dotc/core/Types.scala | 46 +++---- src/dotty/tools/dotc/typer/Namer.scala | 2 +- 5 files changed, 110 insertions(+), 82 deletions(-) diff --git a/src/dotty/tools/dotc/core/ConstraintHandling.scala b/src/dotty/tools/dotc/core/ConstraintHandling.scala index 5911af72c6a2..84f531385e59 100644 --- a/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -35,6 +35,15 @@ trait ConstraintHandling { /** If the constraint is frozen we cannot add new bounds to the constraint. */ protected var frozenConstraint = false + protected var alwaysFluid = false + + /** Perform `op` in a mode where all attempts to set `frozen` to true are ignored */ + def fluidly[T](op: => T): T = { + val saved = alwaysFluid + alwaysFluid = true + try op finally alwaysFluid = saved + } + /** We are currently comparing lambdas. Used as a flag for * optimization: when `false`, no need to do an expensive `pruneLambdaParams` */ @@ -126,14 +135,14 @@ trait ConstraintHandling { final def isSubTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint - frozenConstraint = true + frozenConstraint = !alwaysFluid try isSubType(tp1, tp2) finally frozenConstraint = saved } final def isSameTypeWhenFrozen(tp1: Type, tp2: Type): Boolean = { val saved = frozenConstraint - frozenConstraint = true + frozenConstraint = !alwaysFluid try isSameType(tp1, tp2) finally frozenConstraint = saved } @@ -219,7 +228,7 @@ trait ConstraintHandling { // is not a union type, approximate the union type from above by an intersection // of all common base types. if (fromBelow && isOrType(inst) && isFullyDefined(inst) && !isOrType(upperBound)) - inst = inst.approximateUnion + inst = ctx.harmonizeUnion(inst) // 3. If instance is from below, and upper bound has open named parameters // make sure the instance has all named parameters of the bound. diff --git a/src/dotty/tools/dotc/core/StdNames.scala b/src/dotty/tools/dotc/core/StdNames.scala index c52264637551..920c9635e62d 100644 --- a/src/dotty/tools/dotc/core/StdNames.scala +++ b/src/dotty/tools/dotc/core/StdNames.scala @@ -429,7 +429,6 @@ object StdNames { val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val java: N = "java" - val keepUnions: N = "keepUnions" val key: N = "key" val lang: N = "lang" val length: N = "length" diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index bee69ae691fe..5edd61c70e07 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -173,21 +173,53 @@ trait TypeOps { this: Context => // TODO: Make standalone object. } /** Approximate union type by intersection of its dominators. - * See Type#approximateUnion for an explanation. + * That is, replace a union type Tn | ... | Tn + * by the smallest intersection type of base-class instances of T1,...,Tn. + * Example: Given + * + * trait C[+T] + * trait D + * class A extends C[A] with D + * class B extends C[B] with D with E + * + * we approximate `A | B` by `C[A | B] with D` */ - def approximateUnion(tp: Type): Type = { + def orDominator(tp: Type): Type = { + /** a faster version of cs1 intersect cs2 */ def intersect(cs1: List[ClassSymbol], cs2: List[ClassSymbol]): List[ClassSymbol] = { val cs2AsSet = new util.HashSet[ClassSymbol](100) cs2.foreach(cs2AsSet.addEntry) cs1.filter(cs2AsSet.contains) } + /** The minimal set of classes in `cs` which derive all other classes in `cs` */ def dominators(cs: List[ClassSymbol], accu: List[ClassSymbol]): List[ClassSymbol] = (cs: @unchecked) match { case c :: rest => val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu if (cs == c.baseClasses) accu1 else dominators(rest, accu1) } + + def mergeRefined(tp1: Type, tp2: Type): Type = { + def fail = throw new AssertionError(i"Failure to join alternatives $tp1 and $tp2") + tp1 match { + case tp1 @ RefinedType(parent1, name1, rinfo1) => + tp2 match { + case RefinedType(parent2, `name1`, rinfo2) => + tp1.derivedRefinedType( + mergeRefined(parent1, parent2), name1, rinfo1 | rinfo2) + case _ => fail + } + case tp1 @ TypeRef(pre1, name1) => + tp2 match { + case tp2 @ TypeRef(pre2, `name1`) => + tp1.derivedSelect(pre1 | pre2) + case _ => fail + } + case _ => fail + } + } + def approximateOr(tp1: Type, tp2: Type): Type = { def isClassRef(tp: Type): Boolean = tp match { case tp: TypeRef => tp.symbol.isClass @@ -195,78 +227,70 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case _ => false } - /** If `tp1` and `tp2` are typebounds, try to make one fit into the other - * or to make them equal, by instantiating uninstantiated type variables. - */ - def homogenizedUnion(tp1: Type, tp2: Type): Type = { - tp1 match { - case tp1: TypeBounds => - tp2 match { - case tp2: TypeBounds => - def fitInto(tp1: TypeBounds, tp2: TypeBounds): Unit = { - val nestedCtx = ctx.fresh.setNewTyperState - if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx)) - nestedCtx.typerState.commit() - } - fitInto(tp1, tp2) - fitInto(tp2, tp1) - case _ => - } - case _ => - } - tp1 | tp2 - } - - tp1 match { - case tp1: RefinedType => - tp2 match { - case tp2: RefinedType if tp1.refinedName == tp2.refinedName => - return tp1.derivedRefinedType( - approximateUnion(OrType(tp1.parent, tp2.parent)), - tp1.refinedName, - homogenizedUnion(tp1.refinedInfo, tp2.refinedInfo)) - //.ensuring { x => println(i"approx or $tp1 | $tp2 = $x\n constr = ${ctx.typerState.constraint}"); true } // DEBUG - case _ => - } - case _ => - } - tp1 match { case tp1: RecType => tp1.rebind(approximateOr(tp1.parent, tp2)) case tp1: TypeProxy if !isClassRef(tp1) => - approximateUnion(tp1.superType | tp2) + orDominator(tp1.superType | tp2) case _ => tp2 match { case tp2: RecType => tp2.rebind(approximateOr(tp1, tp2.parent)) case tp2: TypeProxy if !isClassRef(tp2) => - approximateUnion(tp1 | tp2.superType) + orDominator(tp1 | tp2.superType) case _ => val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) val doms = dominators(commonBaseClasses, Nil) - def baseTp(cls: ClassSymbol): Type = - if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls) - else tp.baseTypeWithArgs(cls) + def baseTp(cls: ClassSymbol): Type = { + val base = + if (tp1.typeParams.nonEmpty) tp.baseTypeRef(cls) + else tp.baseTypeWithArgs(cls) + base.mapReduceOr(identity)(mergeRefined) + } doms.map(baseTp).reduceLeft(AndType.apply) } } } - if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp - else tp match { + + tp match { case tp: OrType => - approximateOr(tp.tp1, tp.tp2) // Maybe refactor using liftToRec? - case tp @ AndType(tp1, tp2) => - tp derived_& (approximateUnion(tp1), approximateUnion(tp2)) - case tp: RefinedType => - tp.derivedRefinedType(approximateUnion(tp.parent), tp.refinedName, tp.refinedInfo) - case tp: RecType => - tp.rebind(approximateUnion(tp.parent)) + approximateOr(tp.tp1, tp.tp2) case _ => tp } } + /** Given a disjunction T1 | ... | Tn of types with potentially embedded + * type variables, constrain type variables further if this eliminates + * some of the branches of the disjunction. Do this also for disjunctions + * embedded in intersections, as parents in refinements, and in recursive types. + * + * For instance, if `A` is an unconstrained type variable, then + * + * ArrayBuffer[Int] | ArrayBuffer[A] + * + * is approximated by constraining `A` to be =:= to `Int` and returning `ArrayBuffer[Int]` + * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` + */ + def harmonizeUnion(tp: Type): Type = tp match { + case tp: OrType => + joinIfScala2(typeComparer.fluidly(tp.tp1 | tp.tp2)) + case tp @ AndType(tp1, tp2) => + tp derived_& (harmonizeUnion(tp1), harmonizeUnion(tp2)) + case tp: RefinedType => + tp.derivedRefinedType(harmonizeUnion(tp.parent), tp.refinedName, tp.refinedInfo) + case tp: RecType => + tp.rebind(harmonizeUnion(tp.parent)) + case _ => + tp + } + + /** Under -language:Scala2: Replace or-types with their joins */ + private def joinIfScala2(tp: Type) = tp match { + case tp: OrType if scala2Mode => tp.join + case _ => tp + } + /** Not currently needed: * def liftToRec(f: (Type, Type) => Type)(tp1: Type, tp2: Type)(implicit ctx: Context) = { diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index 2f1b6b829d45..0f81f8c38f5f 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -436,8 +436,12 @@ object Types { tp.cls.findMember(name, pre, excluded) case AndType(l, r) => goAnd(l, r) - case OrType(l, r) => - goOr(l, r) + case tp: OrType => + // we need to keep the invariant that `pre <: tp`. Branch `union-types-narrow-prefix` + // achieved that by narrowing `pre` to each alternative, but it led to merge errors in + // lots of places. The present strategy is instead of widen `tp` using `join` to be a + // supertype of `pre`. + go(tp.join) case tp: JavaArrayType => defn.ObjectType.findMember(name, pre, excluded) case ErrorType => @@ -556,7 +560,6 @@ object Types { def goAnd(l: Type, r: Type) = { go(l) & (go(r), pre, safeIntersection = ctx.pendingMemberSearches.contains(name)) } - def goOr(l: Type, r: Type) = go(l) | (go(r), pre) { val recCount = ctx.findMemberCount + 1 ctx.findMemberCount = recCount @@ -1230,28 +1233,6 @@ object Types { */ def simplified(implicit ctx: Context) = ctx.simplify(this, null) - /** Approximations of union types: We replace a union type Tn | ... | Tn - * by the smallest intersection type of baseclass instances of T1,...,Tn. - * Example: Given - * - * trait C[+T] - * trait D - * class A extends C[A] with D - * class B extends C[B] with D with E - * - * we approximate `A | B` by `C[A | B] with D` - * - * As a second measure we also homogenize refinements containing - * type variables. For instance, if `A` is an instantiatable type variable, - * then - * - * ArrayBuffer[Int] | ArrayBuffer[A] - * - * is approximated by instantiating `A` to `Int` and returning `ArrayBuffer[Int]` - * instead of `ArrayBuffer[_ >: Int | A <: Int & A]` - */ - def approximateUnion(implicit ctx: Context) = ctx.approximateUnion(this) - /** customized hash code of this type. * NotCached for uncached types. Cached types * compute hash and use it as the type's hashCode. @@ -2233,9 +2214,24 @@ object Types { } abstract case class OrType(tp1: Type, tp2: Type) extends CachedGroundType with AndOrType { + assert(tp1.isInstanceOf[ValueType] && tp2.isInstanceOf[ValueType]) def isAnd = false + private[this] var myJoin: Type = _ + private[this] var myJoinPeriod: Period = Nowhere + + /** Replace or type by the closest non-or type above it */ + def join(implicit ctx: Context): Type = { + if (myJoinPeriod != ctx.period) { + myJoin = ctx.orDominator(this) + core.println(i"join of $this == $myJoin") + assert(myJoin != this) + myJoinPeriod = ctx.period + } + myJoin + } + def derivedOrType(tp1: Type, tp2: Type)(implicit ctx: Context): Type = if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this else OrType.make(tp1, tp2) diff --git a/src/dotty/tools/dotc/typer/Namer.scala b/src/dotty/tools/dotc/typer/Namer.scala index 4f4278468f63..00e92cbfb156 100644 --- a/src/dotty/tools/dotc/typer/Namer.scala +++ b/src/dotty/tools/dotc/typer/Namer.scala @@ -898,7 +898,7 @@ class Namer { typer: Typer => // definition is inline (i.e. final in Scala2). def widenRhs(tp: Type): Type = tp.widenTermRefExpr match { case tp: ConstantType if isInline => tp - case _ => tp.widen.approximateUnion + case _ => ctx.harmonizeUnion(tp.widen) } // Replace aliases to Unit by Unit itself. If we leave the alias in From d47f3280a7d46fa5e65ebfc986ed04b7b739aae2 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:48:34 +0200 Subject: [PATCH 05/15] Handle feature interaction between subtyping or types and hk types --- src/dotty/tools/dotc/core/TypeComparer.scala | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 991dd26644d6..0a51b896a690 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -324,8 +324,18 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { case AndType(tp11, tp12) => if (tp11.stripTypeVar eq tp12.stripTypeVar) isSubType(tp11, tp2) else thirdTry(tp1, tp2) - case OrType(tp11, tp12) => - isSubType(tp11, tp2) && isSubType(tp12, tp2) + case tp1 @ OrType(tp11, tp12) => + def joinOK = tp2.dealias match { + case tp12: HKApply => + // If we apply the default algorithm for `A[X] | B[Y] <: C[Z]` where `C` is a + // type parameter, we will instantiate `C` to `A` and then fail when comparing + // with `B[Y]`. To do the right thing, we need to instantiate `C` to the + // common superclass of `A` and `B`. + isSubType(tp1.join, tp2) + case _ => + false + } + joinOK || isSubType(tp11, tp2) && isSubType(tp12, tp2) case ErrorType => true case _ => From 14551e23d2de2db24864560c0b058306d1c78832 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:50:22 +0200 Subject: [PATCH 06/15] Refine mergeEntries If entries are type variables, we have to check their instances for equality. This came up onder the new or handling scheme. --- src/dotty/tools/dotc/core/OrderingConstraint.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/dotty/tools/dotc/core/OrderingConstraint.scala b/src/dotty/tools/dotc/core/OrderingConstraint.scala index 458f8b82f1c3..68c7655ef577 100644 --- a/src/dotty/tools/dotc/core/OrderingConstraint.scala +++ b/src/dotty/tools/dotc/core/OrderingConstraint.scala @@ -521,6 +521,11 @@ class OrderingConstraint(private val boundsMap: ParamBounds, case _ if e1 contains e2 => e2 case _ => mergeError } + case tv1: TypeVar => + e2 match { + case tv2: TypeVar if tv1.instanceOpt eq tv2.instanceOpt => e1 + case _ => mergeError + } case _ if e1 eq e2 => e1 case _ => mergeError } From 3d74bfa72bdc794cfb11b6afe15c77a5357617d1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:50:57 +0200 Subject: [PATCH 07/15] Remove unused language option In fact all of dotty.language can be removed. --- src/dotty/language.scala | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/dotty/language.scala diff --git a/src/dotty/language.scala b/src/dotty/language.scala deleted file mode 100644 index 416a4281ba64..000000000000 --- a/src/dotty/language.scala +++ /dev/null @@ -1,9 +0,0 @@ -package dotty - -object language { - - class Feature - - /** Keep union types */ - val keepUnions = new Feature -} From 25c0398b6f07df2449652e66cef8b6a6d3d4c7ce Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 1 Oct 2016 21:51:34 +0200 Subject: [PATCH 08/15] Adapt tests --- tests/{run => neg}/OrType.scala | 2 +- tests/{pickling => neg}/unions.scala | 9 ++++---- tests/pos/i1045.scala | 17 +++++++++++++++ tests/pos/union.scala | 11 ++++++++++ tests/pos/unions.scala | 32 ---------------------------- tests/run/OrderingTest.scala | 3 ++- tests/run/flat-flat-flat.scala | 4 ++++ 7 files changed, 39 insertions(+), 39 deletions(-) rename tests/{run => neg}/OrType.scala (79%) rename tests/{pickling => neg}/unions.scala (81%) create mode 100644 tests/pos/union.scala delete mode 100644 tests/pos/unions.scala diff --git a/tests/run/OrType.scala b/tests/neg/OrType.scala similarity index 79% rename from tests/run/OrType.scala rename to tests/neg/OrType.scala index 9ab805defbde..a9860db9311c 100644 --- a/tests/run/OrType.scala +++ b/tests/neg/OrType.scala @@ -2,7 +2,7 @@ class B(val x: Int) class C(val x: Double) object Test{ - def bar(x: B | C): Int | Double = x.x + def bar(x: B | C): Int | Double = x.x // error def main(args: Array[String]): Unit = { val b = new B(1) val c = new C(1) diff --git a/tests/pickling/unions.scala b/tests/neg/unions.scala similarity index 81% rename from tests/pickling/unions.scala rename to tests/neg/unions.scala index 22e6391e3f57..099a628c93c1 100644 --- a/tests/pickling/unions.scala +++ b/tests/neg/unions.scala @@ -16,11 +16,10 @@ object unions { val x: A | B = if (true) new A else new B def y: B | A = if (true) new A else new B - println(x.f) - println(x.g(2)) - println(y.f) - println(y.g(1.0)) - println(y.g(1.0f)) + println(x.f) // error + println(x.g(2)) // error + println(y.f) // error + println(y.g(1.0)) // error class C { private def foo = 0 diff --git a/tests/pos/i1045.scala b/tests/pos/i1045.scala index f5985af9235a..f0cf1df90f8a 100644 --- a/tests/pos/i1045.scala +++ b/tests/pos/i1045.scala @@ -1,7 +1,24 @@ import scala.collection._ + +object EmptyHashMap extends mutable.HashMap[Nothing, Nothing] object T { val newSymbolMap: mutable.HashMap[String, mutable.HashMap[Int, Double]] = mutable.HashMap.empty val map = newSymbolMap.getOrElse("a", mutable.HashMap.empty) map.put(1, 0.0) newSymbolMap.put("a", map) + + /** A map storing free variables of functions and classes */ +// type SymSet = Set[Symbol] +// private val free = new collection.mutable.LinkedHashMap[Symbol, SymSet] +// def freeVars(sym: Symbol): List[Symbol] = free.getOrElse(sym, Nil).toList + + class Tree[X >: Null] { def tpe: X = null } + class Ident[X >: Null] extends Tree[X] + class Apply[X >: Null] extends Tree[X] + + val x: Ident[Symbol] | Apply[Symbol] = ??? + val y = x.tpe + val z: Symbol = y + + } diff --git a/tests/pos/union.scala b/tests/pos/union.scala new file mode 100644 index 000000000000..8b20a8458a48 --- /dev/null +++ b/tests/pos/union.scala @@ -0,0 +1,11 @@ +object Test { + + class A + class B extends A + class C extends A + class D extends A + + val b = true + val x = if (b) new B else new C + val y: B | C = x +} diff --git a/tests/pos/unions.scala b/tests/pos/unions.scala deleted file mode 100644 index e57a96fb9010..000000000000 --- a/tests/pos/unions.scala +++ /dev/null @@ -1,32 +0,0 @@ -object unions { - - class A { - def f: String = "abc" - - def g(x: Int): Int = x - def g(x: Double): Double = x - } - - class B { - def f: String = "bcd" - - def g(x: Int) = -x - def g(x: Double): Double = -x - } - - val x: A | B = if (true) new A else new B - def y: B | A = if (true) new A else new B - println(x.f) - println(x.g(2)) - println(y.f) - println(y.g(1.0)) - - class C { - private def foo = 0 - class D extends C { - private def foo = 1 - def test(cd: C | D, dc: D | C) = (cd.foo, dc.foo) - } - } - -} diff --git a/tests/run/OrderingTest.scala b/tests/run/OrderingTest.scala index ad5acfa0cb00..561a3daeb381 100644 --- a/tests/run/OrderingTest.scala +++ b/tests/run/OrderingTest.scala @@ -23,7 +23,8 @@ object Test extends dotty.runtime.LegacyApp { testAll(false, true) testAll(1, 2); testAll(1.0, 2.0); - testAll(None, Some(1)); + // testAll(Some(1), Some(2)) // does not work in nsc or dotty + // testAll(None, Some(1)); // does not work in dotty with or-types testAll[Iterable[Int]](List(1), List(1, 2)); testAll[Iterable[Int]](List(1, 2), List(2)); testAll((1, "bar"), (1, "foo")) diff --git a/tests/run/flat-flat-flat.scala b/tests/run/flat-flat-flat.scala index 80868b9c5e40..e0fabe4e798b 100644 --- a/tests/run/flat-flat-flat.scala +++ b/tests/run/flat-flat-flat.scala @@ -2,10 +2,14 @@ object Test { def f1 = List(Iterator(Some(1), None, Some(2)), Iterator(Some(3), None)) def f2 = Iterator(List(Some(1), None, Some(2)), List(Some(3), None), Nil) def f3 = List(Some(Iterator(1)), None, Some(Iterator(2, 3))) + def f4 = List(Some(Iterator(1)), Some(Iterator(2, 3))) + def f5 = Iterator(List(Some(1), Some(2)), List(Some(3)), Nil) def main(args: Array[String]): Unit = { assert(f1.flatten.flatten.toList == List(1, 2, 3)) + assert(f5.flatten.flatten.toList == List(1, 2, 3)) assert(f2.flatten.flatten.toList == List(1, 2, 3)) assert(f3.flatten.flatten.toList == List(1, 2, 3)) + assert(f4.flatten.flatten.toList == List(1, 2, 3)) } } From 797a9385dc6971a4583c989f2a7ddae4d3c3ab50 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 Oct 2016 12:43:55 +0200 Subject: [PATCH 09/15] Scrutinize selections in TreeChecker Makes sure the symbol in the tree can be approximately reconstructed by calling member on the qualifier type. Approximately means: The two symbols might be different but one still overrides the other. --- .../tools/dotc/transform/TreeChecker.scala | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index fd8e41dc3fa9..808178369154 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -133,10 +133,7 @@ class TreeChecker extends Phase with SymTransformer { catch { case NonFatal(ex) => //TODO CHECK. Check that we are bootstrapped implicit val ctx: Context = checkingCtx - ctx.echo(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") - ctx.echo(ex.toString) - ctx.echo(ex.getStackTrace.take(30).deep.mkString("\n")) - ctx.echo("<<<") + println(i"*** error while checking ${ctx.compilationUnit} after phase ${checkingCtx.phase.prev} ***") throw ex } } @@ -331,8 +328,30 @@ class TreeChecker extends Phase with SymTransformer { checkNotRepeated(super.typedIdent(tree, pt)) } + /** Makes sure the symbol in the tree can be approximately reconstructed by + * calling `member` on the qualifier type. + * Approximately means: The two symbols might be different but one still overrides the other. + */ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { assert(tree.isTerm || !ctx.isAfterTyper, tree.show + " at " + ctx.phase) + val tpe = tree.typeOpt + val sym = tree.symbol + if (!tpe.isInstanceOf[WithFixedSym] && sym.exists && !sym.is(Private)) { + val qualTpe = tree.qualifier.typeOpt + val member = + if (sym.is(Private)) qualTpe.member(tree.name) + else qualTpe.nonPrivateMember(tree.name) + val memberSyms = member.alternatives.map(_.symbol) + assert(memberSyms.exists(mbr => + sym == mbr || + sym.overriddenSymbol(mbr.owner.asClass) == mbr || + mbr.overriddenSymbol(sym.owner.asClass) == sym), + ex"""symbols differ for $tree + |was : $sym + |alternatives by type: $memberSyms%, % of types ${memberSyms.map(_.info)}%, % + |qualifier type : ${tree.qualifier.typeOpt} + |tree type : ${tree.typeOpt} of class ${tree.typeOpt.getClass}""") + } checkNotRepeated(super.typedSelect(tree, pt)) } From 146bc29acaba58391a5462ee26f989debaac9038 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 Oct 2016 12:45:41 +0200 Subject: [PATCH 10/15] Refactor Splitter functionality Splitting or types is no longer needed with new scheme. Replacing idents with This nodes is better done in ExplicitSelf. So splitter now just distributes applications into and ifs. --- .../tools/dotc/transform/ExplicitSelf.scala | 9 ++++ src/dotty/tools/dotc/transform/Splitter.scala | 51 ++++++++----------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/dotty/tools/dotc/transform/ExplicitSelf.scala b/src/dotty/tools/dotc/transform/ExplicitSelf.scala index 618a0f108541..7bb65e5755b2 100644 --- a/src/dotty/tools/dotc/transform/ExplicitSelf.scala +++ b/src/dotty/tools/dotc/transform/ExplicitSelf.scala @@ -20,12 +20,21 @@ import Flags._ * * where `S` is the self type of `C`. * See run/i789.scala for a test case why this is needed. + * + * Also replaces idents referring to the self type with ThisTypes. */ class ExplicitSelf extends MiniPhaseTransform { thisTransform => import ast.tpd._ override def phaseName = "explicitSelf" + override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { + case tp: ThisType => + ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") + This(tp.cls) withPos tree.pos + case _ => tree + } + override def transformSelect(tree: Select)(implicit ctx: Context, info: TransformerInfo): Tree = tree match { case Select(thiz: This, name) if name.isTermName => val cls = thiz.symbol.asClass diff --git a/src/dotty/tools/dotc/transform/Splitter.scala b/src/dotty/tools/dotc/transform/Splitter.scala index efcf95ede016..d62be1a827e0 100644 --- a/src/dotty/tools/dotc/transform/Splitter.scala +++ b/src/dotty/tools/dotc/transform/Splitter.scala @@ -6,25 +6,34 @@ import ast.Trees._ import core._ import Contexts._, Types._, Decorators._, Denotations._, Symbols._, SymDenotations._, Names._ -/** This transform makes sure every identifier and select node - * carries a symbol. To do this, certain qualifiers with a union type - * have to be "splitted" with a type test. - * - * For now, only self references are treated. +/** Distribute applications into Block and If nodes */ class Splitter extends MiniPhaseTransform { thisTransform => import ast.tpd._ override def phaseName: String = "splitter" - /** Replace self referencing idents with ThisTypes. */ - override def transformIdent(tree: Ident)(implicit ctx: Context, info: TransformerInfo) = tree.tpe match { - case tp: ThisType => - ctx.debuglog(s"owner = ${ctx.owner}, context = ${ctx}") - This(tp.cls) withPos tree.pos - case _ => tree + /** Distribute arguments among splitted branches */ + def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { + def recur(fn: Tree): Tree = fn match { + case Block(stats, expr) => Block(stats, recur(expr)) + case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) + case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos + } + recur(tree.fun) } + override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, typeApply) + + override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = + distribute(tree, apply) + + private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) + private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) + +/* The following is no longer necessary, since we select members on the join of an or type: + * /** If we select a name, make sure the node has a symbol. * If necessary, split the qualifier with type tests. * Example: Assume: @@ -108,23 +117,5 @@ class Splitter extends MiniPhaseTransform { thisTransform => evalOnce(qual)(qual => choose(qual, candidates(qual.tpe))) } } - - /** Distribute arguments among splitted branches */ - def distribute(tree: GenericApply[Type], rebuild: (Tree, List[Tree]) => Context => Tree)(implicit ctx: Context) = { - def recur(fn: Tree): Tree = fn match { - case Block(stats, expr) => Block(stats, recur(expr)) - case If(cond, thenp, elsep) => If(cond, recur(thenp), recur(elsep)) - case _ => rebuild(fn, tree.args)(ctx) withPos tree.pos - } - recur(tree.fun) - } - - override def transformTypeApply(tree: TypeApply)(implicit ctx: Context, info: TransformerInfo) = - distribute(tree, typeApply) - - override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo) = - distribute(tree, apply) - - private val typeApply = (fn: Tree, args: List[Tree]) => (ctx: Context) => TypeApply(fn, args)(ctx) - private val apply = (fn: Tree, args: List[Tree]) => (ctx: Context) => Apply(fn, args)(ctx) +*/ } From 2fe7e9220ab12336d4dcddbe9b523a736a6c17e8 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 Oct 2016 13:15:35 +0200 Subject: [PATCH 11/15] Disallow singleton types in unions For the moment, we do not know how to handle something like 1 | 2 or x.type | y.type correctly. So it's better to disallow these situations until we find a proper solution. --- src/dotty/tools/dotc/typer/Checking.scala | 8 ++++++++ src/dotty/tools/dotc/typer/Typer.scala | 5 +++-- tests/neg/singletonOrs.scala | 6 ++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 tests/neg/singletonOrs.scala diff --git a/src/dotty/tools/dotc/typer/Checking.scala b/src/dotty/tools/dotc/typer/Checking.scala index 7ba66e3d8a61..3461facc141e 100644 --- a/src/dotty/tools/dotc/typer/Checking.scala +++ b/src/dotty/tools/dotc/typer/Checking.scala @@ -542,6 +542,13 @@ trait Checking { errorTree(tpt, ex"missing type parameter for ${tpt.tpe}") } else tpt + + /** Check that `tpt` does not refer to a singleton type */ + def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = + if (tpt.tpe.isInstanceOf[SingletonType]) { + errorTree(tpt, ex"Singleton type ${tpt.tpe} is not allowed $where") + } + else tpt } trait NoChecking extends Checking { @@ -556,4 +563,5 @@ trait NoChecking extends Checking { override def checkNoDoubleDefs(cls: Symbol)(implicit ctx: Context): Unit = () override def checkParentCall(call: Tree, caller: ClassSymbol)(implicit ctx: Context) = () override def checkSimpleKinded(tpt: Tree)(implicit ctx: Context): Tree = tpt + override def checkNotSingleton(tpt: Tree, where: String)(implicit ctx: Context): Tree = tpt } diff --git a/src/dotty/tools/dotc/typer/Typer.scala b/src/dotty/tools/dotc/typer/Typer.scala index bbb20bcf5d5d..e423082d5738 100644 --- a/src/dotty/tools/dotc/typer/Typer.scala +++ b/src/dotty/tools/dotc/typer/Typer.scala @@ -999,8 +999,9 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit } def typedOrTypeTree(tree: untpd.OrTypeTree)(implicit ctx: Context): OrTypeTree = track("typedOrTypeTree") { - val left1 = typed(tree.left) - val right1 = typed(tree.right) + val where = "in a union type" + val left1 = checkNotSingleton(typed(tree.left), where) + val right1 = checkNotSingleton(typed(tree.right), where) assignType(cpy.OrTypeTree(tree)(left1, right1), left1, right1) } diff --git a/tests/neg/singletonOrs.scala b/tests/neg/singletonOrs.scala new file mode 100644 index 000000000000..687e491ef7a1 --- /dev/null +++ b/tests/neg/singletonOrs.scala @@ -0,0 +1,6 @@ +object Test { + def foo: 1 | 2 = 1 // error // error + def bar: 3 | 4 = foo // error // error + def foo: 1 | 2 = 1 // error // error + def bar: 1 = foo +} From 2f01c7d1ba4ab4d542c3838e184a5ac9735578f4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 2 Oct 2016 13:17:04 +0200 Subject: [PATCH 12/15] Don't report double def errors if symbol's type is erroneous This happened for singletonOrs, and led to spurious errors there. --- src/dotty/tools/dotc/core/Denotations.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/Denotations.scala b/src/dotty/tools/dotc/core/Denotations.scala index 4f01c43cfb63..0f95fc59190f 100644 --- a/src/dotty/tools/dotc/core/Denotations.scala +++ b/src/dotty/tools/dotc/core/Denotations.scala @@ -336,7 +336,8 @@ object Denotations { (!sym2.isAsConcrete(sym1) || precedes(sym1.owner, sym2.owner) || accessBoundary(sym2).isProperlyContainedIn(accessBoundary(sym1)) || - sym1.is(Method) && !sym2.is(Method)) + sym1.is(Method) && !sym2.is(Method)) || + sym1.info.isErroneous /** Sym preference provided types also override */ def prefer(sym1: Symbol, sym2: Symbol, info1: Type, info2: Type) = From da344548d7425368ccd5bf7a98e522d00cdc95aa Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 10 Oct 2016 09:15:42 +0200 Subject: [PATCH 13/15] Drop dotty.language from Definitions --- src/dotty/tools/dotc/core/Definitions.scala | 4 +--- src/dotty/tools/dotc/core/TypeOps.scala | 6 +++--- tests/pos/intersection.scala | 1 - 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/dotty/tools/dotc/core/Definitions.scala b/src/dotty/tools/dotc/core/Definitions.scala index 75b75d3d595b..b1c2bc5350d4 100644 --- a/src/dotty/tools/dotc/core/Definitions.scala +++ b/src/dotty/tools/dotc/core/Definitions.scala @@ -430,10 +430,8 @@ class Definitions { def Product_productArity(implicit ctx: Context) = Product_productArityR.symbol lazy val Product_productPrefixR = ProductClass.requiredMethodRef(nme.productPrefix) def Product_productPrefix(implicit ctx: Context) = Product_productPrefixR.symbol - lazy val LanguageModuleRef = ctx.requiredModule("dotty.language") + lazy val LanguageModuleRef = ctx.requiredModule("scala.language") def LanguageModuleClass(implicit ctx: Context) = LanguageModuleRef.symbol.moduleClass.asClass - lazy val Scala2LanguageModuleRef = ctx.requiredModule("scala.language") - def Scala2LanguageModuleClass(implicit ctx: Context) = Scala2LanguageModuleRef.symbol.moduleClass.asClass lazy val NonLocalReturnControlType: TypeRef = ctx.requiredClassRef("scala.runtime.NonLocalReturnControl") lazy val ClassTagType = ctx.requiredClassRef("scala.reflect.ClassTag") diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 5edd61c70e07..d480a792bab3 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -517,7 +517,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. */ def featureEnabled(owner: ClassSymbol, feature: TermName): Boolean = { def toPrefix(sym: Symbol): String = - if (!sym.exists || (sym eq defn.LanguageModuleClass) || (sym eq defn.Scala2LanguageModuleClass)) "" + if (!sym.exists || (sym eq defn.LanguageModuleClass)) "" else toPrefix(sym.owner) + sym.name + "." def featureName = toPrefix(owner) + feature def hasImport(implicit ctx: Context): Boolean = { @@ -536,13 +536,13 @@ trait TypeOps { this: Context => // TODO: Make standalone object. /** Is auto-tupling enabled? */ def canAutoTuple = - !featureEnabled(defn.Scala2LanguageModuleClass, nme.noAutoTupling) + !featureEnabled(defn.LanguageModuleClass, nme.noAutoTupling) def scala2Mode = featureEnabled(defn.LanguageModuleClass, nme.Scala2) def dynamicsEnabled = - featureEnabled(defn.Scala2LanguageModuleClass, nme.dynamics) + featureEnabled(defn.LanguageModuleClass, nme.dynamics) def testScala2Mode(msg: String, pos: Position) = { if (scala2Mode) migrationWarning(msg, pos) diff --git a/tests/pos/intersection.scala b/tests/pos/intersection.scala index a4c19b5af406..48551920c318 100644 --- a/tests/pos/intersection.scala +++ b/tests/pos/intersection.scala @@ -1,4 +1,3 @@ -import dotty.language.keepUnions object intersection { class A From 8067b952875426d640968be865773f6ef3783f3c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 10 Oct 2016 18:59:44 +0200 Subject: [PATCH 14/15] Fix cutting problem Test case: orInf.scala. This showed a problem where an `either` operation had to arbitrarily pick one constraint over another, leading to a type error down the line. What happened was that a `constrainResult` generated the constraint Set[A] <: Set[String] | Set[Int] But this constraint cannot be simplified without a cut and a resulting loss of information. We avoid the problem by not constraining the result if the prototype is a disjunction. --- src/dotty/tools/dotc/core/TypeComparer.scala | 7 +++++-- src/dotty/tools/dotc/typer/ProtoTypes.scala | 7 ++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeComparer.scala b/src/dotty/tools/dotc/core/TypeComparer.scala index 0a51b896a690..52b248abb956 100644 --- a/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/src/dotty/tools/dotc/core/TypeComparer.scala @@ -8,7 +8,7 @@ import StdNames.{nme, tpnme} import collection.mutable import util.{Stats, DotClass, SimpleMap} import config.Config -import config.Printers.{typr, constr, subtyping} +import config.Printers.{typr, constr, subtyping, noPrinter} import TypeErasure.{erasedLub, erasedGlb} import TypeApplications._ import scala.util.control.NonFatal @@ -837,8 +837,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling { op1 && { val leftConstraint = constraint constraint = preConstraint - if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) + if (!(op2 && subsumes(leftConstraint, constraint, preConstraint))) { + if (constr != noPrinter && !subsumes(constraint, leftConstraint, preConstraint)) + constr.println(i"CUT - prefer $leftConstraint over $constraint") constraint = leftConstraint + } true } || op2 } diff --git a/src/dotty/tools/dotc/typer/ProtoTypes.scala b/src/dotty/tools/dotc/typer/ProtoTypes.scala index 0e6697fb730a..dd5705fbf6e5 100644 --- a/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -43,6 +43,11 @@ object ProtoTypes { isCompatible(normalize(tp, pt)(nestedCtx), pt)(nestedCtx) } + private def disregardProto(pt: Type)(implicit ctx: Context): Boolean = pt.dealias match { + case _: OrType => true + case pt => pt.isRef(defn.UnitClass) + } + /** Check that the result type of the current method * fits the given expected result type. */ @@ -54,7 +59,7 @@ object ProtoTypes { case _ => true } - case _: ValueTypeOrProto if !(pt isRef defn.UnitClass) => + case _: ValueTypeOrProto if !disregardProto(pt) => mt match { case mt: MethodType => mt.isDependent || isCompatible(normalize(mt, pt), pt) From ba18173c4ac655eb07eca036a81a7a8b9e76caa7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 10 Oct 2016 19:00:38 +0200 Subject: [PATCH 15/15] Add test case --- tests/pos/orinf.scala | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/pos/orinf.scala diff --git a/tests/pos/orinf.scala b/tests/pos/orinf.scala new file mode 100644 index 000000000000..30b7fd2f6353 --- /dev/null +++ b/tests/pos/orinf.scala @@ -0,0 +1,6 @@ +object Test { + + def foo(lis: scala.collection.immutable.Set[Int] | scala.collection.immutable.Set[String]) = lis + foo(Set(1)) + foo(Set("")) +}