From 9f26d32db25824f75e5c5b2c2314352c42b074c1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 17 Feb 2017 11:03:37 +0100 Subject: Treat implicit by-name arguments as lazy values With the previous rules, the two test cases produce a diverging implicit expansion. We avoid this by creating for every implicit by-name argument of type T a lazy implicit value of the same type. The implicit value is visible for all nested implicit searches of by-name arguments. That way, we tie the knot and obtain a recursive lazy value instead of a diverging expansion. --- compiler/src/dotty/tools/dotc/core/Contexts.scala | 3 +- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 + compiler/src/dotty/tools/dotc/core/Symbols.scala | 4 ++ .../src/dotty/tools/dotc/typer/Implicits.scala | 52 +++++++++++++++++++--- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 5 files changed, 53 insertions(+), 9 deletions(-) (limited to 'compiler/src/dotty/tools') diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 29629e505..6b7be12be 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -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 = { @@ -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 } diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index c424c6182..1e36361f8 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -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. diff --git a/compiler/src/dotty/tools/dotc/core/Symbols.scala b/compiler/src/dotty/tools/dotc/core/Symbols.scala index 5d0dd2123..c5e064478 100644 --- a/compiler/src/dotty/tools/dotc/core/Symbols.scala +++ b/compiler/src/dotty/tools/dotc/core/Symbols.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 6949221fb..82eb260a4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -29,6 +29,7 @@ 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 @@ -36,6 +37,11 @@ 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) @@ -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 } @@ -537,27 +543,59 @@ 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 `DelayedIplicit` visible and + * stores in turn the given `lazyImplicit` as new `DelayedImplicit`. + */ + def lazyImplicitCtx(lazyImplicit: Symbol): Context = { + val lctx = ctx.fresh + ctx.property(DelayedImplicit) match { + case Some(delayedRef) => + lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) + case None => + } + lctx.setProperty(DelayedImplicit, lazyImplicit.termRef) + } + + /** formalValue: The value type for which an implicit is searched + * lazyIplicit: 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 diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 2b57cf778..16aeb4c6d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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 -- cgit v1.2.3 From 08f4c3e72485bf01d37f9ea84b2ae8bfea27d049 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 18 Feb 2017 14:07:39 +0100 Subject: Polishing --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'compiler/src/dotty/tools') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 82eb260a4..627ce6f77 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -549,11 +549,8 @@ trait Implicits { self: Typer => */ def lazyImplicitCtx(lazyImplicit: Symbol): Context = { val lctx = ctx.fresh - ctx.property(DelayedImplicit) match { - case Some(delayedRef) => - lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) - case None => - } + for (delayedRef <- ctx.property(DelayedImplicit)) + lctx.setImplicits(new ContextualImplicits(delayedRef :: Nil, ctx.implicits)(ctx)) lctx.setProperty(DelayedImplicit, lazyImplicit.termRef) } -- cgit v1.2.3 From d8c7a7ae0e981a2abd9e973617ef575270dd30a5 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 21 Feb 2017 17:15:02 +0100 Subject: Fix typos --- compiler/src/dotty/tools/dotc/typer/Implicits.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'compiler/src/dotty/tools') diff --git a/compiler/src/dotty/tools/dotc/typer/Implicits.scala b/compiler/src/dotty/tools/dotc/typer/Implicits.scala index 627ce6f77..6c8ecd3c8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Implicits.scala +++ b/compiler/src/dotty/tools/dotc/typer/Implicits.scala @@ -544,7 +544,7 @@ trait Implicits { self: Typer => } /** The context to be used when resolving a by-name implicit argument. - * This makes any implicit stored under `DelayedIplicit` visible and + * This makes any implicit stored under `DelayedImplicit` visible and * stores in turn the given `lazyImplicit` as new `DelayedImplicit`. */ def lazyImplicitCtx(lazyImplicit: Symbol): Context = { @@ -555,8 +555,8 @@ trait Implicits { self: Typer => } /** formalValue: The value type for which an implicit is searched - * lazyIplicit: An implicit symbol to install for nested by-name resolutions - * argCtx : The context to be used for searching the implicit argument + * 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) => -- cgit v1.2.3