Skip to content

Commit 9349faa

Browse files
authored
Merge pull request #6742 from dotty-staging/fix-type-of-quoted-pattern
Type the quoted patterns as precisely as possible
2 parents 2b7cf02 + f5d900e commit 9349faa

File tree

7 files changed

+142
-4
lines changed

7 files changed

+142
-4
lines changed

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

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,42 @@ object desugar {
312312
} else tree
313313
}
314314

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

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2021,8 +2021,12 @@ class Typer extends Namer
20212021
*/
20222022
private def typedQuotePattern(quoted: untpd.Tree, pt: Type, qctx: tpd.Tree)(implicit ctx: Context): Tree = {
20232023
val exprPt = pt.baseType(defn.QuotedExprClass)
2024-
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
2025-
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
2024+
val quotedPt = exprPt.argInfos.headOption match {
2025+
case Some(argPt: ValueType) => argPt // excludes TypeBounds
2026+
case _ => defn.AnyType
2027+
}
2028+
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
2029+
val quoted1 = typedExpr(quoted0, WildcardType)(quoteContext.addMode(Mode.QuotedPattern))
20262030

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

@@ -2070,7 +2074,7 @@ class Typer extends Namer
20702074
Literal(Constant(typeBindings.nonEmpty)) ::
20712075
qctx :: Nil,
20722076
patterns = splicePat :: Nil,
2073-
proto = pt)
2077+
proto = defn.QuotedExprType.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
20742078
}
20752079

20762080
/** 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)