Skip to content

Treat implicit by-name arguments as lazy values #1993

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 3 commits into from
Feb 25, 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
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ object Contexts {
private[core] var pendingMemberSearches: List[Name] = Nil

/** The new implicit references that are introduced by this scope */
private var implicitsCache: ContextualImplicits = null
protected var implicitsCache: ContextualImplicits = null
def implicits: ContextualImplicits = {
if (implicitsCache == null )
implicitsCache = {
Expand Down Expand Up @@ -469,6 +469,7 @@ object Contexts {
def setTypeAssigner(typeAssigner: TypeAssigner): this.type = { this.typeAssigner = typeAssigner; this }
def setTyper(typer: Typer): this.type = { this.scope = typer.scope; setTypeAssigner(typer) }
def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this }
def setImplicits(implicits: ContextualImplicits): this.type = { this.implicitsCache = implicits; this }
def setRunInfo(runInfo: RunInfo): this.type = { this.runInfo = runInfo; this }
def setDiagnostics(diagnostics: Option[StringBuilder]): this.type = { this.diagnostics = diagnostics; this }
def setGadt(gadt: GADTMap): this.type = { this.gadt = gadt; this }
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ object StdNames {
val COMPANION_CLASS_METHOD: N = "companion$class"
val TRAIT_SETTER_SEPARATOR: N = "$_setter_$"
val DIRECT_SUFFIX: N = "$direct"
val LAZY_IMPLICIT_PREFIX: N = "$lazy_implicit$"

// value types (and AnyRef) are all used as terms as well
// as (at least) arguments to the @specialize annotation.
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Symbols.scala
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,10 @@ trait Symbols { this: Context =>
def newDefaultConstructor(cls: ClassSymbol) =
newConstructor(cls, EmptyFlags, Nil, Nil)

/** Create a synthetic lazy implicit value */
def newLazyImplicit(info: Type) =
newSymbol(owner, freshName(nme.LAZY_IMPLICIT_PREFIX).toTermName, Lazy, info)

/** Create a symbol representing a selftype declaration for class `cls`. */
def newSelfSym(cls: ClassSymbol, name: TermName = nme.WILDCARD, selfInfo: Type = NoType): TermSymbol =
ctx.newSymbol(cls, name, SelfSymFlags, selfInfo orElse cls.classInfo.selfType, coord = cls.coord)
Expand Down
49 changes: 42 additions & 7 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,19 @@ import reporting.diagnostic.MessageContainer
import Inferencing.fullyDefinedType
import Trees._
import Hashable._
import util.Property
import config.Config
import config.Printers.{implicits, implicitsDetailed, typr}
import collection.mutable

/** Implicit resolution */
object Implicits {

/** A reference to an implicit value to be made visible on the next nested call to
* inferImplicitArg with a by-name expected type.
*/
val DelayedImplicit = new Property.Key[TermRef]

/** An eligible implicit candidate, consisting of an implicit reference and a nesting level */
case class Candidate(ref: TermRef, level: Int)

Expand Down Expand Up @@ -202,7 +208,7 @@ object Implicits {
}

override def toString = {
val own = s"(implicits: ${refs mkString ","})"
val own = i"(implicits: $refs%, %)"
if (isOuterMost) own else own + "\n " + outerImplicits
}

Expand Down Expand Up @@ -537,27 +543,56 @@ trait Implicits { self: Typer =>
else EmptyTree
}

inferImplicit(formal, EmptyTree, pos) match {
/** The context to be used when resolving a by-name implicit argument.
* This makes any implicit stored under `DelayedImplicit` visible and
* stores in turn the given `lazyImplicit` as new `DelayedImplicit`.
*/
def lazyImplicitCtx(lazyImplicit: Symbol): Context = {
val lctx = ctx.fresh
for (delayedRef <- ctx.property(DelayedImplicit))
lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx))
lctx.setProperty(DelayedImplicit, lazyImplicit.termRef)
}

/** formalValue: The value type for which an implicit is searched
* lazyImplicit: An implicit symbol to install for nested by-name resolutions
* argCtx : The context to be used for searching the implicit argument
*/
val (formalValue, lazyImplicit, argCtx) = formal match {
case ExprType(fv) =>
val lazyImplicit = ctx.newLazyImplicit(fv)
(fv, lazyImplicit, lazyImplicitCtx(lazyImplicit))
case _ => (formal, NoSymbol, ctx)
}

inferImplicit(formalValue, EmptyTree, pos)(argCtx) match {
case SearchSuccess(arg, _, _, _) =>
arg
def refersToLazyImplicit = arg.existsSubTree {
case id: Ident => id.symbol == lazyImplicit
case _ => false
}
if (lazyImplicit.exists && refersToLazyImplicit)
Block(ValDef(lazyImplicit.asTerm, arg).withPos(pos) :: Nil, ref(lazyImplicit))
else
arg
case ambi: AmbiguousImplicits =>
error(where => s"ambiguous implicits: ${ambi.explanation} of $where")
EmptyTree
case failure: SearchFailure =>
val arg = synthesizedClassTag(formal, pos)
val arg = synthesizedClassTag(formalValue, pos)
if (!arg.isEmpty) arg
else {
var msgFn = (where: String) =>
em"no implicit argument of type $formal found for $where" + failure.postscript
for {
notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot)
notFound <- formalValue.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot)
Trees.Literal(Constant(raw: String)) <- notFound.argument(0)
} {
msgFn = where =>
err.implicitNotFoundString(
raw,
formal.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
formal.argInfos)
formalValue.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
formalValue.argInfos)
}
error(msgFn)
EmptyTree
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1885,7 +1885,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
def implicitArgError(msg: String => String) =
errors += (() => msg(em"parameter $pname of $methodStr"))
if (errors.nonEmpty) EmptyTree
else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos)
else inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
}
if (errors.nonEmpty) {
// If there are several arguments, some arguments might already
Expand Down
4 changes: 4 additions & 0 deletions tests/run/lazy-implicit-lists.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
List()
List(1, 2, 3)
List()
List(1, 2, 3)
87 changes: 87 additions & 0 deletions tests/run/lazy-implicit-lists.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
trait L
case class C(hd: Int, tl: L) extends L
case object N extends L

trait Sum[+S1, +S2]
case class Fst[+F](x: F) extends Sum[F, Nothing]
case class Snd[+S](x: S) extends Sum[Nothing, S]

case class Prod[+P1, +P2](fst: P1, snd: P2)

trait shaped[SH1, SH2] {
def toShape(x: SH1): SH2
def fromShape(x: SH2): SH1
}

object Test {

type LShape = Sum[Prod[Int, L], Unit]

implicit def LShape: L `shaped` LShape =
new (L `shaped` LShape) {
def toShape(xs: L) = xs match {
case C(x, xs1) => Fst(Prod(x, xs1))
case N => Snd(())
}
def fromShape(sh: LShape) = sh match {
case Fst(Prod(x, xs1)) => C(x, xs1)
case Snd(()) => N
}
}

trait Listable[T] {
def toList(x: T): List[Int]
}

implicit def ShapedListable[T, U](implicit
ev1: T shaped U,
ev2: Listable[U]
): Listable[T] =
new Listable[T] {
def toList(x: T) = ev2.toList(ev1.toShape(x))
}

implicit def SumListable[T, U](implicit
ev1: => Listable[T],
ev2: => Listable[U]
): Listable[Sum[T, U]] =
new Listable[Sum[T, U]] {
def toList(s: Sum[T, U]) = s match {
case Fst(x) => ev1.toList(x)
case Snd(x) => ev2.toList(x)
}
}

implicit def ProdListable[T, U](implicit
ev1: Listable[T],
ev2: Listable[U]
): Listable[Prod[T, U]] =
new Listable[Prod[T, U]] {
def toList(p: Prod[T, U]) = ev1.toList(p.fst) ++ ev2.toList(p.snd)
}

implicit def IntListable: Listable[Int] =
new Listable[Int] {
def toList(n: Int) = n :: Nil
}


implicit def UnitListable: Listable[Unit] =
new Listable[Unit] {
def toList(u: Unit) = Nil
}

def toList[T, U >: T](x: T)(implicit ev1: Listable[U]) = ev1.toList(x)

def main(args: Array[String]) = {
locally { // with specialized Listable
implicit lazy val LListable: Listable[L] = ShapedListable
println(toList(N))
println(toList(C(1, C(2, C(3, N)))))
}
locally { // without specialized Listable
println(toList(N))
println(toList(C(1, C(2, C(3, N)))))
}
}
}
1 change: 1 addition & 0 deletions tests/run/lazy-implicit-nums.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3
58 changes: 58 additions & 0 deletions tests/run/lazy-implicit-nums.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
trait Nat
case class S(x: Nat) extends Nat
case class Z() extends Nat

trait Sum[+S1, +S2]
case class Fst[+F](x: F) extends Sum[F, Nothing]
case class Snd[+S](x: S) extends Sum[Nothing, S]

trait shaped[SH1, SH2] {
def toShape(x: SH1): SH2
def fromShape(x: SH2): SH1
}

object Test {

type NatShape = Sum[Nat, Z]

implicit def natShape: Nat `shaped` NatShape =
new (Nat `shaped` NatShape) {
def toShape(n: Nat) = n match {
case S(m) => Fst(m)
case Z() => Snd(Z())
}
def fromShape(s: NatShape) = s match {
case Fst(n) => S(n)
case Snd(_) => Z()
}
}

trait Countable[T] {
def count(x: T): Int
}

implicit def ShapedCountable[T, U](implicit
ev1: T shaped U,
ev2: Countable[U]
): Countable[T] =
new Countable[T] {
def count(x: T) = ev2.count(ev1.toShape(x))
}

implicit def SumCountable[T, U](implicit
ev1: => Countable[T]
): Countable[Sum[T, U]] =
new Countable[Sum[T, U]] {
def count(s: Sum[T, U]) = s match {
case Fst(x) => ev1.count(x) + 1
case Snd(_) => 0
}
}

def count[T, U >: T](x: T)(implicit ev1: Countable[U]) = ev1.count(x)

def main(args: Array[String]) = {
println(
count(S(S(S(Z())))))
}
}