diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala index a6bc87b6833b..c24b52719c8c 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala @@ -1,6 +1,7 @@ package dotty.tools.dotc package tastyreflect +import dotty.tools.dotc.ast.Trees.SeqLiteral import dotty.tools.dotc.ast.{Trees, tpd, untpd} import dotty.tools.dotc.ast.tpd.TreeOps import dotty.tools.dotc.typer.Typer @@ -1055,6 +1056,9 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util. def Type_memberType(self: Type)(member: Symbol)(implicit ctx: Context): Type = member.info.asSeenFrom(self, member.owner) + def Type_derivesFrom(self: Type)(cls: ClassDefSymbol)(implicit ctx: Context): Boolean = + self.derivesFrom(cls) + type ConstantType = Types.ConstantType def matchConstantType(tpe: TypeOrBounds)(implicit ctx: Context): Option[ConstantType] = tpe match { @@ -1774,7 +1778,7 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util. def Definitions_Array_length: Symbol = defn.Array_length.asTerm def Definitions_Array_update: Symbol = defn.Array_update.asTerm - def Definitions_RepeatedParamClass: Symbol = defn.RepeatedParamClass + def Definitions_RepeatedParamClass: ClassDefSymbol = defn.RepeatedParamClass def Definitions_OptionClass: Symbol = defn.OptionClass def Definitions_NoneModule: Symbol = defn.NoneClass.companionModule.asTerm diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3ede65162c0c..51982a48fda8 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -580,7 +580,10 @@ class Typer extends Namer if (untpd.isWildcardStarArg(tree)) { def typedWildcardStarArgExpr = { - val tpdExpr = typedExpr(tree.expr) + val ptArg = + if (ctx.mode.is(Mode.QuotedPattern)) pt.underlyingIfRepeated(isJava = false) + else WildcardType + val tpdExpr = typedExpr(tree.expr, ptArg) tpdExpr.tpe.widenDealias match { case defn.ArrayOf(_) => val starType = defn.ArrayType.appliedTo(WildcardType) @@ -1960,12 +1963,17 @@ class Typer extends Namer object splitter extends tpd.TreeMap { val patBuf = new mutable.ListBuffer[Tree] override def transform(tree: Tree)(implicit ctx: Context) = tree match { - case Typed(Splice(pat), tpt) => + case Typed(Splice(pat), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) => val exprTpt = AppliedTypeTree(TypeTree(defn.QuotedExprType), tpt :: Nil) transform(Splice(Typed(pat, exprTpt))) case Splice(pat) => try patternHole(tree) - finally patBuf += pat + finally { + val patType = pat.tpe.widen + val patType1 = patType.underlyingIfRepeated(isJava = false) + val pat1 = if (patType eq patType1) pat else pat.withType(patType1) + patBuf += pat1 + } case _ => super.transform(tree) } diff --git a/library/src-bootstrapped/scala/internal/quoted/Matcher.scala b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala index 3d8ea7bbc467..645cc70715db 100644 --- a/library/src-bootstrapped/scala/internal/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala @@ -64,9 +64,17 @@ object Matcher { (normalize(scrutinee), normalize(pattern)) match { + // Match a scala.internal.Quoted.patternHole typed as a repeated argument and return the scrutinee tree + case (IsTerm(scrutinee @ Typed(s, tpt1)), Typed(TypeApply(patternHole, tpt :: Nil), tpt2)) + if patternHole.symbol == kernel.Definitions_InternalQuoted_patternHole && + s.tpe <:< tpt.tpe && + tpt2.tpe.derivesFrom(definitions.RepeatedParamClass) => + Some(Tuple1(scrutinee.seal)) + // Match a scala.internal.Quoted.patternHole and return the scrutinee tree case (IsTerm(scrutinee), TypeApply(patternHole, tpt :: Nil)) - if patternHole.symbol == kernel.Definitions_InternalQuoted_patternHole && scrutinee.tpe <:< tpt.tpe => + if patternHole.symbol == kernel.Definitions_InternalQuoted_patternHole && + scrutinee.tpe <:< tpt.tpe => Some(Tuple1(scrutinee.seal)) // @@ -85,7 +93,7 @@ object Matcher { case (Select(qual1, _), Select(qual2, _)) if scrutinee.symbol == pattern.symbol => treeMatches(qual1, qual2) - case (IsRef(_), IsRef(_, _)) if scrutinee.symbol == pattern.symbol => + case (IsRef(_), IsRef(_)) if scrutinee.symbol == pattern.symbol => Some(()) case (Apply(fn1, args1), Apply(fn2, args2)) if fn1.symbol == fn2.symbol => diff --git a/library/src/scala/quoted/matching/Repeated.scala b/library/src/scala/quoted/matching/Repeated.scala new file mode 100644 index 000000000000..f5bcf0aab254 --- /dev/null +++ b/library/src/scala/quoted/matching/Repeated.scala @@ -0,0 +1,21 @@ +package scala.quoted.matching + +import scala.quoted.Expr + +import scala.tasty.Reflection // TODO do not depend on reflection directly + +/** Matches a literal sequence of expressions */ +object Repeated { + + def unapply[T](expr: Expr[Seq[T]])(implicit reflect: Reflection): Option[Seq[Expr[T]]] = { + import reflect.{Repeated => RepeatedTree, _} // TODO rename to avoid clash + def repeated(tree: Term): Option[Seq[Expr[T]]] = tree match { + case Typed(RepeatedTree(elems, _), _) => Some(elems.map(x => x.seal.asInstanceOf[Expr[T]])) + case Block(Nil, e) => repeated(e) + case Inlined(_, Nil, e) => repeated(e) + case _ => None + } + repeated(expr.unseal) + } + +} diff --git a/library/src/scala/tasty/reflect/Kernel.scala b/library/src/scala/tasty/reflect/Kernel.scala index 07836b68054f..511f92f1e5f4 100644 --- a/library/src/scala/tasty/reflect/Kernel.scala +++ b/library/src/scala/tasty/reflect/Kernel.scala @@ -844,6 +844,9 @@ trait Kernel { def Type_memberType(self: Type)(member: Symbol)(implicit ctx: Context): Type + /** Is this type an instance of a non-bottom subclass of the given class `cls`? */ + def Type_derivesFrom(self: Type)(cls: ClassDefSymbol)(implicit ctx: Context): Boolean + /** A singleton type representing a known constant value */ type ConstantType <: Type @@ -1434,7 +1437,7 @@ trait Kernel { def Definitions_Array_length: Symbol def Definitions_Array_update: Symbol - def Definitions_RepeatedParamClass: Symbol + def Definitions_RepeatedParamClass: ClassDefSymbol def Definitions_OptionClass: Symbol def Definitions_NoneModule: Symbol diff --git a/library/src/scala/tasty/reflect/StandardDefinitions.scala b/library/src/scala/tasty/reflect/StandardDefinitions.scala index b8e8e3625d88..6b9cf49ced9a 100644 --- a/library/src/scala/tasty/reflect/StandardDefinitions.scala +++ b/library/src/scala/tasty/reflect/StandardDefinitions.scala @@ -106,7 +106,7 @@ trait StandardDefinitions extends Core { /** A dummy class symbol that is used to indicate repeated parameters * compiled by the Scala compiler. */ - def RepeatedParamClass: Symbol = kernel.Definitions_RepeatedParamClass + def RepeatedParamClass: ClassDefSymbol = kernel.Definitions_RepeatedParamClass /** The class symbol of class `scala.Option`. */ def OptionClass: Symbol = kernel.Definitions_OptionClass diff --git a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala index 1a6f162e89ba..80168d653328 100644 --- a/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala +++ b/library/src/scala/tasty/reflect/TypeOrBoundsOps.scala @@ -22,6 +22,11 @@ trait TypeOrBoundsOps extends Core { def typeSymbol(implicit ctx: Context): Symbol = kernel.Type_typeSymbol(self) def isSingleton(implicit ctx: Context): Boolean = kernel.Type_isSingleton(self) def memberType(member: Symbol)(implicit ctx: Context): Type = kernel.Type_memberType(self)(member) + + /** Is this type an instance of a non-bottom subclass of the given class `cls`? */ + def derivesFrom(cls: ClassDefSymbol)(implicit ctx: Context): Boolean = + kernel.Type_derivesFrom(self)(cls) + } object IsType { diff --git a/tests/pos/i6253.scala b/tests/pos/i6253.scala new file mode 100644 index 000000000000..8d006ab30be7 --- /dev/null +++ b/tests/pos/i6253.scala @@ -0,0 +1,10 @@ +import scala.quoted._ +import scala.tasty.Reflection +object Macros { + def impl(self: Expr[StringContext]) given Reflection: Expr[String] = self match { + case '{ StringContext() } => '{""} + case '{ StringContext($part1) } => part1 + case '{ StringContext($part1, $part2) } => '{ $part1 + $part2 } + case '{ StringContext($parts: _*) } => '{ $parts.mkString } + } +} diff --git a/tests/run-with-compiler/i6253-b.check b/tests/run-with-compiler/i6253-b.check new file mode 100644 index 000000000000..8254f875c6f8 --- /dev/null +++ b/tests/run-with-compiler/i6253-b.check @@ -0,0 +1,2 @@ +Hello World +Hello World diff --git a/tests/run-with-compiler/i6253-b/quoted_1.scala b/tests/run-with-compiler/i6253-b/quoted_1.scala new file mode 100644 index 000000000000..baeea7df659d --- /dev/null +++ b/tests/run-with-compiler/i6253-b/quoted_1.scala @@ -0,0 +1,22 @@ +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +object Macros { + + inline def (self: => StringContext) xyz(args: => String*): String = ${impl('self, 'args)} + + private def impl(self: Expr[StringContext], args: Expr[Seq[String]])(implicit reflect: Reflection): Expr[String] = { + self match { + case '{ StringContext($parts: _*) } => + '{ + val p: Seq[String] = $parts + val a: Seq[Any] = $args ++ Seq("") + p.zip(a).map(_ + _.toString).mkString + } + case _ => + '{ "ERROR" } + } + } +} diff --git a/tests/run-with-compiler/i6253-b/quoted_2.scala b/tests/run-with-compiler/i6253-b/quoted_2.scala new file mode 100644 index 000000000000..2a85eca35e41 --- /dev/null +++ b/tests/run-with-compiler/i6253-b/quoted_2.scala @@ -0,0 +1,10 @@ +import Macros._ + +object Test { + + def main(args: Array[String]): Unit = { + println(xyz"Hello World") + println(xyz"Hello ${"World"}") + } + +} diff --git a/tests/run-with-compiler/i6253.check b/tests/run-with-compiler/i6253.check new file mode 100644 index 000000000000..8254f875c6f8 --- /dev/null +++ b/tests/run-with-compiler/i6253.check @@ -0,0 +1,2 @@ +Hello World +Hello World diff --git a/tests/run-with-compiler/i6253/quoted_1.scala b/tests/run-with-compiler/i6253/quoted_1.scala new file mode 100644 index 000000000000..e3162fd042de --- /dev/null +++ b/tests/run-with-compiler/i6253/quoted_1.scala @@ -0,0 +1,18 @@ +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +object Macros { + + inline def (self: => StringContext) xyz(args: => String*): String = ${impl('self, 'args)} + + private def impl(self: Expr[StringContext], args: Expr[Seq[String]])(implicit reflect: Reflection): Expr[String] = { + self match { + case '{ StringContext($parts: _*) } => + '{ StringContext($parts: _*).s($args: _*) } + case _ => + '{ "ERROR" } + } + } +} diff --git a/tests/run-with-compiler/i6253/quoted_2.scala b/tests/run-with-compiler/i6253/quoted_2.scala new file mode 100644 index 000000000000..2a85eca35e41 --- /dev/null +++ b/tests/run-with-compiler/i6253/quoted_2.scala @@ -0,0 +1,10 @@ +import Macros._ + +object Test { + + def main(args: Array[String]): Unit = { + println(xyz"Hello World") + println(xyz"Hello ${"World"}") + } + +} diff --git a/tests/run-with-compiler/quote-matcher-runtime.check b/tests/run-with-compiler/quote-matcher-runtime.check index aad22c102e88..972b113a2074 100644 --- a/tests/run-with-compiler/quote-matcher-runtime.check +++ b/tests/run-with-compiler/quote-matcher-runtime.check @@ -190,7 +190,7 @@ Result: Some(List()) Scrutinee: fs() Pattern: fs((scala.internal.Quoted.patternHole[scala.Seq[scala.Int]]: scala.[scala.Int])) -Result: Some(List(Expr())) +Result: Some(List(Expr((: scala.[scala.Int])))) Scrutinee: fs((1, 2, 3: scala.[scala.Int])) Pattern: fs((1, 2, 3: scala.[scala.Int])) @@ -202,7 +202,7 @@ Result: Some(List(Expr(1), Expr(2))) Scrutinee: fs((1, 2, 3: scala.[scala.Int])) Pattern: fs((scala.internal.Quoted.patternHole[scala.Seq[scala.Int]]: scala.[scala.Int])) -Result: Some(List(Expr(1, 2, 3))) +Result: Some(List(Expr((1, 2, 3: scala.[scala.Int])))) Scrutinee: f2(1, 2) Pattern: f2(1, 2) @@ -246,7 +246,7 @@ Result: Some(List(Expr("abc"), Expr("xyz"))) Scrutinee: scala.StringContext.apply(("abc", "xyz": scala.[scala.Predef.String])) Pattern: scala.StringContext.apply((scala.internal.Quoted.patternHole[scala.Seq[scala.Predef.String]]: scala.[scala.Predef.String])) -Result: Some(List(Expr("abc", "xyz"))) +Result: Some(List(Expr(("abc", "xyz": scala.[scala.Predef.String])))) Scrutinee: { val a: scala.Int = 45 diff --git a/tests/run-with-compiler/quote-matcher-string-interpolator-2.check b/tests/run-with-compiler/quote-matcher-string-interpolator-2.check new file mode 100644 index 000000000000..4f71f81d0e83 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator-2.check @@ -0,0 +1,2 @@ +dlroW olleH + olleHWorld diff --git a/tests/run-with-compiler/quote-matcher-string-interpolator-2/quoted_1.scala b/tests/run-with-compiler/quote-matcher-string-interpolator-2/quoted_1.scala new file mode 100644 index 000000000000..9e3264dd7101 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator-2/quoted_1.scala @@ -0,0 +1,21 @@ +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +object Macros { + + inline def (self: => StringContext) xyz(args: => String*): String = ${impl('self, 'args)} + + private def impl(self: Expr[StringContext], args: Expr[Seq[String]])(implicit reflect: Reflection): Expr[String] = { + (self, args) match { + case ('{ StringContext(${Repeated(parts)}: _*) }, Repeated(args1)) => + val strParts = parts.map { case Literal(str) => str.reverse } + val strArgs = args1.map { case Literal(str) => str } + StringContext(strParts: _*).s(strArgs: _*).toExpr + case _ => ??? + } + + } + +} diff --git a/tests/run-with-compiler/quote-matcher-string-interpolator-2/quoted_2.scala b/tests/run-with-compiler/quote-matcher-string-interpolator-2/quoted_2.scala new file mode 100644 index 000000000000..2a85eca35e41 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator-2/quoted_2.scala @@ -0,0 +1,10 @@ +import Macros._ + +object Test { + + def main(args: Array[String]): Unit = { + println(xyz"Hello World") + println(xyz"Hello ${"World"}") + } + +} diff --git a/tests/run-with-compiler/quote-matcher-string-interpolator.check b/tests/run-with-compiler/quote-matcher-string-interpolator.check new file mode 100644 index 000000000000..4f71f81d0e83 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator.check @@ -0,0 +1,2 @@ +dlroW olleH + olleHWorld diff --git a/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_1.scala b/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_1.scala new file mode 100644 index 000000000000..9b37890beedf --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_1.scala @@ -0,0 +1,21 @@ +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +object Macros { + + inline def (self: => StringContext) xyz(args: => String*): String = ${impl('self, 'args)} + + private def impl(self: Expr[StringContext], args: Expr[Seq[String]])(implicit reflect: Reflection): Expr[String] = { + self match { + case '{ StringContext(${Repeated(parts)}: _*) } => + val parts2 = parts.map(x => '{ $x.reverse }).toList.toExprOfList + '{ StringContext($parts2: _*).s($args: _*) } + case _ => + '{ "ERROR" } + } + + } + +} diff --git a/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_2.scala b/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_2.scala new file mode 100644 index 000000000000..2a85eca35e41 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_2.scala @@ -0,0 +1,10 @@ +import Macros._ + +object Test { + + def main(args: Array[String]): Unit = { + println(xyz"Hello World") + println(xyz"Hello ${"World"}") + } + +}