summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Aliases.scala4
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Enclosures.scala24
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Typers.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Implicits.scala20
-rw-r--r--src/reflect/scala/reflect/macros/Enclosures.scala8
-rw-r--r--src/reflect/scala/reflect/macros/Typers.scala21
-rw-r--r--test/files/neg/macro-divergence-controlled.check4
-rw-r--r--test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala23
-rw-r--r--test/files/neg/macro-divergence-controlled/Test_2.scala3
-rw-r--r--test/files/run/macro-divergence-spurious.check1
-rw-r--r--test/files/run/macro-divergence-spurious/Impls_Macros_1.scala23
-rw-r--r--test/files/run/macro-divergence-spurious/Test_2.scala3
-rw-r--r--test/files/run/macro-sip19-revised/Impls_Macros_1.scala2
-rw-r--r--test/files/run/macro-sip19/Impls_Macros_1.scala2
15 files changed, 117 insertions, 25 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.
diff --git a/test/files/neg/macro-divergence-controlled.check b/test/files/neg/macro-divergence-controlled.check
new file mode 100644
index 0000000000..4876f7cf96
--- /dev/null
+++ b/test/files/neg/macro-divergence-controlled.check
@@ -0,0 +1,4 @@
+Test_2.scala:2: error: could not find implicit value for parameter e: Complex[Foo]
+ println(implicitly[Complex[Foo]])
+ ^
+one error found
diff --git a/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala b/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala
new file mode 100644
index 0000000000..59acaede65
--- /dev/null
+++ b/test/files/neg/macro-divergence-controlled/Impls_Macros_1.scala
@@ -0,0 +1,23 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+trait Complex[T]
+
+class Foo(val foo: Foo)
+
+object Complex {
+ def impl[T: c.WeakTypeTag](c: Context): c.Expr[Complex[T]] = {
+ import c.universe._
+ val tpe = weakTypeOf[T]
+ for (f <- tpe.declarations.collect{case f: TermSymbol if f.isParamAccessor && !f.isMethod => f}) {
+ val trecur = appliedType(typeOf[Complex[_]], List(f.typeSignature))
+ if (c.openImplicits.tail.exists(ic => ic.pt =:= trecur)) c.abort(c.enclosingPosition, "diverging implicit expansion. reported by a macro!")
+ val recur = c.inferImplicitValue(trecur, silent = true)
+ if (recur == EmptyTree) c.abort(c.enclosingPosition, s"couldn't synthesize $trecur")
+ }
+ c.literalNull
+ }
+
+ implicit object ComplexString extends Complex[String]
+ implicit def genComplex[T]: Complex[T] = macro impl[T]
+}
diff --git a/test/files/neg/macro-divergence-controlled/Test_2.scala b/test/files/neg/macro-divergence-controlled/Test_2.scala
new file mode 100644
index 0000000000..dcc4593335
--- /dev/null
+++ b/test/files/neg/macro-divergence-controlled/Test_2.scala
@@ -0,0 +1,3 @@
+object Test extends App {
+ println(implicitly[Complex[Foo]])
+} \ No newline at end of file
diff --git a/test/files/run/macro-divergence-spurious.check b/test/files/run/macro-divergence-spurious.check
new file mode 100644
index 0000000000..19765bd501
--- /dev/null
+++ b/test/files/run/macro-divergence-spurious.check
@@ -0,0 +1 @@
+null
diff --git a/test/files/run/macro-divergence-spurious/Impls_Macros_1.scala b/test/files/run/macro-divergence-spurious/Impls_Macros_1.scala
new file mode 100644
index 0000000000..bc4a9fded7
--- /dev/null
+++ b/test/files/run/macro-divergence-spurious/Impls_Macros_1.scala
@@ -0,0 +1,23 @@
+import scala.reflect.macros.Context
+import language.experimental.macros
+
+trait Complex[T]
+
+class Foo(val bar: Bar)
+class Bar(val s: String)
+
+object Complex {
+ def impl[T: c.WeakTypeTag](c: Context): c.Expr[Complex[T]] = {
+ import c.universe._
+ val tpe = weakTypeOf[T]
+ for (f <- tpe.declarations.collect{case f: TermSymbol if f.isParamAccessor && !f.isMethod => f}) {
+ val trecur = appliedType(typeOf[Complex[_]], List(f.typeSignature))
+ val recur = c.inferImplicitValue(trecur, silent = true)
+ if (recur == EmptyTree) c.abort(c.enclosingPosition, s"couldn't synthesize $trecur")
+ }
+ c.literalNull
+ }
+
+ implicit object ComplexString extends Complex[String]
+ implicit def genComplex[T]: Complex[T] = macro impl[T]
+}
diff --git a/test/files/run/macro-divergence-spurious/Test_2.scala b/test/files/run/macro-divergence-spurious/Test_2.scala
new file mode 100644
index 0000000000..dcc4593335
--- /dev/null
+++ b/test/files/run/macro-divergence-spurious/Test_2.scala
@@ -0,0 +1,3 @@
+object Test extends App {
+ println(implicitly[Complex[Foo]])
+} \ No newline at end of file
diff --git a/test/files/run/macro-sip19-revised/Impls_Macros_1.scala b/test/files/run/macro-sip19-revised/Impls_Macros_1.scala
index 5f3f61ca3f..8d7d3b5d3d 100644
--- a/test/files/run/macro-sip19-revised/Impls_Macros_1.scala
+++ b/test/files/run/macro-sip19-revised/Impls_Macros_1.scala
@@ -7,7 +7,7 @@ object Macros {
val inscope = c.inferImplicitValue(c.mirror.staticClass("SourceLocation").toType)
val outer = c.Expr[SourceLocation](if (!inscope.isEmpty) inscope else Literal(Constant(null)))
- val Apply(fun, args) = c.enclosingImplicits(0)._2
+ val Apply(fun, args) = c.enclosingImplicits(0).tree
val fileName = fun.pos.source.file.file.getName
val line = fun.pos.line
val charOffset = fun.pos.point
diff --git a/test/files/run/macro-sip19/Impls_Macros_1.scala b/test/files/run/macro-sip19/Impls_Macros_1.scala
index 535ec2ccf0..4c165ed1b8 100644
--- a/test/files/run/macro-sip19/Impls_Macros_1.scala
+++ b/test/files/run/macro-sip19/Impls_Macros_1.scala
@@ -3,7 +3,7 @@ import scala.reflect.macros.Context
object Macros {
def impl(c: Context) = {
import c.universe._
- val Apply(fun, args) = c.enclosingImplicits(0)._2
+ val Apply(fun, args) = c.enclosingImplicits(0).tree
val fileName = fun.pos.source.file.file.getName
val line = fun.pos.line
val charOffset = fun.pos.point