Skip to content

Backport "Move treeMatch into QuoteMatcher" to LTS #18937

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions compiler/src/scala/quoted/runtime/impl/QuoteMatcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) =>
Expand Down
57 changes: 3 additions & 54 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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