diff options
author | Eugene Burmako <xeno.by@gmail.com> | 2013-02-23 13:40:14 +0100 |
---|---|---|
committer | Eugene Burmako <xeno.by@gmail.com> | 2013-05-12 22:19:02 +0200 |
commit | c539ae2f56fe9f565cffb4afd6ab131bda89acb7 (patch) | |
tree | 30c9472e80b9732b713ab0efa0c148827808276f /src | |
parent | a35b6bc8e4369db8983d6d0d9881f37d7affbadd (diff) | |
download | scala-c539ae2f56fe9f565cffb4afd6ab131bda89acb7.tar.gz scala-c539ae2f56fe9f565cffb4afd6ab131bda89acb7.tar.bz2 scala-c539ae2f56fe9f565cffb4afd6ab131bda89acb7.zip |
SI-7167 implicit macros decide what is divergence
This is a port of https://github.com/scala/scala/commit/8168f118c9 from 2.10.x,
with an additional change to the `enclosingImplicits` and `openImplicits` APIs,
which encapsulates tuples of `pt` and `tree` into `ImplicitCandidate`.
Diffstat (limited to 'src')
7 files changed, 58 insertions, 23 deletions
diff --git a/src/compiler/scala/reflect/macros/runtime/Aliases.scala b/src/compiler/scala/reflect/macros/runtime/Aliases.scala index ff870e728e..1c6703aeee 100644 --- a/src/compiler/scala/reflect/macros/runtime/Aliases.scala +++ b/src/compiler/scala/reflect/macros/runtime/Aliases.scala @@ -28,4 +28,8 @@ trait Aliases { override def typeTag[T](implicit ttag: TypeTag[T]) = ttag override def weakTypeOf[T](implicit attag: WeakTypeTag[T]): Type = attag.tpe override def typeOf[T](implicit ttag: TypeTag[T]): Type = ttag.tpe + + implicit class RichOpenImplicit(oi: universe.analyzer.OpenImplicit) { + def toImplicitCandidate = ImplicitCandidate(oi.info.pre, oi.info.sym, oi.pt, oi.tree) + } }
\ No newline at end of file diff --git a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala b/src/compiler/scala/reflect/macros/runtime/Enclosures.scala index 8fe0b09700..f3f92550de 100644 --- a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala +++ b/src/compiler/scala/reflect/macros/runtime/Enclosures.scala @@ -21,16 +21,16 @@ trait Enclosures { // vals are eager to simplify debugging // after all we wouldn't save that much time by making them lazy - val macroApplication: Tree = expandee - def enclosingPackage: PackageDef = strictEnclosure[PackageDef] - val enclosingClass: Tree = lenientEnclosure[ImplDef] - def enclosingImpl: ImplDef = strictEnclosure[ImplDef] - def enclosingTemplate: Template = strictEnclosure[Template] - val enclosingImplicits: List[(Type, Tree)] = site.openImplicits - val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self - val enclosingMethod: Tree = lenientEnclosure[DefDef] - def enclosingDef: DefDef = strictEnclosure[DefDef] - val enclosingPosition: Position = if (enclPoses.isEmpty) NoPosition else enclPoses.head.pos - val enclosingUnit: CompilationUnit = universe.currentRun.currentUnit - val enclosingRun: Run = universe.currentRun + val macroApplication: Tree = expandee + def enclosingPackage: PackageDef = strictEnclosure[PackageDef] + val enclosingClass: Tree = lenientEnclosure[ImplDef] + def enclosingImpl: ImplDef = strictEnclosure[ImplDef] + def enclosingTemplate: Template = strictEnclosure[Template] + val enclosingImplicits: List[ImplicitCandidate] = site.openImplicits.map(_.toImplicitCandidate) + val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self + val enclosingMethod: Tree = lenientEnclosure[DefDef] + def enclosingDef: DefDef = strictEnclosure[DefDef] + val enclosingPosition: Position = if (enclPoses.isEmpty) NoPosition else enclPoses.head.pos + val enclosingUnit: CompilationUnit = universe.currentRun.currentUnit + val enclosingRun: Run = universe.currentRun } diff --git a/src/compiler/scala/reflect/macros/runtime/Typers.scala b/src/compiler/scala/reflect/macros/runtime/Typers.scala index 398770ab35..30370119fe 100644 --- a/src/compiler/scala/reflect/macros/runtime/Typers.scala +++ b/src/compiler/scala/reflect/macros/runtime/Typers.scala @@ -8,7 +8,7 @@ trait Typers { def openMacros: List[Context] = this :: universe.analyzer.openMacros - def openImplicits: List[(Type, Tree)] = callsiteTyper.context.openImplicits + def openImplicits: List[ImplicitCandidate] = callsiteTyper.context.openImplicits.map(_.toImplicitCandidate) /** * @see [[scala.tools.reflect.ToolBox.typeCheck]] diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala index e3bb595bd7..82e6de87e3 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala @@ -213,7 +213,7 @@ trait Contexts { self: Analyzer => def isRootImport: Boolean = false /** Types for which implicit arguments are currently searched */ - var openImplicits: List[(Type,Tree)] = List() + var openImplicits: List[OpenImplicit] = List() /* For a named application block (`Tree`) the corresponding `NamedApplyInfo`. */ var namedApplyBlockInfo: Option[(Tree, NamedApplyInfo)] = None diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala index f27c15180e..6b8c01b0a2 100644 --- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala +++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala @@ -224,6 +224,10 @@ trait Implicits { ) } + /** A class which is used to track pending implicits to prevent infinite implicit searches. + */ + case class OpenImplicit(info: ImplicitInfo, pt: Type, tree: Tree) + /** A sentinel indicating no implicit was found */ val NoImplicitInfo = new ImplicitInfo(null, NoType, NoSymbol) { // equals used to be implemented in ImplicitInfo with an `if(this eq NoImplicitInfo)` @@ -407,13 +411,25 @@ trait Implicits { * @pre `info.tpe` does not contain an error */ private def typedImplicit(info: ImplicitInfo, ptChecked: Boolean, isLocal: Boolean): SearchResult = { - (context.openImplicits find { case (tp, tree1) => tree1.symbol == tree.symbol && dominates(pt, tp)}) match { + // SI-7167 let implicit macros decide what amounts for a divergent implicit search + // imagine a macro writer which wants to synthesize a complex implicit Complex[T] by making recursive calls to Complex[U] for its parts + // e.g. we have `class Foo(val bar: Bar)` and `class Bar(val x: Int)` + // then it's quite reasonable for the macro writer to synthesize Complex[Foo] by calling `inferImplicitValue(typeOf[Complex[Bar])` + // however if we didn't insert the `info.sym.isMacro` check here, then under some circumstances + // (e.g. as described here http://groups.google.com/group/scala-internals/browse_thread/thread/545462b377b0ac0a) + // `dominates` might decide that `Bar` dominates `Foo` and therefore a recursive implicit search should be prohibited + // now when we yield control of divergent expansions to the macro writer, what happens next? + // in the worst case, if the macro writer is careless, we'll get a StackOverflowException from repeated macro calls + // otherwise, the macro writer could check `c.openMacros` and `c.openImplicits` and do `c.abort` when expansions are deemed to be divergent + // upon receiving `c.abort` the typechecker will decide that the corresponding implicit search has failed + // which will fail the entire stack of implicit searches, producing a nice error message provided by the programmer + (context.openImplicits find { case OpenImplicit(info, tp, tree1) => !info.sym.isMacro && tree1.symbol == tree.symbol && dominates(pt, tp)}) match { case Some(pending) => //println("Pending implicit "+pending+" dominates "+pt+"/"+undetParams) //@MDEBUG DivergentSearchFailure case None => try { - context.openImplicits = (pt, tree) :: context.openImplicits + context.openImplicits = OpenImplicit(info, pt, tree) :: context.openImplicits // println(" "*context.openImplicits.length+"typed implicit "+info+" for "+pt) //@MDEBUG val result = typedImplicit0(info, ptChecked, isLocal) if (result.isDivergent) { diff --git a/src/reflect/scala/reflect/macros/Enclosures.scala b/src/reflect/scala/reflect/macros/Enclosures.scala index 8ea05500e4..d6ba5f39cd 100644 --- a/src/reflect/scala/reflect/macros/Enclosures.scala +++ b/src/reflect/scala/reflect/macros/Enclosures.scala @@ -45,13 +45,17 @@ trait Enclosures { */ def enclosingMacros: List[Context] - /** Types along with corresponding trees for which implicit arguments are currently searched. + /** Information about one of the currently considered implicit candidates. + * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, + * hence implicit searches can recursively trigger other implicit searches. + * * Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion. + * If we're in an implicit macro being expanded, it's included in this list. * * Unlike `openImplicits`, this is a val, which means that it gets initialized when the context is created * and always stays the same regardless of whatever happens during macro expansion. */ - def enclosingImplicits: List[(Type, Tree)] + def enclosingImplicits: List[ImplicitCandidate] /** Tries to guess a position for the enclosing application. * But that is simple, right? Just dereference `pos` of `macroApplication`? Not really. diff --git a/src/reflect/scala/reflect/macros/Typers.scala b/src/reflect/scala/reflect/macros/Typers.scala index eaf79f2dab..d7aec9b3ef 100644 --- a/src/reflect/scala/reflect/macros/Typers.scala +++ b/src/reflect/scala/reflect/macros/Typers.scala @@ -11,8 +11,6 @@ package macros trait Typers { self: Context => - import universe._ - /** Contexts that represent macros in-flight, including the current one. Very much like a stack trace, but for macros only. * Can be useful for interoperating with other macros and for imposing compiler-friendly limits on macro expansion. * @@ -25,13 +23,26 @@ trait Typers { */ def openMacros: List[Context] - /** Types along with corresponding trees for which implicit arguments are currently searched. + /** Information about one of the currently considered implicit candidates. + * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, + * hence implicit searches can recursively trigger other implicit searches. + * + * `pre` and `sym` provide information about the candidate itself. + * `pt` and `tree` store the parameters of the implicit search the candidate is participating in. + */ + case class ImplicitCandidate(pre: Type, sym: Symbol, pt: Type, tree: Tree) + + /** Information about one of the currently considered implicit candidates. + * Candidates are used in plural form, because implicit parameters may themselves have implicit parameters, + * hence implicit searches can recursively trigger other implicit searches. + * * Can be useful to get information about an application with an implicit parameter that is materialized during current macro expansion. + * If we're in an implicit macro being expanded, it's included in this list. * * Unlike `enclosingImplicits`, this is a def, which means that it gets recalculated on every invocation, * so it might change depending on what is going on during macro expansion. */ - def openImplicits: List[(Type, Tree)] + def openImplicits: List[ImplicitCandidate] /** Typechecks the provided tree against the expected type `pt` in the macro callsite context. * @@ -46,7 +57,7 @@ trait Typers { * * @throws [[scala.reflect.macros.TypecheckException]] */ - def typeCheck(tree: Tree, pt: Type = WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree + def typeCheck(tree: Tree, pt: Type = universe.WildcardType, silent: Boolean = false, withImplicitViewsDisabled: Boolean = false, withMacrosDisabled: Boolean = false): Tree /** Infers an implicit value of the expected type `pt` in the macro callsite context. * Optional `pos` parameter provides a position that will be associated with the implicit search. |