From 41b99697bcfdc03b19b3762891db7e0db4a884a5 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 12 Apr 2023 15:39:38 +0200 Subject: [PATCH 1/2] Move treeMatch into QuoteMatcher [Cherry-picked 7d3f3e4dced204d6d545d189678c33e02d4df3ba] --- .../quoted/runtime/impl/QuoteMatcher.scala | 62 +++++++++++++++++-- .../quoted/runtime/impl/QuotesImpl.scala | 57 +---------------- 2 files changed, 61 insertions(+), 58 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index bfa4c1c6d1f2..462948f7b3ae 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -3,8 +3,10 @@ package runtime.impl import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.Mode.GadtConstraintInference import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.StdNames.nme import dotty.tools.dotc.core.Symbols.* @@ -122,10 +124,62 @@ object QuoteMatcher { private def withEnv[T](env: Env)(body: Env ?=> T): T = body(using env) - def treeMatch(scrutineeTree: Tree, patternTree: Tree)(using Context): Option[MatchingExprs] = - given Env = Map.empty - optional: - scrutineeTree =?= patternTree + /** Evaluate the the result of pattern matching against a quote pattern. + * Implementation of the runtime of `QuoteMatching.{ExprMatch,TypeMatch}.unapply`. + */ + def treeMatch(scrutinee: Tree, pattern: Tree)(using Context): Option[Tuple] = { + def isTypeHoleDef(tree: Tree): Boolean = + tree match + case tree: TypeDef => + tree.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) + case _ => false + + def extractTypeHoles(pat: Tree): (Tree, List[Symbol]) = + pat match + case tpd.Inlined(_, Nil, pat2) => extractTypeHoles(pat2) + case tpd.Block(stats @ ((typeHole: TypeDef) :: _), expr) if isTypeHoleDef(typeHole) => + val holes = stats.takeWhile(isTypeHoleDef).map(_.symbol) + val otherStats = stats.dropWhile(isTypeHoleDef) + (tpd.cpy.Block(pat)(otherStats, expr), holes) + case _ => + (pat, Nil) + + val (pat1, typeHoles) = extractTypeHoles(pattern) + + val ctx1 = + if typeHoles.isEmpty then ctx + else + val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(GadtConstraintInference) + ctx1.gadtState.addToConstraint(typeHoles) + ctx1 + + // After matching and doing all subtype checks, we have to approximate all the type bindings + // that we have found, seal them in a quoted.Type and add them to the result + def typeHoleApproximation(sym: Symbol) = + val fromAboveAnnot = sym.hasAnnotation(defn.QuotedRuntimePatterns_fromAboveAnnot) + val fullBounds = ctx1.gadt.fullBounds(sym) + if fromAboveAnnot then fullBounds.nn.hi else fullBounds.nn.lo + + optional { + given Context = ctx1 + given Env = Map.empty + scrutinee =?= pat1 + }.map { matchings => + import QuoteMatcher.MatchResult.* + lazy val spliceScope = SpliceScope.getCurrent + val typeHoleApproximations = typeHoles.map(typeHoleApproximation) + val typeHoleMapping = Map(typeHoles.zip(typeHoleApproximations)*) + val typeHoleMap = new TypeMap { + def apply(tp: Type): Type = tp match + case TypeRef(NoPrefix, _) => typeHoleMapping.getOrElse(tp.typeSymbol, tp) + case _ => mapOver(tp) + } + val matchedExprs = matchings.map(_.toExpr(typeHoleMap, spliceScope)) + val matchedTypes = typeHoleApproximations.map(tpe => new TypeImpl(TypeTree(tpe), spliceScope)) + val results = matchedTypes ++ matchedExprs + Tuple.fromIArray(IArray.unsafeFromArray(results.toArray)) + } + } /** 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) => MatchingExprs): optional[MatchingExprs] = (l1, l2) match { diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index db4e3e6c6a05..68fb646fe2bb 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -45,7 +45,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler reflect.Printer.TreeCode.show(reflect.asTerm(self)) def matches(that: scala.quoted.Expr[Any]): Boolean = - treeMatch(reflect.asTerm(self), reflect.asTerm(that)).nonEmpty + QuoteMatcher.treeMatch(reflect.asTerm(self), reflect.asTerm(that)).nonEmpty def valueOrAbort(using fromExpr: FromExpr[T]): T = def reportError = @@ -3155,65 +3155,14 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def unapply[TypeBindings, Tup <: Tuple](scrutinee: scala.quoted.Expr[Any])(using pattern: scala.quoted.Expr[Any]): Option[Tup] = val scrutineeTree = reflect.asTerm(scrutinee) val patternTree = reflect.asTerm(pattern) - treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]] + QuoteMatcher.treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]] end ExprMatch object TypeMatch extends TypeMatchModule: def unapply[TypeBindings, Tup <: Tuple](scrutinee: scala.quoted.Type[?])(using pattern: scala.quoted.Type[?]): Option[Tup] = val scrutineeTree = reflect.TypeTree.of(using scrutinee) val patternTree = reflect.TypeTree.of(using pattern) - treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]] + QuoteMatcher.treeMatch(scrutineeTree, patternTree).asInstanceOf[Option[Tup]] end TypeMatch - private def treeMatch(scrutinee: reflect.Tree, pattern: reflect.Tree): Option[Tuple] = { - import reflect._ - def isTypeHoleDef(tree: Tree): Boolean = - tree match - case tree: TypeDef => - tree.symbol.hasAnnotation(dotc.core.Symbols.defn.QuotedRuntimePatterns_patternTypeAnnot) - case _ => false - - def extractTypeHoles(pat: Term): (Term, List[Symbol]) = - pat match - case tpd.Inlined(_, Nil, pat2) => extractTypeHoles(pat2) - case tpd.Block(stats @ ((typeHole: TypeDef) :: _), expr) if isTypeHoleDef(typeHole) => - val holes = stats.takeWhile(isTypeHoleDef).map(_.symbol) - val otherStats = stats.dropWhile(isTypeHoleDef) - (tpd.cpy.Block(pat)(otherStats, expr), holes) - case _ => - (pat, Nil) - - val (pat1, typeHoles) = extractTypeHoles(pattern) - - val ctx1 = - if typeHoles.isEmpty then ctx - else - val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(dotc.core.Mode.GadtConstraintInference) - ctx1.gadtState.addToConstraint(typeHoles) - ctx1 - - // After matching and doing all subtype checks, we have to approximate all the type bindings - // that we have found, seal them in a quoted.Type and add them to the result - def typeHoleApproximation(sym: Symbol) = - val fromAboveAnnot = sym.hasAnnotation(dotc.core.Symbols.defn.QuotedRuntimePatterns_fromAboveAnnot) - val fullBounds = ctx1.gadt.fullBounds(sym) - if fromAboveAnnot then fullBounds.hi else fullBounds.lo - - QuoteMatcher.treeMatch(scrutinee, pat1)(using ctx1).map { matchings => - import QuoteMatcher.MatchResult.* - lazy val spliceScope = SpliceScope.getCurrent - val typeHoleApproximations = typeHoles.map(typeHoleApproximation) - val typeHoleMapping = Map(typeHoles.zip(typeHoleApproximations)*) - val typeHoleMap = new Types.TypeMap { - def apply(tp: Types.Type): Types.Type = tp match - case Types.TypeRef(Types.NoPrefix, _) => typeHoleMapping.getOrElse(tp.typeSymbol, tp) - case _ => mapOver(tp) - } - val matchedExprs = matchings.map(_.toExpr(typeHoleMap, spliceScope)) - val matchedTypes = typeHoleApproximations.map(reflect.TypeReprMethods.asType) - val results = matchedTypes ++ matchedExprs - Tuple.fromIArray(IArray.unsafeFromArray(results.toArray)) - } - } - end QuotesImpl From c3514843b473161ef8102c845e9f17eba0a4595f Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 12 Apr 2023 16:03:58 +0200 Subject: [PATCH 2/2] Refactor and optimize `treeMatch` Now we do not use use the TypeMap if there is no type to map. We also do not create the type map if there are no expression that may contain types that need mapping. [Cherry-picked aad84753a04dfd3ee393d49420feec01c3d52765] --- .../quoted/runtime/impl/QuoteMatcher.scala | 103 +++++++++--------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index 462948f7b3ae..31678a9481e4 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala @@ -124,63 +124,62 @@ object QuoteMatcher { private def withEnv[T](env: Env)(body: Env ?=> T): T = body(using env) - /** Evaluate the the result of pattern matching against a quote pattern. - * Implementation of the runtime of `QuoteMatching.{ExprMatch,TypeMatch}.unapply`. + /** Evaluate the result of pattern matching against a quote pattern. + * Implementation of the runtime of `QuoteMatching.{ExprMatch, TypeMatch}.unapply`. */ def treeMatch(scrutinee: Tree, pattern: Tree)(using Context): Option[Tuple] = { - def isTypeHoleDef(tree: Tree): Boolean = - tree match - case tree: TypeDef => - tree.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) - case _ => false - - def extractTypeHoles(pat: Tree): (Tree, List[Symbol]) = - pat match - case tpd.Inlined(_, Nil, pat2) => extractTypeHoles(pat2) - case tpd.Block(stats @ ((typeHole: TypeDef) :: _), expr) if isTypeHoleDef(typeHole) => - val holes = stats.takeWhile(isTypeHoleDef).map(_.symbol) - val otherStats = stats.dropWhile(isTypeHoleDef) - (tpd.cpy.Block(pat)(otherStats, expr), holes) - case _ => - (pat, Nil) - - val (pat1, typeHoles) = extractTypeHoles(pattern) - - val ctx1 = - if typeHoles.isEmpty then ctx - else - val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(GadtConstraintInference) - ctx1.gadtState.addToConstraint(typeHoles) - ctx1 - - // After matching and doing all subtype checks, we have to approximate all the type bindings - // that we have found, seal them in a quoted.Type and add them to the result - def typeHoleApproximation(sym: Symbol) = - val fromAboveAnnot = sym.hasAnnotation(defn.QuotedRuntimePatterns_fromAboveAnnot) - val fullBounds = ctx1.gadt.fullBounds(sym) - if fromAboveAnnot then fullBounds.nn.hi else fullBounds.nn.lo - - optional { - given Context = ctx1 - given Env = Map.empty - scrutinee =?= pat1 - }.map { matchings => - import QuoteMatcher.MatchResult.* - lazy val spliceScope = SpliceScope.getCurrent - val typeHoleApproximations = typeHoles.map(typeHoleApproximation) - val typeHoleMapping = Map(typeHoles.zip(typeHoleApproximations)*) - val typeHoleMap = new TypeMap { - def apply(tp: Type): Type = tp match - case TypeRef(NoPrefix, _) => typeHoleMapping.getOrElse(tp.typeSymbol, tp) - case _ => mapOver(tp) + val (pat1, typeHoles, ctx1) = instrumentTypeHoles(pattern) + inContext(ctx1) { + optional { + given Env = Map.empty + scrutinee =?= pat1 + }.map { matchings => + import QuoteMatcher.MatchResult.* + lazy val spliceScope = SpliceScope.getCurrent + // After matching and doing all subtype checks, we have to approximate all the type bindings + // that we have found, seal them in a quoted.Type and add them to the result + val typeHoleApproximations = typeHoles.map(typeHoleApproximation) + val matchedTypes = typeHoleApproximations.map(tpe => new TypeImpl(TypeTree(tpe), spliceScope)) + val matchedExprs = + val typeHoleMap: Type => Type = + if typeHoles.isEmpty then identity + else new TypeMap { + private val typeHoleMapping = Map(typeHoles.zip(typeHoleApproximations)*) + def apply(tp: Type): Type = tp match + case TypeRef(NoPrefix, _) => typeHoleMapping.getOrElse(tp.typeSymbol, tp) + case _ => mapOver(tp) + } + if matchings.isEmpty then Nil + else matchings.map(_.toExpr(typeHoleMap, spliceScope)) + val results = matchedTypes ++ matchedExprs + Tuple.fromIArray(IArray.unsafeFromArray(results.toArray)) } - val matchedExprs = matchings.map(_.toExpr(typeHoleMap, spliceScope)) - val matchedTypes = typeHoleApproximations.map(tpe => new TypeImpl(TypeTree(tpe), spliceScope)) - val results = matchedTypes ++ matchedExprs - Tuple.fromIArray(IArray.unsafeFromArray(results.toArray)) } } + def instrumentTypeHoles(pat: Tree)(using Context): (Tree, List[Symbol], Context) = + def isTypeHoleDef(tree: Tree): Boolean = tree match + case tree: TypeDef => tree.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) + case _ => false + pat match + case tpd.Inlined(_, Nil, pat2) => instrumentTypeHoles(pat2) + case tpd.Block(stats @ ((typeHole: TypeDef) :: _), expr) if isTypeHoleDef(typeHole) => + val (holeDefs, otherStats) = stats.span(isTypeHoleDef) + val holeSyms = holeDefs.map(_.symbol) + val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(GadtConstraintInference) + ctx1.gadtState.addToConstraint(holeSyms) + (tpd.cpy.Block(pat)(otherStats, expr), holeSyms, ctx1) + case _ => + (pat, Nil, ctx) + + /** Type approximation of a quote pattern type variable. + * Should only be approximated after matching the tree. + */ + def typeHoleApproximation(sym: Symbol)(using Context): Type = + val fromAboveAnnot = sym.hasAnnotation(defn.QuotedRuntimePatterns_fromAboveAnnot) + val fullBounds = ctx.gadt.fullBounds(sym) + if fromAboveAnnot then fullBounds.nn.hi else fullBounds.nn.lo + /** 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) => MatchingExprs): optional[MatchingExprs] = (l1, l2) match { case (x :: xs, y :: ys) => mtch(x, y) &&& matchLists(xs, ys)(mtch) @@ -511,7 +510,7 @@ object QuoteMatcher { * * This expression is assumed to be a valid expression in the given splice scope. */ - def toExpr(mapTypeHoles: TypeMap, spliceScope: Scope)(using Context): Expr[Any] = this match + def toExpr(mapTypeHoles: Type => Type, spliceScope: Scope)(using Context): Expr[Any] = this match case MatchResult.ClosedTree(tree) => new ExprImpl(tree, spliceScope) case MatchResult.OpenTree(tree, patternTpe, args, env) =>