Skip to content

Commit

Permalink
virtpatmat, hidden behind -Yvirtpatmat
Browse files Browse the repository at this point in the history
at least one imminent TODO: undo hardwired generation of if/then/else,
and decide based on type whether to call flatMap/orElse or inline those
methods from Option

review by extempore
  • Loading branch information
adriaanm committed Oct 20, 2011
1 parent 891a6e4 commit 8a9fd64
Show file tree
Hide file tree
Showing 51 changed files with 1,238 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/compiler/scala/reflect/internal/StdNames.scala
Expand Up @@ -262,6 +262,7 @@ trait StdNames extends /*reflect.generic.StdNames with*/ NameManglers { self: Sy
val productIterator: NameType = "productIterator"
val productPrefix: NameType = "productPrefix"
val readResolve: NameType = "readResolve"
val runOrElse: NameType = "runOrElse"
val sameElements: NameType = "sameElements"
val scala_ : NameType = "scala"
val self: NameType = "self"
Expand Down
Expand Up @@ -29,6 +29,7 @@ trait AestheticSettings {
def target = settings.target.value
def unchecked = settings.unchecked.value
def verbose = settings.verbose.value
def virtPatmat = settings.YvirtPatmat.value

/** Derived values */
def jvm = target startsWith "jvm"
Expand Down
1 change: 1 addition & 0 deletions src/compiler/scala/tools/nsc/settings/ScalaSettings.scala
Expand Up @@ -171,6 +171,7 @@ trait ScalaSettings extends AbsScalaSettings
val YmethodInfer = BooleanSetting ("-Yinfer-argument-types", "Infer types for arguments of overriden methods.")
val etaExpandKeepsStar = BooleanSetting("-Yeta-expand-keeps-star", "Eta-expand varargs methods to T* rather than Seq[T]. This is a temporary option to ease transition.")
val noSelfCheck = BooleanSetting ("-Yno-self-type-checks", "Suppress check for self-type conformance among inherited members.")
val YvirtPatmat = BooleanSetting ("-Yvirtpatmat", "Translate pattern matches into flatMap/orElse calls. See scala.MatchingStrategy.")
val YvirtClasses = false // too embryonic to even expose as a -Y //BooleanSetting ("-Yvirtual-classes", "Support virtual classes")

val exposeEmptyPackage = BooleanSetting("-Yexpose-empty-package", "Internal only: expose the empty package.").internalOnly()
Expand Down
25 changes: 25 additions & 0 deletions src/compiler/scala/tools/nsc/transform/UnCurry.scala
Expand Up @@ -281,6 +281,31 @@ abstract class UnCurry extends InfoTransform
if (cases exists treeInfo.isDefaultCase) Literal(Constant(true))
else Match(substTree(selector.duplicate), (cases map transformCase) :+ defaultCase)
)
case Apply(Apply(TypeApply(Select(tgt, n), targs), args_scrut), args_pm) if opt.virtPatmat && (n == nme.runOrElse) => // TODO: check tgt.tpe.typeSymbol isNonBottomSubclass MatchingStrategyClass
object noOne extends Transformer {
override val treeCopy = newStrictTreeCopier // must duplicate everything
val one = tgt.tpe member "caseResult".toTermName
override def transform(tree: Tree): Tree = tree match {
case Apply(fun, List(a)) if fun.symbol == one =>
// blow one's argument away since all we want to know is whether the match succeeds or not
// (the alternative, making `one` CBN, would entail moving away from Option)
val zero = // must use subtyping (no need for equality thanks to covariance), as otherwise we miss types like `Any with Int`
if (UnitClass.tpe <:< a.tpe) Literal(Constant())
else if (BooleanClass.tpe <:< a.tpe) Literal(Constant(false))
else if (FloatClass.tpe <:< a.tpe) Literal(Constant(0.0f))
else if (DoubleClass.tpe <:< a.tpe) Literal(Constant(0.0d))
else if (ByteClass.tpe <:< a.tpe) Literal(Constant(0.toByte))
else if (ShortClass.tpe <:< a.tpe) Literal(Constant(0.toShort))
else if (IntClass.tpe <:< a.tpe) Literal(Constant(0))
else if (LongClass.tpe <:< a.tpe) Literal(Constant(0L))
else if (CharClass.tpe <:< a.tpe) Literal(Constant(0.toChar))
else NULL AS a.tpe // must cast, at least when a.tpe <:< NothingClass.tpe
Apply(fun.duplicate, List(zero))
case _ =>
super.transform(tree)
}
}
substTree(Apply(Apply(TypeApply(Select(tgt.duplicate, tgt.tpe.member("isSuccess".toTermName)), targs map (_.duplicate)), args_scrut map (_.duplicate)), args_pm map (noOne.transform)))
})
}

Expand Down
854 changes: 854 additions & 0 deletions src/compiler/scala/tools/nsc/typechecker/PatMatVirtualiser.scala

Large diffs are not rendered by default.

22 changes: 17 additions & 5 deletions src/compiler/scala/tools/nsc/typechecker/Typers.scala
Expand Up @@ -27,7 +27,7 @@ import scala.tools.util.StringOps.{ countAsString, countElementsAsString }
* @author Martin Odersky
* @version 1.0
*/
trait Typers extends Modes with Adaptations {
trait Typers extends Modes with Adaptations with PatMatVirtualiser {
self: Analyzer =>

import global._
Expand Down Expand Up @@ -3280,11 +3280,23 @@ trait Typers extends Modes with Adaptations {
} else {
val selector1 = checkDead(typed(selector, EXPRmode | BYVALmode, WildcardType))
var cases1 = typedCases(tree, cases, selector1.tpe.widen, pt)
val (owntype, needAdapt) = ptOrLub(cases1 map (_.tpe))
if (needAdapt) {
cases1 = cases1 map (adaptCase(_, owntype))

if (phase.id > currentRun.typerPhase.id || !opt.virtPatmat) {
val (owntype, needAdapt) = ptOrLub(cases1 map (_.tpe))
if (needAdapt) {
cases1 = cases1 map (adaptCase(_, owntype))
}
treeCopy.Match(tree, selector1, cases1) setType owntype
} else { // don't run translator after typers (see comments in PatMatVirtualiser)
def repackExistential(tp: Type): Type = existentialAbstraction((tp filter {t => t.typeSymbol.isExistentiallyBound}) map (_.typeSymbol), tp)
val (owntype0, needAdapt) = ptOrLub(cases1 map (x => repackExistential(x.tpe)))
val owntype = elimAnonymousClass(owntype0)
if (needAdapt) cases1 = cases1 map (adaptCase(_, owntype))

val translated = (new MatchTranslator(this)).X(treeCopy.Match(tree, selector1, cases1), owntype)

typed1(translated, mode, WildcardType) setType owntype // TODO: get rid of setType owntype -- it should all typecheck
}
treeCopy.Match(tree, selector1, cases1) setType owntype
}
}

Expand Down
32 changes: 32 additions & 0 deletions src/library/scala/MatchingStrategy.scala
@@ -0,0 +1,32 @@
package scala

abstract class MatchingStrategy[M[+x]] {
def zero: M[Nothing]
def one[T](x: T): M[T]
def guard[T](cond: Boolean, then: => T): M[T] // = if(cond) one(then) else zero
def altFlatMap[T, U](f: T => M[U])(a: M[U], b: M[T]): M[U] // = a orElse b.flatMap(f) -- can't easily&efficiently express M[T] should have flatMap and orElse
def runOrElse[T, U](x: T)(f: T => M[U]): U
def isSuccess[T, U](x: T)(f: T => M[U]): Boolean

// find the first alternative to successfully flatMap f
// to avoid code explosion due to alternatives
def or[T, U](f: T => M[U], alts: M[T]*) = (alts foldLeft (zero: M[U]))(altFlatMap(f))

def caseResult[T](x: T): M[T] = one(x) // used as a marker to distinguish the RHS of a case (case pat => RHS) and intermediate successes
// when deriving a partial function from a pattern match, we need to
// distinguish the RHS of a case, which should not be evaluated when computing isDefinedAt,
// from an intermediate result (which must be computed)

}

object MatchingStrategy {
implicit object OptionMatchingStrategy extends MatchingStrategy[Option] {
type M[+x] = Option[x]
@inline def guard[T](cond: Boolean, then: => T): M[T] = if(cond) Some(then) else None
@inline def zero: M[Nothing] = None
@inline def one[T](x: T): M[T] = Some(x)
@inline def altFlatMap[T, U](f: T => M[U])(a: M[U], b: M[T]): M[U] = a orElse b.flatMap(f)
@inline def runOrElse[T, U](x: T)(f: T => M[U]): U = f(x) getOrElse (throw new MatchError(x))
@inline def isSuccess[T, U](x: T)(f: T => M[U]): Boolean = !f(x).isEmpty
}
}
1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_castbinder.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
15 changes: 15 additions & 0 deletions test/files/pos/virtpatmat_castbinder.scala
@@ -0,0 +1,15 @@
class IntMap[+V]
case class Bin[+T](m: IntMap[T]) extends IntMap[T]
case class Tip[+T](x: T) extends IntMap[T]

trait IntMapIterator[V, T] {
def valueOf(tip: Tip[V]): T
def pop: IntMap[V]

def next: T =
pop match {
case Bin(t@Tip(_)) => {
valueOf(t)
}
}
}
1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_exist1.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
24 changes: 24 additions & 0 deletions test/files/pos/virtpatmat_exist1.scala
@@ -0,0 +1,24 @@
import annotation.unchecked.{ uncheckedVariance=> uV }
import scala.collection.immutable.{ListMap, HashMap, ListSet, HashSet}

object Test {
class HashMapCollision1[A, +B](var hash: Int, var kvs: ListMap[A, B @uV]) extends HashMap[A, B @uV]
class HashSetCollision1[A](var hash: Int, var ks: ListSet[A]) extends HashSet[A]

def splitArray[T](ad: Array[Iterable[T]]): Any =
ad(0) match {
case _: HashMapCollision1[_, _] | _: HashSetCollision1[_] => null
}

// without type ascription for the one in the body of the last flatmap of each alternative, type inference borks on the existentials
// def splitArray[T >: Nothing <: Any](ad: Array[Iterable[T]]): Any = { import OptionMatching._
// runOrElse(ad.apply(0))(((x1: Iterable[T]) => (
// or(((x4: Iterable[T]) => one(null)),
// guard(x1.isInstanceOf[Iterable[T] with Test.HashMapCollision1[_,_]], x1.asInstanceOf[Iterable[T] with Test.HashMapCollision1[_,_]]).flatMap(((x2: Iterable[T] with Test.HashMapCollision1[_,_]) => one(x2))),
// guard(x1.isInstanceOf[Test.HashSetCollision1[_]], x1.asInstanceOf[Iterable[T] with Test.HashSetCollision1[_]]).flatMap(((x3: Iterable[T] with Test.HashSetCollision1[_]) => one(x3)))): Option[Any]).orElse(
// (zero: Option[Any])))
// )
// }

}

1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_exist2.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
20 changes: 20 additions & 0 deletions test/files/pos/virtpatmat_exist2.scala
@@ -0,0 +1,20 @@
class ParseResult[+T]
case class MemoEntry[+T](var r: Either[Nothing,ParseResult[_]])

object Test {
def grow[T]: ParseResult[T] = (null: MemoEntry[T]) match {
case MemoEntry(Right(x: ParseResult[_])) => x.asInstanceOf[ParseResult[T]]
}

// what's the _$1 doing there?
// def grow[T >: Nothing <: Any]: ParseResult[T] = {
// import OptionMatching._
// runOrElse[MemoEntry[T], ParseResult[T]]((null: MemoEntry[T]))(((x1: MemoEntry[T]) =>
// (MemoEntry.unapply[T](x1).flatMap[ParseResult[T]](((x4: Either[Nothing,ParseResult[_]]) =>
// guard[Right[Nothing,ParseResult[_]]](x4.isInstanceOf[Right[Nothing,ParseResult[_]]], x4.asInstanceOf[Right[Nothing,ParseResult[_]]]).flatMap[ParseResult[T]](((cp3: Right[Nothing,ParseResult[_]]) =>
// scala.Right.unapply[Nothing, ParseResult[_]](cp3).flatMap[ParseResult[T]](((x5: ParseResult[_]) =>
// guard[ParseResult[_$1]](x5.ne(null), x5.asInstanceOf[ParseResult[_]]).flatMap[ParseResult[T]](((x6: ParseResult[_]) =>
// one[ParseResult[T]](x6.asInstanceOf[ParseResult[T]]))))))))): Option[ParseResult[T]]
// ).orElse[ParseResult[T]]((zero: Option[ParseResult[T]]))))
// }
}
1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_exist3.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
12 changes: 12 additions & 0 deletions test/files/pos/virtpatmat_exist3.scala
@@ -0,0 +1,12 @@
class ReferenceQueue[T] {
def wrapper(jref: ReferenceQueue[_]): ReferenceQueue[T] =
jref match {
case null => null
}

// def wrapper(jref: ReferenceQueue[_]): ReferenceQueue[T] = OptionMatching.runOrElse(jref)(((x1: ReferenceQueue[_]) =>
// (OptionMatching.guard(null.==(x1), x1.asInstanceOf[ReferenceQueue[_]]).flatMap(((x2: ReferenceQueue[_]) =>
// OptionMatching.one(null))): Option[ReferenceQueue[T]]).orElse(
// (OptionMatching.zero: Option[ReferenceQueue[T]])))
// )
}
1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_gadt_array.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
15 changes: 15 additions & 0 deletions test/files/pos/virtpatmat_gadt_array.scala
@@ -0,0 +1,15 @@
import scala.collection.mutable._
object Test {
def genericArrayOps[T](xs: Array[T]): ArrayOps[T] = xs match {
case x: Array[AnyRef] => refArrayOps[AnyRef](x).asInstanceOf[ArrayOps[T]]
case null => null
}
// def genericArrayOps[T >: Nothing <: Any](xs: Array[T]): scala.collection.mutable.ArrayOps[T]
// = OptionMatching.runOrElse(xs)(((x1: Array[T]) =>
// ((OptionMatching.guard(x1.isInstanceOf[Array[AnyRef]], x1.asInstanceOf[Array[T] with Array[AnyRef]]).flatMap(((x2: Array[T] with Array[AnyRef]) =>
// OptionMatching.one(Test.this.refArrayOps[AnyRef](x2).asInstanceOf[scala.collection.mutable.ArrayOps[T]]))): Option[scala.collection.mutable.ArrayOps[T]]).orElse(
// (OptionMatching.guard(null.==(x1), x1.asInstanceOf[Array[T]]).flatMap(((x3: Array[T]) =>
// OptionMatching.one(null))): Option[scala.collection.mutable.ArrayOps[T]])): Option[scala.collection.mutable.ArrayOps[T]]).orElse((OptionMatching.zero: Option[scala.collection.mutable.ArrayOps[T]]))))

def refArrayOps[T <: AnyRef](xs: Array[T]): ArrayOps[T] = new ArrayOps.ofRef[T](xs)
}
1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_infer_single_1.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
7 changes: 7 additions & 0 deletions test/files/pos/virtpatmat_infer_single_1.scala
@@ -0,0 +1,7 @@
case class TypeBounds(a: Type, b: Type)
class Type {
def bounds: TypeBounds = bounds match {
case TypeBounds(_: this.type, _: this.type) => TypeBounds(this, this)
case oftp => oftp
}
}
1 change: 1 addition & 0 deletions test/files/pos/virtpatmat_obj_in_case.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
5 changes: 5 additions & 0 deletions test/files/pos/virtpatmat_obj_in_case.scala
@@ -0,0 +1,5 @@
class ObjInCase {
0 match {
case _ => object o
}
}
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_alts.check
@@ -0,0 +1 @@
OK 5
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_alts.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
12 changes: 12 additions & 0 deletions test/files/run/virtpatmat_alts.scala
@@ -0,0 +1,12 @@
object Test extends App {
(true, true) match {
case (true, true) | (false, false) => 1
}

List(5) match {
case 1 :: Nil | 2 :: Nil => println("FAILED")
case (x@(4 | 5 | 6)) :: Nil => println("OK "+ x)
case 7 :: Nil => println("FAILED")
case Nil => println("FAILED")
}
}
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_apply.check
@@ -0,0 +1 @@
OK 2
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_apply.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
7 changes: 7 additions & 0 deletions test/files/run/virtpatmat_apply.scala
@@ -0,0 +1,7 @@
object Test extends App {
List(1, 2, 3) match {
case Nil => println("FAIL")
case x :: y :: xs if xs.length == 2 => println("FAIL")
case x :: y :: xs if xs.length == 1 => println("OK "+ y)
}
}
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_casting.check
@@ -0,0 +1 @@
List(1)
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_casting.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
8 changes: 8 additions & 0 deletions test/files/run/virtpatmat_casting.scala
@@ -0,0 +1,8 @@
object Test extends App {
println(List(1,2,3) match {
case Nil => List(0)
// since the :: extractor's argument must be a ::, there has to be a cast before its unapply is invoked
case x :: y :: z :: a :: xs => xs ++ List(x)
case x :: y :: z :: xs => xs ++ List(x)
})
}
3 changes: 3 additions & 0 deletions test/files/run/virtpatmat_literal.check
@@ -0,0 +1,3 @@
OK
OK
OK
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_literal.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
21 changes: 21 additions & 0 deletions test/files/run/virtpatmat_literal.scala
@@ -0,0 +1,21 @@
object Test extends App {
1 match {
case 2 => println("FAILED")
case 1 => println("OK")
case 1 => println("FAILED")
}

val one = 1
1 match {
case 2 => println("FAILED")
case `one` => println("OK")
case 1 => println("FAILED")
}

1 match {
case 2 => println("FAILED")
case Test.one => println("OK")
case 1 => println("FAILED")
}

}
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_nested_lists.check
@@ -0,0 +1 @@
2
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_nested_lists.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
3 changes: 3 additions & 0 deletions test/files/run/virtpatmat_nested_lists.scala
@@ -0,0 +1,3 @@
object Test extends App {
List(List(1), List(2)) match { case x :: (y :: Nil) :: Nil => println(y) }
}
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_npe.check
@@ -0,0 +1 @@
OK
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_npe.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental
10 changes: 10 additions & 0 deletions test/files/run/virtpatmat_npe.scala
@@ -0,0 +1,10 @@
class C {
class D
val values = new Array[AnyRef](10)
values(0) match {
case name: D => println("NOK: "+ name) // the outer check on D's outer should not cause a NPE
case null => println("OK")
}
}

object Test extends C with App
2 changes: 2 additions & 0 deletions test/files/run/virtpatmat_partial.check
@@ -0,0 +1,2 @@
Map(a -> Some(1), b -> None)
Map(a -> 1)
1 change: 1 addition & 0 deletions test/files/run/virtpatmat_partial.flags
@@ -0,0 +1 @@
-Yvirtpatmat -Xexperimental

0 comments on commit 8a9fd64

Please sign in to comment.