diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index fd09af8a9ee5..52202774e12e 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -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 { diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3621d093c897..bdb6e4495aef 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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) @@ -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. diff --git a/library/src-bootstrapped/scala/internal/quoted/Matcher.scala b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala index bb3d0c6b4e62..79441feb3ddf 100644 --- a/library/src-bootstrapped/scala/internal/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala @@ -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)) diff --git a/tests/pos/quoted-pattern-type.scala b/tests/pos/quoted-pattern-type.scala new file mode 100644 index 000000000000..0332a2d0a4a9 --- /dev/null +++ b/tests/pos/quoted-pattern-type.scala @@ -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 + + } + } +} diff --git a/tests/run-macros/quoted-pattern-type.check b/tests/run-macros/quoted-pattern-type.check new file mode 100644 index 000000000000..bd45a5fa65f0 --- /dev/null +++ b/tests/run-macros/quoted-pattern-type.check @@ -0,0 +1,5 @@ +Boolean: true +Int: 4 +Printed hello and returned world +Printed world and returned hello +Some: 5 \ No newline at end of file diff --git a/tests/run-macros/quoted-pattern-type/Macro_1.scala b/tests/run-macros/quoted-pattern-type/Macro_1.scala new file mode 100644 index 000000000000..8c63a5b551bb --- /dev/null +++ b/tests/run-macros/quoted-pattern-type/Macro_1.scala @@ -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 => '{ ??? } + } + } +} diff --git a/tests/run-macros/quoted-pattern-type/Test_2.scala b/tests/run-macros/quoted-pattern-type/Test_2.scala new file mode 100644 index 000000000000..60ae48cdfb92 --- /dev/null +++ b/tests/run-macros/quoted-pattern-type/Test_2.scala @@ -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)) + } +}