Skip to content

Type the quoted patterns as precisely as possible #6742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
10 changes: 7 additions & 3 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2021,8 +2021,12 @@ 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 quotedPt = exprPt.argInfos.headOption match {
case Some(argPt: ValueType) => argPt // excludes TypeBounds
case _ => defn.AnyType
}
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 +2074,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))
}
}