diff --git a/docs/docs/reference/other-new-features/quote-pattern-matching.md b/docs/docs/reference/other-new-features/quote-pattern-matching.md new file mode 100644 index 000000000000..e12c449343ba --- /dev/null +++ b/docs/docs/reference/other-new-features/quote-pattern-matching.md @@ -0,0 +1,130 @@ +--- +layout: doc-page +title: "Principled Meta Programming - Pattern matching" +--- + +## Overview + +Quote pattern matching allows to match a quote against another quote while extracting the contents of defined holes. This will be used in as desugaring of quoted patterns. + +```scala +def optimize(expr: Expr[Int]): Expr[Int] = expr match { + case '{ 0 + $y } => optimize(y) + case '{ ${Literal(a)} + ${Literal(b)} } => (a + b).toExpr + case '{ ${x} + ${y @ Literal(_)} } => optimize('{ $y + $x }) + ... + case expr => expr +} +``` + +Where `x: Expr[Int]`, `y: Expr[Int]`, `a: Int` and `b: Int`. + + +### Quote patterns + +Quote extractors are patterns of the shape `'{ ... }` that can match an `Expr[T]`. + +```scala +(expr: Expr[T]) match { + case '{ pattern } => +} +``` + +They allow to extract expresions, types and identifiers + +```scala +(expr: Expr[T]) match { + case '{ ... val x: T = $rhs ... } => + case '{ ... val $y: T = ... } => + case '{ ... val z: $t = ... } => +} +``` + +Where `x: Expr[T]`, `y: Binging[T]` and `z: Type[T]`. + + +#### Desugaring + +A pattern with splices such as + + +```scala +(expr: Expr[T]) match { + case '{ ... ($x: T) ... val $b: U = ... foo[$t] ... } => +} +``` + +is transformed to a call to `Matcher.unapply` with the appropriate types for the splices in a tuple. + +```scala +(expr: Expr[T]) match { + case scala.runtime.quoted.Matcher.unapply + [... *: Expr[X] *: ... *: Binding[T] *: ... *: Type[V] *: ... *: Unit] + (... *: x *: ... *: b *: ... *: t *: ... *: ()) + (/*implicits*/ '{ ... hole[T] ... bindHole[U] ... Hole[V] ... }, reflect) => +} +``` + + +### Other expression patterns + +```scala +(expr: Expr[T]) match { + case Literal(lit) => lit: T + case Binding(binding) => binding: Binding[T] + case IsValRef(expr2) => expr2: expr.type + case IsDefRef(expr) => expr2: expr.type + + case '{ ... ${Literal(lit)} ... } +} +``` + + +## Spec + +### Quote pattern desugaring + +Given a quote pattern `'{ pattern }` the desugaring is defined in terms of a splice types `T`, a runtime pattern `P` and extracted bindings `E`. +```scala +Matcher.unapply[T](E)(/*implicits*/ '{ P }, reflect) +``` + + + +`` is defined recursively in the following way: + +| Quote pattern syntax | Matcher pattern | Splice types | Extracted | +| ------------- |:-------------:| ----------- | ----------- | +| `<$x>` where `x: Expr[X]` | `Matcher.hole[X]` | `Tuple1[Expr[T]]` | `Tuple1[Expr[T]](x)` | +| `<$t>` where `x: Type[X]` | `Matcher.HOLE[X]` | `Tuple1[Type[T]]` | `Tuple1[Type[T]](t)` | +| `<$n: X>` where `n` is a name of `val`, `var`, `def` or parameter definition | `n: bindHole[X]` | `Tuple1[Binding[X]]` | `Tuple1[Binding[X]](n)` | +| `` | `if () else ` | `Concat[T, Concat[T, T]]` | E<`e1`> ++ E<`e2`> ++ E<`e3`>) | +| `` | `while () ` | | E<`e1`> ++ E<`e2`> | +| `` | `[, ..., ](, ..., )` | | E<`f`> ++ E<`T1`> ++ ... ++ E<`Tn`> ++ E<`e1`> ++ ... ++ E<`em`> | +| `<(e1: T1, ..., en: Tn) => body>` | `(, ..., ) => ` | | E<`e1: T1`> ++ ... ++ E<`en: Tn`> ++ E<`body`> | +| `` | `lhs = ` | | E<`rhs`> | +| `` | `new ` | | E<`X`> | +| `` | `this` | | | +| `` | `.super[]` | | E<`qual`> ++ E<`T`) | +| `` | `[, ..., ]` | | E<`K`> ++ E<`T1`> ++ ... ++ E<`Tn`> | +| `` where `x` is an identifier | `x` | | | +| `` where `T` is an identifier | `T` | | | + + +### Quote pattern match runtime + +| Match | Condition | +| ------------- |:-------------:| +| matches(`s`, `hole[T]`) for `s` of type `S` | `S <:< T` | +| matches(`T`, `Hole[T]`) | | +| matches(`n: X1`, `n: bindHole[X2]`) | matches(`X1`, `X2`) | +| matches(`if (s1) s2 else s3`, `if (p1) p2 else p3`) | matches(`s1`, `p1`) && matches(`s2`, `p2`) && matches(`s3`,`p3`) | +| matches(`while (s1) s2`, `while (p1) p2`) | matches(`s1`, `p1`) && matches(`s2`, `p2`) | + +| Match | Result | +| ------------- |:-------------:| +| result(`s`, `hole[T]`) for `s` of type `S` | `s` as an `Expr[T]` | +| result(`T`, `Hole[T]`) | `T` as a `Type[T]` | +| matches(`n: X1`, `n: bindHole[X2]`) | (`n` as a `Binding[X2]`) ++ result(`X1`, `X2`) | +| result(`if (s1) s2 else s3`, `if (p1) p2 else p3`) | result(`s1`, `p1`) ++ result(`s2`, `p2`) ++ result(`s3`,`p3`) | +| result(`while (s1) s2`, `while (p1) p2`) | result(`s1`, `p1`) ++ result(`s2`, `p2`) | \ No newline at end of file diff --git a/library/src-bootstrapped/scala/quoted/Type.scala b/library/src-bootstrapped/scala/quoted/Type.scala index 243b63b3889a..e57a6c9cb8e2 100644 --- a/library/src-bootstrapped/scala/quoted/Type.scala +++ b/library/src-bootstrapped/scala/quoted/Type.scala @@ -13,7 +13,6 @@ sealed abstract class Type[T <: AnyKind] { /** Some basic type tags, currently incomplete */ object Type { - implicit def UnitTag: Type[Unit] = new TaggedType[Unit] implicit def BooleanTag: Type[Boolean] = new TaggedType[Boolean] implicit def ByteTag: Type[Byte] = new TaggedType[Byte] diff --git a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala new file mode 100644 index 000000000000..d7d0939940e8 --- /dev/null +++ b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -0,0 +1,303 @@ +package scala.runtime.quoted + +import scala.annotation.internal.sharable + +import scala.quoted._ +import scala.quoted.matching.Binding +import scala.tasty._ + +object Matcher { + + private final val debug = false + + /** Pattern representing a quoted.Type splice */ + type Hole[T <: AnyKind] = T + + /** Pattern representing a quoted.matching.Binding + * The pattern is placed around the type tree of the `val`, `lazy val`, `var` or `def` + */ + type bindHole[T] = T + + /** Pattern representing a quoted.Expr splice */ + def hole[T]: T = ??? + + /** Pattern representing a quoted.Expr splice in a pattern position */ + object patternHole { + def unapply[T](arg: Any): Boolean = ??? + } + + /** Pattern representing a quoted.matching.Binding in a pattern position */ + object patternBindHole { + def unapply[T](arg: Any): Some[T] = ??? + } + + /** Pattern representing a quoted.matching.Binding on the LHS of an assignment */ + @sharable var varHole: Any = null + + /** + * + * @param scrutineeExpr + * @param patternExpr + * @param reflection + * @return None if it did not match, Some(tup) if it matched where tup contains Expr[_], Type[_] or Binding[_] + */ + def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = { + import reflection._ + + def treeMatches(scrutinee: Tree, pattern: Tree)(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = { + + /** Check that both are `val` or both are `lazy val` or both are `var` **/ + def checkValFlags(): Boolean = { + import Flags._ + val sFlags = scrutinee.symbol.flags + val pFlags = pattern.symbol.flags + sFlags.is(Lazy) == pFlags.is(Lazy) && sFlags.is(Mutable) == pFlags.is(Mutable) + } + + def isMatcherHole(sym: Symbol): Boolean = + sym.owner.fullName == "scala.runtime.quoted.Matcher$" // TODO check symbol equality instead of its name + + def treesMatch(scrutinees: List[Tree], patterns: List[Tree]): Option[Tuple] = + if (scrutinees.size != patterns.size) None + else foldMatchings(scrutinees.zip(patterns).map(treeMatches): _*) + + def bindingAndTptMatch(sym: Symbol, tpt1: TypeTree, tpt2: TypeTree): (Option[Tuple], Option[Tuple]) = tpt2 match { + case bindHole @ TypeTree.Applied(TypeIdent("bindHole"), IsTypeTree(tpt2) :: Nil) + if isMatcherHole(bindHole.symbol) && tpt1.tpe <:< tpt2.tpe => // Is the subtype check required? + val binding = new Binding(sym.name, sym) + (Some(Tuple1(binding)), treeMatches(tpt1, tpt2)) + case returnTpt2 => + (Some(()), treeMatches(tpt1, tpt2)) + } + + (scrutinee, pattern) match { + // Normalize blocks without statements + case (Block(Nil, expr), _) => treeMatches(expr, pattern) + case (_, Block(Nil, pat)) => treeMatches(scrutinee, pat) + + case (IsTerm(scrutinee), TypeApply(Ident("hole"), tpt :: Nil)) + if isMatcherHole(pattern.symbol) && scrutinee.tpe <:< tpt.tpe => + Some(Tuple1(scrutinee.seal)) + + case (IsTypeTree(scrutinee), IsTypeTree(pattern @ TypeTree.Applied(TypeIdent("Hole"), IsTypeTree(tpt) :: Nil))) + if isMatcherHole(pattern.symbol) && scrutinee.tpe <:< tpt.tpe => // Is the subtype check required? + Some(Tuple1(scrutinee.tpe.seal)) + + case (Inlined(_, Nil, scr), _) => + treeMatches(scr, pattern) + case (_, Inlined(_, Nil, pat)) => + treeMatches(scrutinee, pat) + + case (Literal(constant1), Literal(constant2)) if constant1 == constant2 => + Some(()) + + case (Ident(_), Ident(_)) if scrutinee.symbol == pattern.symbol || env((scrutinee.symbol, pattern.symbol)) => + Some(()) + + case (Typed(expr1, tpt1), Typed(expr2, tpt2)) => + foldMatchings(treeMatches(expr1, expr2), treeMatches(tpt1, tpt2)) + + case (Select(qual1, _), Select(qual2, _)) if scrutinee.symbol == pattern.symbol => + treeMatches(qual1, qual2) + + case (Ident(_), Select(_, _)) if scrutinee.symbol == pattern.symbol => + Some(()) + + case (Select(_, _), Ident(_)) if scrutinee.symbol == pattern.symbol => + Some(()) + + case (Apply(fn1, args1), Apply(fn2, args2)) if fn1.symbol == fn2.symbol => + foldMatchings(treeMatches(fn1, fn2), treesMatch(args1, args2)) + + case (TypeApply(fn1, args1), TypeApply(fn2, args2)) if fn1.symbol == fn2.symbol => + foldMatchings(treeMatches(fn1, fn2), treesMatch(args1, args2)) + + case (Block(stats1, expr1), Block(stats2, expr2)) => + foldMatchings(treesMatch(stats1, stats2), treeMatches(expr1, expr2)) + + case (If(cond1, thenp1, elsep1), If(cond2, thenp2, elsep2)) => + foldMatchings(treeMatches(cond1, cond2), treeMatches(thenp1, thenp2), treeMatches(elsep1, elsep2)) + + case (Assign(lhs1, rhs1), Assign(lhs2, rhs2)) => + val lhsMatch = lhs2 match { + case Ident("varHole") if isMatcherHole(lhs2.symbol) => + val sym = lhs1.symbol + Some(Tuple1(new Binding(sym.name, sym))) + case _ => + if (treeMatches(lhs1, lhs2).isDefined) Some(()) + else None + } + foldMatchings(lhsMatch, treeMatches(rhs1, rhs2)) + + case (While(cond1, body1), While(cond2, body2)) => + foldMatchings(treeMatches(cond1, cond2), treeMatches(body1, body2)) + + case (NamedArg(name1, expr1), NamedArg(name2, expr2)) if name1 == name2 => + treeMatches(expr1, expr2) + + case (New(tpt1), New(tpt2)) => + treeMatches(tpt1, tpt2) + + case (This(_), This(_)) if scrutinee.symbol == pattern.symbol => + Some(()) + + case (Super(qual1, mix1), Super(qual2, mix2)) if mix1 == mix2 => + treeMatches(qual1, qual2) + + case (Repeated(elems1, _), Repeated(elems2, _)) if elems1.size == elems2.size => + treesMatch(elems1, elems2) + + case (IsTypeTree(scrutinee @ TypeIdent(_)), IsTypeTree(pattern @ TypeIdent(_))) if scrutinee.symbol == pattern.symbol => + Some(()) + + case (IsInferred(scrutinee), IsInferred(pattern)) if scrutinee.tpe <:< pattern.tpe => + Some(()) + + case (Applied(tycon1, args1), Applied(tycon2, args2)) => + foldMatchings(treeMatches(tycon1, tycon2), treesMatch(args1, args2)) + + case (ValDef(_, tpt1, rhs1), ValDef(_, tpt2, rhs2)) if checkValFlags() => + val (bindMatch, returnTptMatch) = bindingAndTptMatch(scrutinee.symbol, tpt1, tpt2) + val rhsEnv = env + (scrutinee.symbol -> pattern.symbol) + val rhsMatchings = treeOptMatches(rhs1, rhs2)(rhsEnv) + foldMatchings(bindMatch, returnTptMatch, rhsMatchings) + + case (DefDef(_, typeParams1, paramss1, tpt1, Some(rhs1)), DefDef(_, typeParams2, paramss2, tpt2, Some(rhs2))) => + val typeParmasMatch = treesMatch(typeParams1, typeParams2) + val paramssMatch = + if (paramss1.size != paramss2.size) None + else foldMatchings(paramss1.zip(paramss2).map { (params1, params2) => treesMatch(params1, params2) }: _*) + val (bindMatch, tptMatch) = bindingAndTptMatch(scrutinee.symbol, tpt1, tpt2) + val rhsEnv = + env + (scrutinee.symbol -> pattern.symbol) ++ + typeParams1.zip(typeParams2).map((tparam1, tparam2) => tparam1.symbol -> tparam2.symbol) ++ + paramss1.flatten.zip(paramss2.flatten).map((param1, param2) => param1.symbol -> param2.symbol) + val rhsMatch = treeMatches(rhs1, rhs2)(rhsEnv) + + foldMatchings(bindMatch, typeParmasMatch, paramssMatch, tptMatch, rhsMatch) + + case (Term.Lambda(_, tpt1), Term.Lambda(_, tpt2)) => + // TODO match tpt1 with tpt2? + Some(()) + + case (Term.Match(scru1, cases1), Term.Match(scru2, cases2)) => + val scrutineeMacth = treeMatches(scru1, scru2) + val casesMatch = + if (cases1.size != cases2.size) None + else foldMatchings(cases1.zip(cases2).map(caseMatches): _*) + foldMatchings(scrutineeMacth, casesMatch) + + case (Term.Try(body1, cases1, finalizer1), Term.Try(body2, cases2, finalizer2)) => + val bodyMacth = treeMatches(body1, body2) + val casesMatch = + if (cases1.size != cases2.size) None + else foldMatchings(cases1.zip(cases2).map(caseMatches): _*) + val finalizerMatch = treeOptMatches(finalizer1, finalizer2) + foldMatchings(bodyMacth, casesMatch, finalizerMatch) + + case (_, Block(stats, expr)) if stats.forall { case IsTypeDef(s) if s.name.toString.startsWith("Binding$") => true; case _ => false } => + treeMatches(scrutinee, expr) + + case _ => + if (debug) + println( + s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + |Scrutinee + | ${scrutinee.showCode} + | + |${scrutinee.show} + | + |did not match pattern + | ${pattern.showCode} + | + |${pattern.show} + | + | + | + | + |""".stripMargin) + None + } + } + + def treeOptMatches(scrutinee: Option[Tree], pattern: Option[Tree])(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = { + (scrutinee, pattern) match { + case (Some(x), Some(y)) => treeMatches(x, y) + case (None, None) => Some(()) + case _ => None + } + } + + def caseMatches(scrutinee: CaseDef, pattern: CaseDef)(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = { + val (caseEnv, patternMatch) = patternMatches(scrutinee.pattern, pattern.pattern) + val guardMatch = treeOptMatches(scrutinee.guard, pattern.guard)(caseEnv) + val rhsMatch = treeMatches(scrutinee.rhs, pattern.rhs)(caseEnv) + foldMatchings(patternMatch, guardMatch, rhsMatch) + } + + def patternMatches(scrutinee: Pattern, pattern: Pattern)(implicit env: Set[(Symbol, Symbol)]): (Set[(Symbol, Symbol)], Option[Tuple]) = (scrutinee, pattern) match { + case (Pattern.Value(v1), Pattern.Unapply(Term.TypeApply(Term.Select(patternHole @ Term.Ident("patternHole"), "unapply"), List(tpt)), Nil, Nil)) + if patternHole.symbol.owner.fullName == "scala.runtime.quoted.Matcher$" => + (env, Some(Tuple1(v1.seal))) + case (Pattern.Bind(name1, body1), Pattern.Unapply(Term.TypeApply(Term.Select(patternBindHole @ Term.Ident("patternBindHole"), "unapply"), List(tpt)), Nil, List(pattern2 @ Pattern.Bind(name2, body2)))) + if patternBindHole.symbol.owner.fullName == "scala.runtime.quoted.Matcher$" => + val binding = new Binding(scrutinee.symbol.name, scrutinee.symbol) + val env1 = env + (scrutinee.symbol -> pattern2.symbol) + val (env2, bodyMatch) = patternMatches(body1, body2)(env1) + (env2, foldMatchings(Some(Tuple1(binding)), bodyMatch)) + case (Pattern.Value(v1), Pattern.Value(v2)) => + (env, treeMatches(v1, v2)) + case (Pattern.Bind(name1, body1), Pattern.Bind(name2, body2)) => + val bindEnv = env + (scrutinee.symbol -> pattern.symbol) + patternMatches(body1, body2)(bindEnv) + case (Pattern.Unapply(fun1, implicits1, patterns1), Pattern.Unapply(fun2, implicits2, patterns2)) => + val funMatch = treeMatches(fun1, fun2) + val implicitsMatch = + if (implicits1.size != implicits2.size) None + else foldMatchings(implicits1.zip(implicits2).map(treeMatches): _*) + val (patEnv, patternsMatch) = foldPatterns(patterns1, patterns2) + (patEnv, foldMatchings(funMatch, implicitsMatch, patternsMatch)) + case (Pattern.Alternatives(patterns1), Pattern.Alternatives(patterns2)) => + foldPatterns(patterns1, patterns2) + case (Pattern.TypeTest(tpt1), Pattern.TypeTest(tpt2)) => + (env, treeMatches(tpt1, tpt2)) + case _ => + if (debug) + println( + s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + |Scrutinee + | ${scrutinee.showCode} + | + |${scrutinee.show} + | + |did not match pattern + | ${pattern.showCode} + | + |${pattern.show} + | + | + | + | + |""".stripMargin) + (env, None) + } + + def foldPatterns(patterns1: List[Pattern], patterns2: List[Pattern])(implicit env: Set[(Symbol, Symbol)]): (Set[(Symbol, Symbol)], Option[Tuple]) = { + if (patterns1.size != patterns2.size) (env, None) + else patterns1.zip(patterns2).foldLeft((env, Option[Tuple](()))) { (acc, x) => + val (env, res) = patternMatches(x._1, x._2)(acc._1) + (env, foldMatchings(acc._2, res)) + } + } + + treeMatches(scrutineeExpr.unseal, patternExpr.unseal)(Set.empty).asInstanceOf[Option[Tup]] + } + + private def foldMatchings(matchings: Option[Tuple]*): Option[Tuple] = { + matchings.foldLeft[Option[Tuple]](Some(())) { + case (Some(acc), Some(holes)) => Some(acc ++ holes) + case (_, _) => None + } + } + +} diff --git a/library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala new file mode 100644 index 000000000000..90f9bd6d2f87 --- /dev/null +++ b/library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -0,0 +1,16 @@ +package scala.runtime.quoted + +import scala.quoted._ +import scala.tasty._ + +object Matcher { + + type Hole[T /* <: AnyKind */] = T + + def hole[T]: T = ??? + def literal[T]: T = ??? + + def unapplySeq(scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Seq[Any]] = + throw new Exception("running on non bootstrapped library") + +} diff --git a/library/src/scala/quoted/matching/Binding.scala b/library/src/scala/quoted/matching/Binding.scala new file mode 100644 index 000000000000..cbe8c1f96362 --- /dev/null +++ b/library/src/scala/quoted/matching/Binding.scala @@ -0,0 +1,34 @@ +package scala.quoted +package matching + +import scala.tasty.Reflection // TODO do not depend on reflection directly + +/** Binding of an Expr[T] used to know if some Expr[T] is a reference to the binding + * + * @param name string name of this binding + * @param id unique id used for referential equality + */ +class Binding[-T] private[scala](val name: String, private[Binding] val id: Object) { self => + + override def equals(obj: Any): Boolean = obj match { + case obj: Binding[_] => obj.id == id + case _ => false + } + + override def hashCode(): Int = id.hashCode() + +} + +object Binding { + + def unapply[T](expr: Expr[T])(implicit reflect: Reflection): Option[Binding[T]] = { + import reflect._ + expr.unseal match { + case Term.IsIdent(ref) => + val sym = ref.symbol + Some(new Binding[T](sym.name, sym)) + case _ => None + } + } + +} diff --git a/library/src/scala/quoted/matching/Literal.scala b/library/src/scala/quoted/matching/Literal.scala new file mode 100644 index 000000000000..e1f476af7abc --- /dev/null +++ b/library/src/scala/quoted/matching/Literal.scala @@ -0,0 +1,29 @@ +package scala.quoted.matching + +import scala.quoted.Expr + +import scala.tasty.Reflection // TODO do not depend on reflection directly + +/** Matches expressions containing literal values and extracts the value. + * + * Usage: + * ``` + * (x: Expr[B]) match { + * case Literal(value: B) => ... + * } + * ``` + */ +object Literal { + + def unapply[T](expr: Expr[T])(implicit reflect: Reflection): Option[T] = { + import reflect._ + def literal(tree: Term): Option[T] = tree match { + case Term.Literal(c) => Some(c.value.asInstanceOf[T]) + case Term.Block(Nil, e) => literal(e) + case Term.Inlined(_, Nil, e) => literal(e) + case _ => None + } + literal(expr.unseal) + } + +} diff --git a/library/src/scala/quoted/matching/Repeated.scala b/library/src/scala/quoted/matching/Repeated.scala new file mode 100644 index 000000000000..5788ad0724a9 --- /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 sequence of expressions */ +object Repeated { + + def unapply[T](expr: Expr[Seq[T]])(implicit reflect: Reflection): Option[Seq[Expr[T]]] = { + import reflect._ + def repeated(tree: Term): Option[Seq[Expr[T]]] = tree match { + case Term.Repeated(elems, _) => Some(elems.map(x => x.seal.asInstanceOf[Expr[T]])) + case Term.Block(Nil, e) => repeated(e) + case Term.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 c19728b38714..52242e9c6fe5 100644 --- a/library/src/scala/tasty/reflect/Kernel.scala +++ b/library/src/scala/tasty/reflect/Kernel.scala @@ -1387,7 +1387,6 @@ trait Kernel { /** Convert `Term` to an `quoted.Expr[Any]` */ def QuotedExpr_seal(self: Term)(implicit ctx: Context): scala.quoted.Expr[Any] - /** Convert `Type` to an `quoted.Type[_]` */ def QuotedType_seal(self: Type)(implicit ctx: Context): scala.quoted.Type[_] diff --git a/library/src/scala/tasty/util/ConstantExtractor.scala b/library/src/scala/tasty/util/ConstantExtractor.scala index 0ce44288e629..865e26594e6b 100644 --- a/library/src/scala/tasty/util/ConstantExtractor.scala +++ b/library/src/scala/tasty/util/ConstantExtractor.scala @@ -3,6 +3,8 @@ package scala.tasty.util import scala.quoted.Expr import scala.tasty.Reflection +import scala.deprecated + /** * Usage: * @@ -15,6 +17,7 @@ import scala.tasty.Reflection * } * ``` */ +@deprecated("Use scala.quoted.matching.Literal", "0.14.0") class ConstantExtractor[R <: Reflection with Singleton](val reflect: Reflection) { import reflect._ diff --git a/tests/neg/tasty-macro-assert-1/quoted_1.scala b/tests/neg/tasty-macro-assert-1/quoted_1.scala index 01f7b1d0a229..615cac44968e 100644 --- a/tests/neg/tasty-macro-assert-1/quoted_1.scala +++ b/tests/neg/tasty-macro-assert-1/quoted_1.scala @@ -26,14 +26,14 @@ object Asserts { object OpsTree { def unapply(arg: Term): Option[Term] = arg match { - case Apply(TypeApply(term, _), left :: Nil) if isOps(term.tpe) => + case Term.Apply(Term.TypeApply(term, _), left :: Nil) if isOps(term.tpe) => Some(left) case _ => None } } tree match { - case Inlined(_, Nil, Apply(Select(OpsTree(left), op), right :: Nil)) => + case Term.Inlined(_, Nil, Term.Apply(Term.Select(OpsTree(left), op), right :: Nil)) => '{assertTrue(${left.seal.cast[Boolean]})} // Buggy code. To generate the errors case _ => '{assertTrue($cond)} diff --git a/tests/neg/tasty-macro-assert-2/quoted_1.scala b/tests/neg/tasty-macro-assert-2/quoted_1.scala index 0cd616eaf32c..9677dc830184 100644 --- a/tests/neg/tasty-macro-assert-2/quoted_1.scala +++ b/tests/neg/tasty-macro-assert-2/quoted_1.scala @@ -26,14 +26,14 @@ object Asserts { object OpsTree { def unapply(arg: Term): Option[Term] = arg match { - case Apply(TypeApply(term, _), left :: Nil) if isOps(term.tpe) => + case Term.Apply(Term.TypeApply(term, _), left :: Nil) if isOps(term.tpe) => Some(left) case _ => None } } tree match { - case Inlined(_, Nil, Apply(Select(OpsTree(left), op), right :: Nil)) => + case Term.Inlined(_, Nil, Term.Apply(Term.Select(OpsTree(left), op), right :: Nil)) => '{assertTrue(${left.seal.cast[Boolean]})} // Buggy code. To generate the errors case _ => '{assertTrue($cond)} diff --git a/tests/run-with-compiler/quote-matcher-runtime.check b/tests/run-with-compiler/quote-matcher-runtime.check new file mode 100644 index 000000000000..c32952cd20d6 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-runtime.check @@ -0,0 +1,703 @@ +Matches + +Scrutinee: 1 +Pattern: 1 +Result: Some(List()) + +Scrutinee: (1: scala.Int) +Pattern: 1 +Result: None + +Scrutinee: (1: scala.Int) +Pattern: (1: scala.Int) +Result: Some(List()) + +Scrutinee: 1 +Pattern: (1: scala.Int) +Result: None + +Scrutinee: 3 +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(3))) + +Scrutinee: x +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(x))) + +Scrutinee: 5 +Pattern: scala.runtime.quoted.Matcher.hole[scala.Any] +Result: Some(List(Expr(5))) + +Scrutinee: 6.+(x) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(6.+(x)))) + +Scrutinee: 6.+(x) +Pattern: 6.+(scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: Some(List(Expr(x))) + +Scrutinee: 6.+(x) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int].+(x) +Result: Some(List(Expr(6))) + +Scrutinee: 6.+(x) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int].+(scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: Some(List(Expr(6), Expr(x))) + +Scrutinee: 6.+(x).+(y) +Pattern: 6.+(scala.runtime.quoted.Matcher.hole[scala.Int]).+(y) +Result: Some(List(Expr(x))) + +Scrutinee: f(4) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(f(4)))) + +Scrutinee: f(5) +Pattern: f(scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: Some(List(Expr(5))) + +Scrutinee: g[scala.Int] +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(g[scala.Int]))) + +Scrutinee: h[scala.Int](7) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(h[scala.Int](7)))) + +Scrutinee: h[scala.Int](8) +Pattern: h[scala.Int](scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: Some(List(Expr(8))) + +Scrutinee: Test.this +Pattern: Test.this +Result: Some(List()) + +Scrutinee: Test.this +Pattern: scala.runtime.quoted.Matcher.hole[this.type] +Result: Some(List(Expr(Test.this))) + +Scrutinee: new Foo(1) +Pattern: new Foo(1) +Result: Some(List()) + +Scrutinee: new Foo(1) +Pattern: scala.runtime.quoted.Matcher.hole[Foo] +Result: Some(List(Expr(new Foo(1)))) + +Scrutinee: new Foo(1) +Pattern: new Foo(scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: Some(List(Expr(1))) + +Scrutinee: if (b) x else y +Pattern: if (b) x else y +Result: Some(List()) + +Scrutinee: if (b) x else y +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(if (b) x else y))) + +Scrutinee: if (b) x else y +Pattern: if (scala.runtime.quoted.Matcher.hole[scala.Boolean]) scala.runtime.quoted.Matcher.hole[scala.Int] else scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(b), Expr(x), Expr(y))) + +Scrutinee: while (b) { + x + () +} +Pattern: while (b) { + x + () +} +Result: Some(List()) + +Scrutinee: while (b) { + x + () +} +Pattern: scala.runtime.quoted.Matcher.hole[scala.Unit] +Result: Some(List(Expr(while (b) { + x + () +}))) + +Scrutinee: while (b) { + x + () +} +Pattern: while (scala.runtime.quoted.Matcher.hole[scala.Boolean]) { + scala.runtime.quoted.Matcher.hole[scala.Int] + () +} +Result: Some(List(Expr(b), Expr(x))) + +Scrutinee: z = 4 +Pattern: z = 4 +Result: Some(List()) + +Scrutinee: z = 4 +Pattern: scala.runtime.quoted.Matcher.hole[scala.Unit] +Result: Some(List(Expr(z = 4))) + +Scrutinee: z = 4 +Pattern: z = scala.runtime.quoted.Matcher.hole[scala.Int] +Result: Some(List(Expr(4))) + +Scrutinee: z = 4 +Pattern: scala.runtime.quoted.Matcher.varHole = 4 +Result: Some(List(Binding(z))) + +Scrutinee: 1 +Pattern: 1 +Result: Some(List()) + +Scrutinee: 1 +Pattern: 1 +Result: Some(List()) + +Scrutinee: fs() +Pattern: fs() +Result: Some(List()) + +Scrutinee: fs() +Pattern: fs((scala.runtime.quoted.Matcher.hole[scala.Seq[scala.Int]]: scala.[scala.Int])) +Result: Some(List(Expr())) + +Scrutinee: fs((1, 2, 3: scala.[scala.Int])) +Pattern: fs((1, 2, 3: scala.[scala.Int])) +Result: Some(List()) + +Scrutinee: fs((1, 2, 3: scala.[scala.Int])) +Pattern: fs((scala.runtime.quoted.Matcher.hole[scala.Int], scala.runtime.quoted.Matcher.hole[scala.Int], 3: scala.[scala.Int])) +Result: Some(List(Expr(1), Expr(2))) + +Scrutinee: fs((1, 2, 3: scala.[scala.Int])) +Pattern: fs((scala.runtime.quoted.Matcher.hole[scala.Seq[scala.Int]]: scala.[scala.Int])) +Result: Some(List(Expr(1, 2, 3))) + +Scrutinee: f2(1, 2) +Pattern: f2(1, 2) +Result: Some(List()) + +Scrutinee: f2(a = 1, b = 2) +Pattern: f2(a = 1, b = 2) +Result: Some(List()) + +Scrutinee: f2(a = 1, b = 2) +Pattern: f2(a = scala.runtime.quoted.Matcher.hole[scala.Int], b = scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: Some(List(Expr(1), Expr(2))) + +Scrutinee: super.toString() +Pattern: super.toString() +Result: Some(List()) + +Scrutinee: (() => "abc") +Pattern: scala.runtime.quoted.Matcher.hole[scala.Function0[scala.Predef.String]] +Result: Some(List(Expr((() => "abc")))) + +Scrutinee: (() => "abc").apply() +Pattern: scala.runtime.quoted.Matcher.hole[scala.Function0[scala.Predef.String]].apply() +Result: Some(List(Expr((() => "abc")))) + +Scrutinee: ((x: scala.Int) => "abc") +Pattern: scala.runtime.quoted.Matcher.hole[scala.Function1[scala.Int, scala.Predef.String]] +Result: Some(List(Expr(((x: scala.Int) => "abc")))) + +Scrutinee: ((x: scala.Int) => "abc").apply(4) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Function1[scala.Int, scala.Predef.String]].apply(4) +Result: Some(List(Expr(((x: scala.Int) => "abc")))) + +Scrutinee: ((x: scala.Int) => "abc") +Pattern: ((x: scala.runtime.quoted.Matcher.bindHole[scala.Int]) => scala.runtime.quoted.Matcher.hole[scala.Predef.String]) +Result: Some(List(Binding(x), Expr("abc"))) + +Scrutinee: scala.StringContext.apply(("abc", "xyz": scala.[scala.Predef.String])) +Pattern: scala.StringContext.apply(("abc", "xyz": scala.[scala.Predef.String])) +Result: Some(List()) + +Scrutinee: scala.StringContext.apply(("abc", "xyz": scala.[scala.Predef.String])) +Pattern: scala.StringContext.apply((scala.runtime.quoted.Matcher.hole[java.lang.String], scala.runtime.quoted.Matcher.hole[java.lang.String]: scala.[scala.Predef.String])) +Result: Some(List(Expr("abc"), Expr("xyz"))) + +Scrutinee: scala.StringContext.apply(("abc", "xyz": scala.[scala.Predef.String])) +Pattern: scala.StringContext.apply((scala.runtime.quoted.Matcher.hole[scala.Seq[scala.Predef.String]]: scala.[scala.Predef.String])) +Result: Some(List(Expr("abc", "xyz"))) + +Scrutinee: { + val a: scala.Int = 45 + () +} +Pattern: { + val a: scala.Int = 45 + () +} +Result: Some(List()) + +Scrutinee: { + val a: scala.Int = 45 + () +} +Pattern: { + val a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.runtime.quoted.Matcher.bindHole[scala.Int]] + () +} +Result: Some(List(Binding(a), Expr(45))) + +Scrutinee: { + val a: scala.Int = 45 + () +} +Pattern: { + lazy val a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + val a: scala.Int = 45 + () +} +Pattern: { + var a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + val a: scala.Int = 45 + () +} +Pattern: { + var a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.runtime.quoted.Matcher.bindHole[scala.Int]] + () +} +Result: None + +Scrutinee: { + lazy val a: scala.Int = 45 + () +} +Pattern: { + val a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + lazy val a: scala.Int = 45 + () +} +Pattern: { + lazy val a: scala.Int = 45 + () +} +Result: Some(List()) + +Scrutinee: { + lazy val a: scala.Int = 45 + () +} +Pattern: { + var a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + lazy val a: scala.Int = 45 + () +} +Pattern: { + val a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.runtime.quoted.Matcher.bindHole[scala.Int]] + () +} +Result: None + +Scrutinee: { + lazy val a: scala.Int = 45 + () +} +Pattern: { + var a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.runtime.quoted.Matcher.bindHole[scala.Int]] + () +} +Result: None + +Scrutinee: { + var a: scala.Int = 45 + () +} +Pattern: { + val a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + var a: scala.Int = 45 + () +} +Pattern: { + lazy val a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + var a: scala.Int = 45 + () +} +Pattern: { + var a: scala.Int = 45 + () +} +Result: Some(List()) + +Scrutinee: { + var a: scala.Int = 45 + () +} +Pattern: { + val a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.runtime.quoted.Matcher.bindHole[scala.Int]] + () +} +Result: None + +Scrutinee: { + var a: scala.Int = 45 + () +} +Pattern: { + lazy val a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.runtime.quoted.Matcher.bindHole[scala.Int]] + () +} +Result: None + +Scrutinee: { + scala.Predef.println() + scala.Predef.println() +} +Pattern: { + scala.Predef.println() + scala.Predef.println() +} +Result: Some(List()) + +Scrutinee: { + scala.Predef.println() + scala.Predef.println() +} +Pattern: { + scala.Predef.println() + scala.Predef.println() +} +Result: Some(List()) + +Scrutinee: { + scala.Predef.println() + scala.Predef.println() +} +Pattern: { + scala.Predef.println() + scala.Predef.println() +} +Result: Some(List()) + +Scrutinee: { + scala.Predef.println() + scala.Predef.println() +} +Pattern: { + scala.Predef.println() + scala.Predef.println() +} +Result: Some(List()) + +Scrutinee: { + scala.Predef.println() + scala.Predef.println() +} +Pattern: { + scala.Predef.println() + scala.Predef.println() +} +Result: Some(List()) + +Scrutinee: { + def a: scala.Int = 45 + () +} +Pattern: { + def a: scala.Int = 45 + () +} +Result: Some(List()) + +Scrutinee: { + def a: scala.Int = 45 + () +} +Pattern: { + def a: scala.runtime.quoted.Matcher.bindHole[scala.Int] = scala.runtime.quoted.Matcher.hole[scala.Int] + () +} +Result: Some(List(Binding(a), Expr(45))) + +Scrutinee: { + def a(x: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.Int): scala.Int = 45 + () +} +Result: Some(List()) + +Scrutinee: { + def a(x: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.Int, y: scala.Int): scala.Int = 45 + () +} +Result: None + +Scrutinee: { + def a(x: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.Int)(y: scala.Int): scala.Int = 45 + () +} +Result: None + +Scrutinee: { + def a(x: scala.Int, y: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.Int): scala.Int = 45 + () +} +Result: None + +Scrutinee: { + def a(x: scala.Int)(y: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.Int): scala.Int = 45 + () +} +Result: None + +Scrutinee: { + def a(x: scala.Predef.String): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.Predef.String): scala.Int = 45 + () +} +Result: Some(List()) + +Scrutinee: { + def a(x: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.runtime.quoted.Matcher.bindHole[scala.Int]): scala.Int = 45 + () +} +Result: Some(List(Binding(x))) + +Scrutinee: { + def a(x: scala.Int): scala.Int = 45 + () +} +Pattern: { + def a(x: scala.runtime.quoted.Matcher.bindHole[scala.Int]): scala.runtime.quoted.Matcher.bindHole[scala.Int] = 45 + () +} +Result: Some(List(Binding(a), Binding(x))) + +Scrutinee: { + def a(x: scala.Int): scala.Int = x + () +} +Pattern: { + def b(y: scala.Int): scala.Int = y + () +} +Result: Some(List()) + +Scrutinee: { + def a: scala.Int = a + () +} +Pattern: { + def b: scala.Int = b + () +} +Result: Some(List()) + +Scrutinee: { + lazy val a: scala.Int = a + () +} +Pattern: { + lazy val b: scala.Int = b + () +} +Result: Some(List()) + +Scrutinee: 1 match { + case _ => + 2 +} +Pattern: 1 match { + case _ => + 2 +} +Result: Some(List()) + +Scrutinee: 1 match { + case _ => + 2 +} +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int] match { + case _ => + scala.runtime.quoted.Matcher.hole[scala.Int] +} +Result: Some(List(Expr(1), Expr(2))) + +Scrutinee: scala.Predef.??? match { + case scala.None => + 2 +} +Pattern: scala.Predef.??? match { + case scala.None => + 2 +} +Result: Some(List()) + +Scrutinee: scala.Predef.??? match { + case scala.Some(1) => + 2 +} +Pattern: scala.Predef.??? match { + case scala.Some(1) => + 2 +} +Result: Some(List()) + +Scrutinee: scala.Predef.??? match { + case scala.Some(1) => + 2 +} +Pattern: scala.Predef.??? match { + case scala.Some(scala.runtime.quoted.Matcher.patternHole()) => + 2 +} +Result: Some(List(Expr(1))) + +Scrutinee: scala.Predef.??? match { + case scala.Some(n) => + 2 +} +Pattern: scala.Predef.??? match { + case scala.Some(scala.runtime.quoted.Matcher.patternBindHole(n)) => + 2 +} +Result: Some(List(Binding(n))) + +Scrutinee: scala.Predef.??? match { + case scala.Some(n @ scala.Some(m)) => + 2 +} +Pattern: scala.Predef.??? match { + case scala.Some(scala.runtime.quoted.Matcher.patternBindHole(n @ scala.Some(scala.runtime.quoted.Matcher.patternBindHole(m)))) => + 2 +} +Result: Some(List(Binding(n), Binding(m))) + +Scrutinee: try 1 catch { + case _ => + 2 +} +Pattern: try 1 catch { + case _ => + 2 +} +Result: Some(List()) + +Scrutinee: try 1 finally { + 2 + () +} +Pattern: try 1 finally { + 2 + () +} +Result: Some(List()) + +Scrutinee: try 1 catch { + case _ => + 2 +} +Pattern: try scala.runtime.quoted.Matcher.hole[scala.Int] catch { + case _ => + scala.runtime.quoted.Matcher.hole[scala.Int] +} +Result: Some(List(Expr(1), Expr(2))) + +Scrutinee: try 1 finally { + 2 + () +} +Pattern: try scala.runtime.quoted.Matcher.hole[scala.Int] finally { + scala.runtime.quoted.Matcher.hole[scala.Int] + () +} +Result: Some(List(Expr(1), Expr(2))) + + +No match + +Scrutinee: 1 +Pattern: 2 +Result: None + +Scrutinee: 4 +Pattern: scala.runtime.quoted.Matcher.hole[scala.Predef.String] +Result: None + +Scrutinee: 6.+(x) +Pattern: 7.+(scala.runtime.quoted.Matcher.hole[scala.Int]) +Result: None + +Scrutinee: 6.+(x) +Pattern: scala.runtime.quoted.Matcher.hole[scala.Int].+(4) +Result: None + +Scrutinee: g[scala.Int] +Pattern: scala.runtime.quoted.Matcher.hole[scala.Predef.String] +Result: None + +Scrutinee: h[scala.Int](7) +Pattern: h[scala.Predef.String](scala.runtime.quoted.Matcher.hole[scala.Predef.String]) +Result: None + +Scrutinee: h[scala.Int](6) +Pattern: h[scala.Int](7) +Result: None + +Scrutinee: z = 4 +Pattern: z = 5 +Result: None + +Scrutinee: z = 4 +Pattern: z2 = 4 +Result: None + diff --git a/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala b/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala new file mode 100644 index 000000000000..ff3d020db707 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala @@ -0,0 +1,32 @@ +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +object Macros { + + inline def matches[A, B](a: => A, b: => B): Unit = ${impl('a, 'b)} + + private def impl[A, B](a: Expr[A], b: Expr[B])(implicit reflect: Reflection): Expr[Unit] = { + import reflect._ + + val res = scala.runtime.quoted.Matcher.unapply[Tuple](a)(b, reflect).map { tup => + tup.toArray.toList.map { + case r: Expr[_] => + s"Expr(${r.unseal.showCode})" + case r: quoted.Type[_] => + s"Type(${r.unseal.showCode})" + case r: Binding[_] => + s"Binding(${r.name})" + } + } + + '{ + println("Scrutinee: " + ${a.unseal.showCode.toExpr}) + println("Pattern: " + ${b.unseal.showCode.toExpr}) + println("Result: " + ${res.toString.toExpr}) + println() + } + } + +} diff --git a/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala b/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala new file mode 100644 index 000000000000..ee89b8d00941 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala @@ -0,0 +1,144 @@ + +import Macros._ + +import scala.runtime.quoted.Matcher._ + +object Test { + + def main(args: Array[String]): Unit = { + val b: Boolean = true + val x: Int = 42 + val y: Int = 52 + var z: Int = 62 + var z2: Int = 62 + def f(a: Int): Int = 72 + def f2(a: Int, b: Int): Int = 72 + def g[A]: A = ??? + def h[A](a: A): A = a + def fs(a: Int*): Int = 72 + + // Matches + println("Matches") + println() + + matches(1, 1) + matches(1: Int, 1) + matches(1: Int, 1: Int) + matches(1, 1: Int) + matches(3, hole[Int]) + matches(x, hole[Int]) + matches(5, hole[Any]) + matches(6 + x, hole[Int]) + matches(6 + x, 6 + hole[Int]) + matches(6 + x, hole[Int] + x) + matches(6 + x, hole[Int] + hole[Int]) + matches(6 + x + y, 6 + hole[Int] + y) + matches(f(4), hole[Int]) + matches(f(5), f(hole[Int])) + matches(g[Int], hole[Int]) + matches(h[Int](7), hole[Int]) + matches(h[Int](8), h[Int](hole[Int])) + matches(this, this) + matches(this, hole[this.type]) + matches(new Foo(1), new Foo(1)) + matches(new Foo(1), hole[Foo]) + matches(new Foo(1), new Foo(hole[Int])) + matches(if (b) x else y, if (b) x else y) + matches(if (b) x else y, hole[Int]) + matches(if (b) x else y, if (hole[Boolean]) hole[Int] else hole[Int]) + matches(while (b) x, while (b) x) + matches(while (b) x, hole[Unit]) + matches(while (b) x, while (hole[Boolean]) hole[Int]) + matches({z = 4}, {z = 4}) + matches({z = 4}, hole[Unit]) + matches({z = 4}, {z = hole[Int]}) + matches({z = 4}, {varHole = 4}) + matches(1, {1}) + matches({1}, 1) + // Should these match? + // matches({(); 1}, 1) + // matches(1, {(); 1}) + matches(fs(), fs()) + matches(fs(), fs(hole[Seq[Int]]: _*)) + matches(fs(1, 2, 3), fs(1, 2, 3)) + matches(fs(1, 2, 3), fs(hole[Int], hole[Int], 3)) + matches(fs(1, 2, 3), fs(hole[Seq[Int]]: _*)) + matches(f2(1, 2), f2(1, 2)) + matches(f2(a = 1, b = 2), f2(a = 1, b = 2)) + matches(f2(a = 1, b = 2), f2(a = hole[Int], b = hole[Int])) + // Should these match? + // matches(f2(a = 1, b = 2), f2(1, 2)) + // matches(f2(b = 2, a = 1), f2(1, 2)) + matches(super.toString, super.toString) + matches(() => "abc", hole[() => String]) + matches((() => "abc")(), (hole[() => String]).apply()) + matches((x: Int) => "abc", hole[Int=> String]) + matches(((x: Int) => "abc")(4), (hole[Int => String]).apply(4)) + matches((x: Int) => "abc", (x: bindHole[Int]) => hole[String]) + matches(StringContext("abc", "xyz"), StringContext("abc", "xyz")) + matches(StringContext("abc", "xyz"), StringContext(hole, hole)) + matches(StringContext("abc", "xyz"), StringContext(hole[Seq[String]]: _*)) + matches({ val a: Int = 45 }, { val a: Int = 45 }) + matches({ val a: Int = 45 }, { val a: bindHole[Int] = hole }) + matches({ val a: Int = 45 }, { lazy val a: Int = 45 }) + matches({ val a: Int = 45 }, { var a: Int = 45 }) + matches({ val a: Int = 45 }, { var a: bindHole[Int] = hole }) + matches({ lazy val a: Int = 45 }, { val a: Int = 45 }) + matches({ lazy val a: Int = 45 }, { lazy val a: Int = 45 }) + matches({ lazy val a: Int = 45 }, { var a: Int = 45 }) + matches({ lazy val a: Int = 45 }, { val a: bindHole[Int] = hole }) + matches({ lazy val a: Int = 45 }, { var a: bindHole[Int] = hole }) + matches({ var a: Int = 45 }, { val a: Int = 45 }) + matches({ var a: Int = 45 }, { lazy val a: Int = 45 }) + matches({ var a: Int = 45 }, { var a: Int = 45 }) + matches({ var a: Int = 45 }, { val a: bindHole[Int] = hole }) + matches({ var a: Int = 45 }, { lazy val a: bindHole[Int] = hole }) + matches({ println(); println() }, { println(); println() }) + matches({ { println() }; println() }, { println(); println() }) + matches({ println(); { println() } }, { println(); println() }) + matches({ println(); println() }, { println(); { println() } }) + matches({ println(); println() }, { { println() }; println() }) + matches({ def a: Int = 45 }, { def a: Int = 45 }) + matches({ def a: Int = 45 }, { def a: bindHole[Int] = hole[Int] }) + matches({ def a(x: Int): Int = 45 }, { def a(x: Int): Int = 45 }) + matches({ def a(x: Int): Int = 45 }, { def a(x: Int, y: Int): Int = 45 }) + matches({ def a(x: Int): Int = 45 }, { def a(x: Int)(y: Int): Int = 45 }) + matches({ def a(x: Int, y: Int): Int = 45 }, { def a(x: Int): Int = 45 }) + matches({ def a(x: Int)(y: Int): Int = 45 }, { def a(x: Int): Int = 45 }) + matches({ def a(x: String): Int = 45 }, { def a(x: String): Int = 45 }) + matches({ def a(x: Int): Int = 45 }, { def a(x: bindHole[Int]): Int = 45 }) + matches({ def a(x: Int): Int = 45 }, { def a(x: bindHole[Int]): bindHole[Int] = 45 }) + matches({ def a(x: Int): Int = x }, { def b(y: Int): Int = y }) + matches({ def a: Int = a }, { def b: Int = b }) + matches({ lazy val a: Int = a }, { lazy val b: Int = b }) + matches(1 match { case _ => 2 }, 1 match { case _ => 2 }) + matches(1 match { case _ => 2 }, hole[Int] match { case _ => hole[Int] }) + matches(??? match { case None => 2 }, ??? match { case None => 2 }) + matches(??? match { case Some(1) => 2 }, ??? match { case Some(1) => 2 }) + matches(??? match { case Some(1) => 2 }, ??? match { case Some(patternHole()) => 2 }) + matches(??? match { case Some(n) => 2 }, ??? match { case Some(patternBindHole(n)) => 2 }) + matches(??? match { case Some(n @ Some(m)) => 2 }, ??? match { case Some(patternBindHole(n @ Some(patternBindHole(m)))) => 2 }) + matches(try 1 catch { case _ => 2 }, try 1 catch { case _ => 2 }) + matches(try 1 finally 2, try 1 finally 2) + matches(try 1 catch { case _ => 2 }, try hole[Int] catch { case _ => hole[Int] }) + matches(try 1 finally 2, try hole[Int] finally hole[Int]) + + // No match + println() + println("No match") + println() + + matches(1, 2) + matches(4, hole[String]) + matches(6 + x, 7 + hole[Int]) + matches(6 + x, hole[Int] + 4) + matches(g[Int], hole[String]) + matches(h[Int](7), h[String](hole[String])) + matches(h[Int](6), h[Int](7)) + matches({z = 4}, {z = 5}) + matches({z = 4}, {z2 = 4}) + + } +} + +class Foo(a: Int) 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..6cb569f6dec5 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_1.scala @@ -0,0 +1,38 @@ + +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +import scala.runtime.quoted.Matcher._ + +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] = { + object StringContextExpr extends ExprMatch[Tuple1[Expr[Seq[String]]]]('{StringContext(hole[Seq[String]]: _*)}) // case '{ StringContext($args: _*) } => + + self match { + // case '{ StringContext(${args: _*}) } => + // case '{ StringContext(${Repeated(args)}: _*) } => + // scala.runtime.quoted.Matcher.unapplySeq(Tuple1(Repeated(parts: Seq[Expr[String]]): Expr[Seq[String]]))('{ StringContext(hole[Seq[String]]: _*) }, reflect) + case StringContextExpr(Tuple1(Repeated(parts))) => + val parts2 = parts.map(x => '{ $x.reverse }).toList.toExprOfList + '{ StringContext($parts2: _*).s($args: _*) } + case _ => + '{ "ERROR" } + } + + } + +} + +// +// Helper to abstract call to scala.runtime.quoted.Matcher.unapplySeq and setup an object with the unapply +// + +class ExprMatch[Tup <: Tuple](pattern: Expr[_]) { + def unapply(x: Expr[_])(implicit reflect: Reflection): Option[Tup] = + scala.runtime.quoted.Matcher.unapply(x)(pattern, reflect) +} 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..d2940d1cb909 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-string-interpolator/quoted_2.scala @@ -0,0 +1,12 @@ + +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-symantics-1/quoted_1.scala b/tests/run-with-compiler/quote-matcher-symantics-1/quoted_1.scala new file mode 100644 index 000000000000..3dd1e567f7b8 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics-1/quoted_1.scala @@ -0,0 +1,63 @@ + +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +import scala.runtime.quoted.Matcher._ + +object Macros { + + inline def lift[T](sym: Symantics[T])(a: => DSL): T = ${impl[T]('sym, 'a)} + + private def impl[T: Type](sym: Expr[Symantics[T]], a: Expr[DSL])(implicit reflect: Reflection): Expr[T] = { + + object ValueExpr extends ExprMatch[Tuple1[Expr[Int]]]('{LitDSL(hole[Int])}) // case '{ LitDSL($x) } => + object PlusExpr extends ExprMatch[Tuple2[Expr[DSL], Expr[DSL]]]('{hole[DSL] + hole[DSL]}) // case '{ ($x: DSL) + ($y: DSL) } => + object TimesExpr extends ExprMatch[Tuple2[Expr[DSL], Expr[DSL]]]('{hole[DSL] * hole[DSL]}) // case '{ ($x: DSL) * ($y: DSL) } => + + def lift(e: Expr[DSL]): Expr[T] = e match { + + // case '{ LitDSL(${Literal(c)}) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple1[Expr[DSL]]](Tuple1(Literal(c)))(/*implicits*/ '{ LitDSL(hole) }, reflect) => + case ValueExpr(Tuple1(Literal(c: Int))) => + '{ $sym.value(${c.toExpr}) } + + // case '{ ($x: DSL) + ($y: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Expr[DSL], Expr[DSL]]](Tuple2(x, y))(/*implicits*/ '{ (hole: DSL) + (hole: DSL) }, reflect) => + case PlusExpr(Tuple2(x, y)) => + '{ $sym.plus(${lift(x)}, ${lift(y)}) } + + // case '{ ($x: DSL) * ($y: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Expr[DSL], Expr[DSL]]](Tuple(x, y))(/*implicits*/ '{ (hole: DSL) * (hole: DSL) }, reflect) => + case TimesExpr(Tuple2(x, y)) => + '{ $sym.times(${lift(x)}, ${lift(y)}) } + + case _ => + import reflect._ + error("Expected explicit DSL", e.unseal.pos) + '{ ??? } + + } + + lift(a) + } + +} + +trait DSL { + def + (x: DSL): DSL = ??? + def * (x: DSL): DSL = ??? +} +case class LitDSL(x: Int) extends DSL + +trait Symantics[Num] { + def value(x: Int): Num + def plus(x: Num, y: Num): Num + def times(x: Num, y: Num): Num +} + +class ExprMatch[Tup <: Tuple](pattern: Expr[_]) { + def unapply(x: Expr[_])(implicit reflect: Reflection): Option[Tup] = + scala.runtime.quoted.Matcher.unapply(x)(pattern, reflect) +} diff --git a/tests/run-with-compiler/quote-matcher-symantics-1/quoted_2.scala b/tests/run-with-compiler/quote-matcher-symantics-1/quoted_2.scala new file mode 100644 index 000000000000..3a09c1c25b94 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics-1/quoted_2.scala @@ -0,0 +1,48 @@ + +import Macros._ + + +object Test { + + def main(args: Array[String]): Unit = { + println(lift(StringNum)(LitDSL(1))) + println(lift(ComputeNum)(LitDSL(1))) + println(lift(ASTNum)(LitDSL(1))) + println() + println(lift(StringNum)(LitDSL(1) + LitDSL(2))) + println(lift(ComputeNum)(LitDSL(1) + LitDSL(2))) + println(lift(ASTNum)(LitDSL(1) + LitDSL(2))) + println() + println(lift(StringNum)(LitDSL(1) * LitDSL(2))) + println(lift(ComputeNum)(LitDSL(1) * LitDSL(2))) + println(lift(ASTNum)(LitDSL(1) * LitDSL(2))) + println() + println(lift(StringNum)(LitDSL(1) + LitDSL(3) * LitDSL(4))) + println(lift(ComputeNum)(LitDSL(1) + LitDSL(3) * LitDSL(4))) + println(lift(ASTNum)(LitDSL(1) + LitDSL(3) * LitDSL(4))) + } + +} + +object StringNum extends Symantics[String] { + def value(x: Int): String = x.toString + def plus(x: String, y: String): String = s"($x + $y)" + def times(x: String, y: String): String = s"($x * $y)" +} + +object ComputeNum extends Symantics[Int] { + def value(x: Int): Int = x + def plus(x: Int, y: Int): Int = x + y + def times(x: Int, y: Int): Int = x * y +} + +object ASTNum extends Symantics[ASTNum] { + def value(x: Int): ASTNum = LitAST(x) + def plus(x: ASTNum, y: ASTNum): ASTNum = PlusAST(x, y) + def times(x: ASTNum, y: ASTNum): ASTNum = TimesAST(x, y) +} + +trait ASTNum +case class LitAST(x: Int) extends ASTNum +case class PlusAST(x: ASTNum, y: ASTNum) extends ASTNum +case class TimesAST(x: ASTNum, y: ASTNum) extends ASTNum diff --git a/tests/run-with-compiler/quote-matcher-symantics-2.check b/tests/run-with-compiler/quote-matcher-symantics-2.check new file mode 100644 index 000000000000..416ca0ef5f81 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics-2.check @@ -0,0 +1,23 @@ +1 +1 +LitAST(1) + +1 + 2 +3 +PlusAST(LitAST(1),LitAST(2)) + +1 * 2 +2 +TimesAST(LitAST(1),LitAST(2)) + +1 + 3 * 4 +13 +PlusAST(LitAST(1),TimesAST(LitAST(3),LitAST(4))) + +2 + 5 +7 +AppAST(, LitAST(5)) + +2 + 2 +4 +PlusAST(LitAST(2),LitAST(2)) diff --git a/tests/run-with-compiler/quote-matcher-symantics-2/quoted_1.scala b/tests/run-with-compiler/quote-matcher-symantics-2/quoted_1.scala new file mode 100644 index 000000000000..7fcbc1fc5636 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics-2/quoted_1.scala @@ -0,0 +1,143 @@ + +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +import scala.runtime.quoted.Matcher._ + +object Macros { + + inline def liftString(a: => DSL): String = ${impl(StringNum, 'a)} + + inline def liftCompute(a: => DSL): Int = ${impl(ComputeNum, 'a)} + + inline def liftAST(a: => DSL): ASTNum = ${impl(ASTNum, 'a)} + + private def impl[T: Type](sym: Symantics[T], a: Expr[DSL])(implicit reflect: Reflection): Expr[T] = { + + object ValueExpr extends ExprMatch[Tuple1[Expr[Int]]]('{LitDSL(hole[Int])}) // case '{ LitDSL($lit) } => + object PlusExpr extends ExprMatch[Tuple2[Expr[DSL], Expr[DSL]]]('{hole[DSL] + hole[DSL]}) // case '{ ($x: DSL) + ($y: DSL) } => + object TimesExpr extends ExprMatch[Tuple2[Expr[DSL], Expr[DSL]]]('{hole[DSL] * hole[DSL]}) // case '{ ($x: DSL) * ($y: DSL) } => + object AppExpr extends ExprMatch[Tuple2[Expr[DSL => DSL], Expr[DSL]]]('{ (hole[DSL => DSL]).apply(hole[DSL]) }) // case '{ ($f: DSL => DSL)($x: DSL) } => + object LambdaExpr extends ExprMatch[Tuple2[Binding[DSL], Expr[DSL]]]('{ (x: bindHole[DSL]) => hole[DSL] }) // case '{ ($x: DSL) => ($f: DSL) } => + object LetExpr extends ExprMatch[Tuple3[Binding[DSL], Expr[DSL], Expr[DSL]]]('{ val x: bindHole[DSL] = hole[DSL]; hole[DSL] }) // case '{ val $x: DSL = $value; $body: DSL } => + + def lift(e: Expr[DSL])(implicit env: Map[Binding[DSL], Expr[T]]): Expr[T] = e match { + + // case '{ LitDSL(${Literal(c)}) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple1[Expr[Int]]](Tuple1(Literal(c: Int)))(/*implicits*/ '{ LitDSL(hole[Int]) }, reflect) => + case ValueExpr(Tuple1(Literal(c))) => + sym.value(c) + + // case '{ ($x: DSL) + ($y: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Expr[DSL], Expr[DSL]]](Tuple2(x, y))(/*implicits*/ '{ (hole: DSL) + (hole: DSL) }, reflect) => + case PlusExpr(Tuple2(x, y)) => + sym.plus(lift(x), lift(y)) + + // case '{ ($x: DSL) * ($y: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Expr[DSL], Expr[DSL]]](Tuple2(x, y))(/*implicits*/ '{ (hole: DSL) * (hole: DSL) }, reflect) => + case TimesExpr(Tuple2(x, y)) => + sym.times(lift(x), lift(y)) + + // case '{ ($f: DSL => DSL)($x: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Expr[DSL => DSL], Expr[DSL]]](Tuple2(f, x))(/*implicits*/ '{ (hole[DSL => DSL])(hole[DSL]) }, reflect) => + case AppExpr(Tuple2(f, x)) => + sym.app(liftFun(f), lift(x)) + + // case '{ val $x: DSL = $value; $body: DSL } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Binding[DSL], Expr[DSL]]](Tuple2(f, x))(/*implicits*/ '{ val x: bindHole[DSL] = hole[DSL]; hole[DSL] }, reflect) => + case LetExpr(Tuple3(binding, value, rest)) => + // TODO push environment to Symantics: define `let` and `ref` + val liftedValue = lift(value) + lift(rest)(env + (binding -> liftedValue)) + + case Binding(b) if env.contains(b) => + env(b) + + case _ => + import reflect._ + error("Expected explicit DSL", e.unseal.pos) + ??? + + } + + def liftFun(e: Expr[DSL => DSL])(implicit env: Map[Binding[DSL], Expr[T]]): Expr[T => T] = e match { + // case '{ ($binding: DSL) => ($body: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple3[Binding[DSL], Expr[DSL], Expr[DSL]]](Tuple2(x, body))(/*implicits*/ '{ (x: bindHole[DSL]) => hole[DSL] }, reflect) => + case LambdaExpr(Tuple2(binding, body)) => + sym.lam((x: Expr[T]) => lift(body)(env + (binding -> x))) + + case _ => + import reflect._ + error("Expected explicit DSL => DSL", e.unseal.pos) + ??? + } + + lift(a)(Map.empty) + } + +} + +// +// DSL in which the user write the code +// + +trait DSL { + def + (x: DSL): DSL = ??? + def * (x: DSL): DSL = ??? +} +case class LitDSL(x: Int) extends DSL + +// +// Interpretation of the DSL +// + +trait Symantics[Num] { + def value(x: Int): Expr[Num] + def plus(x: Expr[Num], y: Expr[Num]): Expr[Num] + def times(x: Expr[Num], y: Expr[Num]): Expr[Num] + def app(f: Expr[Num => Num], x: Expr[Num]): Expr[Num] + def lam(body: Expr[Num] => Expr[Num]): Expr[Num => Num] +} + +object StringNum extends Symantics[String] { + def value(x: Int): Expr[String] = x.toString.toExpr + def plus(x: Expr[String], y: Expr[String]): Expr[String] = '{ s"${$x} + ${$y}" } // '{ x + " + " + y } + def times(x: Expr[String], y: Expr[String]): Expr[String] = '{ s"${$x} * ${$y}" } + def app(f: Expr[String => String], x: Expr[String]): Expr[String] = f(x) // functions are beta reduced + def lam(body: Expr[String] => Expr[String]): Expr[String => String] = '{ (x: String) => ${body('x)} } +} + +object ComputeNum extends Symantics[Int] { + def value(x: Int): Expr[Int] = x.toExpr + def plus(x: Expr[Int], y: Expr[Int]): Expr[Int] = '{ $x + $y } + def times(x: Expr[Int], y: Expr[Int]): Expr[Int] = '{ $x * $y } + def app(f: Expr[Int => Int], x: Expr[Int]): Expr[Int] = '{ $f($x) } + def lam(body: Expr[Int] => Expr[Int]): Expr[Int => Int] = '{ (x: Int) => ${body('x)} } +} + +object ASTNum extends Symantics[ASTNum] { + def value(x: Int): Expr[ASTNum] = '{ LitAST(${x.toExpr}) } + def plus(x: Expr[ASTNum], y: Expr[ASTNum]): Expr[ASTNum] = '{ PlusAST($x, $y) } + def times(x: Expr[ASTNum], y: Expr[ASTNum]): Expr[ASTNum] = '{ TimesAST($x, $y) } + def app(f: Expr[ASTNum => ASTNum], x: Expr[ASTNum]): Expr[ASTNum] = '{ AppAST($f, $x) } + def lam(body: Expr[ASTNum] => Expr[ASTNum]): Expr[ASTNum => ASTNum] = '{ (x: ASTNum) => ${body('x)} } +} + +trait ASTNum +case class LitAST(x: Int) extends ASTNum +case class PlusAST(x: ASTNum, y: ASTNum) extends ASTNum +case class TimesAST(x: ASTNum, y: ASTNum) extends ASTNum +case class AppAST(x: ASTNum => ASTNum, y: ASTNum) extends ASTNum { + override def toString: String = s"AppAST(, $y)" +} + +// +// Helper to abstract call to scala.runtime.quoted.Matcher.unapply and setup an object with the unapply +// + +class ExprMatch[Tup <: Tuple](pattern: Expr[_]) { + def unapply(x: Expr[_])(implicit reflect: Reflection): Option[Tup] = + scala.runtime.quoted.Matcher.unapply[Tup](x)(pattern, reflect) +} diff --git a/tests/run-with-compiler/quote-matcher-symantics-2/quoted_2.scala b/tests/run-with-compiler/quote-matcher-symantics-2/quoted_2.scala new file mode 100644 index 000000000000..f054aff99eae --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics-2/quoted_2.scala @@ -0,0 +1,33 @@ + +import Macros._ + + +object Test { + + def main(args: Array[String]): Unit = { + println(liftString(LitDSL(1))) + println(liftCompute(LitDSL(1))) + println(liftAST(LitDSL(1))) + println() + println(liftString(LitDSL(1) + LitDSL(2))) + println(liftCompute(LitDSL(1) + LitDSL(2))) + println(liftAST(LitDSL(1) + LitDSL(2))) + println() + println(liftString(LitDSL(1) * LitDSL(2))) + println(liftCompute(LitDSL(1) * LitDSL(2))) + println(liftAST(LitDSL(1) * LitDSL(2))) + println() + println(liftString(LitDSL(1) + LitDSL(3) * LitDSL(4))) + println(liftCompute(LitDSL(1) + LitDSL(3) * LitDSL(4))) + println(liftAST(LitDSL(1) + LitDSL(3) * LitDSL(4))) + println() + println(liftString(((x: DSL) => LitDSL(2) + x).apply(LitDSL(5)))) + println(liftCompute(((x: DSL) => LitDSL(2) + x).apply(LitDSL(5)))) + println(liftAST(((x: DSL) => LitDSL(2) + x).apply(LitDSL(5)))) + println() + println(liftString({ val x: DSL = LitDSL(2); x + x })) + println(liftCompute({ val x: DSL = LitDSL(2); x + x })) + println(liftAST({ val x: DSL = LitDSL(2); x + x })) + } + +} diff --git a/tests/run-with-compiler/quote-matcher-symantics.check b/tests/run-with-compiler/quote-matcher-symantics.check new file mode 100644 index 000000000000..e9c6e9691051 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics.check @@ -0,0 +1,15 @@ +1 +1 +LitAST(1) + +(1 + 2) +3 +PlusAST(LitAST(1),LitAST(2)) + +(1 * 2) +2 +TimesAST(LitAST(1),LitAST(2)) + +(1 + (3 * 4)) +13 +PlusAST(LitAST(1),TimesAST(LitAST(3),LitAST(4))) diff --git a/tests/run-with-compiler/quote-matcher-type-bind.check b/tests/run-with-compiler/quote-matcher-type-bind.check new file mode 100644 index 000000000000..26bc59686c2f --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-type-bind.check @@ -0,0 +1,2 @@ +g: 5 +f: abc diff --git a/tests/run-with-compiler/quote-matcher-type-bind/quoted_1.scala b/tests/run-with-compiler/quote-matcher-type-bind/quoted_1.scala new file mode 100644 index 000000000000..f602dbf829a0 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-type-bind/quoted_1.scala @@ -0,0 +1,52 @@ + +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +import scala.runtime.quoted.Matcher._ + +object Macros { + + inline def swapFandG(x: => Unit): Unit = ${impl('x)} + + private def impl(x: Expr[Unit])(implicit reflect: Reflection): Expr[Unit] = { + + type TT // type binding + object DSLf extends ExprMatch[Tuple2[Type[TT], Expr[TT]]]('{ type Binding$T1; DSL.f[Hole[Binding$T1]](hole[Binding$T1]) }) // case '{ DSL.f[$t]($x) } => + + x match { + // case '{ DSL.f[$t]($x) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Type[tt /*type binding*/], Expr[tt]]](Tuple2(t, x)(/*implicits*/ '{ DSL.f[Hole[Nothing, Any]](hole[Any])] }, reflect) if consforms(x, t) => + case DSLf(t, x) => + implicit val tt = t + '{ DSL.g[$t]($x) } + + // case '{ DSL.f[$t]($x) } => +// case DSLg(t, x) => +// '{ DSL.f[$t]($x) } + + case _ => + x + } + } + +} + +// +// DSL in which the user write the code +// + +object DSL { + def f[T](x: T): Unit = println("f: " + x.toString) + def g[T](x: T): Unit = println("g: " + x.toString) +} + +// +// Helper to abstract call to scala.runtime.quoted.Matcher.unapply and setup an object with the unapply +// + +class ExprMatch[Tup <: Tuple](pattern: Expr[_]) { + def unapply(x: Expr[_])(implicit reflect: Reflection): Option[Tup] = + scala.runtime.quoted.Matcher.unapply[Tup](x)(pattern, reflect) +} diff --git a/tests/run-with-compiler/quote-matcher-type-bind/quoted_2.scala b/tests/run-with-compiler/quote-matcher-type-bind/quoted_2.scala new file mode 100644 index 000000000000..58217ca100cb --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-type-bind/quoted_2.scala @@ -0,0 +1,12 @@ + +import Macros._ + + +object Test { + + def main(args: Array[String]): Unit = { + swapFandG(DSL.f(5)) +// swapFandG(DSL.g("abc")) + } + +}