From 7d1d93e95113802bee77b9d2b89475a56be46bf7 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Feb 2016 12:03:09 +0100 Subject: [PATCH 1/5] Push `|' into corresponding RefinedTypes in approximateUnion This gives in general a supertype, that's OK for approximation. See ee76fda for an explanation. --- src/dotty/tools/dotc/core/TypeOps.scala | 63 ++++++++++++++++--------- tests/pos/intersection.scala | 4 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 70e8302d93dc..7121d94dfdae 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -266,31 +266,50 @@ trait TypeOps { this: Context => // TODO: Make standalone object. val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu if (cs == c.baseClasses) accu1 else dominators(rest, accu1) } + def approximateOr(tp1: Type, tp2: Type)(implicit ctx: Context): Type = { + def isClassRef(tp: Type): Boolean = tp match { + case tp: TypeRef => tp.symbol.isClass + case tp: RefinedType => isClassRef(tp.parent) + case _ => false + } + def next(tp: TypeProxy) = tp.underlying match { + case TypeBounds(_, hi) => hi + case nx => nx + } + 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, + (tp1.refinedInfo | tp2.refinedInfo).substRefinedThis(tp2, RefinedThis(tp1))) + .ensuring { x => println(i"approx or $tp1 | $tp2 = $x"); true } // DEBUG + case _ => + } + case _ => + } + tp1 match { + case tp1: TypeProxy if !isClassRef(tp1) => + approximateUnion(next(tp1) | tp2) + case _ => + tp2 match { + case tp2: TypeProxy if !isClassRef(tp2) => + approximateUnion(tp1 | next(tp2)) + 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) + doms.map(baseTp).reduceLeft(AndType.apply) + } + } + } if (ctx.featureEnabled(defn.LanguageModuleClass, nme.keepUnions)) tp else tp match { case tp: OrType => - def isClassRef(tp: Type): Boolean = tp match { - case tp: TypeRef => tp.symbol.isClass - case tp: RefinedType => isClassRef(tp.parent) - case _ => false - } - def next(tp: TypeProxy) = tp.underlying match { - case TypeBounds(_, hi) => hi - case nx => nx - } - tp.tp1 match { - case tp1: TypeProxy if !isClassRef(tp1) => - approximateUnion(next(tp1) | tp.tp2) - case _ => - tp.tp2 match { - case tp2: TypeProxy if !isClassRef(tp2) => - approximateUnion(tp.tp1 | next(tp2)) - case _ => - val commonBaseClasses = tp.mapReduceOr(_.baseClasses)(intersect) - val doms = dominators(commonBaseClasses, Nil) - doms.map(tp.baseTypeWithArgs).reduceLeft(AndType.apply) - } - } + approximateOr(tp.tp1, tp.tp2) case tp @ AndType(tp1, tp2) => tp derived_& (approximateUnion(tp1), approximateUnion(tp2)) case tp: RefinedType => diff --git a/tests/pos/intersection.scala b/tests/pos/intersection.scala index 2b9f6c0b72ba..a4c19b5af406 100644 --- a/tests/pos/intersection.scala +++ b/tests/pos/intersection.scala @@ -1,3 +1,4 @@ +import dotty.language.keepUnions object intersection { class A @@ -11,9 +12,6 @@ object intersection { val a: A & B => Unit = z val b: (A => Unit) | (B => Unit) = z - - - type needsA = A => Nothing type needsB = B => Nothing } From 8efdbdc9843f66fbb8fc39aa2716da0fe7187fd9 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Feb 2016 14:37:44 +0100 Subject: [PATCH 2/5] Try to make refinements match in approximateUnions See comment in Typer#approximateUnion for an explanation. Fixes #1045. --- src/dotty/tools/dotc/core/TypeOps.scala | 24 ++++++++++++++++++++++-- src/dotty/tools/dotc/core/Types.scala | 19 +++++++++++++++++++ tests/pos/i1045.scala | 7 +++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i1045.scala diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 7121d94dfdae..28730fec98f5 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -276,6 +276,26 @@ trait TypeOps { this: Context => // TODO: Make standalone object. case TypeBounds(_, hi) => hi case nx => nx } + /** 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 = { + def fitInto(tp1: Type, tp2: Type): Unit = tp1 match { + case tp1: TypeBounds => + tp2 match { + case tp2: TypeBounds => + val nestedCtx = ctx.fresh.setNewTyperState + if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx)) + nestedCtx.typerState.commit() + case _ => + } + case _ => + } + fitInto(tp1, tp2) + fitInto(tp2, tp1) + tp1 | tp2 + } + tp1 match { case tp1: RefinedType => tp2 match { @@ -283,8 +303,8 @@ trait TypeOps { this: Context => // TODO: Make standalone object. return tp1.derivedRefinedType( approximateUnion(OrType(tp1.parent, tp2.parent)), tp1.refinedName, - (tp1.refinedInfo | tp2.refinedInfo).substRefinedThis(tp2, RefinedThis(tp1))) - .ensuring { x => println(i"approx or $tp1 | $tp2 = $x"); true } // DEBUG + homogenizedUnion(tp1.refinedInfo, tp2.refinedInfo).substRefinedThis(tp2, RefinedThis(tp1))) + //.ensuring { x => println(i"approx or $tp1 | $tp2 = $x\n constr = ${ctx.typerState.constraint}"); true } // DEBUG case _ => } case _ => diff --git a/src/dotty/tools/dotc/core/Types.scala b/src/dotty/tools/dotc/core/Types.scala index e266bab6f482..812edf2fef0c 100644 --- a/src/dotty/tools/dotc/core/Types.scala +++ b/src/dotty/tools/dotc/core/Types.scala @@ -1199,6 +1199,15 @@ object Types { * 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) @@ -2847,6 +2856,11 @@ object Types { case _ => super.| (that) } + /** The implied bounds, where aliases are mapped to intervals from + * Nothing/Any + */ + def boundsInterval(implicit ctx: Context): TypeBounds = this + /** If this type and that type have the same variance, this variance, otherwise 0 */ final def commonVariance(that: TypeBounds): Int = (this.variance + that.variance) / 2 @@ -2884,6 +2898,11 @@ object Types { else if (v < 0) derivedTypeAlias(this.lo & that.lo, v) else super.| (that) } + + override def boundsInterval(implicit ctx: Context): TypeBounds = + if (variance == 0) this + else if (variance < 0) TypeBounds.lower(alias) + else TypeBounds.upper(alias) } class CachedTypeAlias(alias: Type, variance: Int, hc: Int) extends TypeAlias(alias, variance) { diff --git a/tests/pos/i1045.scala b/tests/pos/i1045.scala new file mode 100644 index 000000000000..f5985af9235a --- /dev/null +++ b/tests/pos/i1045.scala @@ -0,0 +1,7 @@ +import scala.collection._ +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) +} From 6778e3c74ff86c0174e84bb3df08af47931ecbbb Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Feb 2016 16:30:11 +0100 Subject: [PATCH 3/5] Slight optimization --- src/dotty/tools/dotc/core/TypeOps.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index 28730fec98f5..e85bd335a03f 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -280,19 +280,21 @@ trait TypeOps { this: Context => // TODO: Make standalone object. * or to make them equal, by instantiating uninstantiated type variables. */ def homogenizedUnion(tp1: Type, tp2: Type): Type = { - def fitInto(tp1: Type, tp2: Type): Unit = tp1 match { + tp1 match { case tp1: TypeBounds => tp2 match { case tp2: TypeBounds => - val nestedCtx = ctx.fresh.setNewTyperState - if (tp2.boundsInterval.contains(tp1.boundsInterval)(nestedCtx)) - nestedCtx.typerState.commit() + 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 _ => } - fitInto(tp1, tp2) - fitInto(tp2, tp1) tp1 | tp2 } From fc4867eafbf9714bfd053cc594b6763197859001 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 4 Feb 2016 18:06:03 +0100 Subject: [PATCH 4/5] Disable benchmark test The test checks that Scala collections perform within 10x of Java collections. That's not something we need to test for dotty. And because of the heavily parallel execution of the tests it does not always hold. This is the second time in a a month that this particular test failed on jenkins. I think we lost enough cycles on it. --- tests/{ => disabled}/run/t5293-map.scala | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{ => disabled}/run/t5293-map.scala (100%) diff --git a/tests/run/t5293-map.scala b/tests/disabled/run/t5293-map.scala similarity index 100% rename from tests/run/t5293-map.scala rename to tests/disabled/run/t5293-map.scala From ae09ac296f192d9b2229e2cd92bac4715f77e207 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 8 Feb 2016 15:26:49 +0100 Subject: [PATCH 5/5] Drop redundant context parameter --- src/dotty/tools/dotc/core/TypeOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dotty/tools/dotc/core/TypeOps.scala b/src/dotty/tools/dotc/core/TypeOps.scala index e85bd335a03f..34febf3be689 100644 --- a/src/dotty/tools/dotc/core/TypeOps.scala +++ b/src/dotty/tools/dotc/core/TypeOps.scala @@ -266,7 +266,7 @@ trait TypeOps { this: Context => // TODO: Make standalone object. val accu1 = if (accu exists (_ derivesFrom c)) accu else c :: accu if (cs == c.baseClasses) accu1 else dominators(rest, accu1) } - def approximateOr(tp1: Type, tp2: Type)(implicit ctx: Context): Type = { + def approximateOr(tp1: Type, tp2: Type): Type = { def isClassRef(tp: Type): Boolean = tp match { case tp: TypeRef => tp.symbol.isClass case tp: RefinedType => isClassRef(tp.parent)