public
Last active

  • Download Gist
TransformBeforeTyper.scala
Scala
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
// Author: Olivier Chafik (http://ochafik.com)
// Feel free to modify and reuse this for any purpose ("public domain / I don't care").
package scalaxy.pretyping.example
 
import scala.reflect.internal._
import scala.tools.nsc.CompilerCommand
import scala.tools.nsc.Global
import scala.tools.nsc.Phase
import scala.tools.nsc.plugins.Plugin
import scala.tools.nsc.plugins.PluginComponent
import scala.tools.nsc.Settings
import scala.tools.nsc.reporters.ConsoleReporter
import scala.tools.nsc.transform.TypingTransformers
 
/**
* This compiler plugin demonstrates how to do "useful" stuff before the typer phase.
*
* It defines a toy syntax that uses annotations to define implicit classes:
*
* @extend(Int) def toStr = self.toString
*
* Which gets desugared to:
*
* implicit class toStr(self: Int) {
* def toStr = self.toString
* }
*
* This example code doesn't try to be hygienic: it assumes @extend and Int are not locally redefined to something else.
*
* To see the AST before and after the rewrite, run the compiler with -Xprint:parser -Xprint:twist.
*/
object TwistCompiler {
private val scalaLibraryJar =
classOf[List[_]].getProtectionDomain.getCodeSource.getLocation.getFile
 
def main(args: Array[String]) {
try {
val settings = new Settings
val command =
new CompilerCommand(List("-bootclasspath", scalaLibraryJar) ++ args, settings)
 
if (!command.ok)
System.exit(1)
 
val global = new Global(settings, new ConsoleReporter(settings)) {
override protected def computeInternalPhases() {
super.computeInternalPhases
phasesSet += new TwistComponent(this)
}
}
new global.Run().compile(command.files)
} catch {
case ex: Throwable =>
ex.printStackTrace
System.exit(2)
}
}
}
 
/**
* To use this, just write the following in `src/main/resources/scalac-plugin.xml`:
* <plugin>
* <name>twist-plugin</name>
* <classname>scalaxy.pretyping.example.TwistPlugin</classname>
* </plugin>
*/
class TwistPlugin(override val global: Global) extends Plugin {
override val name = "twist"
override val description = "Compiler plugin that adds a `@extend(Int) def toStr = self.toString` syntax to create extension methods."
override val components: List[PluginComponent] =
List(new TwistComponent(global))
}
 
/**
* To understand / reproduce this, you should use paulp's :power mode in the scala console:
*
* scala
* > :power
* > :phase parser // will show us ASTs just after parsing
* > val Some(List(ast)) = intp.parse("@extend(Int) def str = self.toString")
* > nodeToString(ast)
* > val DefDef(mods, name, tparams, vparamss, tpt, rhs) = ast // play with extractors to explore the tree and its properties.
*/
class TwistComponent(val global: Global)
extends PluginComponent
with TypingTransformers
{
import global._
import definitions._
 
override val phaseName = "twister"
 
override val runsRightAfter = Some("parser")
override val runsAfter = runsRightAfter.toList
override val runsBefore = List[String]("typer")
 
def newPhase(prev: Phase): StdPhase = new StdPhase(prev) {
def apply(unit: CompilationUnit) {
val onTransformer = new TypingTransformer(unit) {
override def transform(tree: Tree): Tree = tree match {
case dd @ DefDef(Modifiers(flags, privateWithin, annotations), name, tparams, vparamss, tpt, rhs) =>
annotations match {
case List(Apply(Select(New(Ident(annotationName)), initName), List(Ident(typeName)))) if annotationName.toString == "extend" && initName == nme.CONSTRUCTOR =>
val targetTpt = Ident(typeName.toString: TypeName)
val emptyTpt = TypeTree(null)
// If the type being extended is an AnyVal, make the implicit class a value class :-)
val parentTypeName: TypeName = typeName.toString match {
case "Int" | "Long" | "Short" | "Byte" | "Double" | "Float" | "Char" | "Boolean" | "AnyVal" =>
"AnyVal"
case _ =>
"AnyRef"
}
ClassDef(
Modifiers(flags | Flag.IMPLICIT, privateWithin, Nil),
name.toString: TypeName,
tparams,
Template(
List(Select(Ident("scala": TermName), parentTypeName)),
// Don't even ask what this is.
ValDef(Modifiers(Flag.PRIVATE), "_": TermName, emptyTpt, EmptyTree),
List(
// <paramaccessor> private[this] val self: T = _;
ValDef(Modifiers(Flags.PARAMACCESSOR), "self", targetTpt, EmptyTree),
// def <init>(self: T) = { super.<init>(); () }
DefDef(
NoMods,
nme.CONSTRUCTOR,
Nil,
List(List(ValDef(NoMods, "self", targetTpt, EmptyTree))),
emptyTpt,
Block(
// super.<init>()
Apply(Select(Super(This("": TypeName), "": TypeName), nme.CONSTRUCTOR), Nil),
Literal(Constant(()))
)
),
// Copying the original def over, without its annotation.
DefDef(Modifiers(flags, privateWithin, Nil), name, tparams, vparamss, tpt, rhs)
)
)
)
case _ =>
super.transform(tree)
}
case _ =>
super.transform(tree)
}
}
unit.body = onTransformer.transform(unit.body)
}
}
}

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.