diff --git a/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala b/compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala index bfa4c1c6d1f2..31678a9481e4 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,61 @@ 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 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] = { + 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)) + } + } + } + + 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 { @@ -457,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) => 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