Skip to content

Commit

Permalink
Add missing files from 0.3.0 commit
Browse files Browse the repository at this point in the history
  • Loading branch information
oleg-py committed Apr 13, 2019
1 parent 115ea6e commit 073bbe4
Show file tree
Hide file tree
Showing 8 changed files with 558 additions and 0 deletions.
4 changes: 4 additions & 0 deletions better-monadic-for/src/main/resources/scalac-plugin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<plugin>
<name>bm4</name>
<classname>com.olegpy.bm4.BetterMonadicFor</classname>
</plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package com.olegpy.bm4

import scala.tools.nsc
import nsc.Global
import nsc.plugins.Plugin
import nsc.plugins.PluginComponent
import nsc.transform.{Transform, TypingTransformers}


class BetterMonadicFor(val global: Global) extends Plugin {
val name = "bm4"
val description = "Remove withFilter / partial matches in for-comprehension"
val components =
new ForRewriter(this, global) ::
new MapRemover(this, global) ::
new TupleRemover(this, global) ::
Nil

var noUncheckedFilter = true
var noMapIdentity = true
var noTupling = true
var implicitPatterns = true

val knobs = Map(
"no-filtering" -> "Remove .withFilter from generator desugaring",
"no-map-id" -> "Remove .map(x => x) and .map(_ => ())",
"no-tupling" -> "Change desugaring of = bindings to not use tuples where possible",
"implicit-patterns" -> "Enable syntax for implicit definitions inside patterns"
)


override val optionsHelp: Option[String] = Some(
knobs
.map { case (key, help) =>
s" -P:$name:$key:(y/n)".padTo(31, ' ') ++ help
}
.mkString(System.lineSeparator)
)

override def init(options: List[String], error: String => Unit): Boolean = {
val (known, unknown) = options.partition(s => knobs.keys.exists(s.startsWith))
if (unknown.nonEmpty) {
error(s"Unknown options: ${unknown.mkString(", ")}")
return false
}

val toBoolean = (txt: String) => txt.toLowerCase match {
case "y" | "yes" | "1" | "true" => true
case "n" | "no" | "0" | "false" => false
case _ =>
error(s"Unknown boolean value $txt")
return false
}

for {
key <- known
_ = if (!key.contains(':')) {
error(s"Option $key does not include the parameter (e.g. $key:y)")
return false
}
Array(prefix, value) = key.split(":", 2)
} prefix match {
case "no-filtering" => noUncheckedFilter = toBoolean(value)
case "no-map-id" => noMapIdentity = toBoolean(value)
case "no-tupling" => noTupling = toBoolean(value)
case "implicit-patterns" => implicitPatterns = toBoolean(value)
}

noUncheckedFilter || noMapIdentity || noTupling
}
}

class ForRewriter(plugin: BetterMonadicFor, val global: Global)
extends PluginComponent with Transform with TypingTransformers
with NoUncheckedFilter with ImplicitPatterns
{

import global._

override def implicitPatterns: Boolean = plugin.implicitPatterns
override val noUncheckedFilter: Boolean = plugin.noUncheckedFilter

val runsAfter = "parser" :: Nil
override val runsRightAfter = Some("parser")
override val runsBefore = "namer" :: Nil
val phaseName = "bm4-parser"

def newTransformer(unit: CompilationUnit): Transformer = new MyTransformer(unit)

class MyTransformer(unit: CompilationUnit) extends TypingTransformer(unit) {
// The magic happens in `unapply` of objects defined in mixed in traits
override def transform(tree: Tree): Tree = tree match {
case NoUncheckedFilter(cleaned) =>
transform(cleaned)
case ImplicitPatternDefinition(updated) =>
transform(updated)
case _ =>
super.transform(tree)
}
}
}

class MapRemover(plugin: BetterMonadicFor, val global: Global)
extends PluginComponent with Transform with TypingTransformers
with NoMapIdentity
{
import global._


protected def newTransformer(unit: global.CompilationUnit) =
new MapIdentityRemoveTransformer(unit)

def noMapIdentity = plugin.noMapIdentity

val phaseName = "bm4-typer"
val runsAfter = "typer" :: Nil


override val runsBefore: List[String] = "patmat" :: Nil

class MapIdentityRemoveTransformer(unit: CompilationUnit)
extends TypingTransformer(unit)
{
override def transform(tree: Tree): Tree = {
tree match {
case NoMapIdentity(cleaned) =>
transform(cleaned)
case _ =>
super.transform(tree)
}
}
}
}

class TupleRemover(plugin: BetterMonadicFor, val global: Global)
extends PluginComponent with Transform with TypingTransformers
with NoTupleBinding {
import global._
protected def newTransformer(unit: global.CompilationUnit): Transformer =
new TupleRemoveTransformer(unit)

def noTupling: Boolean = plugin.noTupling
val phaseName: String = "bm4-parser2"
val runsAfter: List[String] = "parser" :: "bm4-parser" :: Nil
override val runsRightAfter: Option[String] = Some("bm4-parser")
override val runsBefore: List[String] = "patmat" :: Nil

class TupleRemoveTransformer(unit: CompilationUnit)
extends TypingTransformer(unit)
{
override def transform(tree: Tree): Tree = tree match {
case NoTupleBinding(cleaned) =>
transform(cleaned)
case _ =>
super.transform(tree)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.olegpy.bm4

trait ImplicitPatterns extends TreeUtils { self =>
import global._

def implicitPatterns: Boolean



object ImplicitPatternDefinition {
lazy val ut = new NoTupleBinding {
val noTupling: Boolean = false
lazy val global: self.global.type = self.global
}

def embedImplicitDefs(tupler: Tree, defns: List[ValDef]): Tree = {
val identMap = defns.map {
case vd @ ValDef(_, TermName(nm), _, Ident(TermName(ident))) if nm contains "$implicit" =>
ident -> vd
}.toMap

tupler match {
case Function(vp, Block(valDefns, expr)) =>
val withImplicits = valDefns.flatMap {
case vd @ ValDef(_, TermName(nm), _, _) if identMap contains nm =>
vd :: identMap(nm) :: Nil
case vd =>
vd :: Nil
}

Function(vp, Block(withImplicits, expr))

case Block(valDefns, expr) =>
val withImplicits = valDefns.flatMap {
case vd @ ValDef(_, TermName(nm), _, _) if identMap contains nm =>
vd :: identMap(nm) :: Nil
case vd =>
vd :: Nil
}

Block(withImplicits, expr)

case other =>
other
}
}

def unapply(tree: Tree): Option[Tree] = tree match {
case _ if !implicitPatterns =>
None
case CaseDef(ImplicitPatternVals(patterns, valDefns), guard, body) =>
val newGuard = if (guard.isEmpty) guard else q"{..$valDefns; $guard}"
val replacement = CaseDef(patterns, newGuard, q"{..$valDefns; $body}")

Some(replaceTree(
tree,
replacement
))

case Block((matcher @ NonLocalImplicits(valdefs)) :: stats, expr) =>
val m = StripImplicitZero.transform(matcher)
val replacement = embedImplicitDefs(Block(m :: stats, expr), valdefs)
Some(replaceTree(tree, replacement))


case q"$main.map(${tupler @ ut.Tupler(_, _)}).${m @ ut.Untuplable()}(${body @ ut.Untupler(_, _)})" if ForArtifact(tree) =>
body match {
case Function(_, Match(_, List(ImplicitPatternVals(_, defns)))) =>
val t = StripImplicitZero.transform(embedImplicitDefs(tupler, defns))
val replacement = q"$main.map($t).$m($body)"
Some(replaceTree(tree, replacement))
case _ => None
}

case _ =>
None
}
}

private def mkValDef(nm: String, tnm: Tree): ValDef = {
implicit val fnc = currentFreshNameCreator
ValDef(
Modifiers(Flag.IMPLICIT | Flag.ARTIFACT | Flag.SYNTHETIC),
freshTermName(nm + "$implicit$"),
tnm,
Ident(TermName(nm))
)
}

object ImplicitPatternVals {
def unapply(arg: Tree): Option[(Tree, List[ValDef])] = arg match {
case HasImplicitPattern() =>
val vals = arg.collect {
case q"implicit0(${Bind(TermName(nm), Typed(_, tpt))})" =>
mkValDef(nm, tpt)
}
// We're done with implicit0 "keyword", exterminate it
Some((StripImplicitZero.transform(arg), vals))
case _ => None
}
}

object HasImplicitPattern {
def unapply(arg: Tree): Boolean = arg.exists {
case q"implicit0(${Bind(t: TermName, Typed(Ident(termNames.WILDCARD), _))})" if t != termNames.WILDCARD =>
true
case q"implicit0($_)" =>
reporter.error(arg.pos, "implicit pattern only supports identifier with type pattern")
false
case q"implicit0(..$_)" =>
reporter.error(arg.pos, "implicit pattern only accepts a single parameter")
false
case _ =>
false
}
}

object StripImplicitZero extends Transformer {
override def transform(tree: Tree): Tree = tree match {
case q"implicit0(${Bind(t: TermName, _)})" => super.transform(Bind(t, Ident(termNames.WILDCARD)))
case _ => super.transform(tree)
}
}

object NonLocalImplicits {
def unapply(vd: ValDef): Option[List[ValDef]] = {
vd.rhs match {
case Match(_, CaseDef(pat, _, _) :: Nil) if vd.mods.hasFlag(Flag.ARTIFACT) =>
val vd = pat.collect {
case q"implicit0(${Bind(TermName(nm), Typed(_, tpt))})" =>
mkValDef(nm, tpt)
}
if (vd.nonEmpty) Some(vd) else None
case _ => None
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.olegpy.bm4

import scala.reflect.internal.{Definitions, ModifierFlags, SymbolTable}
import scala.tools.nsc.Global
import scala.tools.nsc.ast.TreeDSL
import scala.tools.nsc.transform.TypingTransformers


trait NoMapIdentity extends TreeUtils {
import global._

def noMapIdentity: Boolean

object NoMapIdentity {
def unapply(tree: Tree): Option[Tree] = tree match {
case _ if !noMapIdentity => None

// plain monomorphic map
case q"${sel @ q"$body.map"}[..$_](${IdentityFunction()})"
if sel.hasAttachment[ForAttachment.type] &&
tree.tpe =:= body.tpe =>
Some(replaceTree(tree, body))

// map with implicit parameters
case q"${sel @ q"$body.map"}[..$_](${IdentityFunction()})(..$_)"
if sel.hasAttachment[ForAttachment.type] &&
tree.tpe =:= body.tpe =>
Some(replaceTree(tree, body))

// map on implicit conversion with implicit parameters (e.g. simulacrum ops)
case q"${sel @ q"$_($body)(..$_).map"}[..$_](${IdentityFunction()})"
if sel.hasAttachment[ForAttachment.type] &&
body.tpe.widen =:= tree.tpe // body.tpe will be inferred to singleton type
=>
Some(replaceTree(tree, body))
case _ =>
None
}
}

object IdentityFunction {
def unapply(tree: Tree): Boolean = tree match {
case Function(ValDef(mods, TermName(x), tpt, _) :: Nil, i @ Ident(TermName(x2)))
if (x == x2) &&
mods.flags == ModifierFlags.PARAM &&
(i.tpe =:= tpt.tpe) =>
true
case Function(ValDef(_, _, tpt, EmptyTree) :: Nil, LiteralUnit(u))
if tpt.tpe =:= definitions.UnitTpe && tpt.tpe =:= u.tpe =>
true
case _ =>
false
}
}

object LiteralUnit {
def unapply(tree: Tree): Option[Tree] = tree match {
case u @ Literal(Constant(())) =>
Some(u)

// In Scala 2:13, we get `(x$1: Unit @unchecked) match { case _ => () }`
case u @ Match(Typed(Ident(TermName(x)), tpt),
List(
CaseDef(Ident(termNames.WILDCARD), EmptyTree, Literal(Constant(())))))
if tpt.tpe =:= definitions.UnitTpe =>
Some(u)

case _ =>
None
}
}
}
Loading

0 comments on commit 073bbe4

Please sign in to comment.