Skip to content

[WIP] Changes to Interpolation #4065

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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"
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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)
*/
Expand Down
11 changes: 9 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this with Martin on a whiteboard, so a memo (and possible comment).

If X >: List[T] and x: X and we typecheck x.filter(p), how should X be instantiated? Here setting X to its lower bound works, but might violate other constraints — ideally we'd just add some constraint X <: { def filter... }, but since we don't have those, looking up List.filter's definition class (say Traversable[T]) and adding bound X <: Traversable[T] is a more general solution.

This solution is not yet complete because some other ancestor of List[T] which doesn't extend Traversable[T] might have another overload of filter. There might even be a realistic example where this happens, but I can't think of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it makes sense to add this. Unfortunately, it is not enough. We still need to interpolate TypeVar results in some way when selecting because otherwise we might miss an implicit conversion. E.g.

  Array(1, 2, 3).map(_ + 1).deep

The map operation with current stdlib leaves us with result type That where

That >: Array[Int]

But to typecheck deep we need to find an implicit conversion from the result to ArrayOps. There is none from That, but there is one from Array[Int].

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)
}
Expand Down Expand Up @@ -3374,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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
"<derived typetree watching " ~ summarized(toText(tpt.watched)) ~ ">"
Expand Down
28 changes: 20 additions & 8 deletions compiler/src/dotty/tools/dotc/reporting/trace.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@ object trace {
else op1
}

@inline
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, showOp)(op1)
}

@inline
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, show)(op1)
else doTrace[T](question, printer, if (show) showShowable(_) else alwaysToString)(op1)
}

@inline
Expand All @@ -39,16 +46,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 =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
97 changes: 97 additions & 0 deletions compiler/src/dotty/tools/dotc/util/SimpleIdentitySet.scala
Original file line number Diff line number Diff line change
@@ -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 }
}
}
}