Skip to content

Simplify CompilerInterface abstraction #10189

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
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
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/quoted/Matcher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import scala.annotation.internal.sharable
import scala.annotation.{Annotation, compileTimeOnly}

import scala.quoted._
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface

/** Matches a quoted tree against a quoted pattern tree.
* A quoted pattern tree may have type and term holes in addition to normal terms.
Expand Down Expand Up @@ -98,7 +97,7 @@ import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface
*/
object Matcher {

abstract class QuoteMatcher[QCtx <: QuoteContext { val reflect: scala.internal.tasty.CompilerInterface } & Singleton](val qctx: QCtx) {
abstract class QuoteMatcher[QCtx <: QuoteContext & scala.internal.quoted.CompilerInterface & Singleton](val qctx: QCtx) {

// TODO improve performance

Expand Down
118 changes: 61 additions & 57 deletions compiler/src/dotty/tools/dotc/quoted/QuoteContextImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ object QuoteContextImpl {

}

class QuoteContextImpl private (ctx: Context) extends QuoteContext:
class QuoteContextImpl private (ctx: Context) extends QuoteContext, scala.internal.quoted.CompilerInterface:

object reflect extends scala.tasty.Reflection, scala.internal.tasty.CompilerInterface:
object reflect extends scala.tasty.Reflection:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three concepts here:

  • CompilerInterface
  • Reflection
  • QuoteContext

The responsibility for QuoteContext is relatively clear, it's the facade and it mainly holds API for Type[T] and Expr[T].

It seems there is some overlapping of responsibilities between Reflection and CompilerInterface:

  • CompilerInterface is a contract with the compiler, not the API for meta-programmers
  • Reflection is both the contract with the compiler, and the API for meta-programmers

To continue the refactoring in #9818, it would be nice to make the responsibility of each interface clear and disjoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that this PR also removed the overlapping between Reflection and CompilerInterface as now the CompilerInterface only cares about Expr and Type, but nothing within Reflection.

I will still try to make the internal interaction with CompilerInterface simpler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that this PR also removed the overlapping between Reflection and CompilerInterface as now the CompilerInterface only cares about Expr and Type, but nothing within Reflection.

So rename CompilerInterface to QuoteInterface might make things more clear?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would make it clearer.


def rootContext: Context = ctx

Expand Down Expand Up @@ -2615,70 +2615,74 @@ class QuoteContextImpl private (ctx: Context) extends QuoteContext:
private def withDefaultPos[T <: Tree](fn: Context ?=> T): T =
fn(using ctx.withSource(Position.ofMacroExpansion.source)).withSpan(Position.ofMacroExpansion.span)

def unpickleTerm(pickledQuote: PickledQuote): Term =
PickledQuotes.unpickleTerm(pickledQuote)

def unpickleTypeTree(pickledQuote: PickledQuote): TypeTree =
PickledQuotes.unpickleTypeTree(pickledQuote)
end reflect

def termMatch(scrutinee: Term, pattern: Term): Option[Tuple] =
treeMatch(scrutinee, pattern)
def unpickleExpr(pickledQuote: PickledQuote): scala.quoted.Expr[Any] =
val tree = PickledQuotes.unpickleTerm(pickledQuote)(using reflect.rootContext)
new scala.internal.quoted.Expr(tree, hash)

def typeTreeMatch(scrutinee: TypeTree, pattern: TypeTree): Option[Tuple] =
treeMatch(scrutinee, pattern)
def unpickleType(pickledQuote: PickledQuote): scala.quoted.Type[?] =
val tree = PickledQuotes.unpickleTypeTree(pickledQuote)(using reflect.rootContext)
new scala.internal.quoted.Type(tree, hash)

private def treeMatch(scrutinee: Tree, pattern: Tree): Option[Tuple] = {
def isTypeHoleDef(tree: Tree): Boolean =
tree match
case tree: TypeDef =>
tree.symbol.hasAnnotation(dotc.core.Symbols.defn.InternalQuotedPatterns_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)
def exprMatch(scrutinee: scala.quoted.Expr[Any], pattern: scala.quoted.Expr[Any]): Option[Tuple] =
treeMatch(scrutinee.unseal(using this), pattern.unseal(using this))

val (pat1, typeHoles) = extractTypeHoles(pattern)
def typeMatch(scrutinee: scala.quoted.Type[?], pattern: scala.quoted.Type[?]): Option[Tuple] =
treeMatch(scrutinee.unseal(using this), pattern.unseal(using this))

val ctx1 =
if typeHoles.isEmpty then ctx
else
val ctx1 = ctx.fresh.setFreshGADTBounds.addMode(dotc.core.Mode.GadtConstraintInference)
ctx1.gadt.addToConstraint(typeHoles)
ctx1

val qctx1 = dotty.tools.dotc.quoted.QuoteContextImpl()(using ctx1)
.asInstanceOf[QuoteContext { val reflect: QuoteContextImpl.this.reflect.type }]
private def treeMatch(scrutinee: reflect.Tree, pattern: reflect.Tree): Option[Tuple] = {
import reflect._
given Context = rootContext
def isTypeHoleDef(tree: Tree): Boolean =
tree match
case tree: TypeDef =>
tree.symbol.hasAnnotation(dotc.core.Symbols.defn.InternalQuotedPatterns_patternTypeAnnot)
case _ => false

val matcher = new Matcher.QuoteMatcher[qctx1.type](qctx1) {
def patternHoleSymbol: Symbol = dotc.core.Symbols.defn.InternalQuotedPatterns_patternHole
def higherOrderHoleSymbol: Symbol = dotc.core.Symbols.defn.InternalQuotedPatterns_higherOrderHole
}
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.gadt.addToConstraint(typeHoles)
ctx1

val qctx1 = dotty.tools.dotc.quoted.QuoteContextImpl()(using ctx1)
.asInstanceOf[QuoteContext & scala.internal.quoted.CompilerInterface]

val matcher = new Matcher.QuoteMatcher[qctx1.type](qctx1) {
def patternHoleSymbol: qctx1.reflect.Symbol = dotc.core.Symbols.defn.InternalQuotedPatterns_patternHole.asInstanceOf
def higherOrderHoleSymbol: qctx1.reflect.Symbol = dotc.core.Symbols.defn.InternalQuotedPatterns_higherOrderHole.asInstanceOf
}

val matchings =
if pat1.isType then matcher.termMatch(scrutinee, pat1)
else matcher.termMatch(scrutinee, pat1)

// val matchings = matcher.termMatch(scrutinee, pattern)
if typeHoles.isEmpty then matchings
else {
// 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) =
ctx1.gadt.approximation(sym, !sym.hasAnnotation(dotc.core.Symbols.defn.InternalQuotedPatterns_fromAboveAnnot)).seal
matchings.map { tup =>
Tuple.fromIArray(typeHoles.map(typeHoleApproximation).toArray.asInstanceOf[IArray[Object]]) ++ tup
}
val matchings =
if pat1.isType then matcher.termMatch(scrutinee.asInstanceOf[matcher.qctx.reflect.Term], pat1.asInstanceOf[matcher.qctx.reflect.Term])
else matcher.termMatch(scrutinee.asInstanceOf[matcher.qctx.reflect.Term], pat1.asInstanceOf[matcher.qctx.reflect.Term])

// val matchings = matcher.termMatch(scrutinee, pattern)
if typeHoles.isEmpty then matchings
else {
// 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) =
ctx1.gadt.approximation(sym, !sym.hasAnnotation(dotc.core.Symbols.defn.InternalQuotedPatterns_fromAboveAnnot)).asInstanceOf[qctx1.reflect.TypeRepr].seal
matchings.map { tup =>
Tuple.fromIArray(typeHoles.map(typeHoleApproximation).toArray.asInstanceOf[IArray[Object]]) ++ tup
}
}

end reflect
}

private[this] val hash = QuoteContextImpl.scopeId(using ctx)
override def hashCode: Int = hash
Expand Down
4 changes: 2 additions & 2 deletions library/src-bootstrapped/scala/internal/quoted/Expr.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scala.internal.quoted

import scala.quoted._
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface
import scala.internal.quoted.CompilerInterface.quoteContextWithCompilerInterface

/** An Expr backed by a tree. Only the current compiler trees are allowed.
*
Expand Down Expand Up @@ -54,7 +54,7 @@ object Expr {
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: scala.quoted.Expr[Any])
(using patternExpr: scala.quoted.Expr[Any], qctx: QuoteContext): Option[Tup] = {
val qctx1 = quoteContextWithCompilerInterface(qctx)
qctx1.reflect.termMatch(scrutineeExpr.unseal, patternExpr.unseal).asInstanceOf[Option[Tup]]
qctx1.exprMatch(scrutineeExpr, patternExpr).asInstanceOf[Option[Tup]]
}

/** Returns a null expresssion equivalent to `'{null}` */
Expand Down
4 changes: 2 additions & 2 deletions library/src-bootstrapped/scala/internal/quoted/Type.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package scala.internal.quoted

import scala.quoted._
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface
import scala.internal.quoted.CompilerInterface.quoteContextWithCompilerInterface

/** Quoted type (or kind) `T` backed by a tree */
final class Type[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quoted.Type[Any] {
Expand Down Expand Up @@ -37,7 +37,7 @@ object Type {
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeType: scala.quoted.Type[_])
(using patternType: scala.quoted.Type[_], qctx: QuoteContext): Option[Tup] = {
val qctx1 = quoteContextWithCompilerInterface(qctx)
qctx1.reflect.typeTreeMatch(scrutineeType.unseal, patternType.unseal).asInstanceOf[Option[Tup]]
qctx1.typeMatch(scrutineeType, patternType).asInstanceOf[Option[Tup]]
}


Expand Down
2 changes: 0 additions & 2 deletions library/src-bootstrapped/scala/quoted/Expr.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package scala.quoted

import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface

/** Quoted expression of type `T` */
abstract class Expr[+T] private[scala] {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package scala.internal.quoted

import scala.quoted._
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface

/** An Expr backed by a tree. Only the current compiler trees are allowed.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package scala.internal.quoted

import scala.quoted._
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface

/** Quoted type (or kind) `T` backed by a tree */
final class Type[Tree](val typeTree: Tree, val scopeId: Int) extends scala.quoted.Type[Any] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
package scala.internal.tasty
package scala.internal.quoted

import scala.quoted.QuoteContext
import scala.tasty.reflect._
import scala.internal.quoted.PickledQuote

/** Part of the reflection interface that needs to be implemented by the compiler */
trait CompilerInterface { self: scala.tasty.Reflection =>
trait CompilerInterface { self: scala.quoted.QuoteContext =>

//////////////////////
// QUOTE UNPICKLING //
//////////////////////
import self.reflect._

/** Unpickle `repr` which represents a pickled `Expr` tree,
* replacing splice nodes with `holes`
*/
def unpickleTerm(pickledQuote: PickledQuote): Term
def unpickleExpr(pickledQuote: PickledQuote): scala.quoted.Expr[Any]

/** Unpickle `repr` which represents a pickled `Type` tree,
* replacing splice nodes with `holes`
*/
def unpickleTypeTree(pickledQuote: PickledQuote): TypeTree
def unpickleType(pickledQuote: PickledQuote): scala.quoted.Type[?]

/** Pattern matches the scrutinee against the pattern and returns a tuple
* with the matched holes if successful.
Expand All @@ -36,27 +34,27 @@ trait CompilerInterface { self: scala.tasty.Reflection =>
* - 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 scrutinee `Term` on which we are pattern matching
* @param pattern `Term` containing the pattern tree
* @param scrutinee `Expr` on which we are pattern matching
* @param pattern `Expr` containing the pattern tree
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Term``
*/
def termMatch(scrutinee: Term, pattern: Term): Option[Tuple]
def exprMatch(scrutinee: scala.quoted.Expr[Any], pattern: scala.quoted.Expr[Any]): Option[Tuple]

/** Pattern matches the scrutineeType against the patternType and returns a tuple
* with the matched holes if successful.
*
* @param scrutinee `TypeTree` on which we are pattern matching
* @param pattern `TypeTree` containing the pattern tree
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `quoted.Type[Ti]``
* @param scrutinee `Type` on which we are pattern matching
* @param pattern `Type` containing the pattern tree
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `scala.quoted.Type[Ti]``
*/
def typeTreeMatch(scrutinee: TypeTree, pattern: TypeTree): Option[Tuple]
def typeMatch(scrutinee: scala.quoted.Type[?], pattern: scala.quoted.Type[?]): Option[Tuple]

}


object CompilerInterface {

private[scala] def quoteContextWithCompilerInterface(qctx: QuoteContext): qctx.type { val reflect: qctx.reflect.type & scala.internal.tasty.CompilerInterface } =
qctx.asInstanceOf[qctx.type { val reflect: qctx.reflect.type & scala.internal.tasty.CompilerInterface }]
private[scala] def quoteContextWithCompilerInterface(qctx: QuoteContext): qctx.type { val reflect: qctx.reflect.type } & CompilerInterface =
qctx.asInstanceOf[qctx.type { val reflect: qctx.reflect.type } & CompilerInterface]

}
11 changes: 4 additions & 7 deletions library/src/scala/internal/quoted/PickledQuote.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package scala.internal.quoted

import scala.quoted._
import scala.internal.tasty.CompilerInterface.quoteContextWithCompilerInterface

/** Pickled representation of a quoted expression or type */
trait PickledQuote:
Expand All @@ -18,14 +17,12 @@ trait PickledQuote:
object PickledQuote:

def unpickleExpr[T](pickledQuote: PickledQuote): QuoteContext ?=> Expr[T] =
val qctx = quoteContextWithCompilerInterface(summon[QuoteContext])
val tree = qctx.reflect.unpickleTerm(pickledQuote)
new scala.internal.quoted.Expr(tree, qctx.hashCode).asInstanceOf[Expr[T]]
val qctx = CompilerInterface.quoteContextWithCompilerInterface(summon[QuoteContext])
qctx.unpickleExpr(pickledQuote).asInstanceOf[Expr[T]]

def unpickleType[T](pickledQuote: PickledQuote): QuoteContext ?=> Type[T] =
val qctx = quoteContextWithCompilerInterface(summon[QuoteContext])
val tree = qctx.reflect.unpickleTypeTree(pickledQuote)
new scala.internal.quoted.Type(tree, qctx.hashCode).asInstanceOf[Type[T]]
val qctx = CompilerInterface.quoteContextWithCompilerInterface(summon[QuoteContext])
qctx.unpickleType(pickledQuote).asInstanceOf[Type[T]]

/** Create an instance of PickledExpr from encoded tasty and sequence of labmdas to fill holes
*
Expand Down
2 changes: 0 additions & 2 deletions library/src/scala/tasty/Reflection.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package scala.tasty

import scala.internal.tasty.CompilerInterface

import scala.quoted.QuoteContext
import scala.tasty.reflect._

Expand Down
2 changes: 0 additions & 2 deletions tests/run-macros/quote-matcher-runtime/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ object Macros {
s"Expr(${r.unseal.show})"
case r: Type[_] =>
s"Type(${r.unseal.show})"
case r: String =>
s"String($r)"
}
}

Expand Down