From 5ec0731fc3e3585ae6cdccef0e0d465c4bd7ccfc Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Mar 2018 10:56:27 +0100 Subject: [PATCH 1/7] Allow member operation to constrain Previously we failed if a member of a TypeParamRef did not find anything in its upper constraint bound. We now look in the lower bound instead, and if something is found there, add a constraint to make sure that any future instantiation of the parameter has the same members. --- .../src/dotty/tools/dotc/core/ConstraintHandling.scala | 7 ++++++- compiler/src/dotty/tools/dotc/core/Types.scala | 8 +++++++- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala index 2fda43a675a9..c126146ff20a 100644 --- a/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala +++ b/compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala @@ -2,7 +2,7 @@ package dotty.tools package dotc package core -import Types._, Contexts._, Symbols._ +import Types._, Contexts._, Symbols._, Denotations.Denotation, SymDenotations.NoDenotation import Decorators._ import config.Config import config.Printers.{constr, typr} @@ -130,6 +130,11 @@ trait ConstraintHandling { res } + def addMemberBound(param: TypeParamRef, lower: Type, member: Denotation): Denotation = + (member /: member.alternatives.map(_.symbol.owner).distinct) { (mbr, baseCls) => + if (addUpperBound(param, lower.baseType(baseCls))) member else NoDenotation + } + /** Make p2 = p1, transfer all bounds of p2 to p1 * @pre less(p1)(p2) */ diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 5526e288fada..ecaa794e392f 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -591,7 +591,13 @@ object Types { ctx.typerState.constraint.entry(tp) match { case bounds: TypeBounds if bounds ne next => ctx.typerState.ephemeral = true - go(bounds.hi) + val upper = go(bounds.hi) + if (upper.exists) upper + else { + val lower = go(bounds.lo) + if (!lower.exists) upper + else ctx.typeComparer.addMemberBound(tp, bounds.lo, lower) + } case _ => go(next) } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 4c1b2f12b778..fd1d23400c34 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -394,7 +394,7 @@ object ProtoTypes { for (n <- (0 until tl.paramNames.length).toList) yield { val tt = new TypeTree().withPos(owningTree.pos) - tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner)) + tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner, ctx.typeVarLevel)) } /** Ensure that `tl` is not already in constraint, make a copy of necessary */ From 6de750ea910fc1d8fdd359ace4104d7933bd7fdf Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Mar 2018 11:01:18 +0100 Subject: [PATCH 2/7] Don't set ephemeral flag when testing whether a TypeVar is instantiated We (probably) need ephemeral only if we actually compute the current instance type, and this instance type might be retracted later. --- compiler/src/dotty/tools/dotc/core/Types.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index ecaa794e392f..93e5bd1f76e6 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3380,7 +3380,8 @@ object Types { } /** Is the variable already instantiated? */ - def isInstantiated(implicit ctx: Context) = instanceOpt.exists + def isInstantiated(implicit ctx: Context) = + inst.exists || ctx.typerState.instType(this).exists /** Instantiate variable with given type */ def instantiateWith(tp: Type)(implicit ctx: Context): Type = { From 345565648dd34454084186042324bd3bfccda65e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 3 Mar 2018 12:26:39 +0100 Subject: [PATCH 3/7] Retract out-of-sync change --- compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index fd1d23400c34..4c1b2f12b778 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -394,7 +394,7 @@ object ProtoTypes { for (n <- (0 until tl.paramNames.length).toList) yield { val tt = new TypeTree().withPos(owningTree.pos) - tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner, ctx.typeVarLevel)) + tt.withType(new TypeVar(tl.paramRefs(n), state, tt, ctx.owner)) } /** Ensure that `tl` is not already in constraint, make a copy of necessary */ From 11bbb9ccbdf2a8448cd662e3c372b40a976d2f7a Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 08:46:44 +0100 Subject: [PATCH 4/7] More flexible tracing Allow to define what gets shown as a result on a backtrace --- .../dotty/tools/dotc/reporting/trace.scala | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index d0184c4c9e67..a56202818764 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -21,12 +21,16 @@ object trace { } @inline - def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { + def apply[T](question: => String, printer: Printers.Printer, showOp: Any => String)(op: => T)(implicit ctx: Context): T = { def op1 = op if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 - else doTrace[T](question, printer, show)(op1) + else doTrace[T](question, printer, showOp)(op1) } + @inline + def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = + apply[T](question, printer, if (show) showShowable(_) else alwaysToString)(op) + @inline def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = apply[T](question, printer, false)(op) @@ -39,16 +43,21 @@ object trace { def apply[T](question: => String)(op: => T)(implicit ctx: Context): T = apply[T](question, Printers.default, false)(op) - private def doTrace[T](question: => String, printer: Printers.Printer = Printers.default, show: Boolean = false) - (op: => T)(implicit ctx: Context): T = { - def resStr(res: Any): String = res match { - case res: printing.Showable if show => res.show - case _ => String.valueOf(res) - } + private def showShowable(x: Any)(implicit ctx: Context) = x match { + case x: printing.Showable => x.show + case _ => String.valueOf(x) + } + + private val alwaysToString = (x: Any) => String.valueOf(x) + + private def doTrace[T](question: => String, + printer: Printers.Printer = Printers.default, + showOp: Any => String = alwaysToString) + (op: => T)(implicit ctx: Context): T = { // Avoid evaluating question multiple time, since each evaluation // may cause some extra logging output. lazy val q: String = question - apply[T](s"==> $q?", (res: Any) => s"<== $q = ${resStr(res)}")(op) + apply[T](s"==> $q?", (res: Any) => s"<== $q = ${showOp(res)}")(op) } def apply[T](leading: => String, trailing: Any => String)(op: => T)(implicit ctx: Context): T = From 9fc455a0458b77b160b0282c539074025254b7a4 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 09:30:20 +0100 Subject: [PATCH 5/7] Avoid creation of an unused closure value in each trace op The previous scheme created a `val showOp = ` value for each trace operation. It was unused if tracing was disabled. Still might be better to avoid its creation in the first place. --- compiler/src/dotty/tools/dotc/reporting/trace.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/trace.scala b/compiler/src/dotty/tools/dotc/reporting/trace.scala index a56202818764..d42008500bbf 100644 --- a/compiler/src/dotty/tools/dotc/reporting/trace.scala +++ b/compiler/src/dotty/tools/dotc/reporting/trace.scala @@ -28,8 +28,11 @@ object trace { } @inline - def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = - apply[T](question, printer, if (show) showShowable(_) else alwaysToString)(op) + def apply[T](question: => String, printer: Printers.Printer, show: Boolean)(op: => T)(implicit ctx: Context): T = { + def op1 = op + if (!Config.tracingEnabled || printer.eq(config.Printers.noPrinter)) op1 + else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1) + } @inline def apply[T](question: => String, printer: Printers.Printer)(op: => T)(implicit ctx: Context): T = From 60d8b92c08587536adcf345c95fa31cdc88f7111 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 09:36:12 +0100 Subject: [PATCH 6/7] Allow to suppress "inlined from" parts when printing trees -Yshow-no-inline suppresses "inlined from" parts when printing trees. This is useful when one has deeply inlined structures, as is the case when looking at `trace`ed code. --- compiler/src/dotty/tools/dotc/config/ScalaSettings.scala | 3 ++- compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 8437db43fb3b..41d5dfc6d282 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -87,6 +87,7 @@ class ScalaSettings extends Settings.SettingGroup { val YdisableFlatCpCaching = BooleanSetting("-YdisableFlatCpCaching", "Do not cache flat classpath representation of classpath elements from jars across compiler instances.") val YnoImports = BooleanSetting("-Yno-imports", "Compile without importing scala.*, java.lang.*, or Predef.") + val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.") val YnoGenericSig = BooleanSetting("-Yno-generic-signatures", "Suppress generation of generic signatures for Java.") val YnoPredef = BooleanSetting("-Yno-predef", "Compile without importing Predef.") val Yskip = PhasesSetting("-Yskip", "Skip") @@ -126,7 +127,7 @@ class ScalaSettings extends Settings.SettingGroup { val YexplainLowlevel = BooleanSetting("-Yexplain-lowlevel", "When explaining type errors, show types at a lower level.") val YnoDoubleBindings = BooleanSetting("-Yno-double-bindings", "Assert no namedtype is bound twice (should be enabled only if program is error-free).") val YshowVarBounds = BooleanSetting("-Yshow-var-bounds", "Print type variables with their bounds") - val YnoInline = BooleanSetting("-Yno-inline", "Suppress inlining.") + val YshowNoInline = BooleanSetting("-Yshow-no-inline", "Show inlined code without the 'inlined from' info") /** Linker specific flags */ val optimise = BooleanSetting("-optimise", "Generates faster bytecode by applying local optimisations to the .program") withAbbreviation "-optimize" diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index e3bb7ce3ce2b..093da87ccba6 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -359,7 +359,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case SeqLiteral(elems, elemtpt) => "[" ~ toTextGlobal(elems, ",") ~ " : " ~ toText(elemtpt) ~ "]" case tree @ Inlined(call, bindings, body) => - (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView) ~ + (("/* inlined from " ~ toText(call) ~ "*/ ") provided !homogenizedView && !ctx.settings.YshowNoInline.value) ~ blockText(bindings :+ body) case tpt: untpd.DerivedTypeTree => "" From 6b3b5a0342b5057ea99e43250b8678febf2c5995 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 5 Mar 2018 09:46:24 +0100 Subject: [PATCH 7/7] Add SimpleIdentitySet data structure Used for small, linked sets. Normal immutable sets are about as fast for 0 - 4 elements, but are not linked for larger sizes. --- ...impleMap.scala => SimpleIdentityMap.scala} | 3 + .../tools/dotc/util/SimpleIdentitySet.scala | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+) rename compiler/src/dotty/tools/dotc/util/{SimpleMap.scala => SimpleIdentityMap.scala} (98%) create mode 100644 compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala diff --git a/compiler/src/dotty/tools/dotc/util/SimpleMap.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala similarity index 98% rename from compiler/src/dotty/tools/dotc/util/SimpleMap.scala rename to compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala index dbd5b1d1abbb..bdbba5605141 100644 --- a/compiler/src/dotty/tools/dotc/util/SimpleMap.scala +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentityMap.scala @@ -2,6 +2,9 @@ package dotty.tools.dotc.util import collection.mutable.ListBuffer +/** A simple linked map with `eq` as the key comparison, optimized for small maps. + * It has linear complexity for `apply`, `updated`, and `remove`. + */ abstract class SimpleIdentityMap[K <: AnyRef, +V >: Null <: AnyRef] extends (K => V) { def size: Int def apply(k: K): V diff --git a/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala new file mode 100644 index 000000000000..969d7bdb6f70 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala @@ -0,0 +1,97 @@ +package dotty.tools.dotc.util + +import collection.mutable.ListBuffer + +/** A simple linked set with `eq` as the comparison, optimized for small sets. + * It has linear complexity for `contains`, `+`, and `-`. + */ +abstract class SimpleIdentitySet[+Elem <: AnyRef] { + def size: Int + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] + def contains[E >: Elem <: AnyRef](x: E): Boolean + def foreach(f: Elem => Unit): Unit + def toList: List[Elem] = { + val buf = new ListBuffer[Elem] + foreach(buf += _) + buf.toList + } + def ++ [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]) = + ((this: SimpleIdentitySet[E]) /: that.toList)(_ + _) + def -- [E >: Elem <: AnyRef](that: SimpleIdentitySet[E]) = + (this /: that.toList)(_ - _) + override def toString = toList.mkString("(", ", ", ")") +} + +object SimpleIdentitySet { + object empty extends SimpleIdentitySet[Nothing] { + def size = 0 + def + [E <: AnyRef](x: E): SimpleIdentitySet[E] = + new Set1[E](x) + def - [E <: AnyRef](x: E): SimpleIdentitySet[Nothing] = + this + def contains[E <: AnyRef](x: E): Boolean = false + def foreach(f: Nothing => Unit): Unit = () + } + + private class Set1[+Elem <: AnyRef](x0: AnyRef) extends SimpleIdentitySet[Elem] { + def size = 1 + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = + new Set2[E](x0, x) + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = + if (x `eq` x0) empty else this + def contains[E >: Elem <: AnyRef](x: E): Boolean = x `eq` x0 + def foreach(f: Elem => Unit): Unit = f(x0.asInstanceOf[Elem]) + } + + private class Set2[+Elem <: AnyRef](x0: AnyRef, x1: AnyRef) extends SimpleIdentitySet[Elem] { + def size = 2 + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = { + val xs = new Array[AnyRef](3) + xs(0) = x0 + xs(1) = x1 + xs(2) = x + new SetN[E](xs) + } + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = + if (x `eq` x0) new Set1(x1) + else if (x `eq` x1) new Set1(x0) + else this + def contains[E >: Elem <: AnyRef](x: E): Boolean = (x `eq` x0) || (x `eq` x1) + def foreach(f: Elem => Unit): Unit = { f(x0.asInstanceOf[Elem]); f(x1.asInstanceOf[Elem]) } + } + + private class SetN[+Elem <: AnyRef](xs: Array[AnyRef]) extends SimpleIdentitySet[Elem] { + def size = xs.length + def + [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[E] = { + val xs1 = new Array[AnyRef](size + 1) + Array.copy(xs, 0, xs1, 0, size) + xs1(size) = x + new SetN[E](xs1) + } + def - [E >: Elem <: AnyRef](x: E): SimpleIdentitySet[Elem] = { + var i = 0 + while (i < size && (xs(i) `ne` x)) i += 1 + if (i == size) this + else if (size == 3) + if (i == 0) new Set2(xs(1), xs(2)) + else if (i == 1) new Set2(xs(0), xs(2)) + else new Set2(xs(0), xs(1)) + else { + val xs1 = new Array[AnyRef](size - 1) + Array.copy(xs, 0, xs1, 0, i) + Array.copy(xs, i + 1, xs1, i, size - (i + 1)) + new SetN(xs1) + } + } + def contains[E >: Elem <: AnyRef](x: E): Boolean = { + var i = 0 + while (i < size && (xs(i) `ne` x)) i += 1 + i < size + } + def foreach(f: Elem => Unit): Unit = { + var i = 0 + while (i < size) { f(xs(i).asInstanceOf[Elem]); i += 1 } + } + } +} \ No newline at end of file