diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 80254f917f6c..00f99266822b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -640,9 +640,14 @@ class Definitions { @tu lazy val InternalQuoted_patternHole: Symbol = InternalQuotedModule.requiredMethod("patternHole") @tu lazy val InternalQuoted_patternBindHoleAnnot: ClassSymbol = InternalQuotedModule.requiredClass("patternBindHole") @tu lazy val InternalQuoted_QuoteTypeTagAnnot: ClassSymbol = InternalQuotedModule.requiredClass("quoteTypeTag") + @tu lazy val InternalQuoted_fromAboveAnnot: ClassSymbol = InternalQuotedModule.requiredClass("fromAbove") - @tu lazy val InternalQuotedMatcherModule: Symbol = ctx.requiredModule("scala.internal.quoted.Matcher") - @tu lazy val InternalQuotedMatcher_unapply: Symbol = InternalQuotedMatcherModule.requiredMethod(nme.unapply) + + @tu lazy val InternalQuotedExprModule: Symbol = ctx.requiredModule("scala.internal.quoted.Expr") + @tu lazy val InternalQuotedExpr_unapply: Symbol = InternalQuotedExprModule.requiredMethod(nme.unapply) + + @tu lazy val InternalQuotedTypeModule: Symbol = ctx.requiredModule("scala.internal.quoted.Type") + @tu lazy val InternalQuotedType_unapply: Symbol = InternalQuotedTypeModule.requiredMethod(nme.unapply) @tu lazy val QuotedTypeClass: ClassSymbol = ctx.requiredClass("scala.quoted.Type") @tu lazy val QuotedType_splice: Symbol = QuotedTypeClass.requiredType(tpnme.splice) diff --git a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala index 679b45a8077e..4311f2f9c8cf 100644 --- a/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala +++ b/compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala @@ -80,8 +80,13 @@ object PickledQuotes { val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions)) val tpt = unpickled match { case Block(aliases, tpt) => - // `@quoteTypeTag type` aliasses are not required after unpickling - tpt + // `@quoteTypeTag type` aliases are not required after unpickling. + // Type definitions are placeholders for type holes in the pickled quote, at this point + // those holes have been filled. As we already dealias al references to them in `dealiasTypeTags` + // there is no need to keep their definitions in the tree. As artifacts of quote reification + // they also do not have a meaningful position in the source. + val aliases1 = aliases.filter(!_.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot)) + seq(aliases1, tpt) case tpt => tpt } tpt.withType(dealiasTypeTags(tpt.tpe)) diff --git a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala index f26f18f2ea0d..4073654110e7 100644 --- a/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala +++ b/compiler/src/dotty/tools/dotc/tastyreflect/ReflectionCompilerInterface.scala @@ -1830,6 +1830,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend def Definitions_InternalQuoted_patternHole: Symbol = defn.InternalQuoted_patternHole def Definitions_InternalQuoted_patternBindHoleAnnot: Symbol = defn.InternalQuoted_patternBindHoleAnnot + def Definitions_InternalQuoted_fromAboveAnnot: Symbol = defn.InternalQuoted_fromAboveAnnot // Types diff --git a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala index ebdc5523f29e..bab7932762a8 100644 --- a/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala +++ b/compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala @@ -46,8 +46,8 @@ trait QuotesAndSplices { case _ => } val tree1 = - if (tree.quoted.isType) typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(quoteContext) - else if (ctx.mode.is(Mode.Pattern) && level == 0) typedQuotePattern(tree, pt) + if (ctx.mode.is(Mode.Pattern) && level == 0) typedQuotePattern(tree, pt) + else if (tree.quoted.isType) typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(quoteContext) else typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuote.termRef), tree.quoted), pt)(quoteContext) tree1.withSpan(tree.span) } @@ -163,6 +163,16 @@ trait QuotesAndSplices { }) object splitter extends tpd.TreeMap { + private var variance: Int = 1 + + @forceInline private def atVariance[T](v: Int)(op: => T): T = { + val saved = variance + variance = v + val res = op + variance = saved + res + } + val patBuf = new mutable.ListBuffer[Tree] val freshTypePatBuf = new mutable.ListBuffer[Tree] val freshTypeBindingsBuff = new mutable.ListBuffer[Tree] @@ -206,11 +216,21 @@ trait QuotesAndSplices { super.transform(tree) case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) => transformTypeBindingTypeDef(tdef, typePatBuf) + case tree @ AppliedTypeTree(tpt, args) => + val args1: List[Tree] = args.zipWithConserve(tpt.tpe.typeParams.map(_.paramVariance)) { (arg, v) => + arg.tpe match { + case _: TypeBounds => transform(arg) + case _ => atVariance(variance * v)(transform(arg)) + } + } + cpy.AppliedTypeTree(tree)(transform(tpt), args1) case _ => super.transform(tree) } - def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]]): Tree = { + private def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(implicit ctx: Context): Tree = { + if (variance == -1) + tdef.symbol.addAnnotation(Annotation(New(ref(defn.InternalQuoted_fromAboveAnnot.typeRef)).withSpan(tdef.span))) val bindingType = getBinding(tdef.symbol).symbol.typeRef val bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil) assert(tdef.name.startsWith("$")) @@ -248,7 +268,7 @@ trait QuotesAndSplices { } /** Type a quote pattern `case '{ } =>` qiven the a current prototype. Typing the pattern - * will also transform it into a call to `scala.internal.quoted.Matcher.unapply`. + * will also transform it into a call to `scala.internal.quoted.Expr.unapply`. * * Code directly inside the quote is typed as an expression using Mode.QuotedPattern. Splices * within the quotes become patterns again and typed acordingly. @@ -275,7 +295,7 @@ trait QuotesAndSplices { * and the patterns in the splices. All these are recombined into a call to `Matcher.unapply`. * * ``` - * case scala.internal.quoted.Matcher.unapply[ + * case scala.internal.quoted.Expr.unapply[ * Tuple1[$t @ _], // Type binging definition * Tuple2[Type[$t], Expr[List[$t]]] // Typing the result of the pattern match * ]( @@ -344,14 +364,16 @@ trait QuotesAndSplices { val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType) + val unapplySym = if (tree.quoted.isTerm) defn.InternalQuotedExpr_unapply else defn.InternalQuotedType_unapply + val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass + val quotedPattern = + if (tree.quoted.isTerm) ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx) + else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTrees(shape :: Nil) UnApply( - fun = ref(defn.InternalQuotedMatcher_unapply.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil), - implicits = - ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx) :: - Literal(Constant(typeBindings.nonEmpty)) :: - qctx :: Nil, + fun = ref(unapplySym.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil), + implicits = quotedPattern :: Literal(Constant(typeBindings.nonEmpty)) :: qctx :: Nil, patterns = splicePat :: Nil, - proto = defn.QuotedExprClass.typeRef.appliedTo(replaceBindings(quoted1.tpe) & quotedPt)) + proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe) & quotedPt)) } } diff --git a/docs/docs/reference/other-new-features/quoted-pattern-spec.md b/docs/docs/reference/other-new-features/quoted-pattern-spec.md index 10e72c0bbd77..46f40edbb2e5 100644 --- a/docs/docs/reference/other-new-features/quoted-pattern-spec.md +++ b/docs/docs/reference/other-new-features/quoted-pattern-spec.md @@ -18,18 +18,18 @@ def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match { ``` In the example above we have `$a` which provides a `Bind[Int]`, `$x` which provides an `Expr[Int]` and `${Bind(`a`)}` which probides an `Expr[Int]` that is pattern matched against `Bind(`a`)` to check that it is a reference to `a`. -Quoted patterns are transformed during typer to a call of `scala.internal.quoted.Matcher.unapply` which splits the quoted code into the patterns and a reifiable quote that will be used as witnesses at runtime. +Quoted patterns are transformed during typer to a call of `scala.internal.quoted.Expr.unapply` which splits the quoted code into the patterns and a reifiable quote that will be used as witnesses at runtime. ```scala def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match { - case scala.internal.quoted.Matcher.unapply[Tuple3[Bind[Int], Expr[Int], Expr[Int]]](Tuple3(a, x, Bind(`a`), y))('{ @patternBindHole val a: Int = patternHole[Int]; patternHole[Int] + 1 }) => + case scala.internal.quoted.Expr.unapply[Tuple3[Bind[Int], Expr[Int], Expr[Int]]](Tuple3(a, x, Bind(`a`), y))('{ @patternBindHole val a: Int = patternHole[Int]; patternHole[Int] + 1 }) => } ``` ## Runtime semantics -At runtime to a `quoted.Expr` can be matched to another using `scala.internal.quoted.Matcher.unapply`. +At runtime to a `quoted.Expr` can be matched to another using `scala.internal.quoted.Expr.unapply`. ```scala def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] @@ -49,7 +49,7 @@ def matched[T](x: T) = Some(Tuple1(x)) def (x: Matching) && (y: Matching) = if (x == None || y == None) None else Some(x.get ++ y.get) def fold[T](m: Mattching*) given Env: Matching = m.fold(matched)(_ && _) -// `a =#= b` stands for `a` matches `b` +// `a =#= b` stands for `a` matches `b` def (scrutinee: Tree) =#= pattern: Tree) given Env: Matching // described by cases in the tables below def envWith(equiv: (Symbol, Symbol)*) given Env: Env // Adds to the current environment the fact that s1 from the scrutinee is equivalent to s2 in the pattern diff --git a/library/src-bootstrapped/scala/internal/quoted/Matcher.scala b/library/src-bootstrapped/scala/internal/quoted/Matcher.scala deleted file mode 100644 index 0e6e95ecb0c3..000000000000 --- a/library/src-bootstrapped/scala/internal/quoted/Matcher.scala +++ /dev/null @@ -1,395 +0,0 @@ -package scala.internal.quoted - -import scala.annotation.internal.sharable - -import scala.quoted._ -import scala.quoted.matching.Bind - -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. - * - * 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 hasTypeSplices `Boolean` notify if the pattern has type splices (if so we use a GADT context) - * @param qctx the current QuoteContext - * @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]`` - */ - def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], - hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = { - - // TODO improve performance - import qctx.tasty.{Bind => BindPattern, _} - import Matching._ - - type Env = Set[(Symbol, Symbol)] - - class SymBinding(val sym: Symbol) - - inline def withEnv[T](env: Env)(body: => given Env => T): T = body given env - - def hasBindTypeAnnotation(tpt: TypeTree): Boolean = tpt match { - case Annotated(tpt2, annot) => isBindAnnotation(annot) || hasBindTypeAnnotation(tpt2) - case _ => false - } - - def hasBindAnnotation(sym: Symbol) = sym.annots.exists(isBindAnnotation) - - def isBindAnnotation(tree: Tree): Boolean = tree match { - case New(tpt) => tpt.symbol == internal.Definitions_InternalQuoted_patternBindHoleAnnot - case annot => annot.symbol.owner == internal.Definitions_InternalQuoted_patternBindHoleAnnot - } - - /** Check that all trees match with `mtch` and concatenate the results with && */ - def matchLists[T](l1: List[T], l2: List[T])(mtch: (T, T) => Matching): Matching = (l1, l2) match { - case (x :: xs, y :: ys) => mtch(x, y) && matchLists(xs, ys)(mtch) - case (Nil, Nil) => matched - case _ => notMatched - } - - /** Check that all trees match with =#= and concatenate the results with && */ - def (scrutinees: List[Tree]) =##= (patterns: List[Tree]) given Context, Env: Matching = - matchLists(scrutinees, patterns)(_ =#= _) - - /** 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 `the[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 (scrutinee0: Tree) =#= (pattern0: Tree) given Context, Env: Matching = { - - /** Normalize the tree */ - def normalize(tree: Tree): Tree = tree match { - case Block(Nil, expr) => normalize(expr) - case Block(stats1, Block(stats2, expr)) => normalize(Block(stats1 ::: stats2, expr)) - case Inlined(_, Nil, expr) => normalize(expr) - case _ => tree - } - - val scrutinee = normalize(scrutinee0) - val pattern = normalize(pattern0) - - /** 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 bindingMatch(sym: Symbol) = - matched(new Bind(sym.name, sym)) - - (scrutinee, pattern) match { - - // Match a scala.internal.Quoted.patternHole typed as a repeated argument and return the scrutinee tree - case (IsTerm(scrutinee @ Typed(s, tpt1)), Typed(TypeApply(patternHole, tpt :: Nil), tpt2)) - if patternHole.symbol == internal.Definitions_InternalQuoted_patternHole && - s.tpe <:< tpt.tpe && - tpt2.tpe.derivesFrom(defn.RepeatedParamClass) => - matched(scrutinee.seal) - - // Match a scala.internal.Quoted.patternHole and return the scrutinee tree - case (IsTerm(scrutinee), TypeApply(patternHole, tpt :: Nil)) - if patternHole.symbol == internal.Definitions_InternalQuoted_patternHole && - scrutinee.tpe <:< tpt.tpe => - matched(scrutinee.seal) - - // - // Match two equivalent trees - // - - case (Literal(constant1), Literal(constant2)) if constant1 == constant2 => - matched - - case (Typed(expr1, tpt1), Typed(expr2, tpt2)) => - expr1 =#= expr2 && tpt1 =#= tpt2 - - case (scrutinee, Typed(expr2, _)) => - scrutinee =#= expr2 - - case (Ident(_), Ident(_)) if scrutinee.symbol == pattern.symbol || the[Env].apply((scrutinee.symbol, pattern.symbol)) => - matched - - case (Select(qual1, _), Select(qual2, _)) if scrutinee.symbol == pattern.symbol => - qual1 =#= qual2 - - case (IsRef(_), IsRef(_)) if scrutinee.symbol == pattern.symbol => - matched - - case (Apply(fn1, args1), Apply(fn2, args2)) if fn1.symbol == fn2.symbol => - fn1 =#= fn2 && args1 =##= args2 - - case (TypeApply(fn1, args1), TypeApply(fn2, args2)) if fn1.symbol == fn2.symbol => - fn1 =#= fn2 && args1 =##= args2 - - case (Block(stats1, expr1), Block(binding :: stats2, expr2)) if isTypeBinding(binding) => - qctx.tasty.internal.Context_GADT_addToConstraint(the[Context])(binding.symbol :: Nil) - matched(new SymBinding(binding.symbol)) && Block(stats1, expr1) =#= Block(stats2, expr2) - - case (Block(stat1 :: stats1, expr1), Block(stat2 :: stats2, expr2)) => - withEnv(the[Env] + (stat1.symbol -> stat2.symbol)) { - stat1 =#= stat2 && Block(stats1, expr1) =#= Block(stats2, expr2) - } - - case (scrutinee, Block(typeBindings, expr2)) if typeBindings.forall(isTypeBinding) => - val bindingSymbols = typeBindings.map(_.symbol) - qctx.tasty.internal.Context_GADT_addToConstraint(the[Context])(bindingSymbols) - bindingSymbols.foldRight(scrutinee =#= expr2)((x, acc) => matched(new SymBinding(x)) && acc) - - case (If(cond1, thenp1, elsep1), If(cond2, thenp2, elsep2)) => - cond1 =#= cond2 && thenp1 =#= thenp2 && elsep1 =#= elsep2 - - case (Assign(lhs1, rhs1), Assign(lhs2, rhs2)) => - val lhsMatch = - if ((lhs1 =#= lhs2).isMatch) matched - else notMatched - lhsMatch && rhs1 =#= rhs2 - - case (While(cond1, body1), While(cond2, body2)) => - cond1 =#= cond2 && body1 =#= body2 - - case (New(tpt1), New(tpt2)) => - tpt1 =#= tpt2 - - case (This(_), This(_)) if scrutinee.symbol == pattern.symbol => - matched - - case (Super(qual1, mix1), Super(qual2, mix2)) if mix1 == mix2 => - qual1 =#= qual2 - - case (Repeated(elems1, _), Repeated(elems2, _)) if elems1.size == elems2.size => - elems1 =##= elems2 - - case (IsTypeTree(scrutinee), IsTypeTree(pattern)) if scrutinee.tpe <:< pattern.tpe => - matched - - case (Applied(tycon1, args1), Applied(tycon2, args2)) => - tycon1 =#= tycon2 && args1 =##= args2 - - case (ValDef(_, tpt1, rhs1), ValDef(_, tpt2, rhs2)) if checkValFlags() => - val bindMatch = - if (hasBindAnnotation(pattern.symbol) || hasBindTypeAnnotation(tpt2)) bindingMatch(scrutinee.symbol) - else matched - def rhsEnv = the[Env] + (scrutinee.symbol -> pattern.symbol) - bindMatch && tpt1 =#= tpt2 && (treeOptMatches(rhs1, rhs2) given (the[Context], rhsEnv)) - - case (DefDef(_, typeParams1, paramss1, tpt1, Some(rhs1)), DefDef(_, typeParams2, paramss2, tpt2, Some(rhs2))) => - val bindMatch = - if (hasBindAnnotation(pattern.symbol)) bindingMatch(scrutinee.symbol) - else matched - def rhsEnv = - the[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) - - bindMatch && - typeParams1 =##= typeParams2 && - matchLists(paramss1, paramss2)(_ =##= _) && - tpt1 =#= tpt2 && - withEnv(rhsEnv)(rhs1 =#= rhs2) - - case (Closure(_, tpt1), Closure(_, tpt2)) => - // TODO match tpt1 with tpt2? - matched - - case (Match(scru1, cases1), Match(scru2, cases2)) => - scru1 =#= scru2 && matchLists(cases1, cases2)(caseMatches) - - case (Try(body1, cases1, finalizer1), Try(body2, cases2, finalizer2)) => - body1 =#= body2 && matchLists(cases1, cases2)(caseMatches) && treeOptMatches(finalizer1, finalizer2) - - // Ignore type annotations - case (Annotated(tpt, _), _) => - tpt =#= pattern - case (_, Annotated(tpt, _)) => - scrutinee =#= tpt - - // No Match - case _ => - if (debug) - println( - s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - |Scrutinee - | ${scrutinee.show} - | - |${scrutinee.showExtractors} - | - |did not match pattern - | ${pattern.show} - | - |${pattern.showExtractors} - | - | - | - | - |""".stripMargin) - notMatched - } - } - - def treeOptMatches(scrutinee: Option[Tree], pattern: Option[Tree]) given Context, Env: Matching = { - (scrutinee, pattern) match { - case (Some(x), Some(y)) => x =#= y - case (None, None) => matched - case _ => notMatched - } - } - - def caseMatches(scrutinee: CaseDef, pattern: CaseDef) given Context, Env: Matching = { - val (caseEnv, patternMatch) = scrutinee.pattern =%= pattern.pattern - withEnv(caseEnv) { - patternMatch && - treeOptMatches(scrutinee.guard, pattern.guard) && - scrutinee.rhs =#= pattern.rhs - } - } - - /** 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 `the[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 (scrutinee: Pattern) =%= (pattern: Pattern) given Context, Env: (Env, Matching) = (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$" => - (the[Env], matched(v1.seal)) - - case (Pattern.Value(v1), Pattern.Value(v2)) => - (the[Env], v1 =#= v2) - - case (Pattern.Bind(name1, body1), Pattern.Bind(name2, body2)) => - val bindEnv = the[Env] + (scrutinee.symbol -> pattern.symbol) - (body1 =%= body2) given (the[Context], bindEnv) - - case (Pattern.Unapply(fun1, implicits1, patterns1), Pattern.Unapply(fun2, implicits2, patterns2)) => - val (patEnv, patternsMatch) = foldPatterns(patterns1, patterns2) - (patEnv, fun1 =#= fun2 && implicits1 =##= implicits2 && patternsMatch) - - case (Pattern.Alternatives(patterns1), Pattern.Alternatives(patterns2)) => - foldPatterns(patterns1, patterns2) - - case (Pattern.TypeTest(tpt1), Pattern.TypeTest(tpt2)) => - (the[Env], tpt1 =#= tpt2) - - case (Pattern.WildcardPattern(), Pattern.WildcardPattern()) => - (the[Env], matched) - - case _ => - if (debug) - println( - s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - |Scrutinee - | ${scrutinee.show} - | - |${scrutinee.showExtractors} - | - |did not match pattern - | ${pattern.show} - | - |${pattern.showExtractors} - | - | - | - | - |""".stripMargin) - (the[Env], notMatched) - } - - def foldPatterns(patterns1: List[Pattern], patterns2: List[Pattern]) given Context, Env: (Env, Matching) = { - if (patterns1.size != patterns2.size) (the[Env], notMatched) - else patterns1.zip(patterns2).foldLeft((the[Env], matched)) { (acc, x) => - val (env, res) = (x._1 =%= x._2) given (the[Context], acc._1) - (env, acc._2 && res) - } - } - - def isTypeBinding(tree: Tree): Boolean = tree match { - case IsTypeDef(tree) => hasBindAnnotation(tree.symbol) - case _ => false - } - - implicit val env: Env = Set.empty - - val res = { - if (hasTypeSplices) { - implicit val ctx: Context = qctx.tasty.internal.Context_GADT_setFreshGADTBounds(rootContext) - val matchings = scrutineeExpr.unseal.underlyingArgument =#= patternExpr.unseal.underlyingArgument - // After matching and doing all subtype check, we have to aproximate all the type bindings - // that we have found and seal them in a quoted.Type - matchings.asOptionOfTuple.map { tup => - Tuple.fromArray(tup.toArray.map { // TODO improve performace - case x: SymBinding => internal.Context_GADT_approximation(the[Context])(x.sym, true).seal - case x => x - }) - } - } - else { - scrutineeExpr.unseal.underlyingArgument =#= patternExpr.unseal.underlyingArgument - } - } - res.asInstanceOf[Option[Tup]] - } - - /** Result of matching a part of an expression */ - private opaque type Matching = Option[Tuple] - - private object Matching { - - def notMatched: Matching = None - val matched: Matching = Some(()) - def matched(x: Any): Matching = Some(Tuple1(x)) - - def (self: Matching) asOptionOfTuple: Option[Tuple] = self - - /** Concatenates the contents of two sucessful matchings or return a `notMatched` */ - // FIXME inline to avoid alocation of by name closure (see #6395) - /*inline*/ def (self: Matching) && (that: => Matching): Matching = self match { - case Some(x) => - that match { - case Some(y) => Some(x ++ y) - case _ => None - } - case _ => None - } - - /** Is this matching the result of a successful match */ - def (self: Matching) isMatch: Boolean = self.isDefined - - /** 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. - */ - def foldMatchings(matchings: Matching*): Matching = { - // TODO improve performance - matchings.foldLeft[Matching](Some(())) { - case (Some(acc), Some(holes)) => Some(acc ++ holes) - case (_, _) => None - } - } - - } - -} diff --git a/library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala b/library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala deleted file mode 100644 index d00c5f6e8cdb..000000000000 --- a/library/src-non-bootstrapped/scala/internal/quoted/Matcher.scala +++ /dev/null @@ -1,11 +0,0 @@ -package scala.internal.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/internal/Quoted.scala b/library/src/scala/internal/Quoted.scala index bcab397baa73..46e77aeecd19 100644 --- a/library/src/scala/internal/Quoted.scala +++ b/library/src/scala/internal/Quoted.scala @@ -28,6 +28,9 @@ object Quoted { /** A splice of a name in a quoted pattern is that marks the definition of a type splice */ class patternType extends Annotation + /** A type pattern that must be aproximated from above */ + class fromAbove extends Annotation + /** Artifact of pickled type splices * * During quote reification a quote `'{ ... F[$t] ... }` will be transformed into diff --git a/library/src/scala/internal/quoted/Expr.scala b/library/src/scala/internal/quoted/Expr.scala new file mode 100644 index 000000000000..dd28d1c2dc99 --- /dev/null +++ b/library/src/scala/internal/quoted/Expr.scala @@ -0,0 +1,34 @@ +package scala.internal.quoted + +import scala.quoted._ + +object Expr { + + /** Pattern matches an the scrutineeExpr against the patternExpr and returns a tuple + * with the matched holes if successful. + * + * 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 hasTypeSplices `Boolean` notify if the pattern has type splices (if so we use a GADT context) + * @param qctx the current QuoteContext + * @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]`` + */ + def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], + hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = { + import qctx.tasty._ + new Matcher.QuoteMatcher[qctx.type].termMatch(scrutineeExpr.unseal, patternExpr.unseal, hasTypeSplices).asInstanceOf[Option[Tup]] + } + +} diff --git a/library/src/scala/internal/quoted/Matcher.scala b/library/src/scala/internal/quoted/Matcher.scala new file mode 100644 index 000000000000..918a2aa414ab --- /dev/null +++ b/library/src/scala/internal/quoted/Matcher.scala @@ -0,0 +1,408 @@ +package scala.internal.quoted + +import scala.annotation.internal.sharable + +import scala.quoted._ +import scala.quoted.matching.Bind + +private[quoted] object Matcher { + + class QuoteMatcher[QCtx <: QuoteContext & Singleton] given (val qctx: QCtx) { + // TODO improve performance + + private final val debug = false + + import qctx.tasty.{Bind => BindPattern, _} + import Matching._ + + private type Env = Set[(Symbol, Symbol)] + + inline private def withEnv[T](env: Env)(body: => given Env => T): T = body given env + + class SymBinding(val sym: Symbol, val fromAbove: Boolean) + + def termMatch(scrutineeTerm: Term, patternTerm: Term, hasTypeSplices: Boolean): Option[Tuple] = { + implicit val env: Env = Set.empty + if (hasTypeSplices) { + implicit val ctx: Context = internal.Context_GADT_setFreshGADTBounds(rootContext) + val matchings = scrutineeTerm.underlyingArgument =?= patternTerm.underlyingArgument + // After matching and doing all subtype checks, we have to aproximate all the type bindings + // that we have found and seal them in a quoted.Type + matchings.asOptionOfTuple.map { tup => + Tuple.fromArray(tup.toArray.map { // TODO improve performace + case x: SymBinding => internal.Context_GADT_approximation(the[Context])(x.sym, !x.fromAbove).seal + case x => x + }) + } + } + else { + scrutineeTerm.underlyingArgument =?= patternTerm.underlyingArgument + } + } + + // TODO factor out common logic with `termMatch` + def typeTreeMatch(scrutineeTypeTree: TypeTree, patternTypeTree: TypeTree, hasTypeSplices: Boolean): Option[Tuple] = { + implicit val env: Env = Set.empty + if (hasTypeSplices) { + implicit val ctx: Context = internal.Context_GADT_setFreshGADTBounds(rootContext) + val matchings = scrutineeTypeTree =?= patternTypeTree + // After matching and doing all subtype checks, we have to aproximate all the type bindings + // that we have found and seal them in a quoted.Type + matchings.asOptionOfTuple.map { tup => + Tuple.fromArray(tup.toArray.map { // TODO improve performace + case x: SymBinding => internal.Context_GADT_approximation(the[Context])(x.sym, !x.fromAbove).seal + case x => x + }) + } + } + else { + scrutineeTypeTree =?= patternTypeTree + } + } + + private def hasBindTypeAnnotation(tpt: TypeTree): Boolean = tpt match { + case Annotated(tpt2, annot) => isBindAnnotation(annot) || hasBindTypeAnnotation(tpt2) + case _ => false + } + + private def hasBindAnnotation(sym: Symbol) = sym.annots.exists(isBindAnnotation) + + private def hasFromAboveAnnotation(sym: Symbol) = sym.annots.exists(isFromAboveAnnotation) + + private def isBindAnnotation(tree: Tree): Boolean = tree match { + case New(tpt) => tpt.symbol == internal.Definitions_InternalQuoted_patternBindHoleAnnot + case annot => annot.symbol.owner == internal.Definitions_InternalQuoted_patternBindHoleAnnot + } + + private def isFromAboveAnnotation(tree: Tree): Boolean = tree match { + case New(tpt) => tpt.symbol == internal.Definitions_InternalQuoted_fromAboveAnnot + case annot => annot.symbol.owner == internal.Definitions_InternalQuoted_fromAboveAnnot + } + + /** Check that all trees match with `mtch` and concatenate the results with && */ + private def matchLists[T](l1: List[T], l2: List[T])(mtch: (T, T) => Matching): Matching = (l1, l2) match { + case (x :: xs, y :: ys) => mtch(x, y) && matchLists(xs, ys)(mtch) + case (Nil, Nil) => matched + case _ => notMatched + } + + private given TreeListOps { + + /** Check that all trees match with =?= and concatenate the results with && */ + def (scrutinees: List[Tree]) =?= (patterns: List[Tree]) given Context, Env: Matching = + matchLists(scrutinees, patterns)(_ =?= _) + + } + + private given TreeOps { + + /** 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 `the[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 (scrutinee0: Tree) =?= (pattern0: Tree) given Context, Env: Matching = { + + /** Normalize the tree */ + def normalize(tree: Tree): Tree = tree match { + case Block(Nil, expr) => normalize(expr) + case Block(stats1, Block(stats2, expr)) => normalize(Block(stats1 ::: stats2, expr)) + case Inlined(_, Nil, expr) => normalize(expr) + case _ => tree + } + + val scrutinee = normalize(scrutinee0) + val pattern = normalize(pattern0) + + /** 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 bindingMatch(sym: Symbol) = + matched(new Bind(sym.name, sym)) + + (scrutinee, pattern) match { + + // Match a scala.internal.Quoted.patternHole typed as a repeated argument and return the scrutinee tree + case (IsTerm(scrutinee @ Typed(s, tpt1)), Typed(TypeApply(patternHole, tpt :: Nil), tpt2)) + if patternHole.symbol == internal.Definitions_InternalQuoted_patternHole && + s.tpe <:< tpt.tpe && + tpt2.tpe.derivesFrom(defn.RepeatedParamClass) => + matched(scrutinee.seal) + + // Match a scala.internal.Quoted.patternHole and return the scrutinee tree + case (IsTerm(scrutinee), TypeApply(patternHole, tpt :: Nil)) + if patternHole.symbol == internal.Definitions_InternalQuoted_patternHole && + scrutinee.tpe <:< tpt.tpe => + matched(scrutinee.seal) + + // + // Match two equivalent trees + // + + case (Literal(constant1), Literal(constant2)) if constant1 == constant2 => + matched + + case (Typed(expr1, tpt1), Typed(expr2, tpt2)) => + expr1 =?= expr2 && tpt1 =?= tpt2 + + case (scrutinee, Typed(expr2, _)) => + scrutinee =?= expr2 + + case (Ident(_), Ident(_)) if scrutinee.symbol == pattern.symbol || the[Env].apply((scrutinee.symbol, pattern.symbol)) => + matched + + case (Select(qual1, _), Select(qual2, _)) if scrutinee.symbol == pattern.symbol => + qual1 =?= qual2 + + case (IsRef(_), IsRef(_)) if scrutinee.symbol == pattern.symbol => + matched + + case (Apply(fn1, args1), Apply(fn2, args2)) if fn1.symbol == fn2.symbol => + fn1 =?= fn2 && args1 =?= args2 + + case (TypeApply(fn1, args1), TypeApply(fn2, args2)) if fn1.symbol == fn2.symbol => + fn1 =?= fn2 && args1 =?= args2 + + case (Block(stats1, expr1), Block(binding :: stats2, expr2)) if isTypeBinding(binding) => + qctx.tasty.internal.Context_GADT_addToConstraint(the[Context])(binding.symbol :: Nil) + matched(new SymBinding(binding.symbol, hasFromAboveAnnotation(binding.symbol))) && Block(stats1, expr1) =?= Block(stats2, expr2) + + case (Block(stat1 :: stats1, expr1), Block(stat2 :: stats2, expr2)) => + withEnv(the[Env] + (stat1.symbol -> stat2.symbol)) { + stat1 =?= stat2 && Block(stats1, expr1) =?= Block(stats2, expr2) + } + + case (scrutinee, Block(typeBindings, expr2)) if typeBindings.forall(isTypeBinding) => + val bindingSymbols = typeBindings.map(_.symbol) + qctx.tasty.internal.Context_GADT_addToConstraint(the[Context])(bindingSymbols) + bindingSymbols.foldRight(scrutinee =?= expr2)((x, acc) => matched(new SymBinding(x, hasFromAboveAnnotation(x))) && acc) + + case (If(cond1, thenp1, elsep1), If(cond2, thenp2, elsep2)) => + cond1 =?= cond2 && thenp1 =?= thenp2 && elsep1 =?= elsep2 + + case (Assign(lhs1, rhs1), Assign(lhs2, rhs2)) => + val lhsMatch = + if ((lhs1 =?= lhs2).isMatch) matched + else notMatched + lhsMatch && rhs1 =?= rhs2 + + case (While(cond1, body1), While(cond2, body2)) => + cond1 =?= cond2 && body1 =?= body2 + + case (New(tpt1), New(tpt2)) => + tpt1 =?= tpt2 + + case (This(_), This(_)) if scrutinee.symbol == pattern.symbol => + matched + + case (Super(qual1, mix1), Super(qual2, mix2)) if mix1 == mix2 => + qual1 =?= qual2 + + case (Repeated(elems1, _), Repeated(elems2, _)) if elems1.size == elems2.size => + elems1 =?= elems2 + + case (IsTypeTree(scrutinee), IsTypeTree(pattern)) if scrutinee.tpe <:< pattern.tpe => + matched + + case (Applied(tycon1, args1), Applied(tycon2, args2)) => + tycon1 =?= tycon2 && args1 =?= args2 + + case (ValDef(_, tpt1, rhs1), ValDef(_, tpt2, rhs2)) if checkValFlags() => + val bindMatch = + if (hasBindAnnotation(pattern.symbol) || hasBindTypeAnnotation(tpt2)) bindingMatch(scrutinee.symbol) + else matched + def rhsEnv = the[Env] + (scrutinee.symbol -> pattern.symbol) + bindMatch && tpt1 =?= tpt2 && (treeOptMatches(rhs1, rhs2) given (the[Context], rhsEnv)) + + case (DefDef(_, typeParams1, paramss1, tpt1, Some(rhs1)), DefDef(_, typeParams2, paramss2, tpt2, Some(rhs2))) => + val bindMatch = + if (hasBindAnnotation(pattern.symbol)) bindingMatch(scrutinee.symbol) + else matched + def rhsEnv = + the[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) + + bindMatch && + typeParams1 =?= typeParams2 && + matchLists(paramss1, paramss2)(_ =?= _) && + tpt1 =?= tpt2 && + withEnv(rhsEnv)(rhs1 =?= rhs2) + + case (Closure(_, tpt1), Closure(_, tpt2)) => + // TODO match tpt1 with tpt2? + matched + + case (Match(scru1, cases1), Match(scru2, cases2)) => + scru1 =?= scru2 && matchLists(cases1, cases2)(caseMatches) + + case (Try(body1, cases1, finalizer1), Try(body2, cases2, finalizer2)) => + body1 =?= body2 && matchLists(cases1, cases2)(caseMatches) && treeOptMatches(finalizer1, finalizer2) + + // Ignore type annotations + case (Annotated(tpt, _), _) => + tpt =?= pattern + case (_, Annotated(tpt, _)) => + scrutinee =?= tpt + + // No Match + case _ => + if (debug) + println( + s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + |Scrutinee + | ${scrutinee.show} + | + |${scrutinee.showExtractors} + | + |did not match pattern + | ${pattern.show} + | + |${pattern.showExtractors} + | + | + | + | + |""".stripMargin) + notMatched + } + } + } + + private def treeOptMatches(scrutinee: Option[Tree], pattern: Option[Tree]) given Context, Env: Matching = { + (scrutinee, pattern) match { + case (Some(x), Some(y)) => x =?= y + case (None, None) => matched + case _ => notMatched + } + } + + private def caseMatches(scrutinee: CaseDef, pattern: CaseDef) given Context, Env: Matching = { + val (caseEnv, patternMatch) = scrutinee.pattern =?= pattern.pattern + withEnv(caseEnv) { + patternMatch && + treeOptMatches(scrutinee.guard, pattern.guard) && + scrutinee.rhs =?= pattern.rhs + } + } + + private given PatternOps { + + /** 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 `the[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 (scrutinee: Pattern) =?= (pattern: Pattern) given Context, Env: (Env, Matching) = (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$" => + (the[Env], matched(v1.seal)) + + case (Pattern.Value(v1), Pattern.Value(v2)) => + (the[Env], v1 =?= v2) + + case (Pattern.Bind(name1, body1), Pattern.Bind(name2, body2)) => + val bindEnv = the[Env] + (scrutinee.symbol -> pattern.symbol) + (body1 =?= body2) given (the[Context], bindEnv) + + case (Pattern.Unapply(fun1, implicits1, patterns1), Pattern.Unapply(fun2, implicits2, patterns2)) => + val (patEnv, patternsMatch) = foldPatterns(patterns1, patterns2) + (patEnv, fun1 =?= fun2 && implicits1 =?= implicits2 && patternsMatch) + + case (Pattern.Alternatives(patterns1), Pattern.Alternatives(patterns2)) => + foldPatterns(patterns1, patterns2) + + case (Pattern.TypeTest(tpt1), Pattern.TypeTest(tpt2)) => + (the[Env], tpt1 =?= tpt2) + + case (Pattern.WildcardPattern(), Pattern.WildcardPattern()) => + (the[Env], matched) + + case _ => + if (debug) + println( + s""">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + |Scrutinee + | ${scrutinee.show} + | + |${scrutinee.showExtractors} + | + |did not match pattern + | ${pattern.show} + | + |${pattern.showExtractors} + | + | + | + | + |""".stripMargin) + (the[Env], notMatched) + } + + } + + private def foldPatterns(patterns1: List[Pattern], patterns2: List[Pattern]) given Context, Env: (Env, Matching) = { + if (patterns1.size != patterns2.size) (the[Env], notMatched) + else patterns1.zip(patterns2).foldLeft((the[Env], matched)) { (acc, x) => + val (env, res) = (x._1 =?= x._2) given (the[Context], acc._1) + (env, acc._2 && res) + } + } + + private def isTypeBinding(tree: Tree): Boolean = tree match { + case IsTypeDef(tree) => hasBindAnnotation(tree.symbol) + case _ => false + } + } + + /** Result of matching a part of an expression */ + private opaque type Matching = Option[Tuple] + + private object Matching { + + def notMatched: Matching = None + val matched: Matching = Some(()) + def matched(x: Any): Matching = Some(Tuple1(x)) + + def (self: Matching) asOptionOfTuple: Option[Tuple] = self + + /** Concatenates the contents of two sucessful matchings or return a `notMatched` */ + def (self: Matching) && (that: => Matching): Matching = self match { + case Some(x) => + that match { + case Some(y) => Some(x ++ y) + case _ => None + } + case _ => None + } + + /** Is this matching the result of a successful match */ + def (self: Matching) isMatch: Boolean = self.isDefined + + /** 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. + */ + def foldMatchings(matchings: Matching*): Matching = { + // TODO improve performance + matchings.foldLeft[Matching](Some(())) { + case (Some(acc), Some(holes)) => Some(acc ++ holes) + case (_, _) => None + } + } + + } + +} diff --git a/library/src/scala/internal/quoted/Type.scala b/library/src/scala/internal/quoted/Type.scala new file mode 100644 index 000000000000..c754aa79441e --- /dev/null +++ b/library/src/scala/internal/quoted/Type.scala @@ -0,0 +1,26 @@ +package scala.internal.quoted + +import scala.quoted._ + +object Type { + + /** Pattern matches an the scrutineeType against the patternType and returns a tuple + * with the matched holes if successful. + * + * Holes: + * - scala.internal.Quoted.patternHole[T]: hole that matches an expression `x` of type `Type[U]` + * if `U <:< T` and returns `x` as part of the match. + * + * @param scrutineeType `Type[_]` on which we are pattern matching + * @param patternType `Type[_]` containing the pattern tree + * @param hasTypeSplices `Boolean` notify if the pattern has type splices (if so we use a GADT context) + * @param qctx the current QuoteContext + * @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Type[Ti]`` + */ + def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeType: Type[_])(implicit patternType: Type[_], + hasTypeSplices: Boolean, qctx: QuoteContext): Option[Tup] = { + import qctx.tasty._ + new Matcher.QuoteMatcher[qctx.type].typeTreeMatch(scrutineeType.unseal, patternType.unseal, hasTypeSplices).asInstanceOf[Option[Tup]] + } + +} diff --git a/library/src/scala/tasty/reflect/CompilerInterface.scala b/library/src/scala/tasty/reflect/CompilerInterface.scala index f4508de375dc..1cf8a6d9c08c 100644 --- a/library/src/scala/tasty/reflect/CompilerInterface.scala +++ b/library/src/scala/tasty/reflect/CompilerInterface.scala @@ -1498,6 +1498,9 @@ trait CompilerInterface { /** Symbol of scala.internal.Quoted.patternBindHole */ def Definitions_InternalQuoted_patternBindHoleAnnot: Symbol + /** Symbol of scala.internal.Quoted.fromAbove */ + def Definitions_InternalQuoted_fromAboveAnnot: Symbol + def Definitions_UnitType: Type def Definitions_ByteType: Type def Definitions_ShortType: Type diff --git a/tests/run-macros/quote-matcher-runtime/quoted_1.scala b/tests/run-macros/quote-matcher-runtime/quoted_1.scala index de2b3a777736..3fe098d59e16 100644 --- a/tests/run-macros/quote-matcher-runtime/quoted_1.scala +++ b/tests/run-macros/quote-matcher-runtime/quoted_1.scala @@ -9,7 +9,7 @@ object Macros { private def impl[A, B](a: Expr[A], b: Expr[B]) given (qctx: QuoteContext): Expr[Unit] = { import qctx.tasty.{Bind => _, _} - val res = scala.internal.quoted.Matcher.unapply[Tuple, Tuple](a)(b, true, qctx).map { tup => + val res = scala.internal.quoted.Expr.unapply[Tuple, Tuple](a)(b, true, qctx).map { tup => tup.toArray.toList.map { case r: Expr[_] => s"Expr(${r.unseal.show})" diff --git a/tests/run-macros/quote-matcher-runtime/quoted_2.scala b/tests/run-macros/quote-matcher-runtime/quoted_2.scala index 800819c3ad01..ce7209e73f0a 100644 --- a/tests/run-macros/quote-matcher-runtime/quoted_2.scala +++ b/tests/run-macros/quote-matcher-runtime/quoted_2.scala @@ -1,7 +1,7 @@ import Macros._ -import scala.internal.quoted.Matcher._ +import scala.internal.quoted.Expr._ import scala.internal.Quoted._ diff --git a/tests/run-macros/quote-type-matcher-2.check b/tests/run-macros/quote-type-matcher-2.check new file mode 100644 index 000000000000..c76c9f31e805 --- /dev/null +++ b/tests/run-macros/quote-type-matcher-2.check @@ -0,0 +1,4 @@ +%Int% +%List[%Int%]% +%Option[%Int%]% +%%Int% => scala.Double% diff --git a/tests/run-macros/quote-type-matcher-2/quoted_1.scala b/tests/run-macros/quote-type-matcher-2/quoted_1.scala new file mode 100644 index 000000000000..b268014de709 --- /dev/null +++ b/tests/run-macros/quote-type-matcher-2/quoted_1.scala @@ -0,0 +1,18 @@ +import scala.quoted._ + +object Macros { + + inline def lift[A]: String = ${ matchesExpr('[A]) } + + private def matchesExpr(tp: Type[_]) given QuoteContext: Expr[String] = { + def lift(tp: Type[_]): String = tp match { + case '[Int] => "%Int%" + case '[List[$t]] => s"%List[${lift(t)}]%" + case '[Option[$t]] => s"%Option[${lift(t)}]%" + case '[Function1[$t, $u]] => s"%${lift(t)} => ${lift(u)}%" + case _ => tp.show + } + lift(tp).toExpr + } + +} diff --git a/tests/run-macros/quote-type-matcher-2/quoted_2.scala b/tests/run-macros/quote-type-matcher-2/quoted_2.scala new file mode 100644 index 000000000000..c7b3aefd4d13 --- /dev/null +++ b/tests/run-macros/quote-type-matcher-2/quoted_2.scala @@ -0,0 +1,16 @@ + +import Macros._ + +import scala.internal.Quoted._ + +object Test { + + def main(args: Array[String]): Unit = { + + println(lift[Int]) + println(lift[List[Int]]) + println(lift[Option[Int]]) + println(lift[Int => Double]) + + } +} diff --git a/tests/run-macros/quote-type-matcher.check b/tests/run-macros/quote-type-matcher.check new file mode 100644 index 000000000000..7ad33da44a64 --- /dev/null +++ b/tests/run-macros/quote-type-matcher.check @@ -0,0 +1,20 @@ +Scrutinee: scala.Int +Pattern: scala.Int +Result: Some(List()) + +Scrutinee: 1 +Pattern: scala.Int +Result: Some(List()) + +Scrutinee: scala.Int +Pattern: 2 +Result: None + +Scrutinee: scala.collection.immutable.List[scala.Int] +Pattern: scala.collection.immutable.List[scala.Int] +Result: Some(List()) + +Scrutinee: scala.collection.immutable.List[scala.Int] +Pattern: scala.collection.immutable.List[scala.Double] +Result: None + diff --git a/tests/run-macros/quote-type-matcher/quoted_1.scala b/tests/run-macros/quote-type-matcher/quoted_1.scala new file mode 100644 index 000000000000..65cb9d129eff --- /dev/null +++ b/tests/run-macros/quote-type-matcher/quoted_1.scala @@ -0,0 +1,29 @@ +import scala.quoted._ +import scala.quoted.matching._ + + +object Macros { + + inline def matches[A, B]: Unit = ${ matchesExpr('[A], '[B]) } + + private def matchesExpr[A, B](a: Type[A], b: Type[B]) given (qctx: QuoteContext): Expr[Unit] = { + import qctx.tasty.{Bind => _, _} + + val res = scala.internal.quoted.Type.unapply[Tuple, Tuple](a)(b, true, qctx).map { tup => + tup.toArray.toList.map { + case r: quoted.Type[_] => + s"Type(${r.unseal.show})" + case r: Bind[_] => + s"Bind(${r.name})" + } + } + + '{ + println("Scrutinee: " + ${a.unseal.show.toExpr}) + println("Pattern: " + ${b.unseal.show.toExpr}) + println("Result: " + ${res.toString.toExpr}) + println() + } + } + +} diff --git a/tests/run-macros/quote-type-matcher/quoted_2.scala b/tests/run-macros/quote-type-matcher/quoted_2.scala new file mode 100644 index 000000000000..b16af1e4aee1 --- /dev/null +++ b/tests/run-macros/quote-type-matcher/quoted_2.scala @@ -0,0 +1,19 @@ + +import Macros._ + +import scala.internal.Quoted._ + +object Test { + + def main(args: Array[String]): Unit = { + + matches[Int, Int] + matches[1, Int] + matches[Int, 2] + + matches[List[Int], List[Int]] + matches[List[Int], List[Double]] + + } +} + diff --git a/tests/run-staging/quote-type-matcher.check b/tests/run-staging/quote-type-matcher.check new file mode 100644 index 000000000000..e27d39931aa6 --- /dev/null +++ b/tests/run-staging/quote-type-matcher.check @@ -0,0 +1,8 @@ +scala.Int + +scala.Int +scala.Double + +scala.Int +scala.Short +scala.Double diff --git a/tests/run-staging/quote-type-matcher.scala b/tests/run-staging/quote-type-matcher.scala new file mode 100644 index 000000000000..7736712c4ebb --- /dev/null +++ b/tests/run-staging/quote-type-matcher.scala @@ -0,0 +1,28 @@ +import scala.quoted._ +import scala.quoted.staging._ +import scala.reflect.ClassTag + +object Test { + given as Toolbox = Toolbox.make(this.getClass.getClassLoader) + def main(args: Array[String]): Unit = withQuoteContext { + val '[List[Int]] = '[List[Int]] + + val '[List[$int]] = '[List[Int]] + println(int.show) + println() + + { + val '[Function1[$t1, $r]] = '[Int => Double] + println(t1.show) + println(r.show) + println() + } + + { + val '[Function1[Function1[$t1, $r0], $r]] = '[(Int => Short) => Double] + println(t1.show) + println(r0.show) + println(r.show) + } + } +}