Skip to content

Commit 8467e55

Browse files
authored
Merge pull request #1993 from dotty-staging/add-lazy-implicits
Treat implicit by-name arguments as lazy values
2 parents 8a0a4fb + d8c7a7a commit 8467e55

File tree

9 files changed

+200
-9
lines changed

9 files changed

+200
-9
lines changed

compiler/src/dotty/tools/dotc/core/Contexts.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ object Contexts {
202202
private[core] var pendingMemberSearches: List[Name] = Nil
203203

204204
/** The new implicit references that are introduced by this scope */
205-
private var implicitsCache: ContextualImplicits = null
205+
protected var implicitsCache: ContextualImplicits = null
206206
def implicits: ContextualImplicits = {
207207
if (implicitsCache == null )
208208
implicitsCache = {
@@ -469,6 +469,7 @@ object Contexts {
469469
def setTypeAssigner(typeAssigner: TypeAssigner): this.type = { this.typeAssigner = typeAssigner; this }
470470
def setTyper(typer: Typer): this.type = { this.scope = typer.scope; setTypeAssigner(typer) }
471471
def setImportInfo(importInfo: ImportInfo): this.type = { this.importInfo = importInfo; this }
472+
def setImplicits(implicits: ContextualImplicits): this.type = { this.implicitsCache = implicits; this }
472473
def setRunInfo(runInfo: RunInfo): this.type = { this.runInfo = runInfo; this }
473474
def setDiagnostics(diagnostics: Option[StringBuilder]): this.type = { this.diagnostics = diagnostics; this }
474475
def setGadt(gadt: GADTMap): this.type = { this.gadt = gadt; this }

compiler/src/dotty/tools/dotc/core/StdNames.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ object StdNames {
130130
val COMPANION_CLASS_METHOD: N = "companion$class"
131131
val TRAIT_SETTER_SEPARATOR: N = "$_setter_$"
132132
val DIRECT_SUFFIX: N = "$direct"
133+
val LAZY_IMPLICIT_PREFIX: N = "$lazy_implicit$"
133134

134135
// value types (and AnyRef) are all used as terms as well
135136
// as (at least) arguments to the @specialize annotation.

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,10 @@ trait Symbols { this: Context =>
258258
def newDefaultConstructor(cls: ClassSymbol) =
259259
newConstructor(cls, EmptyFlags, Nil, Nil)
260260

261+
/** Create a synthetic lazy implicit value */
262+
def newLazyImplicit(info: Type) =
263+
newSymbol(owner, freshName(nme.LAZY_IMPLICIT_PREFIX).toTermName, Lazy, info)
264+
261265
/** Create a symbol representing a selftype declaration for class `cls`. */
262266
def newSelfSym(cls: ClassSymbol, name: TermName = nme.WILDCARD, selfInfo: Type = NoType): TermSymbol =
263267
ctx.newSymbol(cls, name, SelfSymFlags, selfInfo orElse cls.classInfo.selfType, coord = cls.coord)

compiler/src/dotty/tools/dotc/typer/Implicits.scala

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@ import reporting.diagnostic.MessageContainer
2929
import Inferencing.fullyDefinedType
3030
import Trees._
3131
import Hashable._
32+
import util.Property
3233
import config.Config
3334
import config.Printers.{implicits, implicitsDetailed, typr}
3435
import collection.mutable
3536

3637
/** Implicit resolution */
3738
object Implicits {
3839

40+
/** A reference to an implicit value to be made visible on the next nested call to
41+
* inferImplicitArg with a by-name expected type.
42+
*/
43+
val DelayedImplicit = new Property.Key[TermRef]
44+
3945
/** An eligible implicit candidate, consisting of an implicit reference and a nesting level */
4046
case class Candidate(ref: TermRef, level: Int)
4147

@@ -202,7 +208,7 @@ object Implicits {
202208
}
203209

204210
override def toString = {
205-
val own = s"(implicits: ${refs mkString ","})"
211+
val own = i"(implicits: $refs%, %)"
206212
if (isOuterMost) own else own + "\n " + outerImplicits
207213
}
208214

@@ -537,27 +543,56 @@ trait Implicits { self: Typer =>
537543
else EmptyTree
538544
}
539545

540-
inferImplicit(formal, EmptyTree, pos) match {
546+
/** The context to be used when resolving a by-name implicit argument.
547+
* This makes any implicit stored under `DelayedImplicit` visible and
548+
* stores in turn the given `lazyImplicit` as new `DelayedImplicit`.
549+
*/
550+
def lazyImplicitCtx(lazyImplicit: Symbol): Context = {
551+
val lctx = ctx.fresh
552+
for (delayedRef <- ctx.property(DelayedImplicit))
553+
lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx))
554+
lctx.setProperty(DelayedImplicit, lazyImplicit.termRef)
555+
}
556+
557+
/** formalValue: The value type for which an implicit is searched
558+
* lazyImplicit: An implicit symbol to install for nested by-name resolutions
559+
* argCtx : The context to be used for searching the implicit argument
560+
*/
561+
val (formalValue, lazyImplicit, argCtx) = formal match {
562+
case ExprType(fv) =>
563+
val lazyImplicit = ctx.newLazyImplicit(fv)
564+
(fv, lazyImplicit, lazyImplicitCtx(lazyImplicit))
565+
case _ => (formal, NoSymbol, ctx)
566+
}
567+
568+
inferImplicit(formalValue, EmptyTree, pos)(argCtx) match {
541569
case SearchSuccess(arg, _, _, _) =>
542-
arg
570+
def refersToLazyImplicit = arg.existsSubTree {
571+
case id: Ident => id.symbol == lazyImplicit
572+
case _ => false
573+
}
574+
if (lazyImplicit.exists && refersToLazyImplicit)
575+
Block(ValDef(lazyImplicit.asTerm, arg).withPos(pos) :: Nil, ref(lazyImplicit))
576+
else
577+
arg
543578
case ambi: AmbiguousImplicits =>
544579
error(where => s"ambiguous implicits: ${ambi.explanation} of $where")
545580
EmptyTree
546581
case failure: SearchFailure =>
547-
val arg = synthesizedClassTag(formal, pos)
582+
val arg = synthesizedClassTag(formalValue, pos)
548583
if (!arg.isEmpty) arg
549584
else {
550585
var msgFn = (where: String) =>
551586
em"no implicit argument of type $formal found for $where" + failure.postscript
552587
for {
553-
notFound <- formal.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot)
588+
notFound <- formalValue.typeSymbol.getAnnotation(defn.ImplicitNotFoundAnnot)
554589
Trees.Literal(Constant(raw: String)) <- notFound.argument(0)
555590
} {
556591
msgFn = where =>
557592
err.implicitNotFoundString(
558593
raw,
559-
formal.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
560-
formal.argInfos)
594+
formalValue.typeSymbol.typeParams.map(_.name.unexpandedName.toString),
595+
formalValue.argInfos)
561596
}
562597
error(msgFn)
563598
EmptyTree

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1890,7 +1890,7 @@ class Typer extends Namer with TypeAssigner with Applications with Implicits wit
18901890
def implicitArgError(msg: String => String) =
18911891
errors += (() => msg(em"parameter $pname of $methodStr"))
18921892
if (errors.nonEmpty) EmptyTree
1893-
else inferImplicitArg(formal.widenExpr, implicitArgError, tree.pos.endPos)
1893+
else inferImplicitArg(formal, implicitArgError, tree.pos.endPos)
18941894
}
18951895
if (errors.nonEmpty) {
18961896
// If there are several arguments, some arguments might already

tests/run/lazy-implicit-lists.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
List()
2+
List(1, 2, 3)
3+
List()
4+
List(1, 2, 3)

tests/run/lazy-implicit-lists.scala

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
trait L
2+
case class C(hd: Int, tl: L) extends L
3+
case object N extends L
4+
5+
trait Sum[+S1, +S2]
6+
case class Fst[+F](x: F) extends Sum[F, Nothing]
7+
case class Snd[+S](x: S) extends Sum[Nothing, S]
8+
9+
case class Prod[+P1, +P2](fst: P1, snd: P2)
10+
11+
trait shaped[SH1, SH2] {
12+
def toShape(x: SH1): SH2
13+
def fromShape(x: SH2): SH1
14+
}
15+
16+
object Test {
17+
18+
type LShape = Sum[Prod[Int, L], Unit]
19+
20+
implicit def LShape: L `shaped` LShape =
21+
new (L `shaped` LShape) {
22+
def toShape(xs: L) = xs match {
23+
case C(x, xs1) => Fst(Prod(x, xs1))
24+
case N => Snd(())
25+
}
26+
def fromShape(sh: LShape) = sh match {
27+
case Fst(Prod(x, xs1)) => C(x, xs1)
28+
case Snd(()) => N
29+
}
30+
}
31+
32+
trait Listable[T] {
33+
def toList(x: T): List[Int]
34+
}
35+
36+
implicit def ShapedListable[T, U](implicit
37+
ev1: T shaped U,
38+
ev2: Listable[U]
39+
): Listable[T] =
40+
new Listable[T] {
41+
def toList(x: T) = ev2.toList(ev1.toShape(x))
42+
}
43+
44+
implicit def SumListable[T, U](implicit
45+
ev1: => Listable[T],
46+
ev2: => Listable[U]
47+
): Listable[Sum[T, U]] =
48+
new Listable[Sum[T, U]] {
49+
def toList(s: Sum[T, U]) = s match {
50+
case Fst(x) => ev1.toList(x)
51+
case Snd(x) => ev2.toList(x)
52+
}
53+
}
54+
55+
implicit def ProdListable[T, U](implicit
56+
ev1: Listable[T],
57+
ev2: Listable[U]
58+
): Listable[Prod[T, U]] =
59+
new Listable[Prod[T, U]] {
60+
def toList(p: Prod[T, U]) = ev1.toList(p.fst) ++ ev2.toList(p.snd)
61+
}
62+
63+
implicit def IntListable: Listable[Int] =
64+
new Listable[Int] {
65+
def toList(n: Int) = n :: Nil
66+
}
67+
68+
69+
implicit def UnitListable: Listable[Unit] =
70+
new Listable[Unit] {
71+
def toList(u: Unit) = Nil
72+
}
73+
74+
def toList[T, U >: T](x: T)(implicit ev1: Listable[U]) = ev1.toList(x)
75+
76+
def main(args: Array[String]) = {
77+
locally { // with specialized Listable
78+
implicit lazy val LListable: Listable[L] = ShapedListable
79+
println(toList(N))
80+
println(toList(C(1, C(2, C(3, N)))))
81+
}
82+
locally { // without specialized Listable
83+
println(toList(N))
84+
println(toList(C(1, C(2, C(3, N)))))
85+
}
86+
}
87+
}

tests/run/lazy-implicit-nums.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3

tests/run/lazy-implicit-nums.scala

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
trait Nat
2+
case class S(x: Nat) extends Nat
3+
case class Z() extends Nat
4+
5+
trait Sum[+S1, +S2]
6+
case class Fst[+F](x: F) extends Sum[F, Nothing]
7+
case class Snd[+S](x: S) extends Sum[Nothing, S]
8+
9+
trait shaped[SH1, SH2] {
10+
def toShape(x: SH1): SH2
11+
def fromShape(x: SH2): SH1
12+
}
13+
14+
object Test {
15+
16+
type NatShape = Sum[Nat, Z]
17+
18+
implicit def natShape: Nat `shaped` NatShape =
19+
new (Nat `shaped` NatShape) {
20+
def toShape(n: Nat) = n match {
21+
case S(m) => Fst(m)
22+
case Z() => Snd(Z())
23+
}
24+
def fromShape(s: NatShape) = s match {
25+
case Fst(n) => S(n)
26+
case Snd(_) => Z()
27+
}
28+
}
29+
30+
trait Countable[T] {
31+
def count(x: T): Int
32+
}
33+
34+
implicit def ShapedCountable[T, U](implicit
35+
ev1: T shaped U,
36+
ev2: Countable[U]
37+
): Countable[T] =
38+
new Countable[T] {
39+
def count(x: T) = ev2.count(ev1.toShape(x))
40+
}
41+
42+
implicit def SumCountable[T, U](implicit
43+
ev1: => Countable[T]
44+
): Countable[Sum[T, U]] =
45+
new Countable[Sum[T, U]] {
46+
def count(s: Sum[T, U]) = s match {
47+
case Fst(x) => ev1.count(x) + 1
48+
case Snd(_) => 0
49+
}
50+
}
51+
52+
def count[T, U >: T](x: T)(implicit ev1: Countable[U]) = ev1.count(x)
53+
54+
def main(args: Array[String]) = {
55+
println(
56+
count(S(S(S(Z())))))
57+
}
58+
}

0 commit comments

Comments
 (0)