Skip to content

Commit ecee17d

Browse files
committed
Type the quoted patterns as precisely as possible
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] } ```
1 parent 8212f60 commit ecee17d

File tree

7 files changed

+137
-2
lines changed

7 files changed

+137
-2
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,35 @@ object desugar {
316316
} else tree
317317
}
318318

319+
// TODO doc
320+
def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(implicit ctx: Context): untpd.Tree = {
321+
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
322+
// Add the expected type as an ascription
323+
case _: untpd.Splice =>
324+
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
325+
case Typed(expr: untpd.Splice, tpt) =>
326+
cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span))
327+
328+
// Propagate down the expected type to the leafs of the expression
329+
case Block(stats, expr) =>
330+
cpy.Block(tree)(stats, adaptToExpectedTpt(expr))
331+
case If(cond, thenp, elsep) =>
332+
cpy.If(tree)(cond, adaptToExpectedTpt(thenp), adaptToExpectedTpt(elsep))
333+
case untpd.Parens(expr) =>
334+
cpy.Parens(tree)(adaptToExpectedTpt(expr))
335+
case Match(selector, cases) =>
336+
val newCases = cases.map(cdef => cpy.CaseDef(cdef)(body = adaptToExpectedTpt(cdef.body)))
337+
cpy.Match(tree)(selector, newCases)
338+
case untpd.ParsedTry(expr, handler, finalizer) =>
339+
cpy.ParsedTry(tree)(adaptToExpectedTpt(expr), adaptToExpectedTpt(handler), finalizer)
340+
341+
// Tree does not need to be ascribed
342+
case _ =>
343+
tree
344+
}
345+
adaptToExpectedTpt(tree)
346+
}
347+
319348
// Add all evidence parameters in `params` as implicit parameters to `meth` */
320349
private def addEvidenceParams(meth: DefDef, params: List[ValDef])(implicit ctx: Context): DefDef =
321350
params match {

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1970,7 +1970,8 @@ class Typer extends Namer
19701970
if (ctx.mode.is(Mode.Pattern) && level == 0) {
19711971
val exprPt = pt.baseType(defn.QuotedExprClass)
19721972
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
1973-
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
1973+
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
1974+
val quoted1 = typedExpr(quoted0, WildcardType)(quoteContext.addMode(Mode.QuotedPattern))
19741975
val (shape, splices) = splitQuotePattern(quoted1)
19751976
val patType = defn.tupleType(splices.tpes.map(_.widen))
19761977
val splicePat = typed(untpd.Tuple(splices.map(untpd.TypedSplice(_))).withSpan(quoted.span), patType)
@@ -1980,7 +1981,7 @@ class Typer extends Namer
19801981
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
19811982
implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil,
19821983
patterns = splicePat :: Nil,
1983-
proto = pt)
1984+
proto = defn.QuotedExprType.appliedTo(quoted1.tpe & quotedPt))
19841985
}
19851986
else
19861987
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuoteR), quoted), pt)(quoteContext).withSpan(tree.span)

library/src-3.x/scala/internal/quoted/Matcher.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ object Matcher {
8383
/** Normalieze the tree */
8484
def normalize(tree: Tree): Tree = tree match {
8585
case Block(Nil, expr) => normalize(expr)
86+
case Block(stats, expr) =>
87+
val stats1 = stats.filter { // TODO filterConserve
88+
case IsTypeDef(s) => !hasBindAnnotation(s.symbol)
89+
case _ => true
90+
}
91+
if (stats1.isEmpty) expr
92+
else Block.copy(tree)(stats1, expr)
8693
case Inlined(_, Nil, expr) => normalize(expr)
8794
case _ => tree
8895
}

tests/pos/quoted-pattern-type.scala

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import scala.quoted._
2+
import scala.tasty.Reflection
3+
4+
object Lib {
5+
6+
def impl[T: Type](arg: Expr[T])(implicit refl: Reflection): Expr[T] = {
7+
arg match {
8+
case e @ '{ $x: Boolean } =>
9+
e: Expr[T & Boolean]
10+
x: Expr[T & Boolean]
11+
e
12+
13+
case e @ '{ println("hello"); $x } =>
14+
e: Expr[T]
15+
x: Expr[T]
16+
e
17+
18+
case e @ '{ println("hello"); $x: T } =>
19+
e: Expr[T]
20+
x: Expr[T]
21+
e
22+
23+
case e @ '{ Some($x: Int) } =>
24+
e: Expr[T & Some[Int]]
25+
x: Expr[Int]
26+
e
27+
28+
case e @ '{ if ($x) ($y: Boolean) else ($z: Int) } =>
29+
e: Expr[T & (Boolean | Int)]
30+
y: Expr[T & Boolean]
31+
z: Expr[T & Int]
32+
e
33+
34+
case e @ '{ if ($x) $y else $z } =>
35+
e: Expr[T]
36+
y: Expr[T]
37+
z: Expr[T]
38+
e
39+
40+
case e @ '{ if ($x) $y else ($z: Int) } =>
41+
e: Expr[T & (T | Int)]
42+
y: Expr[T]
43+
z: Expr[T & Int]
44+
e
45+
46+
case e @ '{ ($x: Boolean) match { case _ => $y: Int } } =>
47+
e: Expr[T & Int]
48+
y: Expr[T & Int]
49+
e
50+
51+
case e @ '{ ($x: Boolean) match { case _ => $y } } =>
52+
e: Expr[T]
53+
y: Expr[T]
54+
e
55+
56+
case e @ '{ try ($x: Boolean) catch { case _ => $y: Int } } =>
57+
e: Expr[T & (Boolean | Int)]
58+
x: Expr[T & Boolean]
59+
y: Expr[T & Int]
60+
e
61+
62+
}
63+
}
64+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Boolean: true
2+
Int: 4
3+
Printed hello and returned world
4+
Printed world and returned hello
5+
Some: 5
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import scala.quoted._
2+
import scala.tasty.Reflection
3+
4+
object Lib {
5+
6+
inline def foo[T](arg: => T): T = ${ impl('arg) }
7+
8+
private def impl[T: Type](arg: Expr[T])(implicit refl: Reflection): Expr[T] = {
9+
arg match {
10+
case e @ '{ $x: Boolean } => '{ println("Boolean: " + $e); $e }
11+
case e @ '{ $x: Int } => '{ println("Int: " + $x); $x }
12+
case '{ println("hello"); $arg } => '{ println("Printed hello and returned " + $arg); $arg }
13+
case '{ println("world"); $arg: T } => '{ println("Printed world and returned " + $arg); $arg }
14+
case e @ '{ Some($x: Int) } => '{ println("Some: " + $x); $e }
15+
case arg => '{ ??? }
16+
}
17+
}
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
object Test {
2+
import Lib._
3+
4+
def main(args: Array[String]): Unit = {
5+
foo(true)
6+
foo(4)
7+
foo { println("hello"); "world" }
8+
foo { println("world"); "hello" }
9+
foo(Some(5))
10+
}
11+
}

0 commit comments

Comments
 (0)