diff options
author | Eugene Burmako <xeno.by@gmail.com> | 2013-05-11 12:14:19 -0700 |
---|---|---|
committer | Eugene Burmako <xeno.by@gmail.com> | 2013-05-11 12:14:19 -0700 |
commit | aa7568e8161552952ae16e0a5a79ce3ea517abe3 (patch) | |
tree | 679819f1381ec39b79169c2c21ca5d6acca72b63 /src | |
parent | 0ae7e55209129dc3d76d56887e88b2c817e6b904 (diff) | |
parent | 4e64a2731d6e4c27e2fd4c75559e118708e79ad5 (diff) | |
download | scala-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')
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. |