From 35575aaaa9803bb802958cc208319a12a9a4a5e1 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 2 Apr 2019 09:22:20 +0200 Subject: [PATCH 1/8] Implement Matcher runtime logic This PR adds: * runtime logic for pattern matching on `'{ ... }` * `Literal` pattern to match a literal expression and extract its value --- .../tools/dotc/tastyreflect/KernelImpl.scala | 6 + .../scala/runtime/quoted/Matcher.scala | 255 +++++++++ .../scala/runtime/quoted/Matcher.scala | 11 + .../src/scala/quoted/matching/Literal.scala | 31 + .../src/scala/runtime/quoted/Matcher.scala | 10 - library/src/scala/tasty/reflect/Kernel.scala | 3 + .../quote-matcher-runtime.check | 540 ++++++++++++++++++ .../quote-matcher-runtime/quoted_1.scala | 30 + .../quote-matcher-runtime/quoted_2.scala | 137 +++++ .../quote-matcher-symantics-1/quoted_1.scala | 53 ++ .../quote-matcher-symantics-1/quoted_2.scala | 48 ++ .../quote-matcher-symantics.check | 15 + 12 files changed, 1129 insertions(+), 10 deletions(-) create mode 100644 library/src-bootstrapped/scala/runtime/quoted/Matcher.scala create mode 100644 library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala create mode 100644 library/src/scala/quoted/matching/Literal.scala delete mode 100644 library/src/scala/runtime/quoted/Matcher.scala create mode 100644 tests/run-with-compiler/quote-matcher-runtime.check create mode 100644 tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala create mode 100644 tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala create mode 100644 tests/run-with-compiler/quote-matcher-symantics-1/quoted_1.scala create mode 100644 tests/run-with-compiler/quote-matcher-symantics-1/quoted_2.scala create mode 100644 tests/run-with-compiler/quote-matcher-symantics.check diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala index 29b357bc238c..50162e45e613 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala @@ -1730,6 +1730,8 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util. // DEFINITIONS // + // Symbols + def Definitions_RootPackage: Symbol = defn.RootPackage def Definitions_RootClass: Symbol = defn.RootClass @@ -1778,6 +1780,10 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util. defn.FunctionClass(arity, isImplicit, isErased).asClass def Definitions_TupleClass(arity: Int): Symbol = defn.TupleType(arity).classSymbol.asClass + def Definitions_InternalQuoted_patternHole: Symbol = defn.InternalQuoted_patternHole + + // Types + def Definitions_UnitType: Type = defn.UnitType def Definitions_ByteType: Type = defn.ByteType def Definitions_ShortType: Type = defn.ShortType 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..c9e5383a4064 --- /dev/null +++ b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -0,0 +1,255 @@ +package scala.runtime.quoted + +import scala.annotation.internal.sharable + +import scala.quoted._ +import scala.tasty._ + +object Matcher { + + private final val debug = false + + /** + * + * @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 treesMatch(scrutinees: List[Tree], patterns: List[Tree]): Option[Tuple] = + if (scrutinees.size != patterns.size) None + else foldMatchings(scrutinees.zip(patterns).map(treeMatches): _*) + + (scrutinee, pattern) match { + // Normalize blocks without statements + case (Block(Nil, expr), _) => treeMatches(expr, pattern) + case (_, Block(Nil, pat)) => treeMatches(scrutinee, pat) + + // Match + case (IsTerm(scrutinee), TypeApply(patternHole, tpt :: Nil)) + if patternHole.symbol == kernel.Definitions_InternalQuoted_patternHole && scrutinee.tpe <:< tpt.tpe => + Some(Tuple1(scrutinee.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 = + 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 returnTptMatch = treeMatches(tpt1, tpt2) + val rhsEnv = env + (scrutinee.symbol -> pattern.symbol) + val rhsMatchings = treeOptMatches(rhs1, rhs2)(rhsEnv) + foldMatchings(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 tptMatch = treeMatches(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(typeParmasMatch, paramssMatch, tptMatch, rhsMatch) + + case (Lambda(_, tpt1), Lambda(_, tpt2)) => + // TODO match tpt1 with tpt2? + Some(()) + + case (Match(scru1, cases1), 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 (Try(body1, cases1, finalizer1), 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 _ => + 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(TypeApply(Select(patternHole @ Ident("patternHole"), "unapply"), List(tpt)), Nil, Nil)) + if patternHole.symbol.owner.fullName == "scala.runtime.quoted.Matcher$" => + (env, Some(Tuple1(v1.seal))) + + 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..c3a3a7f8cc3b --- /dev/null +++ b/library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -0,0 +1,11 @@ +package scala.runtime.quoted + +import scala.quoted._ +import scala.tasty._ + +object Matcher { + + def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = + throw new Exception("running on non bootstrapped library") + +} diff --git a/library/src/scala/quoted/matching/Literal.scala b/library/src/scala/quoted/matching/Literal.scala new file mode 100644 index 000000000000..c1257bbefbbd --- /dev/null +++ b/library/src/scala/quoted/matching/Literal.scala @@ -0,0 +1,31 @@ +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. + * It may match expressions of type Boolean, Byte, Short, Int, Long, + * Float, Double, Char, String, ClassTag, scala.Symbol, Null and Unit. + * + * Usage: + * ``` + * (x: Expr[B]) match { + * case Literal(value: B) => ... + * } + * ``` + */ +object Literal { + + def unapply[T](expr: Expr[T])(implicit reflect: Reflection): Option[T] = { + import reflect.{Literal => LiteralTree, _} // TODO rename reflect.Literal to avoid this clash + def literal(tree: Term): Option[T] = tree match { + case LiteralTree(c) => Some(c.value.asInstanceOf[T]) + case Block(Nil, e) => literal(e) + case Inlined(_, Nil, e) => literal(e) + case _ => None + } + literal(expr.unseal) + } + +} diff --git a/library/src/scala/runtime/quoted/Matcher.scala b/library/src/scala/runtime/quoted/Matcher.scala deleted file mode 100644 index b77baec24aec..000000000000 --- a/library/src/scala/runtime/quoted/Matcher.scala +++ /dev/null @@ -1,10 +0,0 @@ -package scala.runtime.quoted - -import scala.quoted.Expr -import scala.tasty.Reflection - -/** THIS IS A PLACEHOLDER - */ -object Matcher { - def unapply[Tup <: Tuple](scrut: Expr[_])(implicit pattern: Expr[_], reflection: Reflection): Option[Tup] = ??? -} diff --git a/library/src/scala/tasty/reflect/Kernel.scala b/library/src/scala/tasty/reflect/Kernel.scala index c19728b38714..9a5aa6687bc1 100644 --- a/library/src/scala/tasty/reflect/Kernel.scala +++ b/library/src/scala/tasty/reflect/Kernel.scala @@ -1444,6 +1444,9 @@ trait Kernel { def Definitions_TupleClass(arity: Int): Symbol + /** Symbol of scala.runtime.Quoted.patternHole */ + def Definitions_InternalQuoted_patternHole: Symbol + def Definitions_UnitType: Type def Definitions_ByteType: Type def Definitions_ShortType: Type 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..aad22c102e88 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-runtime.check @@ -0,0 +1,540 @@ +Scrutinee: 1 +Pattern: 1 +Result: Some(List()) + +Scrutinee: 1 +Pattern: 2 +Result: None + +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.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(3))) + +Scrutinee: x +Pattern: scala.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(x))) + +Scrutinee: 5 +Pattern: scala.internal.Quoted.patternHole[scala.Any] +Result: Some(List(Expr(5))) + +Scrutinee: 6.+(x) +Pattern: scala.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(6.+(x)))) + +Scrutinee: 6.+(x) +Pattern: 6.+(scala.internal.Quoted.patternHole[scala.Int]) +Result: Some(List(Expr(x))) + +Scrutinee: 6.+(x) +Pattern: scala.internal.Quoted.patternHole[scala.Int].+(x) +Result: Some(List(Expr(6))) + +Scrutinee: 6.+(x) +Pattern: scala.internal.Quoted.patternHole[scala.Int].+(scala.internal.Quoted.patternHole[scala.Int]) +Result: Some(List(Expr(6), Expr(x))) + +Scrutinee: 6.+(x).+(y) +Pattern: 6.+(scala.internal.Quoted.patternHole[scala.Int]).+(y) +Result: Some(List(Expr(x))) + +Scrutinee: 4 +Pattern: scala.internal.Quoted.patternHole[scala.Predef.String] +Result: None + +Scrutinee: 6.+(x) +Pattern: 7.+(scala.internal.Quoted.patternHole[scala.Int]) +Result: None + +Scrutinee: 6.+(x) +Pattern: scala.internal.Quoted.patternHole[scala.Int].+(4) +Result: None + +Scrutinee: g[scala.Int] +Pattern: scala.internal.Quoted.patternHole[scala.Predef.String] +Result: None + +Scrutinee: h[scala.Int](7) +Pattern: h[scala.Predef.String](scala.internal.Quoted.patternHole[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 + +Scrutinee: f(4) +Pattern: scala.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(f(4)))) + +Scrutinee: f(5) +Pattern: f(scala.internal.Quoted.patternHole[scala.Int]) +Result: Some(List(Expr(5))) + +Scrutinee: g[scala.Int] +Pattern: scala.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(g[scala.Int]))) + +Scrutinee: h[scala.Int](7) +Pattern: scala.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(h[scala.Int](7)))) + +Scrutinee: h[scala.Int](8) +Pattern: h[scala.Int](scala.internal.Quoted.patternHole[scala.Int]) +Result: Some(List(Expr(8))) + +Scrutinee: Test.this +Pattern: Test.this +Result: Some(List()) + +Scrutinee: Test.this +Pattern: scala.internal.Quoted.patternHole[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.internal.Quoted.patternHole[Foo] +Result: Some(List(Expr(new Foo(1)))) + +Scrutinee: new Foo(1) +Pattern: new Foo(scala.internal.Quoted.patternHole[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.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(if (b) x else y))) + +Scrutinee: if (b) x else y +Pattern: if (scala.internal.Quoted.patternHole[scala.Boolean]) scala.internal.Quoted.patternHole[scala.Int] else scala.internal.Quoted.patternHole[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.internal.Quoted.patternHole[scala.Unit] +Result: Some(List(Expr(while (b) { + x + () +}))) + +Scrutinee: while (b) { + x + () +} +Pattern: while (scala.internal.Quoted.patternHole[scala.Boolean]) { + scala.internal.Quoted.patternHole[scala.Int] + () +} +Result: Some(List(Expr(b), Expr(x))) + +Scrutinee: z = 4 +Pattern: z = 4 +Result: Some(List()) + +Scrutinee: z = 4 +Pattern: scala.internal.Quoted.patternHole[scala.Unit] +Result: Some(List(Expr(z = 4))) + +Scrutinee: z = 4 +Pattern: z = scala.internal.Quoted.patternHole[scala.Int] +Result: Some(List(Expr(4))) + +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.internal.Quoted.patternHole[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.internal.Quoted.patternHole[scala.Int], scala.internal.Quoted.patternHole[scala.Int], 3: scala.[scala.Int])) +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))) + +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.internal.Quoted.patternHole[scala.Int], b = scala.internal.Quoted.patternHole[scala.Int]) +Result: Some(List(Expr(1), Expr(2))) + +Scrutinee: super.toString() +Pattern: super.toString() +Result: Some(List()) + +Scrutinee: (() => "abc") +Pattern: scala.internal.Quoted.patternHole[scala.Function0[scala.Predef.String]] +Result: Some(List(Expr((() => "abc")))) + +Scrutinee: (() => "abc").apply() +Pattern: scala.internal.Quoted.patternHole[scala.Function0[scala.Predef.String]].apply() +Result: Some(List(Expr((() => "abc")))) + +Scrutinee: ((x: scala.Int) => "abc") +Pattern: scala.internal.Quoted.patternHole[scala.Function1[scala.Int, scala.Predef.String]] +Result: Some(List(Expr(((x: scala.Int) => "abc")))) + +Scrutinee: ((x: scala.Int) => "abc").apply(4) +Pattern: scala.internal.Quoted.patternHole[scala.Function1[scala.Int, scala.Predef.String]].apply(4) +Result: Some(List(Expr(((x: scala.Int) => "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.internal.Quoted.patternHole[java.lang.String], scala.internal.Quoted.patternHole[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.internal.Quoted.patternHole[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: { + lazy val a: scala.Int = 45 + () +} +Result: None + +Scrutinee: { + val a: scala.Int = 45 + () +} +Pattern: { + var a: scala.Int = 45 + () +} +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: { + 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: { + 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(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 = 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: 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: 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()) + 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..ff6f3a79aef9 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala @@ -0,0 +1,30 @@ +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})" + } + } + + '{ + 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..c014fb18406f --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala @@ -0,0 +1,137 @@ + +import Macros._ + +import scala.runtime.quoted.Matcher._ + +import scala.internal.Quoted.patternHole + +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(1, 1) + matches(1, 2) + matches(1: Int, 1) + matches(1: Int, 1: Int) + matches(1, 1: Int) + matches(3, patternHole[Int]) + matches(x, patternHole[Int]) + matches(5, patternHole[Any]) + matches(6 + x, patternHole[Int]) + matches(6 + x, 6 + patternHole[Int]) + matches(6 + x, patternHole[Int] + x) + matches(6 + x, patternHole[Int] + patternHole[Int]) + matches(6 + x + y, 6 + patternHole[Int] + y) + matches(4, patternHole[String]) + matches(6 + x, 7 + patternHole[Int]) + matches(6 + x, patternHole[Int] + 4) + matches(g[Int], patternHole[String]) + matches(h[Int](7), h[String](patternHole[String])) + matches(h[Int](6), h[Int](7)) + matches({z = 4}, {z = 5}) + matches({z = 4}, {z2 = 4}) + matches(f(4), patternHole[Int]) + matches(f(5), f(patternHole[Int])) + matches(g[Int], patternHole[Int]) + matches(h[Int](7), patternHole[Int]) + matches(h[Int](8), h[Int](patternHole[Int])) + matches(this, this) + matches(this, patternHole[this.type]) + matches(new Foo(1), new Foo(1)) + matches(new Foo(1), patternHole[Foo]) + matches(new Foo(1), new Foo(patternHole[Int])) + matches(if (b) x else y, if (b) x else y) + matches(if (b) x else y, patternHole[Int]) + matches(if (b) x else y, if (patternHole[Boolean]) patternHole[Int] else patternHole[Int]) + matches(while (b) x, while (b) x) + matches(while (b) x, patternHole[Unit]) + matches(while (b) x, while (patternHole[Boolean]) patternHole[Int]) + matches({z = 4}, {z = 4}) + matches({z = 4}, patternHole[Unit]) + matches({z = 4}, {z = patternHole[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(patternHole[Seq[Int]]: _*)) + matches(fs(1, 2, 3), fs(1, 2, 3)) + matches(fs(1, 2, 3), fs(patternHole[Int], patternHole[Int], 3)) + matches(fs(1, 2, 3), fs(patternHole[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 = patternHole[Int], b = patternHole[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", patternHole[() => String]) + matches((() => "abc")(), (patternHole[() => String]).apply()) + matches((x: Int) => "abc", patternHole[Int=> String]) + matches(((x: Int) => "abc")(4), (patternHole[Int => String]).apply(4)) + // matches((x: Int) => "abc", (x: bindHole[Int]) => patternHole[String]) + matches(StringContext("abc", "xyz"), StringContext("abc", "xyz")) + matches(StringContext("abc", "xyz"), StringContext(patternHole, patternHole)) + matches(StringContext("abc", "xyz"), StringContext(patternHole[Seq[String]]: _*)) + matches({ val a: Int = 45 }, { val a: Int = 45 }) + // matches({ val a: Int = 45 }, { val a: bindHole[Int] = patternHole }) + 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] = patternHole }) + 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] = patternHole }) + // matches({ lazy val a: Int = 45 }, { var a: bindHole[Int] = patternHole }) + 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] = patternHole }) + // matches({ var a: Int = 45 }, { lazy val a: bindHole[Int] = patternHole }) + 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] = patternHole[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 }, patternHole[Int] match { case _ => patternHole[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(patternMatchHole()) => 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 patternHole[Int] catch { case _ => patternHole[Int] }) + // matches(try 1 finally 2, try patternHole[Int] finally patternHole[Int]) + + + } +} + +class Foo(a: Int) 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..4f09d4afcc97 --- /dev/null +++ b/tests/run-with-compiler/quote-matcher-symantics-1/quoted_1.scala @@ -0,0 +1,53 @@ + +import scala.quoted._ +import scala.quoted.matching._ + +import scala.tasty.Reflection + +import scala.runtime.quoted.Matcher._ + +import scala.internal.Quoted.patternHole + +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] = { + + def lift(e: Expr[DSL]): Expr[T] = e match { + + case '{ LitDSL(${ Literal(c) }) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple1[Expr[Int]]](Tuple1(Literal(c)))(/*implicits*/ '{ LitDSL(patternHole[Int]) }, reflect) => + '{ $sym.value(${c.toExpr}) } + + case '{ ($x: DSL) + ($y: DSL) } => + // case scala.runtime.quoted.Matcher.unapply[Tuple2[Expr[DSL], Expr[DSL]]](Tuple2(x, y))(/*implicits*/ '{ patternHole[DSL] + patternHole[DSL] }, reflect) => + '{ $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*/ '{ patternHole[DSL] * patternHole[DSL] }, reflect) => + '{ $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 +} 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.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))) From 4f42d5f99a2cd5a5ba2b09e22ed220b90618111a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 2 Apr 2019 13:42:44 +0200 Subject: [PATCH 2/8] Replace tasty.util.ConstantExtractor with quoted.matching.Literal --- .../scala/tasty/util/ConstantExtractor.scala | 30 ------------------- .../quoted_1.scala | 6 ++-- .../quoted_1.scala | 15 +++++----- 3 files changed, 9 insertions(+), 42 deletions(-) delete mode 100644 library/src/scala/tasty/util/ConstantExtractor.scala diff --git a/library/src/scala/tasty/util/ConstantExtractor.scala b/library/src/scala/tasty/util/ConstantExtractor.scala deleted file mode 100644 index 0ce44288e629..000000000000 --- a/library/src/scala/tasty/util/ConstantExtractor.scala +++ /dev/null @@ -1,30 +0,0 @@ -package scala.tasty.util - -import scala.quoted.Expr -import scala.tasty.Reflection - -/** - * Usage: - * - * ``` - * val Constant = new ConstantExtractor(reflect) - * - * (x: Expr[B]) match { - * case Constant(value) => ... - * case x => ... - * } - * ``` - */ -class ConstantExtractor[R <: Reflection with Singleton](val reflect: Reflection) { - import reflect._ - - def unapply[T](expr: Expr[T]): Option[T] = { - def const(tree: Term): Option[T] = tree match { - case Literal(c) => Some(c.value.asInstanceOf[T]) - case Block(Nil, e) => const(e) - case Inlined(_, Nil, e) => const(e) - case _ => None - } - const(expr.unseal) - } -} diff --git a/tests/run-with-compiler/tasty-extractors-constants-2/quoted_1.scala b/tests/run-with-compiler/tasty-extractors-constants-2/quoted_1.scala index 14ebf857b29e..0e340d219a10 100644 --- a/tests/run-with-compiler/tasty-extractors-constants-2/quoted_1.scala +++ b/tests/run-with-compiler/tasty-extractors-constants-2/quoted_1.scala @@ -45,11 +45,9 @@ object Macros { } def power(n: Expr[Int], x: Expr[Double])(implicit reflect: Reflection): Expr[Double] = { - import reflect._ - - val Constant = new ConstantExtractor(reflect) + import quoted.matching.Literal n match { - case Constant(n1) => powerCode(n1, x) + case Literal(n1) => powerCode(n1, x) case _ => '{ dynamicPower($n, $x) } } } diff --git a/tests/run/tasty-extractors-constants-1/quoted_1.scala b/tests/run/tasty-extractors-constants-1/quoted_1.scala index b6bc1e221f0f..7daac0f6c867 100644 --- a/tests/run/tasty-extractors-constants-1/quoted_1.scala +++ b/tests/run/tasty-extractors-constants-1/quoted_1.scala @@ -1,6 +1,8 @@ import scala.quoted._ import scala.quoted.autolift._ +import scala.quoted.matching._ + import scala.tasty._ import scala.tasty.util._ @@ -9,19 +11,16 @@ object Macros { implicit inline def testMacro: Unit = ${impl} def impl(implicit reflect: Reflection): Expr[Unit] = { - import reflect._ val buff = new StringBuilder def stagedPrintln(x: Any): Unit = buff append java.util.Objects.toString(x) append "\n" - val Constant = new ConstantExtractor(reflect) - - 3.toExpr match { case Constant(n) => stagedPrintln(n) } - '{4} match { case Constant(n) => stagedPrintln(n) } - '{"abc"} match { case Constant(n) => stagedPrintln(n) } - '{null} match { case Constant(n) => stagedPrintln(n) } + 3.toExpr match { case Literal(n) => stagedPrintln(n) } + '{4} match { case Literal(n) => stagedPrintln(n) } + '{"abc"} match { case Literal(n) => stagedPrintln(n) } + '{null} match { case Literal(n) => stagedPrintln(n) } - '{new Object} match { case Constant(n) => println(n); case _ => stagedPrintln("OK") } + '{new Object} match { case Literal(n) => println(n); case _ => stagedPrintln("OK") } '{print(${buff.result()})} } From e917b1333958611f362c383ea4405e5239f3b1d7 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 2 Apr 2019 14:01:09 +0200 Subject: [PATCH 3/8] Add documentation --- .../scala/runtime/quoted/Matcher.scala | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala index c9e5383a4064..cc013c82c65e 100644 --- a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -9,15 +9,29 @@ object Matcher { private final val debug = false - /** + /** Pattern matches an the scrutineeExpr aquainsnt the patternExpr and returns a tuple + * with the matched holes if successful. * - * @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[_] + * Examples: + * - `Matcher.unapply('{ f(0, myInt) })('{ f(0, myInt) }, _)` + * will return `Some(())` (where `()` is a tuple of arity 0) + * - `Matcher.unapply('{ f(0, myInt) })('{ f(patternHole[Int], patternHole[Int]) }, _)` + * will return `Some(Tuple2('{0}, '{ myInt }))` + * - `Matcher.unapply('{ f(0, "abc") })('{ f(0, patternHole[Int]) }, _)` + * will return `None` due to the missmatch of types in the hole + * + * Holes: + * - scala.internal.Quoted.patternHole[T]: hole that matches an expression `x` of type `Expr[U]` + * if `U <:< T` and returns `x` as part of the match. + * + * @param scrutineeExpr `Expr[_]` on which we are pattern matching + * @param patternExpr `Expr[_]` containing the pattern tree + * @param reflection instance of the reflection API (implicitly provided by the macro) + * @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]`` */ def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = { import reflection._ + // TODO improve performance def treeMatches(scrutinee: Tree, pattern: Tree)(implicit env: Set[(Symbol, Symbol)]): Option[Tuple] = { @@ -38,15 +52,18 @@ object Matcher { case (Block(Nil, expr), _) => treeMatches(expr, pattern) case (_, Block(Nil, pat)) => treeMatches(scrutinee, pat) - // Match + // 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 => Some(Tuple1(scrutinee.seal)) - case (Inlined(_, Nil, scr), _) => - treeMatches(scr, pattern) - case (_, Inlined(_, Nil, pat)) => - treeMatches(scrutinee, pat) + // Normalize inline trees + case (Inlined(_, Nil, scr), _) => treeMatches(scr, pattern) + case (_, Inlined(_, Nil, pat)) => treeMatches(scrutinee, pat) + + // + // Match two equivalent trees + // case (Literal(constant1), Literal(constant2)) if constant1 == constant2 => Some(()) @@ -150,6 +167,7 @@ object Matcher { val finalizerMatch = treeOptMatches(finalizer1, finalizer2) foldMatchings(bodyMacth, casesMatch, finalizerMatch) + // No Match case _ => if (debug) println( @@ -245,7 +263,11 @@ object Matcher { treeMatches(scrutineeExpr.unseal, patternExpr.unseal)(Set.empty).asInstanceOf[Option[Tup]] } + /** Joins the mattchings into a single matching. If any matching is `None` the result is `None`. + * Otherwise the result is `Some` of the concatenation of the tupples. + */ private def foldMatchings(matchings: Option[Tuple]*): Option[Tuple] = { + // TODO improve performance matchings.foldLeft[Option[Tuple]](Some(())) { case (Some(acc), Some(holes)) => Some(acc ++ holes) case (_, _) => None From 41d24df474823659a9b779065d244e1eeba170b4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 2 Apr 2019 17:22:37 +0200 Subject: [PATCH 4/8] Fix check file name --- ...te-matcher-symantics.check => quote-matcher-symantics-1.check} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/run-with-compiler/{quote-matcher-symantics.check => quote-matcher-symantics-1.check} (100%) diff --git a/tests/run-with-compiler/quote-matcher-symantics.check b/tests/run-with-compiler/quote-matcher-symantics-1.check similarity index 100% rename from tests/run-with-compiler/quote-matcher-symantics.check rename to tests/run-with-compiler/quote-matcher-symantics-1.check From f04972f90d0ad5ed33a7445b01c7490c109f8418 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 5 Apr 2019 16:54:05 +0200 Subject: [PATCH 5/8] Normalize tree before pattern matching --- .../scala/runtime/quoted/Matcher.scala | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala index cc013c82c65e..72638014782b 100644 --- a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -47,20 +47,19 @@ object Matcher { if (scrutinees.size != patterns.size) None else foldMatchings(scrutinees.zip(patterns).map(treeMatches): _*) - (scrutinee, pattern) match { - // Normalize blocks without statements - case (Block(Nil, expr), _) => treeMatches(expr, pattern) - case (_, Block(Nil, pat)) => treeMatches(scrutinee, pat) + def normalize(tree: Tree): Tree = tree match { + case Block(Nil, expr) => normalize(expr) + case Inlined(_, Nil, expr) => normalize(expr) + case _ => tree + } + + (normalize(scrutinee), normalize(pattern)) match { // 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 => Some(Tuple1(scrutinee.seal)) - // Normalize inline trees - case (Inlined(_, Nil, scr), _) => treeMatches(scr, pattern) - case (_, Inlined(_, Nil, pat)) => treeMatches(scrutinee, pat) - // // Match two equivalent trees // From 1db26d55f1d8c2175ec2dac4d7ff246d0856b24f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 5 Apr 2019 17:16:22 +0200 Subject: [PATCH 6/8] Add docmentation --- .../scala/runtime/quoted/Matcher.scala | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala index 72638014782b..f3bfafd1c5a3 100644 --- a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -33,6 +33,14 @@ object Matcher { import reflection._ // TODO improve performance + /** Check that the trees match and return the contents from the pattern holes. + * Return None if the trees do not match otherwise return Some of a tuple containing all the contents in the holes. + * + * @param scrutinee The tree beeing matched + * @param pattern The pattern tree that the scrutinee should match. Contains `patternHole` holes. + * @param env Set of tuples containing pairs of symbols (s, p) where s defines a symbol in `scrutinee` which corresponds to symbol p in `pattern`. + * @return `None` if it did not match or `Some(tup: Tuple)` if it matched where `tup` contains the contents of the holes. + */ 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` **/ @@ -47,6 +55,7 @@ object Matcher { if (scrutinees.size != patterns.size) None else foldMatchings(scrutinees.zip(patterns).map(treeMatches): _*) + /** Normalieze the tree */ def normalize(tree: Tree): Tree = tree match { case Block(Nil, expr) => normalize(expr) case Inlined(_, Nil, expr) => normalize(expr) @@ -204,6 +213,16 @@ object Matcher { foldMatchings(patternMatch, guardMatch, rhsMatch) } + /** Check that the pattern trees match and return the contents from the pattern holes. + * Return a tuple with the new environment containing the bindings defined in this pattern and a matching. + * The matching is None if the pattern trees do not match otherwise return Some of a tuple containing all the contents in the holes. + * + * @param scrutinee The pattern tree beeing matched + * @param pattern The pattern tree that the scrutinee should match. Contains `patternHole` holes. + * @param env Set of tuples containing pairs of symbols (s, p) where s defines a symbol in `scrutinee` which corresponds to symbol p in `pattern`. + * @return The new environment containing the bindings defined in this pattern tuppled with + * `None` if it did not match or `Some(tup: Tuple)` if it matched where `tup` contains the contents of the holes. + */ 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(TypeApply(Select(patternHole @ Ident("patternHole"), "unapply"), List(tpt)), Nil, Nil)) if patternHole.symbol.owner.fullName == "scala.runtime.quoted.Matcher$" => From ea3ecf6841c8c3dd52dc25aa0b94c35670e338af Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 5 Apr 2019 17:30:06 +0200 Subject: [PATCH 7/8] Use Ref trees --- .../dotty/tools/dotc/tastyreflect/KernelImpl.scala | 5 +++++ .../scala/runtime/quoted/Matcher.scala | 11 ++++------- library/src/scala/tasty/reflect/Kernel.scala | 2 ++ library/src/scala/tasty/reflect/TreeOps.scala | 5 +++++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala index 50162e45e613..a6bc87b6833b 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/KernelImpl.scala @@ -239,6 +239,11 @@ class KernelImpl(val rootContext: core.Contexts.Context, val rootPosition: util. type Ref = tpd.RefTree + def matchRef(tree: Tree)(implicit ctx: Context): Option[Ref] = tree match { + case x: tpd.RefTree if x.isTerm => Some(x) + case _ => None + } + def Ref_apply(sym: Symbol)(implicit ctx: Context): Ref = withDefaultPos(ctx => tpd.ref(sym)(ctx).asInstanceOf[tpd.RefTree]) diff --git a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala index f3bfafd1c5a3..6f43bf5b22b9 100644 --- a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala @@ -76,19 +76,16 @@ object Matcher { 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 (Ident(_), Ident(_)) if scrutinee.symbol == pattern.symbol || env((scrutinee.symbol, pattern.symbol)) => + Some(()) + 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 => + 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/tasty/reflect/Kernel.scala b/library/src/scala/tasty/reflect/Kernel.scala index 9a5aa6687bc1..07836b68054f 100644 --- a/library/src/scala/tasty/reflect/Kernel.scala +++ b/library/src/scala/tasty/reflect/Kernel.scala @@ -295,6 +295,8 @@ trait Kernel { /** Tree representing a reference to definition */ type Ref <: Term + def matchRef(tree: Tree)(implicit ctx: Context): Option[Ref] + def Ref_apply(sym: Symbol)(implicit ctx: Context): Ref /** Tree representing a reference to definition with a given name */ diff --git a/library/src/scala/tasty/reflect/TreeOps.scala b/library/src/scala/tasty/reflect/TreeOps.scala index e89b39c4b95b..f1403a07cb6e 100644 --- a/library/src/scala/tasty/reflect/TreeOps.scala +++ b/library/src/scala/tasty/reflect/TreeOps.scala @@ -187,6 +187,11 @@ trait TreeOps extends Core { kernel.matchTerm(tree) } + object IsRef { + /** Matches any Ref and returns it */ + def unapply(tree: Tree)(implicit ctx: Context): Option[Ref] = kernel.matchRef(tree) + } + object Ref { /** Create a reference tree */ From fb889a2eefa8817767283f6a9412153c039882d4 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 5 Apr 2019 17:37:52 +0200 Subject: [PATCH 8/8] Move quoted.Matcher to scala.internal --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 12 ++++++------ compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- .../scala/{runtime => internal}/quoted/Matcher.scala | 2 +- .../scala/{runtime => internal}/quoted/Matcher.scala | 6 +++--- .../quote-matcher-runtime/quoted_1.scala | 2 +- .../quote-matcher-runtime/quoted_2.scala | 2 +- .../quote-matcher-symantics-1/quoted_1.scala | 3 --- 7 files changed, 13 insertions(+), 16 deletions(-) rename library/src-bootstrapped/scala/{runtime => internal}/quoted/Matcher.scala (99%) rename library/src-non-bootstrapped/scala/{runtime => internal}/quoted/Matcher.scala (70%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0af0526cf542..6383a07e6882 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -723,6 +723,12 @@ class Definitions { lazy val InternalQuoted_patternHoleR: TermRef = InternalQuotedModule.requiredMethodRef("patternHole") def InternalQuoted_patternHole(implicit ctx: Context): Symbol = InternalQuoted_patternHoleR.symbol + lazy val InternalQuotedMatcherModuleRef: TermRef = ctx.requiredModuleRef("scala.internal.quoted.Matcher") + def InternalQuotedMatcherModule(implicit ctx: Context): Symbol = InternalQuotedMatcherModuleRef.symbol + + lazy val InternalQuotedMatcher_unapplyR: TermRef = InternalQuotedMatcherModule.requiredMethodRef(nme.unapply) + def InternalQuotedMatcher_unapply(implicit ctx: Context) = InternalQuotedMatcher_unapplyR.symbol + lazy val QuotedExprsModule: TermSymbol = ctx.requiredModule("scala.quoted.Exprs") def QuotedExprsClass(implicit ctx: Context): ClassSymbol = QuotedExprsModule.asClass @@ -745,12 +751,6 @@ class Definitions { lazy val TastyReflectionModule: TermSymbol = ctx.requiredModule("scala.tasty.Reflection") lazy val TastyReflection_macroContext: TermSymbol = TastyReflectionModule.requiredMethod("macroContext") - lazy val QuotedMatcherModuleRef: TermRef = ctx.requiredModuleRef("scala.runtime.quoted.Matcher") - def QuotedMatcherModule(implicit ctx: Context): Symbol = QuotedMatcherModuleRef.symbol - - lazy val QuotedMatcher_unapplyR: TermRef = QuotedMatcherModule.requiredMethodRef(nme.unapply) - def QuotedMatcher_unapply(implicit ctx: Context) = QuotedMatcher_unapplyR.symbol - lazy val EqlType: TypeRef = ctx.requiredClassRef("scala.Eql") def EqlClass(implicit ctx: Context): ClassSymbol = EqlType.symbol.asClass def EqlModule(implicit ctx: Context): Symbol = EqlClass.companionModule diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index effe488555ab..feffc6a85894 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1944,7 +1944,7 @@ class Typer extends Namer val patType = defn.tupleType(splices.tpes.map(_.widen)) val splicePat = typed(untpd.Tuple(splices.map(untpd.TypedSplice(_))).withSpan(quoted.span), patType) UnApply( - fun = ref(defn.QuotedMatcher_unapplyR).appliedToType(patType), + fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToType(patType), implicits = ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) :: implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil, diff --git a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala similarity index 99% rename from library/src-bootstrapped/scala/runtime/quoted/Matcher.scala rename to library/src-bootstrapped/scala/internal/quoted/Matcher.scala index 6f43bf5b22b9..3d8ea7bbc467 100644 --- a/library/src-bootstrapped/scala/runtime/quoted/Matcher.scala +++ b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala @@ -1,4 +1,4 @@ -package scala.runtime.quoted +package scala.internal.quoted import scala.annotation.internal.sharable diff --git a/library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala b/library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala similarity index 70% rename from library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala rename to library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala index c3a3a7f8cc3b..1e80a2a69fdf 100644 --- a/library/src-non-bootstrapped/scala/runtime/quoted/Matcher.scala +++ b/library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala @@ -1,7 +1,7 @@ -package scala.runtime.quoted +package scala.internal.quoted -import scala.quoted._ -import scala.tasty._ +import scala.quoted.Expr +import scala.tasty.Reflection object Matcher { diff --git a/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala b/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala index ff6f3a79aef9..cb3ced71fd61 100644 --- a/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala +++ b/tests/run-with-compiler/quote-matcher-runtime/quoted_1.scala @@ -10,7 +10,7 @@ object Macros { 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 => + val res = scala.internal.quoted.Matcher.unapply[Tuple](a)(b, reflect).map { tup => tup.toArray.toList.map { case r: Expr[_] => s"Expr(${r.unseal.showCode})" diff --git a/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala b/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala index c014fb18406f..5caec627a51e 100644 --- a/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala +++ b/tests/run-with-compiler/quote-matcher-runtime/quoted_2.scala @@ -1,7 +1,7 @@ import Macros._ -import scala.runtime.quoted.Matcher._ +import scala.internal.quoted.Matcher._ import scala.internal.Quoted.patternHole 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 index 4f09d4afcc97..da66bbc38e5a 100644 --- a/tests/run-with-compiler/quote-matcher-symantics-1/quoted_1.scala +++ b/tests/run-with-compiler/quote-matcher-symantics-1/quoted_1.scala @@ -4,9 +4,6 @@ import scala.quoted.matching._ import scala.tasty.Reflection -import scala.runtime.quoted.Matcher._ - -import scala.internal.Quoted.patternHole object Macros {