summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEugene Burmako <xeno.by@gmail.com>2013-05-11 12:14:19 -0700
committerEugene Burmako <xeno.by@gmail.com>2013-05-11 12:14:19 -0700
commitaa7568e8161552952ae16e0a5a79ce3ea517abe3 (patch)
tree679819f1381ec39b79169c2c21ca5d6acca72b63 /src
parent0ae7e55209129dc3d76d56887e88b2c817e6b904 (diff)
parent4e64a2731d6e4c27e2fd4c75559e118708e79ad5 (diff)
downloadscala-aa7568e8161552952ae16e0a5a79ce3ea517abe3.tar.gz
scala-aa7568e8161552952ae16e0a5a79ce3ea517abe3.tar.bz2
scala-aa7568e8161552952ae16e0a5a79ce3ea517abe3.zip
Merge pull request #2494 from scalamacros/ticket/5923
makes sense of implicit macros!
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Aliases.scala5
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Enclosures.scala16
-rw-r--r--src/compiler/scala/reflect/macros/runtime/Typers.scala23
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala9
-rw-r--r--src/compiler/scala/tools/nsc/settings/ScalaSettings.scala1
-rw-r--r--src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala6
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala29
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Contexts.scala2
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Implicits.scala125
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Macros.scala60
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala31
-rw-r--r--src/compiler/scala/tools/reflect/ToolBoxFactory.scala10
-rw-r--r--src/reflect/scala/reflect/macros/Enclosures.scala6
-rw-r--r--src/reflect/scala/reflect/macros/Typers.scala6
14 files changed, 258 insertions, 71 deletions
diff --git a/src/compiler/scala/reflect/macros/runtime/Aliases.scala b/src/compiler/scala/reflect/macros/runtime/Aliases.scala
index ff870e728e..96cf50e498 100644
--- a/src/compiler/scala/reflect/macros/runtime/Aliases.scala
+++ b/src/compiler/scala/reflect/macros/runtime/Aliases.scala
@@ -28,4 +28,9 @@ 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
+
+ type ImplicitCandidate = (Type, Tree)
+ implicit class RichOpenImplicit(oi: universe.analyzer.OpenImplicit) {
+ def toImplicitCandidate = (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 be5f2dbe83..2a4a22f81c 100644
--- a/src/compiler/scala/reflect/macros/runtime/Enclosures.scala
+++ b/src/compiler/scala/reflect/macros/runtime/Enclosures.scala
@@ -13,12 +13,12 @@ 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
- val enclosingClass: Tree = enclTrees collectFirst { case x: ImplDef => x } getOrElse EmptyTree
- val enclosingImplicits: List[(Type, Tree)] = site.openImplicits
- val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self
- val enclosingMethod: Tree = site.enclMethod.tree
- 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
+ val enclosingClass: Tree = enclTrees collectFirst { case x: ImplDef => x } getOrElse EmptyTree
+ val enclosingImplicits: List[ImplicitCandidate] = site.openImplicits.map(_.toImplicitCandidate)
+ val enclosingMacros: List[Context] = this :: universe.analyzer.openMacros // include self
+ val enclosingMethod: Tree = site.enclMethod.tree
+ 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 f9add91b9a..4d333f180b 100644
--- a/src/compiler/scala/reflect/macros/runtime/Typers.scala
+++ b/src/compiler/scala/reflect/macros/runtime/Typers.scala
@@ -6,7 +6,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]]
@@ -35,31 +35,16 @@ trait Typers {
def inferImplicitValue(pt: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = {
macroLogVerbose("inferring implicit value of type %s, macros = %s".format(pt, !withMacrosDisabled))
- inferImplicit(universe.EmptyTree, pt, isView = false, silent = silent, withMacrosDisabled = withMacrosDisabled, pos = pos)
+ universe.analyzer.inferImplicit(universe.EmptyTree, pt, false, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg))
}
def inferImplicitView(tree: Tree, from: Type, to: Type, silent: Boolean = true, withMacrosDisabled: Boolean = false, pos: Position = enclosingPosition): Tree = {
macroLogVerbose("inferring implicit view from %s to %s for %s, macros = %s".format(from, to, tree, !withMacrosDisabled))
val viewTpe = universe.appliedType(universe.definitions.FunctionClass(1).toTypeConstructor, List(from, to))
- inferImplicit(tree, viewTpe, isView = true, silent = silent, withMacrosDisabled = withMacrosDisabled, pos = pos)
- }
-
- private def inferImplicit(tree: Tree, pt: Type, isView: Boolean, silent: Boolean, withMacrosDisabled: Boolean, pos: Position): Tree = {
- import universe.analyzer.SearchResult
- val context = callsiteTyper.context
- val wrapper1 = if (!withMacrosDisabled) (context.withMacrosEnabled[SearchResult] _) else (context.withMacrosDisabled[SearchResult] _)
- def wrapper (inference: => SearchResult) = wrapper1(inference)
- wrapper(universe.analyzer.inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) match {
- case failure if failure.tree.isEmpty =>
- macroLogVerbose("implicit search has failed. to find out the reason, turn on -Xlog-implicits")
- if (context.hasErrors) throw new TypecheckException(context.errBuffer.head.errPos, context.errBuffer.head.errMsg)
- universe.EmptyTree
- case success =>
- success.tree
- }
+ universe.analyzer.inferImplicit(tree, viewTpe, true, callsiteTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw TypecheckException(pos, msg))
}
def resetAllAttrs(tree: Tree): Tree = universe.resetAllAttrs(tree)
def resetLocalAttrs(tree: Tree): Tree = universe.resetLocalAttrs(tree)
-} \ No newline at end of file
+}
diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala
index c63cca9b88..84670750d7 100644
--- a/src/compiler/scala/tools/nsc/interactive/Global.scala
+++ b/src/compiler/scala/tools/nsc/interactive/Global.scala
@@ -1174,8 +1174,13 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "")
debugLog("type error caught: "+ex)
alt
case ex: DivergentImplicit =>
- debugLog("divergent implicit caught: "+ex)
- alt
+ if (settings.Xdivergence211.value) {
+ debugLog("this shouldn't happen. DivergentImplicit exception has been thrown with -Xdivergence211 turned on: "+ex)
+ alt
+ } else {
+ debugLog("divergent implicit caught: "+ex)
+ alt
+ }
}
}
diff --git a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
index 3df6334ec1..dbfaa2c531 100644
--- a/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
+++ b/src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -110,6 +110,7 @@ trait ScalaSettings extends AbsScalaSettings
val XoldPatmat = BooleanSetting ("-Xoldpatmat", "Use the pre-2.10 pattern matcher. Otherwise, the 'virtualizing' pattern matcher is used in 2.10.")
val XnoPatmatAnalysis = BooleanSetting ("-Xno-patmat-analysis", "Don't perform exhaustivity/unreachability analysis. Also, ignore @switch annotation.")
val XfullLubs = BooleanSetting ("-Xfull-lubs", "Retains pre 2.10 behavior of less aggressive truncation of least upper bounds.")
+ val Xdivergence211 = BooleanSetting ("-Xdivergence211", "Turn on the 2.11 behavior of implicit divergence not terminating recursive implicit searches (SI-7291).")
/** Compatibility stubs for options whose value name did
* not previously match the option name.
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala
index c3aded2b2d..ed7eb6d307 100644
--- a/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala
+++ b/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala
@@ -69,11 +69,7 @@ abstract class Pickler extends SubComponent {
}
if (!t.isDef && t.hasSymbol && t.symbol.isTermMacro) {
- unit.error(t.pos, t.symbol.typeParams.length match {
- case 0 => "macro has not been expanded"
- case 1 => "this type parameter must be specified"
- case _ => "these type parameters must be specified"
- })
+ unit.error(t.pos, "macro has not been expanded")
return
}
}
diff --git a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
index 49049f110d..e0dbe98780 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
@@ -60,6 +60,24 @@ trait ContextErrors {
def errPos = tree.pos
}
+ // Unlike other type errors diverging implicit expansion
+ // will be re-issued explicitly on failed implicit argument search.
+ // This is because we want to:
+ // 1) provide better error message than just "implicit not found"
+ // 2) provide the type of the implicit parameter for which we got diverging expansion
+ // (pt at the point of divergence gives less information to the user)
+ // Note: it is safe to delay error message generation in this case
+ // becasue we don't modify implicits' infos.
+ // only issued when -Xdivergence211 is turned on
+ case class DivergentImplicitTypeError(tree: Tree, pt0: Type, sym: Symbol) extends AbsTypeError {
+ def errPos: Position = tree.pos
+ def errMsg: String = errMsgForPt(pt0)
+ def kind = ErrorKinds.Divergent
+ def withPt(pt: Type): AbsTypeError = NormalTypeError(tree, errMsgForPt(pt), kind)
+ private def errMsgForPt(pt: Type) =
+ s"diverging implicit expansion for type ${pt}\nstarting with ${sym.fullLocationString}"
+ }
+
case class AmbiguousTypeError(underlyingTree: Tree, errPos: Position, errMsg: String, kind: ErrorKind = ErrorKinds.Ambiguous) extends AbsTypeError
case class PosAndMsgTypeError(errPos: Position, errMsg: String, kind: ErrorKind = ErrorKinds.Normal) extends AbsTypeError
@@ -73,6 +91,7 @@ trait ContextErrors {
issueTypeError(SymbolTypeError(sym, msg))
}
+ // only called when -Xdivergence211 is turned off
def issueDivergentImplicitsError(tree: Tree, msg: String)(implicit context: Context) {
issueTypeError(NormalTypeError(tree, msg, ErrorKinds.Divergent))
}
@@ -1152,9 +1171,13 @@ trait ContextErrors {
}
def DivergingImplicitExpansionError(tree: Tree, pt: Type, sym: Symbol)(implicit context0: Context) =
- issueDivergentImplicitsError(tree,
- "diverging implicit expansion for type "+pt+"\nstarting with "+
- sym.fullLocationString)
+ if (settings.Xdivergence211.value) {
+ issueTypeError(DivergentImplicitTypeError(tree, pt, sym))
+ } else {
+ issueDivergentImplicitsError(tree,
+ "diverging implicit expansion for type "+pt+"\nstarting with "+
+ sym.fullLocationString)
+ }
}
object NamesDefaultsErrorsGen {
diff --git a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
index 6c2945cad3..3fe98ed127 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Contexts.scala
@@ -120,7 +120,7 @@ trait Contexts { self: Analyzer =>
// not inherited to child contexts
var depth: Int = 0
var imports: List[ImportInfo] = List() // currently visible imports
- var openImplicits: List[(Type,Tree)] = List() // types for which implicit arguments
+ var openImplicits: List[OpenImplicit] = List() // types for which implicit arguments
// are currently searched
// 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 04e0b9d653..193e589470 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -80,7 +80,7 @@ trait Implicits {
printTyping("typing implicit: %s %s".format(tree, context.undetparamsString))
val implicitSearchContext = context.makeImplicit(reportAmbiguous)
val result = new ImplicitSearch(tree, pt, isView, implicitSearchContext, pos).bestImplicit
- if (saveAmbiguousDivergent && implicitSearchContext.hasErrors) {
+ if ((result.isFailure || !settings.Xdivergence211.value) && saveAmbiguousDivergent && implicitSearchContext.hasErrors) {
context.updateBuffer(implicitSearchContext.errBuffer.filter(err => err.kind == ErrorKinds.Ambiguous || err.kind == ErrorKinds.Divergent))
debugwarn("update buffer: " + implicitSearchContext.errBuffer)
}
@@ -96,6 +96,31 @@ trait Implicits {
result
}
+ /** A friendly wrapper over inferImplicit to be used in macro contexts and toolboxes.
+ */
+ def inferImplicit(tree: Tree, pt: Type, isView: Boolean, context: Context, silent: Boolean, withMacrosDisabled: Boolean, pos: Position, onError: (Position, String) => Unit): Tree = {
+ val wrapper1 = if (!withMacrosDisabled) (context.withMacrosEnabled[SearchResult] _) else (context.withMacrosDisabled[SearchResult] _)
+ def wrapper(inference: => SearchResult) = wrapper1(inference)
+ def fail(reason: Option[String]) = {
+ if (!silent) {
+ if (context.hasErrors) onError(context.errBuffer.head.errPos, context.errBuffer.head.errMsg)
+ else onError(pos, reason getOrElse "implicit search has failed. to find out the reason, turn on -Xlog-implicits")
+ }
+ EmptyTree
+ }
+ try {
+ wrapper(inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos)) match {
+ case failure if failure.tree.isEmpty => fail(None)
+ case success => success.tree
+ }
+ } catch {
+ case ex: DivergentImplicit =>
+ if (settings.Xdivergence211.value)
+ debugwarn("this shouldn't happen. DivergentImplicit exception has been thrown with -Xdivergence211 turned on: "+ex)
+ fail(Some("divergent implicit expansion"))
+ }
+ }
+
/** Find all views from type `tp` (in which `tpars` are free)
*
* Note that the trees in the search results in the returned list share the same type variables.
@@ -152,6 +177,8 @@ trait Implicits {
def isFailure = false
def isAmbiguousFailure = false
+ // only used when -Xdivergence211 is turned on
+ def isDivergent = false
final def isSuccess = !isFailure
}
@@ -159,6 +186,12 @@ trait Implicits {
override def isFailure = true
}
+ // only used when -Xdivergence211 is turned on
+ lazy val DivergentSearchFailure = new SearchResult(EmptyTree, EmptyTreeTypeSubstituter) {
+ override def isFailure = true
+ override def isDivergent = true
+ }
+
lazy val AmbiguousSearchFailure = new SearchResult(EmptyTree, EmptyTreeTypeSubstituter) {
override def isFailure = true
override def isAmbiguousFailure = true
@@ -219,6 +252,10 @@ trait Implicits {
override def toString = name + ": " + tpe
}
+ /** 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)`
@@ -406,20 +443,34 @@ 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
- throw DivergentImplicit
+ if (settings.Xdivergence211.value) DivergentSearchFailure
+ else throw DivergentImplicit
case None =>
+ def pre211DivergenceLogic() = {
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
typedImplicit0(info, ptChecked, isLocal)
} catch {
case ex: DivergentImplicit =>
//println("DivergentImplicit for pt:"+ pt +", open implicits:"+context.openImplicits) //@MDEBUG
if (context.openImplicits.tail.isEmpty) {
- if (!(pt.isErroneous))
+ if (!pt.isErroneous && !info.sym.isMacro)
DivergingImplicitExpansionError(tree, pt, info.sym)(context)
SearchFailure
} else {
@@ -428,6 +479,24 @@ trait Implicits {
} finally {
context.openImplicits = context.openImplicits.tail
}
+ }
+ def post211DivergenceLogic() = {
+ try {
+ 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) {
+ //println("DivergentImplicit for pt:"+ pt +", open implicits:"+context.openImplicits) //@MDEBUG
+ if (context.openImplicits.tail.isEmpty && !pt.isErroneous)
+ DivergingImplicitExpansionError(tree, pt, info.sym)(context)
+ }
+ result
+ } finally {
+ context.openImplicits = context.openImplicits.tail
+ }
+ }
+ if (settings.Xdivergence211.value) post211DivergenceLogic()
+ else pre211DivergenceLogic()
}
}
@@ -796,6 +865,9 @@ trait Implicits {
/** Preventing a divergent implicit from terminating implicit search,
* so that if there is a best candidate it can still be selected.
+ *
+ * The old way of handling divergence.
+ * Only enabled when -Xdivergence211 is turned off.
*/
private var divergence = false
private val divergenceHandler: PartialFunction[Throwable, SearchResult] = {
@@ -808,6 +880,28 @@ trait Implicits {
}
}
+ /** Preventing a divergent implicit from terminating implicit search,
+ * so that if there is a best candidate it can still be selected.
+ *
+ * The new way of handling divergence.
+ * Only enabled when -Xdivergence211 is turned on.
+ */
+ object DivergentImplicitRecovery {
+ // symbol of the implicit that caused the divergence.
+ // Initially null, will be saved on first diverging expansion.
+ private var implicitSym: Symbol = _
+ private var countdown: Int = 1
+
+ def sym: Symbol = implicitSym
+ def apply(search: SearchResult, i: ImplicitInfo): SearchResult =
+ if (search.isDivergent && countdown > 0) {
+ countdown -= 1
+ implicitSym = i.sym
+ log("discarding divergent implicit ${implicitSym} during implicit search")
+ SearchFailure
+ } else search
+ }
+
/** Sorted list of eligible implicits.
*/
val eligible = {
@@ -834,11 +928,20 @@ trait Implicits {
@tailrec private def rankImplicits(pending: Infos, acc: Infos): Infos = pending match {
case Nil => acc
case i :: is =>
- def tryImplicitInfo(i: ImplicitInfo) =
+ def pre211tryImplicitInfo(i: ImplicitInfo) =
try typedImplicit(i, ptChecked = true, isLocal)
catch divergenceHandler
- tryImplicitInfo(i) match {
+ def post211tryImplicitInfo(i: ImplicitInfo) =
+ DivergentImplicitRecovery(typedImplicit(i, ptChecked = true, isLocal), i)
+
+ {
+ if (settings.Xdivergence211.value) post211tryImplicitInfo(i)
+ else pre211tryImplicitInfo(i)
+ } match {
+ // only used if -Xdivergence211 is turned on
+ case sr if sr.isDivergent =>
+ Nil
case sr if sr.isFailure =>
// We don't want errors that occur during checking implicit info
// to influence the check of further infos.
@@ -890,9 +993,10 @@ trait Implicits {
/** If there is no winner, and we witnessed and caught divergence,
* now we can throw it for the error message.
*/
- if (divergence)
- throw DivergentImplicit
- else invalidImplicits take 1 foreach { sym =>
+ if (divergence || DivergentImplicitRecovery.sym != null) {
+ if (settings.Xdivergence211.value) DivergingImplicitExpansionError(tree, pt, DivergentImplicitRecovery.sym)(context)
+ else throw DivergentImplicit
+ } else invalidImplicits take 1 foreach { sym =>
def isSensibleAddendum = pt match {
case Function1(_, out) => out <:< sym.tpe.finalResultType
case tp => tp <:< sym.tpe.finalResultType
@@ -1462,5 +1566,6 @@ object ImplicitsStats {
val implicitCacheHits = Statistics.newSubCounter("implicit cache hits", implicitCacheAccs)
}
+// only used when -Xdivergence211 is turned off
class DivergentImplicit extends Exception
object DivergentImplicit extends DivergentImplicit
diff --git a/src/compiler/scala/tools/nsc/typechecker/Macros.scala b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
index 5955081345..5ad568b1e6 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Macros.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -391,6 +391,8 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
} finally {
popMacroContext()
}
+ case Delay(delayed) =>
+ typer.instantiate(delayed, EXPRmode, WildcardType)
case Fallback(fallback) =>
typer.typed1(fallback, EXPRmode, WildcardType)
case Other(result) =>
@@ -655,9 +657,9 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
private sealed abstract class MacroExpansionResult
private case class Success(expanded: Tree) extends MacroExpansionResult
+ private case class Delay(delayed: Tree) extends MacroExpansionResult
private case class Fallback(fallback: Tree) extends MacroExpansionResult { currentRun.seenMacroExpansionsFallingBack = true }
private case class Other(result: Tree) extends MacroExpansionResult
- private def Delay(expanded: Tree) = Other(expanded)
private def Skip(expanded: Tree) = Other(expanded)
private def Cancel(expandee: Tree) = Other(expandee)
private def Failure(expandee: Tree) = Other(expandee)
@@ -710,6 +712,54 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
} finally {
popMacroContext()
}
+ case Delay(delayed) =>
+ // =========== THE SITUATION ===========
+ //
+ // If we've been delayed (i.e. bailed out of the expansion because of undetermined type params present in the expandee),
+ // then there are two possible situations we're in:
+ //
+ // 1) We're in POLYmode, when the typer tests the waters wrt type inference
+ // (e.g. as in typedArgToPoly in doTypedApply).
+ //
+ // 2) We're out of POLYmode, which means that the typer is out of tricks to infer our type
+ // (e.g. if we're an argument to a function call, then this means that no previous argument lists
+ // can determine our type variables for us).
+ //
+ // Situation #1 is okay for us, since there's no pressure. In POLYmode we're just verifying that
+ // there's nothing outrageously wrong with our undetermined type params (from what I understand!).
+ //
+ // Situation #2 requires measures to be taken. If we're in it, then noone's going to help us infer
+ // the undetermined type params. Therefore we need to do something ourselves or otherwise this
+ // expandee will forever remaing not expanded (see SI-5692).
+ //
+ // A traditional way out of this conundrum is to call `instantiate` and let the inferencer
+ // try to find the way out. It works for simple cases, but sometimes, if the inferencer lacks
+ // information, it will be forced to approximate.
+ //
+ // =========== THE PROBLEM ===========
+ //
+ // Consider the following example (thanks, Miles!):
+ //
+ // // Iso represents an isomorphism between two datatypes:
+ // // 1) An arbitrary one (e.g. a random case class)
+ // // 2) A uniform representation for all datatypes (e.g. an HList)
+ // trait Iso[T, U] {
+ // def to(t : T) : U
+ // def from(u : U) : T
+ // }
+ // implicit def materializeIso[T, U]: Iso[T, U] = macro ???
+ //
+ // case class Foo(i: Int, s: String, b: Boolean)
+ // def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c)
+ // foo(Foo(23, "foo", true))
+ //
+ // In the snippet above, even though we know that there's a fundep going from T to U
+ // (in a sense that a datatype's uniform representation is unambiguously determined by the datatype,
+ // e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information
+ // to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want.
+ val shouldInstantiate = typer.context.undetparams.nonEmpty && !inPolyMode(mode)
+ if (shouldInstantiate) typer.instantiatePossiblyExpectingUnit(delayed, mode, pt)
+ else delayed
case Fallback(fallback) =>
typer.context.withImplicitsEnabled(typer.typed(fallback, EXPRmode, pt))
case Other(result) =>
@@ -750,8 +800,12 @@ trait Macros extends scala.tools.reflect.FastTrack with Traces {
val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty
(wasDelayed, nowDelayed) match {
- case (true, true) => Delay(expandee)
- case (true, false) => Skip(macroExpandAll(typer, expandee))
+ case (true, true) =>
+ Delay(expandee)
+ case (true, false) =>
+ val expanded = macroExpandAll(typer, expandee)
+ if (expanded exists (_.isErroneous)) Failure(expandee)
+ else Skip(expanded)
case (false, true) =>
macroLogLite("macro expansion is delayed: %s".format(expandee))
delayed += expandee -> undetparams
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index bb3ebb4e0f..b89b570cd8 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -138,13 +138,18 @@ trait Typers extends Modes with Adaptations with Tags {
mkArg = mkNamedArg // don't pass the default argument (if any) here, but start emitting named arguments for the following args
if (!param.hasDefault && !paramFailed) {
context.errBuffer.find(_.kind == ErrorKinds.Divergent) match {
- case Some(divergentImplicit) =>
+ case Some(divergentImplicit) if !settings.Xdivergence211.value =>
// DivergentImplicit error has higher priority than "no implicit found"
// no need to issue the problem again if we are still in silent mode
if (context.reportErrors) {
context.issue(divergentImplicit)
context.condBufferFlush(_.kind == ErrorKinds.Divergent)
}
+ case Some(divergentImplicit: DivergentImplicitTypeError) if settings.Xdivergence211.value =>
+ if (context.reportErrors) {
+ context.issue(divergentImplicit.withPt(paramTp))
+ context.condBufferFlush(_.kind == ErrorKinds.Divergent)
+ }
case None =>
NoImplicitFoundError(fun, param)
}
@@ -1149,12 +1154,9 @@ trait Typers extends Modes with Adaptations with Tags {
adaptConstrPattern()
else if (shouldInsertApply(tree))
insertApply()
- else if (!context.undetparams.isEmpty && !inPolyMode(mode)) { // (9)
+ else if (context.undetparams.nonEmpty && !inPolyMode(mode)) { // (9)
assert(!inHKMode(mode), modeString(mode)) //@M
- if (inExprModeButNot(mode, FUNmode) && pt.typeSymbol == UnitClass)
- instantiateExpectingUnit(tree, mode)
- else
- instantiate(tree, mode, pt)
+ instantiatePossiblyExpectingUnit(tree, mode, pt)
} else if (tree.tpe <:< pt) {
tree
} else {
@@ -1302,6 +1304,13 @@ trait Typers extends Modes with Adaptations with Tags {
}
}
+ def instantiatePossiblyExpectingUnit(tree: Tree, mode: Int, pt: Type): Tree = {
+ if (inExprModeButNot(mode, FUNmode) && pt.typeSymbol == UnitClass)
+ instantiateExpectingUnit(tree, mode)
+ else
+ instantiate(tree, mode, pt)
+ }
+
private def isAdaptableWithView(qual: Tree) = {
val qtpe = qual.tpe.widen
( !isPastTyper
@@ -3055,8 +3064,7 @@ trait Typers extends Modes with Adaptations with Tags {
else if (isByNameParamType(formals.head)) 0
else BYVALmode
)
- var tree = typedArg(args.head, mode, typedMode, adapted.head)
- if (hasPendingMacroExpansions) tree = macroExpandAll(this, tree)
+ val tree = typedArg(args.head, mode, typedMode, adapted.head)
// formals may be empty, so don't call tail
tree :: loop(args.tail, formals drop 1, adapted.tail)
}
@@ -5608,7 +5616,12 @@ trait Typers extends Modes with Adaptations with Tags {
}
tree1.tpe = pluginsTyped(tree1.tpe, this, tree1, mode, ptPlugins)
- val result = if (tree1.isEmpty) tree1 else adapt(tree1, mode, ptPlugins, tree)
+ val result =
+ if (tree1.isEmpty) tree1
+ else {
+ val result = adapt(tree1, mode, ptPlugins, tree)
+ if (hasPendingMacroExpansions) macroExpandAll(this, result) else result
+ }
if (!alreadyTyped) {
printTyping("adapted %s: %s to %s, %s".format(
diff --git a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
index c05c59d5ff..4b5dcadfa2 100644
--- a/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
+++ b/src/compiler/scala/tools/reflect/ToolBoxFactory.scala
@@ -181,15 +181,7 @@ abstract class ToolBoxFactory[U <: JavaUniverse](val u: U) { factorySelf =>
transformDuringTyper(tree, withImplicitViewsDisabled = false, withMacrosDisabled = withMacrosDisabled)(
(currentTyper, tree) => {
trace("inferring implicit %s (macros = %s): ".format(if (isView) "view" else "value", !withMacrosDisabled))(showAttributed(pt, true, true, settings.Yshowsymkinds.value))
- val context = currentTyper.context
- analyzer.inferImplicit(tree, pt, reportAmbiguous = true, isView = isView, context = context, saveAmbiguousDivergent = !silent, pos = pos) match {
- case failure if failure.tree.isEmpty =>
- trace("implicit search has failed. to find out the reason, turn on -Xlog-implicits: ")(failure.tree)
- if (context.hasErrors) throw ToolBoxError("reflective implicit search has failed: %s".format(context.errBuffer.head.errMsg))
- EmptyTree
- case success =>
- success.tree
- }
+ analyzer.inferImplicit(tree, pt, isView, currentTyper.context, silent, withMacrosDisabled, pos, (pos, msg) => throw ToolBoxError(msg))
})
def compile(expr0: Tree): () => Any = {
diff --git a/src/reflect/scala/reflect/macros/Enclosures.scala b/src/reflect/scala/reflect/macros/Enclosures.scala
index c48656b366..a4ad71c348 100644
--- a/src/reflect/scala/reflect/macros/Enclosures.scala
+++ b/src/reflect/scala/reflect/macros/Enclosures.scala
@@ -29,8 +29,12 @@ trait Enclosures {
*/
val 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.
diff --git a/src/reflect/scala/reflect/macros/Typers.scala b/src/reflect/scala/reflect/macros/Typers.scala
index 427e7854b2..d36636a6d2 100644
--- a/src/reflect/scala/reflect/macros/Typers.scala
+++ b/src/reflect/scala/reflect/macros/Typers.scala
@@ -24,8 +24,12 @@ 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.
+ *
* 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.