Skip to content
Browse files

Use macro expandee, rather than expansion, in pres. compiler

The presentation compiler is primarily interested in trees that
represent the code that one sees in the IDE, not the expansion of
macros.

This commit continues to expand macros, but adds a hook in which
the presentation compiler discards the expansion, retaining instead
the expandee. The expandee is attributed with the type of the
expansion, which allows white box macros to work. In addition,
any domain specific errors and warnings issued by the macro will
still be reported, as a side-effect of the expansion.

The failing test from the last commit now correctly resolves
hyperlinks in macro arguments.

Related IDE ticket:

  https://www.assembla.com/spaces/scala-ide/tickets/1001449#

This facility is configured as follows:

    // expand macros as per normal
    -Ymacro-expand:normal

    // don't expand the macro, takes the place of -Ymacro-no-expand
    -Ymacro-expand:none

    // expand macros to compute type and emit warnings,
    // but retain expandee. Set automatically be the presentation
    // compiler
    -Ymacro-expand:discard

This leaves to door ajar for a new option:

    // Don't expand blackbox macros; expand whitebox
    // but retain expandee
    -Ymacro-expand:discard-whitebox-only

The existing test for SI-6812 has been duplicated. One copy exercises
the now-deprecated -Ymacro-no-expand, and the other uses the new
option.
  • Loading branch information...
1 parent d744921 commit 6e4c926b4a4c5e8dd350ae3a150490a794b139ca @retronym retronym committed with xeno-by
View
8 src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
@@ -172,7 +172,8 @@ trait ScalaSettings extends AbsScalaSettings
val Yrangepos = BooleanSetting ("-Yrangepos", "Use range positions for syntax trees.")
val Ymemberpos = StringSetting ("-Yshow-member-pos", "output style", "Show start and end positions of members", "") withPostSetHook (_ => Yrangepos.value = true)
val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.")
- val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.")
+ val Ymacroexpand = ChoiceSetting ("-Ymacro-expand", "policy", "Control expansion of macros, useful for scaladoc and presentation compiler", List(MacroExpand.Normal, MacroExpand.None, MacroExpand.Discard), MacroExpand.Normal)
+ val Ymacronoexpand = BooleanSetting ("-Ymacro-no-expand", "Don't expand macros. Might be useful for scaladoc and presentation compiler, but will crash anything which uses macros and gets past typer.") withDeprecationMessage(s"Use ${Ymacroexpand.name}:${MacroExpand.None}") withPostSetHook(_ => Ymacroexpand.value = MacroExpand.None)
val Yreplsync = BooleanSetting ("-Yrepl-sync", "Do not use asynchronous code for repl startup")
val Yreplclassbased = BooleanSetting ("-Yrepl-class-based", "Use classes to wrap REPL snippets instead of objects")
val Yreploutdir = StringSetting ("-Yrepl-outdir", "path", "Write repl-generated classfiles to given output directory (use \"\" to generate a temporary dir)" , "")
@@ -249,4 +250,9 @@ trait ScalaSettings extends AbsScalaSettings
def isBCodeAskedFor = (Ybackend.value != "GenASM")
def isICodeAskedFor = ((Ybackend.value == "GenASM") || optimiseSettings.exists(_.value) || writeICode.isSetByUser)
+ object MacroExpand {
+ val None = "none"
+ val Normal = "normal"
+ val Discard = "discard"
+ }
}
View
27 src/compiler/scala/tools/nsc/typechecker/Macros.scala
@@ -535,25 +535,25 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
* the expandee with an error marker set if the expansion has been cancelled due malformed arguments or implementation
* the expandee with an error marker set if there has been an error
*/
- abstract class MacroExpander[Result: ClassTag](val role: MacroRole, val typer: Typer, val expandee: Tree) {
+ abstract class MacroExpander(val role: MacroRole, val typer: Typer, val expandee: Tree) {
def allowExpandee(expandee: Tree): Boolean = true
def allowExpanded(expanded: Tree): Boolean = true
def allowedExpansions: String = "anything"
- def allowResult(result: Result): Boolean = true
+ def allowResult(result: Tree): Boolean = true
- def onSuccess(expanded: Tree): Result
- def onFallback(expanded: Tree): Result
- def onSuppressed(expandee: Tree): Result = expandee match { case expandee: Result => expandee }
- def onDelayed(expanded: Tree): Result = expanded match { case expanded: Result => expanded }
- def onSkipped(expanded: Tree): Result = expanded match { case expanded: Result => expanded }
- def onFailure(expanded: Tree): Result = { typer.infer.setError(expandee); expandee match { case expandee: Result => expandee } }
+ def onSuccess(expanded: Tree): Tree
+ def onFallback(expanded: Tree): Tree
+ def onSuppressed(expandee: Tree): Tree = expandee
+ def onDelayed(expanded: Tree): Tree = expanded
+ def onSkipped(expanded: Tree): Tree = expanded
+ def onFailure(expanded: Tree): Tree = { typer.infer.setError(expandee); expandee }
- def apply(desugared: Tree): Result = {
+ def apply(desugared: Tree): Tree = {
if (isMacroExpansionSuppressed(desugared)) onSuppressed(expandee)
else expand(desugared)
}
- protected def expand(desugared: Tree): Result = {
+ protected def expand(desugared: Tree): Tree = {
def showDetailed(tree: Tree) = showRaw(tree, printIds = true, printTypes = true)
def summary() = s"expander = $this, expandee = ${showDetailed(expandee)}, desugared = ${if (expandee == desugared) () else showDetailed(desugared)}"
if (macroDebugVerbose) println(s"macroExpand: ${summary()}")
@@ -580,7 +580,10 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
// also see http://groups.google.com/group/scala-internals/browse_thread/thread/492560d941b315cc
val expanded1 = try onSuccess(duplicateAndKeepPositions(expanded)) finally popMacroContext()
if (!hasMacroExpansionAttachment(expanded1)) linkExpandeeAndExpanded(expandee, expanded1)
- if (allowResult(expanded1)) expanded1 else onFailure(expanded)
+ if (allowResult(expanded1)) {
+ if (settings.Ymacroexpand.value == settings.MacroExpand.Discard) expandee.setType(expanded1.tpe)
+ else expanded1
+ } else onFailure(expanded)
} else {
typer.TyperErrorGen.MacroInvalidExpansionError(expandee, role.name, allowedExpansions)
onFailure(expanded)
@@ -605,7 +608,7 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
* @param innerPt Expected type that comes from the signature of a macro def, possibly wildcarded to help type inference.
*/
class DefMacroExpander(typer: Typer, expandee: Tree, mode: Mode, outerPt: Type)
- extends MacroExpander[Tree](APPLY_ROLE, typer, expandee) {
+ extends MacroExpander(APPLY_ROLE, typer, expandee) {
lazy val innerPt = {
val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe
if (isBlackbox(expandee)) tp
View
2 src/compiler/scala/tools/nsc/typechecker/StdAttachments.scala
@@ -95,7 +95,7 @@ trait StdAttachments {
/** Determines whether a tree should not be expanded, because someone has put SuppressMacroExpansionAttachment on it or one of its children.
*/
def isMacroExpansionSuppressed(tree: Tree): Boolean =
- ( settings.Ymacronoexpand.value // SI-6812
+ ( settings.Ymacroexpand.value == settings.MacroExpand.None // SI-6812
|| tree.attachments.get[SuppressMacroExpansionAttachment.type].isDefined
|| (tree match {
// we have to account for the fact that during typechecking an expandee might become wrapped,
View
4 src/interactive/scala/tools/nsc/interactive/Global.scala
@@ -88,7 +88,6 @@ trait InteractiveAnalyzer extends Analyzer {
}
}
-
/** The main class of the presentation compiler in an interactive environment such as an IDE
*/
class Global(settings: Settings, _reporter: Reporter, projectName: String = "") extends {
@@ -105,6 +104,9 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "")
import definitions._
+ if (!settings.Ymacroexpand.isSetByUser)
+ settings.Ymacroexpand.value = settings.MacroExpand.Discard
+
val debugIDE: Boolean = settings.YpresentationDebug.value
val verboseIDE: Boolean = settings.YpresentationVerbose.value
View
4 test/files/presentation/hyperlinks-macro.check
@@ -1,8 +1,8 @@
reload: MacroCall.scala
-askHyperlinkPos for `<local Test>` at (5,7) MacroCall.scala
+askHyperlinkPos for `foo` at (5,7) MacroCall.scala
================================================================================
-[response] found askHyperlinkPos for `<local Test>` at (1,13) MacroCall.scala
+[response] found askHyperlinkPos for `foo` at (2,7) MacroCall.scala
================================================================================
askHyperlinkPos for `foo` at (9,7) MacroCall.scala
View
1 test/files/presentation/hyperlinks-macro/src/MacroCall.scala
@@ -9,4 +9,3 @@ object Test {
foo/*#*/
}
}
-// Currently, the hyperlink within the argument to the macro `reify` does not resolve correctly.
View
1 test/scaladoc/run/SI-6812.check
@@ -1 +1,2 @@
+warning: -Ymacro-no-expand is deprecated: Use -Ymacro-expand:none
Done.
View
2 test/scaladoc/run/SI-6812.scala
@@ -19,6 +19,6 @@ object Test extends ScaladocModelTest {
"""
def scaladocSettings = ""
- override def extraSettings = super.extraSettings + " -Ymacro-no-expand"
+ override def extraSettings = super.extraSettings + " -Ymacro-no-expand -deprecation"
def testModel(root: Package) = ()
}
View
1 test/scaladoc/run/SI-6812b.check
@@ -0,0 +1 @@
+Done.
View
24 test/scaladoc/run/SI-6812b.scala
@@ -0,0 +1,24 @@
+import scala.tools.nsc.doc.model._
+import scala.tools.partest.ScaladocModelTest
+import language._
+
+object Test extends ScaladocModelTest {
+
+ override def code = """
+ import scala.reflect.macros.BlackboxContext
+ import language.experimental.macros
+
+ object Macros {
+ def impl(c: BlackboxContext) = c.literalUnit
+ def foo: Unit = macro impl
+ }
+
+ class C {
+ def bar = Macros.foo
+ }
+ """
+
+ def scaladocSettings = ""
+ override def extraSettings = super.extraSettings + " -Ymacro-expand:none"
+ def testModel(root: Package) = ()
+}

0 comments on commit 6e4c926

Please sign in to comment.
Something went wrong with that request. Please try again.