Skip to content

Commit 279e2e3

Browse files
committed
unprivates important helpers in Macros.scala
This is the first commit in a pull request that makes macro engine extensible by analyzer plugins. Such helpers as `macroArgs` and `macroExpandWithRuntime` have proven to be indispensable in macro paradise, so it’ll be important to have them available to macro plugins.
1 parent 447e737 commit 279e2e3

File tree

1 file changed

+114
-119
lines changed

1 file changed

+114
-119
lines changed

src/compiler/scala/tools/nsc/typechecker/Macros.scala

Lines changed: 114 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
361361
*/
362362
case class MacroArgs(c: MacroContext, others: List[Any])
363363

364-
private def macroArgs(typer: Typer, expandee: Tree): MacroArgs = {
364+
def macroArgs(typer: Typer, expandee: Tree): MacroArgs = {
365365
val macroDef = expandee.symbol
366366
val paramss = macroDef.paramss
367367
val treeInfo.Applied(core, targs, argss) = expandee
@@ -473,10 +473,10 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
473473
/** Keeps track of macros in-flight.
474474
* See more informations in comments to `openMacros` in `scala.reflect.macros.WhiteboxContext`.
475475
*/
476-
private var _openMacros = List[MacroContext]()
476+
var _openMacros = List[MacroContext]()
477477
def openMacros = _openMacros
478-
private def pushMacroContext(c: MacroContext) = _openMacros ::= c
479-
private def popMacroContext() = _openMacros = _openMacros.tail
478+
def pushMacroContext(c: MacroContext) = _openMacros ::= c
479+
def popMacroContext() = _openMacros = _openMacros.tail
480480
def enclosingMacroPosition = openMacros map (_.macroApplication.pos) find (_ ne NoPosition) getOrElse NoPosition
481481

482482
/** Describes the role that the macro expandee is performing.
@@ -527,7 +527,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
527527
* the expandee with an error marker set if the expansion has been cancelled due malformed arguments or implementation
528528
* the expandee with an error marker set if there has been an error
529529
*/
530-
private abstract class MacroExpander[Result: ClassTag](val role: MacroRole, val typer: Typer, val expandee: Tree) {
530+
abstract class MacroExpander[Result: ClassTag](val role: MacroRole, val typer: Typer, val expandee: Tree) {
531531
def allowExpandee(expandee: Tree): Boolean = true
532532
def allowExpanded(expanded: Tree): Boolean = true
533533
def allowedExpansions: String = "anything"
@@ -592,136 +592,131 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
592592
}
593593
}
594594

595-
/** Expands a tree that carries a term, which happens to be a term macro.
596-
* @see MacroExpander
597-
*/
598-
private abstract class TermMacroExpander(role: MacroRole, typer: Typer, expandee: Tree, mode: Mode, pt: Type)
599-
extends MacroExpander[Tree](role, typer, expandee) {
600-
override def allowedExpansions: String = "term trees"
601-
override def allowExpandee(expandee: Tree) = expandee.isTerm
602-
override def onSuccess(expanded: Tree) = typer.typed(expanded, mode, pt)
603-
override def onFallback(fallback: Tree) = typer.typed(fallback, mode, pt)
604-
}
605-
606595
/** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`.
607596
* @param outerPt Expected type that comes from enclosing context (something that's traditionally called `pt`).
608597
* @param innerPt Expected type that comes from the signature of a macro def, possibly wildcarded to help type inference.
609-
* @see MacroExpander
610598
*/
611-
def macroExpand(typer: Typer, expandee: Tree, mode: Mode, outerPt: Type): Tree = {
612-
object expander extends TermMacroExpander(APPLY_ROLE, typer, expandee, mode, outerPt) {
613-
lazy val innerPt = {
614-
val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe
615-
if (isBlackbox(expandee)) tp
616-
else {
617-
// approximation is necessary for whitebox macros to guide type inference
618-
// read more in the comments for onDelayed below
619-
val undetparams = tp collect { case tp if tp.typeSymbol.isTypeParameter => tp.typeSymbol }
620-
deriveTypeWithWildcards(undetparams)(tp)
621-
}
599+
class DefMacroExpander(typer: Typer, expandee: Tree, mode: Mode, outerPt: Type)
600+
extends MacroExpander[Tree](APPLY_ROLE, typer, expandee) {
601+
lazy val innerPt = {
602+
val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe
603+
if (isBlackbox(expandee)) tp
604+
else {
605+
// approximation is necessary for whitebox macros to guide type inference
606+
// read more in the comments for onDelayed below
607+
val undetparams = tp collect { case tp if tp.typeSymbol.isTypeParameter => tp.typeSymbol }
608+
deriveTypeWithWildcards(undetparams)(tp)
622609
}
623-
override def onSuccess(expanded0: Tree) = {
624-
// prematurely annotate the tree with a macro expansion attachment
625-
// so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup
626-
linkExpandeeAndExpanded(expandee, expanded0)
627-
628-
def typecheck(label: String, tree: Tree, pt: Type): Tree = {
629-
if (tree.isErrorTyped) tree
630-
else {
631-
if (macroDebugVerbose) println(s"$label (against pt = $pt): $tree")
632-
// `macroExpandApply` is called from `adapt`, where implicit conversions are disabled
633-
// therefore we need to re-enable the conversions back temporarily
634-
val result = typer.context.withImplicitsEnabled(typer.typed(tree, mode, pt))
635-
if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reportBuffer.errors}")
636-
result
637-
}
638-
}
610+
}
611+
override def onSuccess(expanded0: Tree) = {
612+
// prematurely annotate the tree with a macro expansion attachment
613+
// so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup
614+
linkExpandeeAndExpanded(expandee, expanded0)
639615

640-
if (isBlackbox(expandee)) {
641-
val expanded1 = atPos(enclosingMacroPosition.makeTransparent)(Typed(expanded0, TypeTree(innerPt)))
642-
typecheck("blackbox typecheck", expanded1, outerPt)
643-
} else {
644-
val expanded1 = expanded0
645-
val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt)
646-
typecheck("whitebox typecheck #2", expanded2, innerPt)
616+
def typecheck(label: String, tree: Tree, pt: Type): Tree = {
617+
if (tree.isErrorTyped) tree
618+
else {
619+
if (macroDebugVerbose) println(s"$label (against pt = $pt): $tree")
620+
// `macroExpandApply` is called from `adapt`, where implicit conversions are disabled
621+
// therefore we need to re-enable the conversions back temporarily
622+
val result = typer.context.withImplicitsEnabled(typer.typed(tree, mode, pt))
623+
if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reportBuffer.errors}")
624+
result
647625
}
648626
}
649-
override def onDelayed(delayed: Tree) = {
650-
// =========== THE SITUATION ===========
651-
//
652-
// If we've been delayed (i.e. bailed out of the expansion because of undetermined type params present in the expandee),
653-
// then there are two possible situations we're in:
654-
// 1) We're in POLYmode, when the typer tests the waters wrt type inference
655-
// (e.g. as in typedArgToPoly in doTypedApply).
656-
// 2) We're out of POLYmode, which means that the typer is out of tricks to infer our type
657-
// (e.g. if we're an argument to a function call, then this means that no previous argument lists
658-
// can determine our type variables for us).
659-
//
660-
// Situation #1 is okay for us, since there's no pressure. In POLYmode we're just verifying that
661-
// there's nothing outrageously wrong with our undetermined type params (from what I understand!).
662-
//
663-
// Situation #2 requires measures to be taken. If we're in it, then noone's going to help us infer
664-
// the undetermined type params. Therefore we need to do something ourselves or otherwise this
665-
// expandee will forever remaing not expanded (see SI-5692). A traditional way out of this conundrum
666-
// is to call `instantiate` and let the inferencer try to find the way out. It works for simple cases,
667-
// but sometimes, if the inferencer lacks information, it will be forced to approximate.
668-
//
669-
// =========== THE PROBLEM ===========
670-
//
671-
// Consider the following example (thanks, Miles!):
672-
//
673-
// Iso represents an isomorphism between two datatypes:
674-
// 1) An arbitrary one (e.g. a random case class)
675-
// 2) A uniform representation for all datatypes (e.g. an HList)
676-
//
677-
// trait Iso[T, U] {
678-
// def to(t : T) : U
679-
// def from(u : U) : T
680-
// }
681-
// implicit def materializeIso[T, U]: Iso[T, U] = macro ???
682-
//
683-
// case class Foo(i: Int, s: String, b: Boolean)
684-
// def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c)
685-
// foo(Foo(23, "foo", true))
686-
//
687-
// In the snippet above, even though we know that there's a fundep going from T to U
688-
// (in a sense that a datatype's uniform representation is unambiguously determined by the datatype,
689-
// e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information
690-
// to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want.
691-
//
692-
// =========== THE SOLUTION (ENABLED ONLY FOR WHITEBOX MACROS) ===========
693-
//
694-
// To give materializers a chance to say their word before vanilla inference kicks in,
695-
// we infer as much as possible (e.g. in the example above even though L is hopeless, C still can be inferred to Foo)
696-
// and then trigger macro expansion with the undetermined type parameters still there.
697-
// Thanks to that the materializer can take a look at what's going on and react accordingly.
698-
val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode
699-
if (shouldInstantiate) {
700-
if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt)
701-
else {
702-
forced += delayed
703-
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false)
704-
macroExpand(typer, delayed, mode, outerPt)
705-
}
706-
} else delayed
627+
628+
if (isBlackbox(expandee)) {
629+
val expanded1 = atPos(enclosingMacroPosition.makeTransparent)(Typed(expanded0, TypeTree(innerPt)))
630+
typecheck("blackbox typecheck", expanded1, outerPt)
631+
} else {
632+
val expanded1 = expanded0
633+
val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt)
634+
typecheck("whitebox typecheck #2", expanded2, innerPt)
707635
}
708636
}
637+
override def onDelayed(delayed: Tree) = {
638+
// =========== THE SITUATION ===========
639+
//
640+
// If we've been delayed (i.e. bailed out of the expansion because of undetermined type params present in the expandee),
641+
// then there are two possible situations we're in:
642+
// 1) We're in POLYmode, when the typer tests the waters wrt type inference
643+
// (e.g. as in typedArgToPoly in doTypedApply).
644+
// 2) We're out of POLYmode, which means that the typer is out of tricks to infer our type
645+
// (e.g. if we're an argument to a function call, then this means that no previous argument lists
646+
// can determine our type variables for us).
647+
//
648+
// Situation #1 is okay for us, since there's no pressure. In POLYmode we're just verifying that
649+
// there's nothing outrageously wrong with our undetermined type params (from what I understand!).
650+
//
651+
// Situation #2 requires measures to be taken. If we're in it, then noone's going to help us infer
652+
// the undetermined type params. Therefore we need to do something ourselves or otherwise this
653+
// expandee will forever remaing not expanded (see SI-5692). A traditional way out of this conundrum
654+
// is to call `instantiate` and let the inferencer try to find the way out. It works for simple cases,
655+
// but sometimes, if the inferencer lacks information, it will be forced to approximate.
656+
//
657+
// =========== THE PROBLEM ===========
658+
//
659+
// Consider the following example (thanks, Miles!):
660+
//
661+
// Iso represents an isomorphism between two datatypes:
662+
// 1) An arbitrary one (e.g. a random case class)
663+
// 2) A uniform representation for all datatypes (e.g. an HList)
664+
//
665+
// trait Iso[T, U] {
666+
// def to(t : T) : U
667+
// def from(u : U) : T
668+
// }
669+
// implicit def materializeIso[T, U]: Iso[T, U] = macro ???
670+
//
671+
// case class Foo(i: Int, s: String, b: Boolean)
672+
// def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c)
673+
// foo(Foo(23, "foo", true))
674+
//
675+
// In the snippet above, even though we know that there's a fundep going from T to U
676+
// (in a sense that a datatype's uniform representation is unambiguously determined by the datatype,
677+
// e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information
678+
// to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want.
679+
//
680+
// =========== THE SOLUTION (ENABLED ONLY FOR WHITEBOX MACROS) ===========
681+
//
682+
// To give materializers a chance to say their word before vanilla inference kicks in,
683+
// we infer as much as possible (e.g. in the example above even though L is hopeless, C still can be inferred to Foo)
684+
// and then trigger macro expansion with the undetermined type parameters still there.
685+
// Thanks to that the materializer can take a look at what's going on and react accordingly.
686+
val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode
687+
if (shouldInstantiate) {
688+
if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt)
689+
else {
690+
forced += delayed
691+
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false)
692+
macroExpand(typer, delayed, mode, outerPt)
693+
}
694+
} else delayed
695+
}
696+
override def onFallback(fallback: Tree) = typer.typed(fallback, mode, outerPt)
697+
}
698+
699+
/** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`.
700+
* @see DefMacroExpander
701+
*/
702+
def macroExpand(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = {
703+
val expander = new DefMacroExpander(typer, expandee, mode, pt)
709704
expander(expandee)
710705
}
711706

712-
private sealed abstract class MacroStatus(val result: Tree)
713-
private case class Success(expanded: Tree) extends MacroStatus(expanded)
714-
private case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true }
715-
private case class Delayed(delayed: Tree) extends MacroStatus(delayed)
716-
private case class Skipped(skipped: Tree) extends MacroStatus(skipped)
717-
private case class Failure(failure: Tree) extends MacroStatus(failure)
718-
private def Delay(expanded: Tree) = Delayed(expanded)
719-
private def Skip(expanded: Tree) = Skipped(expanded)
707+
sealed abstract class MacroStatus(val result: Tree)
708+
case class Success(expanded: Tree) extends MacroStatus(expanded)
709+
case class Fallback(fallback: Tree) extends MacroStatus(fallback) { currentRun.seenMacroExpansionsFallingBack = true }
710+
case class Delayed(delayed: Tree) extends MacroStatus(delayed)
711+
case class Skipped(skipped: Tree) extends MacroStatus(skipped)
712+
case class Failure(failure: Tree) extends MacroStatus(failure)
713+
def Delay(expanded: Tree) = Delayed(expanded)
714+
def Skip(expanded: Tree) = Skipped(expanded)
720715

721716
/** Expands a macro when a runtime (i.e. the macro implementation) can be successfully loaded
722717
* Meant for internal use within the macro infrastructure, don't use it elsewhere.
723718
*/
724-
private def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroStatus = {
719+
def macroExpandWithRuntime(typer: Typer, expandee: Tree, runtime: MacroRuntime): MacroStatus = {
725720
val wasDelayed = isDelayed(expandee)
726721
val undetparams = calculateUndetparams(expandee)
727722
val nowDelayed = !typer.context.macrosEnabled || undetparams.nonEmpty
@@ -778,7 +773,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
778773
/** Expands a macro when a runtime (i.e. the macro implementation) cannot be loaded
779774
* Meant for internal use within the macro infrastructure, don't use it elsewhere.
780775
*/
781-
private def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroStatus = {
776+
def macroExpandWithoutRuntime(typer: Typer, expandee: Tree): MacroStatus = {
782777
import typer.TyperErrorGen._
783778
val fallbackSym = expandee.symbol.nextOverriddenSymbol orElse MacroImplementationNotFoundError(expandee)
784779
macroLogLite(s"falling back to: $fallbackSym")

0 commit comments

Comments
 (0)