Skip to content

Commit

Permalink
SI-8366 make partial function and match trees disjoint
Browse files Browse the repository at this point in the history
Previously one could match a partial function with match quasiquote:

    scala> val q"$scrutinee match { case ..$cases }" = q"{ case Foo => Bar
    }"
    scrutinee: universe.Tree = <empty>
    cases: List[universe.CaseDef] = List(case Foo => Bar)

This was quite annoying as it leaked encoding of partial functions as
Match trees with empty tree in place of scrutinee.

This commit make sure that matches and partial functions are disjoint
and don't match one another (while preserving original encoding under
the hood out of sight of the end user.)
  • Loading branch information
densh committed Mar 10, 2014
1 parent 7f07d44 commit 6dbd770
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 12 deletions.
5 changes: 5 additions & 0 deletions bincompat-backward.whitelist.conf
Expand Up @@ -122,6 +122,11 @@ filter {
{
matchName="scala.reflect.api.Internals#ReificationSupportApi.SyntacticSelectTerm"
problemName=MissingMethodProblem
},
// see SI-8366
{
matchName="scala.reflect.api.Internals#ReificationSupportApi.SyntacticPartialFunction"
problemName=MissingMethodProblem
}
]
}
11 changes: 10 additions & 1 deletion bincompat-forward.whitelist.conf
Expand Up @@ -130,6 +130,15 @@ filter {
{
matchName="scala.reflect.api.Internals$ReificationSupportApi$SyntacticSelectTypeExtractor"
problemName=MissingClassProblem
}
},
// see SI-8366
{
matchName="scala.reflect.api.Internals$ReificationSupportApi$SyntacticPartialFunctionExtractor"
problemName=MissingClassProblem
},
{
matchName="scala.reflect.api.Internals#ReificationSupportApi.SyntacticPartialFunction"
problemName=MissingMethodProblem
}
]
}
6 changes: 4 additions & 2 deletions src/compiler/scala/tools/reflect/quasiquotes/Reifiers.scala
Expand Up @@ -199,6 +199,10 @@ trait Reifiers { self: Quasiquotes =>
reifyBuildCall(nme.SyntacticEmptyTypeTree)
case SyntacticImport(expr, selectors) =>
reifyBuildCall(nme.SyntacticImport, expr, selectors)
case SyntacticPartialFunction(cases) =>
reifyBuildCall(nme.SyntacticPartialFunction, cases)
case SyntacticMatch(scrutinee, cases) =>
reifyBuildCall(nme.SyntacticMatch, scrutinee, cases)
case Q(tree) if fillListHole.isDefinedAt(tree) =>
mirrorBuildCall(nme.SyntacticBlock, fillListHole(tree))
case Q(other) =>
Expand All @@ -211,8 +215,6 @@ trait Reifiers { self: Quasiquotes =>
reifyBuildCall(nme.SyntacticBlock, Nil)
case Try(block, catches, finalizer) =>
reifyBuildCall(nme.SyntacticTry, block, catches, finalizer)
case Match(selector, cases) =>
reifyBuildCall(nme.SyntacticMatch, selector, cases)
case CaseDef(pat, guard, body) if fillListHole.isDefinedAt(body) =>
mirrorCall(nme.CaseDef, reify(pat), reify(guard), mirrorBuildCall(nme.SyntacticBlock, fillListHole(body)))
// parser emits trees with scala package symbol to ensure
Expand Down
8 changes: 7 additions & 1 deletion src/reflect/scala/reflect/api/Internals.scala
Expand Up @@ -762,9 +762,15 @@ trait Internals { self: Universe =>
def unapply(lst: List[List[Tree]]): Option[List[List[T]]]
}

val SyntacticPartialFunction: SyntacticPartialFunctionExtractor
trait SyntacticPartialFunctionExtractor {
def apply(cases: List[Tree]): Match
def unapply(tree: Match): Option[List[CaseDef]]
}

val SyntacticMatch: SyntacticMatchExtractor
trait SyntacticMatchExtractor {
def apply(selector: Tree, cases: List[Tree]): Match
def apply(scrutinee: Tree, cases: List[Tree]): Match
def unapply(tree: Match): Option[(Tree, List[CaseDef])]
}

Expand Down
19 changes: 17 additions & 2 deletions src/reflect/scala/reflect/internal/ReificationSupport.scala
Expand Up @@ -850,9 +850,24 @@ trait ReificationSupport { self: SymbolTable =>
case tree => throw new IllegalArgumentException("$tree is not valid representation of pattern match case")
}

object SyntacticPartialFunction extends SyntacticPartialFunctionExtractor {
def apply(cases: List[Tree]): Match = Match(EmptyTree, mkCases(cases))
def unapply(tree: Match): Option[List[CaseDef]] = tree match {
case Match(EmptyTree, cases) => Some(cases)
case _ => None
}
}

object SyntacticMatch extends SyntacticMatchExtractor {
def apply(selector: Tree, cases: List[Tree]) = Match(selector, mkCases(cases))
def unapply(tree: Match): Option[(Tree, List[CaseDef])] = Match.unapply(tree)
def apply(scrutinee: Tree, cases: List[Tree]) = {
require(scrutinee.nonEmpty, "match's scrutinee may not be empty")
Match(scrutinee, mkCases(cases))
}

def unapply(tree: Match): Option[(Tree, List[CaseDef])] = tree match {
case Match(scrutinee, cases) if scrutinee.nonEmpty => Some((scrutinee, cases))
case _ => None
}
}

object SyntacticTry extends SyntacticTryExtractor {
Expand Down
1 change: 1 addition & 0 deletions src/reflect/scala/reflect/internal/StdNames.scala
Expand Up @@ -630,6 +630,7 @@ trait StdNames {
val SyntacticNew: NameType = "SyntacticNew"
val SyntacticObjectDef: NameType = "SyntacticObjectDef"
val SyntacticPackageObjectDef: NameType = "SyntacticPackageObjectDef"
val SyntacticPartialFunction: NameType = "SyntacticPartialFunction"
val SyntacticPatDef: NameType = "SyntacticPatDef"
val SyntacticSelectType: NameType = "SyntacticSelectType"
val SyntacticSelectTerm: NameType = "SyntacticSelectTerm"
Expand Down
14 changes: 8 additions & 6 deletions test/files/scalacheck/quasiquotes/TermConstructionProps.scala
Expand Up @@ -95,12 +95,6 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") {
body1 ≈ body && cond1 ≈ cond
}

property("unquote trees into alternative") = forAll { (c: Tree, A: Tree, B: Tree) =>
q"$c match { case $A | $B => }"
Match(c, List(
CaseDef(Alternative(List(A, B)), EmptyTree, Literal(Constant(())))))
}

def blockInvariant(quote: Tree, trees: List[Tree]) =
quote ≈ (trees match {
case Nil => q"{}"
Expand Down Expand Up @@ -303,4 +297,12 @@ object TermConstructionProps extends QuasiquoteProperties("term construction") {
property("SI-8385 b") = test {
assertEqAst(q"(() => ())()", "(() => ())()")
}

property("match scrutinee may not be empty") = test {
assertThrows[IllegalArgumentException] {
val scrutinee = q""
val cases = List(cq"_ =>")
q"$scrutinee match { case ..$cases }"
}
}
}
Expand Up @@ -211,4 +211,10 @@ object TermDeconstructionProps extends QuasiquoteProperties("term deconstruction
val q"$f[..$targs]" = tq"foo[bar]"
}
}

property("match doesn't match partial function") = test {
assertThrows[MatchError] {
val q"$_ match { case ..$_ }" = q"{ case _ => }"
}
}
}

0 comments on commit 6dbd770

Please sign in to comment.