From a9c1bfd07c0a716c3e921d2e7cb4211394443c4d Mon Sep 17 00:00:00 2001 From: Pascal Weisenburger Date: Thu, 14 May 2015 12:34:18 +0200 Subject: [PATCH] use type-based instead of annotation-based approach; reorganized and simplified codebase a little --- .../src/main/scala/dslparadise/Plugin.scala | 98 +++++++++++++++++++ .../dslparadise/embedded/scalac-plugin.xml | 4 + .../src/main/scala/dslparadise/package.scala | 40 ++++++++ .../dslparadise/typechecker/Analyzer.scala | 9 ++ .../dslparadise/typechecker/Typers.scala | 42 ++++++++ .../main/scala/org/dslparadise/Plugin.scala | 90 ----------------- .../main/scala/org/dslparadise/Settings.scala | 21 ---- .../dslparadise/annotations/Annotations.scala | 7 -- .../dslparadise/embedded/scalac-plugin.xml | 4 - .../org/dslparadise/reflect/Enrichments.scala | 13 --- .../dslparadise/typechecker/Analyzer.scala | 13 --- .../typechecker/ContextErrors.scala | 19 ---- .../org/dslparadise/typechecker/Typers.scala | 31 ------ project/build.scala | 4 +- 14 files changed, 195 insertions(+), 200 deletions(-) create mode 100644 plugin/src/main/scala/dslparadise/Plugin.scala create mode 100644 plugin/src/main/scala/dslparadise/embedded/scalac-plugin.xml create mode 100644 plugin/src/main/scala/dslparadise/package.scala create mode 100644 plugin/src/main/scala/dslparadise/typechecker/Analyzer.scala create mode 100644 plugin/src/main/scala/dslparadise/typechecker/Typers.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/Plugin.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/Settings.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/annotations/Annotations.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/embedded/scalac-plugin.xml delete mode 100644 plugin/src/main/scala/org/dslparadise/reflect/Enrichments.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/typechecker/Analyzer.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/typechecker/ContextErrors.scala delete mode 100644 plugin/src/main/scala/org/dslparadise/typechecker/Typers.scala diff --git a/plugin/src/main/scala/dslparadise/Plugin.scala b/plugin/src/main/scala/dslparadise/Plugin.scala new file mode 100644 index 0000000..c2d7fb4 --- /dev/null +++ b/plugin/src/main/scala/dslparadise/Plugin.scala @@ -0,0 +1,98 @@ +package dslparadise + +import scala.tools.nsc.{Global, SubComponent} +import scala.tools.nsc.plugins.{Plugin => NscPlugin} +import scala.collection.mutable.{Map, Set} +import dslparadise.typechecker.Analyzer + +class Plugin(val global: Global) extends NscPlugin { + import global._ + + val name = "dslparadise" + val description = "DSL Paradise" + val components = List.empty + + // The initialization process (i.e. rewiring phase factories to modify typer + // functionality) is based on "Macro Paradise" code from the Scala 2.10 branch. + // Scala 2.11 introduced new complier plugin capabilities which simplified + // "Macro Paradise" code but cannot be employed for our use case. + + val phasesDescMap = classOf[Global] + .getDeclaredMethod("phasesDescMap") + .invoke(global) + .asInstanceOf[Map[SubComponent, String]] + + // Replace Global.analyzer to customize namer and typer (step 1 of 3) + // + // Unfortunately compiler plugins are instantiated too late. Therefore by now + // analyzer has already been used to instantiate the "namer", "packageobjects" + // and "typer" subcomponents. These are not phases yet -- they are just phase + // factories -- so no disaster yet, but we have to be quick. This warrants the + // second step in this customization - rewiring phase factories. + val analyzer = new { val global = Plugin.this.global } with Analyzer + val analyzerField = classOf[Global].getDeclaredField("analyzer") + analyzerField.setAccessible(true) + analyzerField.set(global, analyzer) + + // Replace Global.analyzer to customize namer and typer (step 2 of 3) + // + // Luckily for us compiler plugins are instantiated quite early. So by now, + // internal phases have only left a trace in "phasesSet" and in + // "phasesDescMap". Also, up until now no one has really used the standard + // analyzer, so we're almost all set except for the standard + // `object typer extends analyzer.Typer()` + // that is a member of `Global` and hence has been pre-initialized now. + // Good news is that it's only used in later phases or as a host for less + // important activities (error reporting, printing, etc). + val phasesSetMapGetter = classOf[Global].getDeclaredMethod("phasesSet") + val phasesSet = phasesSetMapGetter.invoke(global).asInstanceOf[Set[SubComponent]] + // `scalac -help` doesn't instantiate standard phases + if (phasesSet exists { _.phaseName == "typer" }) { + def subComponentByName(name: String) = + (phasesSet find { _.phaseName == name }).head + def hijackDescription(pt: SubComponent, sc: SubComponent) = + phasesDescMap(sc) = phasesDescMap(pt) + " in dslparadise" + + val oldScs @ List(oldNamer, oldPackageobjects, oldTyper) = List( + subComponentByName("namer"), subComponentByName("packageobjects"), subComponentByName("typer")) + val newScs = List( + analyzer.namerFactory, analyzer.packageObjects, analyzer.typerFactory) + oldScs zip newScs foreach { case (pt, sc) => hijackDescription(pt, sc) } + phasesSet --= oldScs + phasesSet ++= newScs + } + + // Replace Global.analyzer to customize namer and typer (step 3 of 3) + // + // Now let's take a look at what we couldn't replace during steps 1 and 2. + // here's what gets printed if we add the following line + // `if (!getClass.getName.startsWith("org.dslparadise")) println(getClass.getName)` + // to the standard namer and typer classes + // + // scala.tools.nsc.Global$typer$ + // scala.tools.nsc.typechecker.Implicits$ImplicitSearch + // ... + // scala.tools.nsc.transform.Erasure$Eraser + // ... + // scala.tools.nsc.typechecker.Namers$NormalNamer + // scala.tools.nsc.transform.Erasure$Eraser + // scala.tools.nsc.transform.Erasure$Eraser + // scala.tools.nsc.typechecker.Namers$NormalNamer + // ... + // + // Duh, we're still not done. But the good news is that it doesn't matter for + // DSL paradise: + // 1) ImplicitSearch is easily worked around by overriding `inferImplicit` and + // `allViewsFrom` + // 2) `scala.tools.nsc.Global$typer$` is only used in later phases or as a + // host for less important activities (error reporting, printing, etc) + // 3) Custom erasure typer and namers it spawns are also only used in later + // phases + // + // TODO: Theoretically, points 2 and 3 can still be customizable. + // `Global.typer` can have itself set as a specialized subclass of + // `scala.tools.nsc.Global$typer$` (it's possible because by now it's still + // null, as object initialization is lazy). Erasure can be hijacked in the + // same way as we hijack "namer", "packageobjects" and "typer". However, + // for now this is all not essential, so I'm moving on +} diff --git a/plugin/src/main/scala/dslparadise/embedded/scalac-plugin.xml b/plugin/src/main/scala/dslparadise/embedded/scalac-plugin.xml new file mode 100644 index 0000000..13582dc --- /dev/null +++ b/plugin/src/main/scala/dslparadise/embedded/scalac-plugin.xml @@ -0,0 +1,4 @@ + + dslparadise + dslparadise.Plugin + \ No newline at end of file diff --git a/plugin/src/main/scala/dslparadise/package.scala b/plugin/src/main/scala/dslparadise/package.scala new file mode 100644 index 0000000..4214025 --- /dev/null +++ b/plugin/src/main/scala/dslparadise/package.scala @@ -0,0 +1,40 @@ +package object dslparadise { + import scala.language.implicitConversions + + trait ImplicitFunction1[-T, +R] extends Function1[T, R] + + object ImplicitFunction1 { + implicit def apply[T, R](f: T => R): T `implicit =>` R = + new ImplicitFunction1[T, R] { + def apply(v: T): R = f(v) + } + } + + type `implicit =>`[-T, +R] = ImplicitFunction1[T, R] + + +// trait ImportFunction1[-T, +R] extends Function1[T, R] +// +// object ImportFunction1 { +// implicit def apply[T, R](f: T => R): T `import._ =>` R = +// new ImportFunction1[T, R] { +// def apply(v: T): R = f(v) +// } +// } +// +// type `import._ =>`[-T, +R] = ImportFunction1[T, R] +// +// +// trait ImportValue[+T, +I] { +// val value: T +// } +// +// object ImportValue { +// implicit def apply[T](v: T): T `import._` Nothing = +// new ImportValue[T, Nothing] { +// val value: T = v +// } +// } +// +// type `import._`[+T, +I] = ImportValue[T, I] +} diff --git a/plugin/src/main/scala/dslparadise/typechecker/Analyzer.scala b/plugin/src/main/scala/dslparadise/typechecker/Analyzer.scala new file mode 100644 index 0000000..9122263 --- /dev/null +++ b/plugin/src/main/scala/dslparadise/typechecker/Analyzer.scala @@ -0,0 +1,9 @@ +package dslparadise +package typechecker + +import scala.tools.nsc.typechecker.{Analyzer => NscAnalyzer} + +trait Analyzer extends NscAnalyzer with Typers { + override def newTyper(context: Context) = + new Typer(context) with ParadiseTyper +} diff --git a/plugin/src/main/scala/dslparadise/typechecker/Typers.scala b/plugin/src/main/scala/dslparadise/typechecker/Typers.scala new file mode 100644 index 0000000..ed2716f --- /dev/null +++ b/plugin/src/main/scala/dslparadise/typechecker/Typers.scala @@ -0,0 +1,42 @@ +package dslparadise +package typechecker + +import scala.reflect.internal.Mode +import dslparadise._ + +trait Typers { + self: Analyzer => + + import global._ + + trait ParadiseTyper extends Typer with TyperContextErrors { + override def typedArg(arg: Tree, mode: Mode, newmode: Mode, pt: Type): Tree = { + if (pt != WildcardType && pt <:< typeOf[ImplicitFunction1[_, _]]) { + val convertArg = context inSilentMode { + super.typedArg(arg.duplicate, mode, newmode, pt) + context.reporter.hasErrors + } + + val newarg = if (convertArg) { + val newarg = + q"""_root_.dslparadise.ImplicitFunction1 { + implicit! => $arg + }""" + + val keepArg = context inSilentMode { + super.typedArg(newarg.duplicate, mode, newmode, pt) + context.reporter.errors exists { _.errPos == NoPosition } + } + + if (keepArg) arg else newarg + } + else + arg + + super.typedArg(newarg, mode, newmode, pt) + } + else + super.typedArg(arg, mode, newmode, pt) + } + } +} diff --git a/plugin/src/main/scala/org/dslparadise/Plugin.scala b/plugin/src/main/scala/org/dslparadise/Plugin.scala deleted file mode 100644 index 79626f7..0000000 --- a/plugin/src/main/scala/org/dslparadise/Plugin.scala +++ /dev/null @@ -1,90 +0,0 @@ -package org.dslparadise - -import scala.tools.nsc.{Global, Phase, SubComponent} -import scala.tools.nsc.plugins.{Plugin => NscPlugin, PluginComponent => NscPluginComponent} -import scala.collection.{mutable, immutable} -import org.dslparadise.typechecker.Analyzer - -class Plugin(val global: Global) extends NscPlugin { - import global._ - - val name = "dsl-paradise" - val description = "DSL paradise" - val components = List[NscPluginComponent](PluginComponent) - - // install a pretty description for our plugin phase instead of empty string hardcoded for all plugins - val phasesDescMapGetter = classOf[Global].getDeclaredMethod("phasesDescMap") - val phasesDescMap = phasesDescMapGetter.invoke(global).asInstanceOf[mutable.Map[SubComponent, String]] - phasesDescMap(PluginComponent) = "dslparadise" - - // replace Global.analyzer to customize namer and typer (step 1 of 3) - // unfortunately compiler plugins are instantiated too late - // therefore by now analyzer has already been used to instantiate the namer, packageobjects and typer subcomponents - // these are not phases yet - they are just phase factories - so no disaster yet, but we have to be quick - // this warrants the second step in this customization - rewiring phase factories - val analyzer = new { val global: Plugin.this.global.type = Plugin.this.global } with Analyzer - val analyzerField = classOf[Global].getDeclaredField("analyzer") - analyzerField.setAccessible(true) - analyzerField.set(global, analyzer) - - // replace Global.analyzer to customize namer and typer (step 2 of 3) - // luckily for us compiler plugins are instantiated quite early - // so by now internal phases have only left a trace in phasesSet and in phasesDescMap - // also up until now noone has really used the standard analyzer, so we're almost all set - // except for the standard `object typer extends analyzer.Typer()` - // that is a member of Global and hence has been pre-initialized now - // good news is that it's only used in later phases or as a host for less important activities (error reporting, printing, etc) - val phasesSetMapGetter = classOf[Global].getDeclaredMethod("phasesSet") - val phasesSet = phasesSetMapGetter.invoke(global).asInstanceOf[mutable.Set[SubComponent]] - if (phasesSet.exists(_.phaseName == "typer")) { // `scalac -help` doesn't instantiate standard phases - def subcomponentNamed(name: String) = phasesSet.find(_.phaseName == name).head - val oldScs @ List(oldNamer, oldPackageobjects, oldTyper) = List(subcomponentNamed("namer"), subcomponentNamed("packageobjects"), subcomponentNamed("typer")) - val newScs = List(analyzer.namerFactory, analyzer.packageObjects, analyzer.typerFactory) - def hijackDescription(pt: SubComponent, sc: SubComponent) = phasesDescMap(sc) = phasesDescMap(pt) + " in paradise" - oldScs zip newScs foreach { case (pt, sc) => hijackDescription(pt, sc) } - phasesSet --= oldScs - phasesSet ++= newScs - } - - // replace Global.analyzer to customize namer and typer (step 3 of 3) - // now let's take a look at what we couldn't replace during steps 1 and 2 - // here's what gets printed if add the following line to the standard Namer and Typer classes - // if (!getClass.getName.startsWith("org.dslparadise")) println(getClass.getName) - // - // scala.tools.nsc.Global$typer$ - // scala.tools.nsc.typechecker.Implicits$ImplicitSearch - // ... - // scala.tools.nsc.transform.Erasure$Eraser - // ... - // scala.tools.nsc.typechecker.Namers$NormalNamer - // scala.tools.nsc.transform.Erasure$Eraser - // scala.tools.nsc.transform.Erasure$Eraser - // scala.tools.nsc.typechecker.Namers$NormalNamer - // ... - // - // duh, we're still not done. but the good news is that it doesn't matter for macro paradise: - // 1) ImplicitSearch is easily worked around by overriding inferImplicit and allViewsFrom - // 2) scala.tools.nsc.Global$typer$ is only used in later phases or as a host for less important activities (error reporting, printing, etc) - // 3) Custom erasure typer and namers it spawns are also only used in later phases - // - // TODO: theoretically, points 2 and 3 can still be customizable - // Global.typer can have itself set as a specialized subclass of scala.tools.nsc.Global$typer$ - // (it's possible because by now it's still null, as object initialization is lazy) - // Erasure can be hijacked in the same way as we hijack namer, packageobjects and typer - // however, for now this is all not essential, so I'm moving on - () - - object PluginComponent extends NscPluginComponent { - val global = Plugin.this.global - import global._ - - override val runsAfter = List("parser") - val phaseName = "dslparadise" - - override def newPhase(prev: Phase): StdPhase = new StdPhase(prev) { - override def apply(unit: CompilationUnit) { - // do nothing: everything's already hijacked - } - } - } -} diff --git a/plugin/src/main/scala/org/dslparadise/Settings.scala b/plugin/src/main/scala/org/dslparadise/Settings.scala deleted file mode 100644 index d70370d..0000000 --- a/plugin/src/main/scala/org/dslparadise/Settings.scala +++ /dev/null @@ -1,21 +0,0 @@ -package org.dslparadise - -object Settings { - class Setting[T](get: () => T, set: T => Unit) { - def value = get() - def value_=(value: T) = set(value) - } - - def boolSetting(key: String) = new Setting[Boolean]( - get = () => { - val svalue = System.getProperty("spores." + key) - svalue != null - }, - set = value => { - val svalue = if (value) "true" else null - System.setProperty("spores." + key, svalue) - } - ) - - def Ydebug = boolSetting("debug") -} \ No newline at end of file diff --git a/plugin/src/main/scala/org/dslparadise/annotations/Annotations.scala b/plugin/src/main/scala/org/dslparadise/annotations/Annotations.scala deleted file mode 100644 index 1166e11..0000000 --- a/plugin/src/main/scala/org/dslparadise/annotations/Annotations.scala +++ /dev/null @@ -1,7 +0,0 @@ -package org.dslparadise.annotations - -import scala.annotation.StaticAnnotation - -class Implicit extends StaticAnnotation - -class Import extends StaticAnnotation diff --git a/plugin/src/main/scala/org/dslparadise/embedded/scalac-plugin.xml b/plugin/src/main/scala/org/dslparadise/embedded/scalac-plugin.xml deleted file mode 100644 index b8c792f..0000000 --- a/plugin/src/main/scala/org/dslparadise/embedded/scalac-plugin.xml +++ /dev/null @@ -1,4 +0,0 @@ - - dsl-paradise - org.dslparadise.Plugin - \ No newline at end of file diff --git a/plugin/src/main/scala/org/dslparadise/reflect/Enrichments.scala b/plugin/src/main/scala/org/dslparadise/reflect/Enrichments.scala deleted file mode 100644 index 34089f8..0000000 --- a/plugin/src/main/scala/org/dslparadise/reflect/Enrichments.scala +++ /dev/null @@ -1,13 +0,0 @@ -package org.dslparadise -package reflect - -import scala.language.implicitConversions -import scala.tools.nsc.{Global => NscGlobal} -import scala.tools.nsc.{Settings => NscSettings} -import org.dslparadise.{Settings => ParadiseSettings} - -trait Enrichments { - val global: NscGlobal - implicit def paradiseSettings(settings: NscSettings) = ParadiseSettings - def installationFailure() = global.abort("failed to install dslparadise plugin") -} diff --git a/plugin/src/main/scala/org/dslparadise/typechecker/Analyzer.scala b/plugin/src/main/scala/org/dslparadise/typechecker/Analyzer.scala deleted file mode 100644 index 78eadd9..0000000 --- a/plugin/src/main/scala/org/dslparadise/typechecker/Analyzer.scala +++ /dev/null @@ -1,13 +0,0 @@ -package org.dslparadise -package typechecker - -import scala.tools.nsc.typechecker.{Analyzer => NscAnalyzer} -import org.dslparadise.reflect.Enrichments - -trait Analyzer extends NscAnalyzer - with Typers - with Enrichments - with ContextErrors { - - override def newTyper(context: Context) = new Typer(context) with ParadiseTyper -} diff --git a/plugin/src/main/scala/org/dslparadise/typechecker/ContextErrors.scala b/plugin/src/main/scala/org/dslparadise/typechecker/ContextErrors.scala deleted file mode 100644 index a735d51..0000000 --- a/plugin/src/main/scala/org/dslparadise/typechecker/ContextErrors.scala +++ /dev/null @@ -1,19 +0,0 @@ -package org.dslparadise -package typechecker - -trait ContextErrors { - self: Analyzer => - - import global._ - import ErrorUtils._ - - trait ParadiseTyperContextErrors extends TyperContextErrors { - self: Typer => - - import infer.setError - - object ParadiseTyperErrorGen { - implicit val contextTyperErrorGen: Context = infer.getContext - } - } -} diff --git a/plugin/src/main/scala/org/dslparadise/typechecker/Typers.scala b/plugin/src/main/scala/org/dslparadise/typechecker/Typers.scala deleted file mode 100644 index 6adc067..0000000 --- a/plugin/src/main/scala/org/dslparadise/typechecker/Typers.scala +++ /dev/null @@ -1,31 +0,0 @@ -package org.dslparadise -package typechecker - -trait Typers { - self: Analyzer => - - import global._ - import definitions._ - import scala.reflect.internal.Mode - - trait ParadiseTyper extends Typer with ParadiseTyperContextErrors { - import TyperErrorGen._ - import ParadiseTyperErrorGen._ - import infer._ - - - override def typedArgsForFormals(args: List[Tree], formals: List[Type], mode: Mode): List[Tree] = { - val Impl = typeOf[org.dslparadise.annotations.Implicit] - val Impo = typeOf[org.dslparadise.annotations.Import] - val desugared = (args zip formals) map { - // TODO: expand typeref to Function1 - case (tree, formal @ TypeRef(_, _, List(AnnotatedType(List(AnnotationInfo(Impl, _, _)), left), right))) ⇒ - q"{ implicit u: $left ⇒ $tree }" - - case (tree, tp) ⇒ - tree - } - super.typedArgsForFormals(desugared, formals, mode) - } - } -} diff --git a/project/build.scala b/project/build.scala index cdeb6de..5f201a9 100644 --- a/project/build.scala +++ b/project/build.scala @@ -6,7 +6,7 @@ object build extends Build { scalaVersion := "2.11.0-SNAPSHOT", crossVersion := CrossVersion.full, version := "0.0.1-SNAPSHOT", - organization := "org.dslparadise", + organization := "dslparadise", resolvers ++= Seq( Resolver.sonatypeRepo("snapshots"), Resolver.sonatypeRepo("releases") @@ -23,7 +23,7 @@ object build extends Build { ) settings ( sharedSettings : _* ) settings ( - resourceDirectory in Compile := baseDirectory.value / "src" / "main" / "scala" / "org" / "dslparadise" / "embedded", + resourceDirectory in Compile := baseDirectory.value / "src" / "main" / "scala" / "dslparadise" / "embedded", libraryDependencies ++= Seq( "org.scala-lang" % "scala-library" % scalaVersion.value, "org.scala-lang" % "scala-compiler" % scalaVersion.value