Skip to content

Commit

Permalink
Type the quoted patterns as precisely as possible
Browse files Browse the repository at this point in the history
In a pattern match where the contents of the quote are statically known we want to propagate the
type inside the pattern the the binding. For example `e` will not only be an `Expr[T]` but also
an `Expr[Some[Int]]`.

```scala
(s: Expr[T]) match {
  case e @ '{ Some($x: Int) } =>
    // e: Expr[T & Some[Int]]
    // x: Expr[Int]
}
```

If the expression in the pattern contains a spliced expression, possibly typed, we also need to propagate the
type of the scrutinee down into the pattern. For example `x` will not only be an `Expr[Boolean]` but also
an `Expr[T]`.

```scala
(s: Expr[T]) match {
    case e @ '{ $x: Boolean } =>
      // e: Expr[T & Boolean]
      // x: Expr[T & Boolean]
}
```
  • Loading branch information
nicolasstucki committed Jul 2, 2019
1 parent 0e5e541 commit 1a1e8f6
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 3 deletions.
36 changes: 36 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,42 @@ object desugar {
} else tree
}

/** Add an explicit ascription to the `expectedTpt` to every tail splice.
*
* - `'{ x }` -> `'{ x }`
* - `'{ $x }` -> `'{ $x: T }`
* - `'{ if (...) $x else $y }` -> `'{ if (...) ($x: T) else ($y: T) }`
*
* Note that the splice `$t: T` will be typed as `${t: Expr[T]}`
*/
def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(implicit ctx: Context): untpd.Tree = {
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
// Add the expected type as an ascription
case _: untpd.Splice =>
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
case Typed(expr: untpd.Splice, tpt) =>
cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span))

// Propagate down the expected type to the leafs of the expression
case Block(stats, expr) =>
cpy.Block(tree)(stats, adaptToExpectedTpt(expr))
case If(cond, thenp, elsep) =>
cpy.If(tree)(cond, adaptToExpectedTpt(thenp), adaptToExpectedTpt(elsep))
case untpd.Parens(expr) =>
cpy.Parens(tree)(adaptToExpectedTpt(expr))
case Match(selector, cases) =>
val newCases = cases.map(cdef => cpy.CaseDef(cdef)(body = adaptToExpectedTpt(cdef.body)))
cpy.Match(tree)(selector, newCases)
case untpd.ParsedTry(expr, handler, finalizer) =>
cpy.ParsedTry(tree)(adaptToExpectedTpt(expr), adaptToExpectedTpt(handler), finalizer)

// Tree does not need to be ascribed
case _ =>
tree
}
adaptToExpectedTpt(tree)
}

// Add all evidence parameters in `params` as implicit parameters to `meth` */
private def addEvidenceParams(meth: DefDef, params: List[ValDef])(implicit ctx: Context): DefDef =
params match {
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2022,7 +2022,8 @@ class Typer extends Namer
private def typedQuotePattern(quoted: untpd.Tree, pt: Type, quoteSpan: Span)(implicit ctx: Context): Tree = {
val exprPt = pt.baseType(defn.QuotedExprClass)
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
val quoted1 = typedExpr(quoted0, WildcardType)(quoteContext.addMode(Mode.QuotedPattern))

val (typeBindings, shape, splices) = splitQuotePattern(quoted1)

Expand Down Expand Up @@ -2070,7 +2071,7 @@ class Typer extends Namer
Literal(Constant(typeBindings.nonEmpty)) ::
implicitArgTree(defn.QuoteContextType, quoteSpan) :: Nil,
patterns = splicePat :: Nil,
proto = pt)
proto = defn.QuotedExprType.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
}

/** Split a typed quoted pattern is split into its type bindings, pattern expression and inner patterns.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ object Matcher {
*/
def (scrutinee0: Tree) =#= (pattern0: Tree) given Context, Env: Matching = {

/** Normalieze the tree */
/** Normalize the tree */
def normalize(tree: Tree): Tree = tree match {
case Block(Nil, expr) => normalize(expr)
case Block(stats1, Block(stats2, expr)) => normalize(Block(stats1 ::: stats2, expr))
Expand Down
64 changes: 64 additions & 0 deletions tests/pos/quoted-pattern-type.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import scala.quoted._
import scala.tasty.Reflection

object Lib {

def impl[T: Type](arg: Expr[T])(implicit refl: Reflection): Expr[T] = {
arg match {
case e @ '{ $x: Boolean } =>
e: Expr[T & Boolean]
x: Expr[T & Boolean]
e

case e @ '{ println("hello"); $x } =>
e: Expr[T]
x: Expr[T]
e

case e @ '{ println("hello"); $x: T } =>
e: Expr[T]
x: Expr[T]
e

case e @ '{ Some($x: Int) } =>
e: Expr[T & Some[Int]]
x: Expr[Int]
e

case e @ '{ if ($x) ($y: Boolean) else ($z: Int) } =>
e: Expr[T & (Boolean | Int)]
y: Expr[T & Boolean]
z: Expr[T & Int]
e

case e @ '{ if ($x) $y else $z } =>
e: Expr[T]
y: Expr[T]
z: Expr[T]
e

case e @ '{ if ($x) $y else ($z: Int) } =>
e: Expr[T & (T | Int)]
y: Expr[T]
z: Expr[T & Int]
e

case e @ '{ ($x: Boolean) match { case _ => $y: Int } } =>
e: Expr[T & Int]
y: Expr[T & Int]
e

case e @ '{ ($x: Boolean) match { case _ => $y } } =>
e: Expr[T]
y: Expr[T]
e

case e @ '{ try ($x: Boolean) catch { case _ => $y: Int } } =>
e: Expr[T & (Boolean | Int)]
x: Expr[T & Boolean]
y: Expr[T & Int]
e

}
}
}
5 changes: 5 additions & 0 deletions tests/run-macros/quoted-pattern-type.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Boolean: true
Int: 4
Printed hello and returned world
Printed world and returned hello
Some: 5
18 changes: 18 additions & 0 deletions tests/run-macros/quoted-pattern-type/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import scala.quoted._
import scala.tasty.Reflection

object Lib {

inline def foo[T](arg: => T): T = ${ impl('arg) }

private def impl[T: Type](arg: Expr[T])(implicit refl: Reflection): Expr[T] = {
arg match {
case e @ '{ $x: Boolean } => '{ println("Boolean: " + $e); $e }
case e @ '{ $x: Int } => '{ println("Int: " + $x); $x }
case '{ println("hello"); $arg } => '{ println("Printed hello and returned " + $arg); $arg }
case '{ println("world"); $arg: T } => '{ println("Printed world and returned " + $arg); $arg }
case e @ '{ Some($x: Int) } => '{ println("Some: " + $x); $e }
case arg => '{ ??? }
}
}
}
11 changes: 11 additions & 0 deletions tests/run-macros/quoted-pattern-type/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
object Test {
import Lib._

def main(args: Array[String]): Unit = {
foo(true)
foo(4)
foo { println("hello"); "world" }
foo { println("world"); "hello" }
foo(Some(5))
}
}

0 comments on commit 1a1e8f6

Please sign in to comment.