Skip to content

Improvements to implicits #1918

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

Merged
merged 6 commits into from
Feb 8, 2017
Merged
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
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/core/ConstraintHandling.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,12 @@ trait ConstraintHandling {
if (Config.failOnInstantiationToNothing) assert(false, msg)
else ctx.log(msg)
}
constr.println(i"adding $description")
constr.println(i"adding $description in ${ctx.typerState.hashesStr}")
val lower = constraint.lower(param)
val res =
addOneBound(param, bound, isUpper = true) &&
lower.forall(addOneBound(_, bound, isUpper = true))
constr.println(i"added $description = $res")
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
res
}

Expand All @@ -95,7 +95,7 @@ trait ConstraintHandling {
val res =
addOneBound(param, bound, isUpper = false) &&
upper.forall(addOneBound(_, bound, isUpper = false))
constr.println(i"added $description = $res")
constr.println(i"added $description = $res in ${ctx.typerState.hashesStr}")
res
}

Expand All @@ -108,12 +108,12 @@ trait ConstraintHandling {
val up2 = p2 :: constraint.exclusiveUpper(p2, p1)
val lo1 = constraint.nonParamBounds(p1).lo
val hi2 = constraint.nonParamBounds(p2).hi
constr.println(i"adding $description down1 = $down1, up2 = $up2")
constr.println(i"adding $description down1 = $down1, up2 = $up2 ${ctx.typerState.hashesStr}")
constraint = constraint.addLess(p1, p2)
down1.forall(addOneBound(_, hi2, isUpper = true)) &&
up2.forall(addOneBound(_, lo1, isUpper = false))
}
constr.println(i"added $description = $res")
constr.println(i"added $description = $res ${ctx.typerState.hashesStr}")
res
}

Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TyperState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ class TyperState(r: Reporter) extends DotClass with Showable {
def tryWithFallback[T](op: => T)(fallback: => T)(implicit ctx: Context): T = unsupported("tryWithFallBack")

override def toText(printer: Printer): Text = "ImmutableTyperState"

/** A string showing the hashes of all nested mutable typerstates */
def hashesStr: String = ""
}

class MutableTyperState(previous: TyperState, r: Reporter, override val isCommittable: Boolean)
Expand Down Expand Up @@ -207,4 +210,7 @@ extends TyperState(r) {
}

override def toText(printer: Printer): Text = constraint.toText(printer)

override def hashesStr: String = hashCode.toString + " -> " + previous.hashesStr

}
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1765,12 +1765,8 @@ object Parsers {
TypeTree() // XX-METHOD-INFER
} else {
accept(COLON)
if (in.token == ARROW) {
if (owner.isTypeName && !(mods is Local))
syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name")
else if (imods.hasFlags)
syntaxError("implicit parameters may not be call-by-name")
}
if (in.token == ARROW && owner.isTypeName && !(mods is Local))
syntaxError(s"${if (mods is Mutable) "`var'" else "`val'"} parameters may not be call-by-name")
paramType()
}
val default =
Copy link

Choose a reason for hiding this comment

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

Why was this prohibited in scalac?

Expand Down
18 changes: 18 additions & 0 deletions compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Texts._, Types._, Flags._, Names._, Symbols._, NameOps._, Constants._, De
import Contexts.Context, Scopes.Scope, Denotations.Denotation, Annotations.Annotation
import StdNames.{nme, tpnme}
import ast.Trees._, ast._
import typer.Implicits._
import config.Config
import java.lang.Integer.toOctalString
import config.Config.summarizeDepth
Expand Down Expand Up @@ -484,6 +485,23 @@ class PlainPrinter(_ctx: Context) extends Printer {
}
}.close // todo: override in refined printer

def toText(result: SearchResult): Text = result match {
case result: SearchSuccess =>
"SearchSuccess: " ~ toText(result.ref) ~ " via " ~ toText(result.tree)
case result: NonMatchingImplicit =>
"NoImplicitMatches"
case result: DivergingImplicit =>
"Diverging Implicit"
case result: ShadowedImplicit =>
"Shadowed Implicit"
case result: FailedImplicit =>
"Failed Implicit"
case result: AmbiguousImplicits =>
"Ambiguous Implicit: " ~ toText(result.alt1) ~ " and " ~ toText(result.alt2)
case _ =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we seal SearchResult instead of having a default case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like we can

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, in fact we cannot. There are too many other cases we'd have to handle. E.g. ExplainedSearchResult. Sometimes it's better not to seal.

"?Unknown Implicit Result?"
}

private var maxSummarized = Int.MaxValue

def summarized[T](depth: Int)(op: => T): T = {
Expand Down
6 changes: 5 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import core._
import Texts._, ast.Trees._
import Types.Type, Symbols.Symbol, Contexts.Context, Scopes.Scope, Constants.Constant,
Names.Name, Denotations._, Annotations.Annotation
import typer.Implicits.SearchResult

/** The base class of all printers
*/
Expand Down Expand Up @@ -94,7 +95,10 @@ abstract class Printer {
/** Textual representation of tree */
def toText[T >: Untyped](tree: Tree[T]): Text

/** Perform string or text-producing operation `op` so that only a
/** Textual representation of implicit search result */
def toText(result: SearchResult): Text

/** Perform string or text-producing operation `op` so that only a
* summarized text with given recursion depth is shown
*/
def summarized[T](depth: Int)(op: => T): T
Expand Down
22 changes: 13 additions & 9 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import core._
import ast.{Trees, untpd, tpd, TreeInfo}
import util.Positions._
import util.Stats.{track, record, monitored}
import printing.Showable
import printing.{Showable, Printer}
import printing.Texts._
import Contexts._
import Types._
import Flags._
Expand Down Expand Up @@ -219,14 +220,16 @@ object Implicits {
}

/** The result of an implicit search */
abstract class SearchResult
abstract class SearchResult extends Showable {
def toText(printer: Printer): Text = printer.toText(this)
}

/** A successful search
* @param ref The implicit reference that succeeded
* @param tree The typed tree that needs to be inserted
* @param ctx The context after the implicit search
*/
case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult {
case class SearchSuccess(tree: tpd.Tree, ref: TermRef, level: Int, tstate: TyperState) extends SearchResult with Showable {
override def toString = s"SearchSuccess($tree, $ref, $level)"
}

Expand Down Expand Up @@ -256,7 +259,7 @@ object Implicits {
}

/** An ambiguous implicits failure */
class AmbiguousImplicits(alt1: TermRef, alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure {
class AmbiguousImplicits(val alt1: TermRef, val alt2: TermRef, val pt: Type, val argument: tpd.Tree) extends ExplainedSearchFailure {
def explanation(implicit ctx: Context): String =
em"both ${err.refStr(alt1)} and ${err.refStr(alt2)} $qualify"
override def postscript(implicit ctx: Context) =
Expand Down Expand Up @@ -380,7 +383,9 @@ trait ImplicitRunInfo { self: RunInfo =>
EmptyTermRefSet // on the other hand, the refs of `tp` are now not accurate, so `tp` is marked incomplete.
} else {
seen += t
iscope(t).companionRefs
val is = iscope(t)
if (!implicitScopeCache.contains(t)) incomplete += tp
is.companionRefs
}
}

Expand Down Expand Up @@ -436,10 +441,8 @@ trait ImplicitRunInfo { self: RunInfo =>
if (ctx.typerState.ephemeral)
record("ephemeral cache miss: implicitScope")
else if (canCache &&
((tp eq rootTp) || // first type traversed is always cached
!incomplete.contains(tp) && // other types are cached if they are not incomplete
result.companionRefs.forall( // and all their companion refs are cached
implicitScopeCache.contains)))
((tp eq rootTp) || // first type traversed is always cached
!incomplete.contains(tp))) // other types are cached if they are not incomplete
implicitScopeCache(tp) = result
result
}
Expand Down Expand Up @@ -604,6 +607,7 @@ trait Implicits { self: Typer =>
result match {
case result: SearchSuccess =>
result.tstate.commit()
implicits.println(i"committing ${result.tstate.constraint} yielding ${ctx.typerState.constraint} ${ctx.typerState.hashesStr}")
result
case result: AmbiguousImplicits =>
val deepPt = pt.deepenProto
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1879,7 +1879,8 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
val args = (wtp.paramNames, wtp.paramTypes).zipped map { (pname, formal) =>
def implicitArgError(msg: String => String) =
errors += (() => msg(em"parameter $pname of $methodStr"))
inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
if (errors.nonEmpty) EmptyTree
else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos)
}
if (errors.nonEmpty) {
// If there are several arguments, some arguments might already
Expand Down
30 changes: 30 additions & 0 deletions tests/run/generic/Color.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package generic

import Shapes._

/** enum Color {
* case Red
* case Green
* case Blue
* }
*/
sealed trait Color extends Enum

object Color extends EnumValues[Color](3) {

private def $new(tag: Int, name: String) = new Color {
def enumTag = tag
override def toString = name
registerEnumValue(this)
}
Copy link

Choose a reason for hiding this comment

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

Can we have a named subclass of Color defined somewhere in the companion object and used here? I think that classnames of anonymous classes are quite ugly.


val Red: Color = $new(0, "Red")
val Green: Color = $new(1, "Green")
val Blue: Color = $new(2, "Blue")

implicit val ColorShape: Color `shaped` EnumValue[Color] =
new (Color `shaped` EnumValue[Color]) {
def toShape(x: Color) = EnumValue(x.enumTag)
def fromShape(x: EnumValue[Color]) = Color.value(x.tag)
}
}
18 changes: 18 additions & 0 deletions tests/run/generic/Enum.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package generic

import Shapes.Singleton

trait Enum {
def enumTag: Int
Copy link

Choose a reason for hiding this comment

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

What use cases do you envision for enumTag? Back then, @densh and I planned to use tags to speed up pattern matching. Do you think that'd make sense?

}

trait FiniteEnum extends Enum
Copy link

Choose a reason for hiding this comment

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

What's that?


abstract class EnumValues[E <: Enum](numVals: Int) {
private var myValues = new Array[AnyRef](numVals)

def registerEnumValue(v: E) =
myValues(v.enumTag) = v
Copy link

Choose a reason for hiding this comment

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

Can we somehow restrict the visibility of this method?


def value: IndexedSeq[E] = (myValues: IndexedSeq[AnyRef]).asInstanceOf[IndexedSeq[E]]
}
89 changes: 89 additions & 0 deletions tests/run/generic/List.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package generic

import Shapes._

/** enum List[T] {
* case Cons(x: T, xs: List[T])
* case Nil()
* }
*/
sealed trait List0[T] extends Enum
object List0 {
abstract case class Cons[T](hd: T, tl: List0[T]) extends List0[T] {
def enumTag = 0
}
object Cons {
def apply[T](x: T, xs: List0[T]): List0[T] = new Cons(x, xs) {}
implicit def ConsShape[T]: Cons[T] `shaped` Prod[T, List0[T]] =
new (Cons[T] `shaped` Prod[T, List0[T]]) {
def toShape(x: Cons[T]) = Prod(x.hd, x.tl)
def fromShape(p: Prod[T, List0[T]]) = new Cons(p.fst, p.snd) {}
}
Copy link

Choose a reason for hiding this comment

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

@odersky Am I right in thinking that you'd like the XXXShape implicits to be derived by macros?

Copy link

Choose a reason for hiding this comment

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

And, in general, which parts of the enum proposal do you see implementable as macros, and what you'd like to hardcode in the compiler?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xeno-by The XXXShape implicits can be done by the compiler. The part I need from macros are the implicits in Test.scala.

}

abstract case class Nil[T]() extends List0[T] {
def enumTag = 1
}
object Nil {
def apply[T](): List0[T] = new Nil[T]() {}
implicit def NilShape[T]: Nil[T] `shaped` Unit =
new (Nil[T] `shaped` Unit) {
def toShape(x: Nil[T]) = ()
def fromShape(x: Unit) = new Nil[T]() {}
}
}

implicit def List0Shape[T]: List0[T] `shaped` Sum[Cons[T], Nil[T]] =
new (List0[T] `shaped` Sum[Cons[T], Nil[T]]) {
def toShape(x: List0[T]) = x match {
case x: Cons[T] => Fst(x)
case x: Nil[T] => Snd(x)
}
def fromShape(x: Sum[Cons[T], Nil[T]]) = x match {
case Fst(c) => c
case Snd(n) => n
}
}
}

/** enum List[T] {
Copy link

Choose a reason for hiding this comment

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

Is the variance modifier omitted here on purpose?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are actually two sources, one without variance for List0, the other with variance for List1.

* case Cons(x: T, xs: List[T])
* case Nil extends List[Nothing]
* }
*/
sealed trait List[+T] extends Enum
object List {
abstract case class Cons[T](hd: T, tl: List[T]) extends List[T] {
def enumTag = 0
}
object Cons {
def apply[T](x: T, xs: List[T]): List[T] = new Cons(x, xs) {}
type Shape[T] = Prod[T, List[T]]
implicit def ConsShape[T]: Cons[T] `shaped` Shape[T] =
new (Cons[T] `shaped` Shape[T]) {
def toShape(x: Cons[T]) = Prod(x.hd, x.tl)
def fromShape(p: Shape[T]) = new Cons(p.fst, p.snd) {}
}
}

val Nil = new List[Nothing] {
def enumTag = 1
override def toString = "Nil"
}

implicit def NilSingleton: Singleton[Nil.type] = new Singleton[Nil.type](Nil)

type Shape[T] = Sum[Cons[T], Nil.type]

implicit def ListShape[T]: List[T] `unfolds` Shape[T] =
new (List[T] `shaped` Shape[T]) {
def toShape(x: List[T]) = x match {
case x: Cons[T] => Fst(x)
case Nil => Snd(Nil)
}
def fromShape(x: Shape[T]): List[T] = x match {
case Fst(c) => c
case Snd(n) => n
}
}
}
Loading