Skip to content

Commit 1a1e8f6

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 0e5e541 commit 1a1e8f6

File tree

7 files changed

+138
-3
lines changed

7 files changed

+138
-3
lines changed

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

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

319+
/** Add an explicit ascription to the `expectedTpt` to every tail splice.
320+
*
321+
* - `'{ x }` -> `'{ x }`
322+
* - `'{ $x }` -> `'{ $x: T }`
323+
* - `'{ if (...) $x else $y }` -> `'{ if (...) ($x: T) else ($y: T) }`
324+
*
325+
* Note that the splice `$t: T` will be typed as `${t: Expr[T]}`
326+
*/
327+
def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(implicit ctx: Context): untpd.Tree = {
328+
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
329+
// Add the expected type as an ascription
330+
case _: untpd.Splice =>
331+
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
332+
case Typed(expr: untpd.Splice, tpt) =>
333+
cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span))
334+
335+
// Propagate down the expected type to the leafs of the expression
336+
case Block(stats, expr) =>
337+
cpy.Block(tree)(stats, adaptToExpectedTpt(expr))
338+
case If(cond, thenp, elsep) =>
339+
cpy.If(tree)(cond, adaptToExpectedTpt(thenp), adaptToExpectedTpt(elsep))
340+
case untpd.Parens(expr) =>
341+
cpy.Parens(tree)(adaptToExpectedTpt(expr))
342+
case Match(selector, cases) =>
343+
val newCases = cases.map(cdef => cpy.CaseDef(cdef)(body = adaptToExpectedTpt(cdef.body)))
344+
cpy.Match(tree)(selector, newCases)
345+
case untpd.ParsedTry(expr, handler, finalizer) =>
346+
cpy.ParsedTry(tree)(adaptToExpectedTpt(expr), adaptToExpectedTpt(handler), finalizer)
347+
348+
// Tree does not need to be ascribed
349+
case _ =>
350+
tree
351+
}
352+
adaptToExpectedTpt(tree)
353+
}
354+
319355
// Add all evidence parameters in `params` as implicit parameters to `meth` */
320356
private def addEvidenceParams(meth: DefDef, params: List[ValDef])(implicit ctx: Context): DefDef =
321357
params match {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,7 +2022,8 @@ class Typer extends Namer
20222022
private def typedQuotePattern(quoted: untpd.Tree, pt: Type, quoteSpan: Span)(implicit ctx: Context): Tree = {
20232023
val exprPt = pt.baseType(defn.QuotedExprClass)
20242024
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
2025-
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
2025+
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
2026+
val quoted1 = typedExpr(quoted0, WildcardType)(quoteContext.addMode(Mode.QuotedPattern))
20262027

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

@@ -2070,7 +2071,7 @@ class Typer extends Namer
20702071
Literal(Constant(typeBindings.nonEmpty)) ::
20712072
implicitArgTree(defn.QuoteContextType, quoteSpan) :: Nil,
20722073
patterns = splicePat :: Nil,
2073-
proto = pt)
2074+
proto = defn.QuotedExprType.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
20742075
}
20752076

20762077
/** Split a typed quoted pattern is split into its type bindings, pattern expression and inner patterns.

library/src-bootstrapped/scala/internal/quoted/Matcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ object Matcher {
7676
*/
7777
def (scrutinee0: Tree) =#= (pattern0: Tree) given Context, Env: Matching = {
7878

79-
/** Normalieze the tree */
79+
/** Normalize the tree */
8080
def normalize(tree: Tree): Tree = tree match {
8181
case Block(Nil, expr) => normalize(expr)
8282
case Block(stats1, Block(stats2, expr)) => normalize(Block(stats1 ::: stats2, expr))

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)