Skip to content

Commit

Permalink
Integrate macro paradise under -Ymacro-annotations
Browse files Browse the repository at this point in the history
Importing from scalamacros/paradise, since xeno-by has stepped down
as maintainer. Since the eco-system has come to rely on macro paradise,
we acknowledge the status quo by integrating paradise.

HOWEVER, please note that support for macro annotations remains
EXPERIMENTAL, and support for them will be much more restricted
in Scala 3. We will try to provide a preview of this subset in 2.14.

Most notably, we do not intend to let macro annotations
synthesize code that is visible during synthesis. Concretely,
you'd have to first compile the macros, then the code that
uses them as annotations, and finally the code the needs to
see the code synthesized by the annotation macros. This is
the same workflow as usual with code generation.

The tests are included as a new project (macroAnnot),
which uses the quick compiler as a scalaInstance to compile
the tests, so that we could integrate the test suite more easily.

Finally, note the diff re: `List toStream flatMap headOption`:
its behavior changed with the new collections.
  • Loading branch information
adriaanm committed May 8, 2018
1 parent 00dac59 commit 50077e9
Show file tree
Hide file tree
Showing 82 changed files with 3,602 additions and 13 deletions.
55 changes: 52 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@
* - to modularize the Scala compiler or library further
*/

import java.io.{PrintWriter, StringWriter}

import sbt.TestResult
import sbt.testing.TestSelector

import scala.build._
import VersionUtil._
import scala.tools.nsc.util.ScalaClassLoader.URLClassLoader

// Scala dependencies:
val partestDep = scalaDep("org.scala-lang.modules", "scala-partest", versionProp = "partest")
Expand Down Expand Up @@ -118,6 +117,34 @@ lazy val instanceSettings = Seq[Setting[_]](
Quiet.silenceScalaBinaryVersionWarning
)

// be careful with using this instance, as it may cause performance problems (e.g., MetaSpace exhaustion)r
lazy val quickInstanceSettings = Seq[Setting[_]](
organization := "org.scala-lang",
// we don't cross build Scala itself
crossPaths := false,
// do not add Scala library jar as a dependency automatically
autoScalaLibrary := false,
// Avoid circular dependencies for scalaInstance (see https://github.com/sbt/sbt/issues/1872)
managedScalaInstance := false,
scalaInstance := {
// TODO: express in terms of distDependencies?
val cpElems: Seq[java.io.File] = (fullClasspath in Compile in replFrontend).value.map(_.data) ++ (fullClasspath in Compile in scaladoc).value.map(_.data)
val libraryJar = cpElems.find(_.getPath.endsWith("classes/library")).get
val compilerJar = cpElems.find(_.getPath.endsWith("classes/compiler")).get
val extraJars = cpElems.filter(f => (f ne libraryJar) && (f ne compilerJar))
val v = (version in Global).value
new ScalaInstance(v, new URLClassLoader(cpElems.map(_.toURI.toURL).toArray[URL], null), libraryJar, compilerJar, extraJars, Some(v))
},
// As of sbt 0.13.12 (sbt/sbt#2634) sbt endeavours to align both scalaOrganization and scalaVersion
// in the Scala artefacts, for example scala-library and scala-compiler.
// This doesn't work in the scala/scala build because the version of scala-library and the scalaVersion of
// scala-library are correct to be different. So disable overriding.
ivyScala ~= (_ map (_ copy (overrideScalaVersion = false))),
Quiet.silenceScalaBinaryVersionWarning
)



lazy val commonSettings = instanceSettings ++ clearSourceAndResourceDirectories ++ publishSettings ++ Seq[Setting[_]](
// we always assume that Java classes are standalone and do not have any dependency
// on Scala classes
Expand Down Expand Up @@ -560,6 +587,27 @@ lazy val junit = project.in(file("test") / "junit")
unmanagedSourceDirectories in Test := List(baseDirectory.value)
)

// imported from scalamacros/paradise for the test suite -- TODO: integrate into build structure, get rid of quickInstanceSettings?
lazy val macroAnnot = project.in(file("test") / "macro-annot")
.dependsOn(library, reflect, compiler, repl, replFrontend, scaladoc)
.settings(disableDocs)
.settings(disablePublishing)
.settings(quickInstanceSettings) // use quick compiler as Scala Instance
.settings(
fork in Test := true,
javaOptions in Test += "-Xss1M",
libraryDependencies ++= Seq(junitDep, junitInterfaceDep),
testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v",
s"-Dsbt.paths.tests.classpath=${(fullClasspath in Test).value.files.map(_.getAbsolutePath).mkString(java.io.File.pathSeparatorChar.toString)}"),

baseDirectory in Compile := (baseDirectory in ThisBuild).value,
baseDirectory in Test := (baseDirectory in ThisBuild).value,

scalacOptions += "-Ymacro-annotations",
scalacOptions += "-Ywarn-unused-import",
scalacOptions += "-Xfatal-warnings"
)

lazy val scalacheck = project.in(file("test") / "scalacheck")
.dependsOn(library, reflect, compiler, scaladoc)
.settings(clearSourceAndResourceDirectories)
Expand Down Expand Up @@ -804,6 +852,7 @@ lazy val root: Project = (project in file("."))
val results = ScriptCommands.sequence[(Result[Unit], String)](List(
(Keys.test in Test in junit).result map (_ -> "junit/test"),
(Keys.test in Test in scalacheck).result map (_ -> "scalacheck/test"),
(Keys.test in Test in macroAnnot).result map (_ -> "macroAnnot/test"),
(testOnly in IntegrationTest in testP).toTask(" -- run").result map (_ -> "partest run"),
(testOnly in IntegrationTest in testP).toTask(" -- pos neg jvm").result map (_ -> "partest pos neg jvm"),
(testOnly in IntegrationTest in testP).toTask(" -- res scalap specialized").result map (_ -> "partest res scalap specialized"),
Expand Down Expand Up @@ -871,7 +920,7 @@ lazy val root: Project = (project in file("."))
}
)
.aggregate(library, reflect, compiler, interactive, repl, replFrontend,
scaladoc, scalap, partestExtras, junit, scalaDist).settings(
scaladoc, scalap, partestExtras, junit, scalaDist, macroAnnot).settings(
sources in Compile := Seq.empty,
onLoadMessage := """|*** Welcome to the sbt build definition for Scala! ***
|Check README.md for more information.""".stripMargin
Expand Down
2 changes: 1 addition & 1 deletion project/ScalaOptionParser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ object ScalaOptionParser {
"-Ybreak-cycles", "-Ydebug", "-Ycompact-trees", "-YdisableFlatCpCaching", "-Ydoc-debug",
"-Yide-debug", "-Yinfer-argument-types",
"-Yissue-debug", "-Ylog-classpath", "-Ymacro-debug-lite", "-Ymacro-debug-verbose", "-Ymacro-no-expand",
"-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-predef",
"-Yno-completion", "-Yno-generic-signatures", "-Yno-imports", "-Yno-predef", "-Ymacro-annotations",
"-Yoverride-objects", "-Yoverride-vars", "-Ypatmat-debug", "-Yno-adapted-args", "-Ypos-debug", "-Ypresentation-debug",
"-Ypresentation-strict", "-Ypresentation-verbose", "-Yquasiquote-debug", "-Yrangepos", "-Yreify-copypaste", "-Yreify-debug", "-Yrepl-class-based",
"-Yrepl-sync", "-Yshow-member-pos", "-Yshow-symkinds", "-Yshow-symowners", "-Yshow-syms", "-Yshow-trees", "-Yshow-trees-compact", "-Yshow-trees-stringified", "-Ytyper-debug",
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/scala/tools/nsc/Global.scala
Original file line number Diff line number Diff line change
Expand Up @@ -445,9 +445,9 @@ class Global(var currentSettings: Settings, reporter0: Reporter)
// I only changed analyzer.
//
// factory for phases: namer, packageobjects, typer
lazy val analyzer = new {
val global: Global.this.type = Global.this
} with Analyzer
lazy val analyzer =
if (settings.YmacroAnnotations) new { val global: Global.this.type = Global.this } with Analyzer with MacroAnnotationNamers
else new { val global: Global.this.type = Global.this } with Analyzer

// phaseName = "patmat"
object patmat extends {
Expand Down
4 changes: 3 additions & 1 deletion src/compiler/scala/tools/nsc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
package scala.tools.nsc
package ast

import scala.reflect.internal.MacroAnnotionTreeInfo

/** This class ...
*
* @author Martin Odersky
* @version 1.0
*/
abstract class TreeInfo extends scala.reflect.internal.TreeInfo {
abstract class TreeInfo extends scala.reflect.internal.TreeInfo with MacroAnnotionTreeInfo {
val global: Global
import global._
import definitions._
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ trait ScalaSettings extends AbsScalaSettings
val Yreifycopypaste = BooleanSetting ("-Yreify-copypaste", "Dump the reified trees in copypasteable representation.")
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 YmacroAnnotations = BooleanSetting ("-Ymacro-annotations", "Enable support for macro annotations, formerly in macro paradise.")
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)" , "")
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/typechecker/Analyzer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ trait Analyzer extends AnyRef
with TypeDiagnostics
with ContextErrors
with StdAttachments
with MacroAnnotationAttachments
with AnalyzerPlugins
{
val global : Global
Expand Down
62 changes: 62 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/ContextErrors.scala
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,68 @@ trait ContextErrors {

def MacroImplementationNotFoundError(expandee: Tree) =
macroExpansionError(expandee, macroImplementationNotFoundMessage(expandee.symbol.name))

def MacroAnnotationShapeError(clazz: Symbol) = {
val sym = clazz.info.member(nme.macroTransform)
var actualSignature = sym.toString
if (sym.isOverloaded) actualSignature += "(...) = ..."
else if (sym.isMethod) {
if (sym.typeParams.nonEmpty) {
def showTparam(tparam: Symbol) =
tparam.typeSignature match {
case tpe @ TypeBounds(_, _) => s"${tparam.name}$tpe"
case _ => tparam.name
}
def showTparams(tparams: List[Symbol]) = "[" + (tparams map showTparam mkString ", ") + "]"
actualSignature += showTparams(sym.typeParams)
}
if (sym.paramss.nonEmpty) {
def showParam(param: Symbol) = s"${param.name}: ${param.typeSignature}"
def showParams(params: List[Symbol]) = {
val s_mods = if (params.nonEmpty && params(0).hasFlag(scala.reflect.internal.Flags.IMPLICIT)) "implicit " else ""
val s_params = params map showParam mkString ", "
"(" + s_mods + s_params + ")"
}
def showParamss(paramss: List[List[Symbol]]) = paramss map showParams mkString ""
actualSignature += showParamss(sym.paramss)
}
if (sym.isTermMacro) actualSignature = actualSignature.replace("macro method", "def") + " = macro ..."
else actualSignature = actualSignature.replace("method", "def") + " = ..."
}
issueSymbolTypeError(clazz, s"""
|macro annotation has wrong shape:
| required: def macroTransform(annottees: Any*) = macro ...
| found : $actualSignature
""".trim.stripMargin)
}

def MacroAnnotationMustBeStaticError(clazz: Symbol) =
issueSymbolTypeError(clazz, s"macro annotation must extend scala.annotation.StaticAnnotation")

def MacroAnnotationCannotBeInheritedError(clazz: Symbol) =
issueSymbolTypeError(clazz, s"macro annotation cannot be @Inherited")

def MacroAnnotationCannotBeMemberError(clazz: Symbol) =
issueSymbolTypeError(clazz, s"macro annotation cannot be a member of another class")

def MacroAnnotationNotExpandedMessage: String = {
"macro annotation could not be expanded " + (
if (!settings.YmacroAnnotations) "(since these are experimental, you must enable them with -Ymacro-annotations)"
else "(you cannot use a macro annotation in the same compilation run that defines it)")
}

def MacroAnnotationOnlyDefinitionError(ann: Tree) =
issueNormalTypeError(ann, "macro annotations can only be put on definitions")

def MacroAnnotationTopLevelClassWithCompanionBadExpansion(ann: Tree) =
issueNormalTypeError(ann, "top-level class with companion can only expand into a block consisting in eponymous companions")

def MacroAnnotationTopLevelClassWithoutCompanionBadExpansion(ann: Tree) =
issueNormalTypeError(ann, "top-level class without companion can only expand either into an eponymous class or into a block consisting in eponymous companions")

def MacroAnnotationTopLevelModuleBadExpansion(ann: Tree) =
issueNormalTypeError(ann, "top-level object can only expand into an eponymous object")

}

/** This file will be the death of me. */
Expand Down
Loading

0 comments on commit 50077e9

Please sign in to comment.