Skip to content

Commit 08de2ba

Browse files
Add QuotePattern AST (#17935)
We add the definition of the `QuotePattern` AST. In this first step, keep the typing of quote patterns untouched and decode the resulting `unapply` into a `QuotePattern`. This AST is used to perform all transformations until the `staging` phase, here it is encoded into an `unapply` and a `Quote` AST. We also encode the AST into an `unapply` when we pickle the tree to keep the same pickled representation as before. Based on #17956
2 parents 1803802 + 351456e commit 08de2ba

25 files changed

+510
-66
lines changed

compiler/src/dotty/tools/dotc/ast/TreeInfo.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,12 +807,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
807807

808808
/** The variables defined by a pattern, in reverse order of their appearance. */
809809
def patVars(tree: Tree)(using Context): List[Symbol] = {
810-
val acc = new TreeAccumulator[List[Symbol]] {
810+
val acc = new TreeAccumulator[List[Symbol]] { outer =>
811811
def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match {
812812
case Bind(_, body) => apply(tree.symbol :: syms, body)
813813
case Annotated(tree, id @ Ident(tpnme.BOUNDTYPE_ANNOT)) => apply(id.symbol :: syms, tree)
814+
case QuotePattern(bindings, body, _) => quotePatVars(bindings.map(_.symbol) ::: syms, body)
814815
case _ => foldOver(syms, tree)
815816
}
817+
private object quotePatVars extends TreeAccumulator[List[Symbol]] {
818+
def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match {
819+
case SplicePattern(pat, _) => outer.apply(syms, pat)
820+
case _ => foldOver(syms, tree)
821+
}
822+
}
816823
}
817824
acc(Nil, tree)
818825
}
@@ -1048,6 +1055,21 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
10481055
if tree.symbol.isTypeSplice then Some(tree.qualifier) else None
10491056
}
10501057

1058+
extension (tree: tpd.Quote)
1059+
/** Type of the quoted expression as seen from outside the quote */
1060+
def bodyType(using Context): Type =
1061+
val quoteType = tree.tpe // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
1062+
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
1063+
exprType.argInfos.head // T
1064+
end extension
1065+
1066+
extension (tree: tpd.QuotePattern)
1067+
/** Type of the quoted pattern */
1068+
def bodyType(using Context): Type =
1069+
val quoteType = tree.tpe // `Expr[T]` or `Type[T]`
1070+
quoteType.argInfos.head // T
1071+
end extension
1072+
10511073
/** Extractor for not-null assertions.
10521074
* A not-null assertion for reference `x` has the form `x.$asInstanceOf$[x.type & T]`.
10531075
*/

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -686,9 +686,9 @@ object Trees {
686686
* phases. After `pickleQuotes` phase, the only quotes that exist are in `inline`
687687
* methods. These are dropped when we remove the inline method implementations.
688688
*
689-
* Type quotes `'[body]` from the parser are desugared into quote patterns (using a `Type.of[T]]`)
690-
* when type checking. TASTy files will not contain type quotes. Type quotes are used again
691-
* in the `staging` phase to represent the reification of `Type.of[T]]`.
689+
* Type quotes `'[body]` from the parser are typed into `QuotePattern`s when type checking.
690+
* TASTy files will not contain type quotes. Type quotes are used again in the `staging`
691+
* phase to represent the reification of `Type.of[T]]`.
692692
*
693693
* Type tags `tags` are always empty before the `staging` phase. Tags for stage inconsistent
694694
* types are added in the `staging` phase to level 0 quotes. Tags for types that refer to
@@ -704,12 +704,6 @@ object Trees {
704704
/** Is this a type quote `'[tpe]' */
705705
def isTypeQuote = body.isType
706706

707-
/** Type of the quoted expression as seen from outside the quote */
708-
def bodyType(using Context): Type =
709-
val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
710-
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
711-
exprType.argInfos.head // T
712-
713707
/** Set the type of the body of the quote */
714708
def withBodyType(tpe: Type)(using Context): Quote[Type] =
715709
val exprType = // `Expr[T]` or `Type[T]`
@@ -737,13 +731,30 @@ object Trees {
737731
type ThisTree[+T <: Untyped] = Splice[T]
738732
}
739733

734+
/** A tree representing a quote pattern `'{ type binding1; ...; body }` or `'[ type binding1; ...; body ]`.
735+
* `QuotePattern`s are created the type checker when typing an `untpd.Quote` in a pattern context.
736+
*
737+
* `QuotePattern`s are checked are encoded into `unapply`s in the `staging` phase.
738+
*
739+
* The `bindings` contain the list of quote pattern type variable definitions (`Bind`s) in the oreder in
740+
* which they are defined in the source.
741+
*
742+
* @param bindings Type variable definitions (`Bind` tree)
743+
* @param body Quoted pattern (without type variable definitions)
744+
* @param quotes A reference to the given `Quotes` instance in scope
745+
*/
746+
case class QuotePattern[+T <: Untyped] private[ast] (bindings: List[Tree[T]], body: Tree[T], quotes: Tree[T])(implicit @constructorOnly src: SourceFile)
747+
extends PatternTree[T] {
748+
type ThisTree[+T <: Untyped] = QuotePattern[T]
749+
}
750+
740751
/** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern.
741752
*
742753
* Parser will only create `${ pattern }` and `$ident`, hence they will not have args.
743754
* While typing, the `$ident(args*)` the args are identified and desugared into a `SplicePattern`
744755
* containing them.
745756
*
746-
* SplicePattern are removed after typing the pattern and are not present in TASTy.
757+
* `SplicePattern` can only be contained within a `QuotePattern`.
747758
*
748759
* @param body The tree that was spliced
749760
* @param args The arguments of the splice (the HOAS arguments)
@@ -1163,6 +1174,7 @@ object Trees {
11631174
type Inlined = Trees.Inlined[T]
11641175
type Quote = Trees.Quote[T]
11651176
type Splice = Trees.Splice[T]
1177+
type QuotePattern = Trees.QuotePattern[T]
11661178
type SplicePattern = Trees.SplicePattern[T]
11671179
type TypeTree = Trees.TypeTree[T]
11681180
type InferredTypeTree = Trees.InferredTypeTree[T]
@@ -1341,6 +1353,10 @@ object Trees {
13411353
case tree: Splice if (expr eq tree.expr) => tree
13421354
case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree)))
13431355
}
1356+
def QuotePattern(tree: Tree)(bindings: List[Tree], body: Tree, quotes: Tree)(using Context): QuotePattern = tree match {
1357+
case tree: QuotePattern if (bindings eq tree.bindings) && (body eq tree.body) && (quotes eq tree.quotes) => tree
1358+
case _ => finalize(tree, untpd.QuotePattern(bindings, body, quotes)(sourceFile(tree)))
1359+
}
13441360
def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match {
13451361
case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree
13461362
case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree)))
@@ -1586,6 +1602,8 @@ object Trees {
15861602
cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags))
15871603
case tree @ Splice(expr) =>
15881604
cpy.Splice(tree)(transform(expr)(using spliceContext))
1605+
case tree @ QuotePattern(bindings, body, quotes) =>
1606+
cpy.QuotePattern(tree)(transform(bindings), transform(body)(using quoteContext), transform(quotes))
15891607
case tree @ SplicePattern(body, args) =>
15901608
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
15911609
case tree @ Hole(isTerm, idx, args, content) =>
@@ -1733,6 +1751,8 @@ object Trees {
17331751
this(this(x, body)(using quoteContext), tags)
17341752
case Splice(expr) =>
17351753
this(x, expr)(using spliceContext)
1754+
case QuotePattern(bindings, body, quotes) =>
1755+
this(this(this(x, bindings), body)(using quoteContext), quotes)
17361756
case SplicePattern(body, args) =>
17371757
this(this(x, body)(using spliceContext), args)
17381758
case Hole(_, _, args, content) =>

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
173173
def Quote(body: Tree, tags: List[Tree])(using Context): Quote =
174174
untpd.Quote(body, tags).withBodyType(body.tpe)
175175

176+
def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree, proto: Type)(using Context): QuotePattern =
177+
ta.assignType(untpd.QuotePattern(bindings, body, quotes), proto)
178+
176179
def Splice(expr: Tree, tpe: Type)(using Context): Splice =
177180
untpd.Splice(expr).withType(tpe)
178181

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
406406
def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion)
407407
def Quote(body: Tree, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags)
408408
def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr)
409+
def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree)(implicit src: SourceFile): QuotePattern = new QuotePattern(bindings, body, quotes)
409410
def SplicePattern(body: Tree, args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, args)
410411
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
411412
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -853,9 +853,9 @@ class Definitions {
853853

854854
@tu lazy val QuoteMatchingClass: ClassSymbol = requiredClass("scala.quoted.runtime.QuoteMatching")
855855
@tu lazy val QuoteMatching_ExprMatch: Symbol = QuoteMatchingClass.requiredMethod("ExprMatch")
856-
@tu lazy val QuoteMatching_ExprMatchModule: Symbol = QuoteMatchingClass.requiredClass("ExprMatchModule")
856+
@tu lazy val QuoteMatching_ExprMatch_unapply: Symbol = QuoteMatchingClass.requiredClass("ExprMatchModule").requiredMethod(nme.unapply)
857857
@tu lazy val QuoteMatching_TypeMatch: Symbol = QuoteMatchingClass.requiredMethod("TypeMatch")
858-
@tu lazy val QuoteMatching_TypeMatchModule: Symbol = QuoteMatchingClass.requiredClass("TypeMatchModule")
858+
@tu lazy val QuoteMatching_TypeMatch_unapply: Symbol = QuoteMatchingClass.requiredClass("TypeMatchModule").requiredMethod(nme.unapply)
859859
@tu lazy val QuoteMatchingModule: Symbol = requiredModule("scala.quoted.runtime.QuoteMatching")
860860
@tu lazy val QuoteMatching_KNil: Symbol = QuoteMatchingModule.requiredType("KNil")
861861
@tu lazy val QuoteMatching_KCons: Symbol = QuoteMatchingModule.requiredType("KCons")
@@ -942,6 +942,7 @@ class Definitions {
942942
def TupleXXLModule(using Context): Symbol = TupleXXLClass.companionModule
943943

944944
def TupleXXL_fromIterator(using Context): Symbol = TupleXXLModule.requiredMethod("fromIterator")
945+
def TupleXXL_unapplySeq(using Context): Symbol = TupleXXLModule.requiredMethod(nme.unapplySeq)
945946

946947
@tu lazy val RuntimeTupleMirrorTypeRef: TypeRef = requiredClassRef("scala.runtime.TupleMirror")
947948

compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import config.Config
1919
import collection.mutable
2020
import reporting.{Profile, NoProfile}
2121
import dotty.tools.tasty.TastyFormat.ASTsSection
22+
import quoted.QuotePatterns
2223

2324
object TreePickler:
2425
class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception
@@ -685,6 +686,9 @@ class TreePickler(pickler: TastyPickler) {
685686
.appliedTo(expr)
686687
.withSpan(tree.span)
687688
)
689+
case tree: QuotePattern =>
690+
// TODO: Add QUOTEPATTERN tag to TASTy
691+
pickleTree(QuotePatterns.encode(tree))
688692
case Hole(_, idx, args, _) =>
689693
writeByte(HOLE)
690694
withLength {

compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import Trees._
3333
import Decorators._
3434
import transform.SymUtils._
3535
import cc.{adaptFunctionTypeUnderPureFuns, adaptByNameArgUnderPureFuns}
36+
import dotty.tools.dotc.quoted.QuotePatterns
3637

3738
import dotty.tools.tasty.{TastyBuffer, TastyReader}
3839
import TastyBuffer._
@@ -1419,7 +1420,11 @@ class TreeUnpickler(reader: TastyReader,
14191420
}
14201421
val patType = readType()
14211422
val argPats = until(end)(readTree())
1422-
UnApply(fn, implicitArgs, argPats, patType)
1423+
val unapply = UnApply(fn, implicitArgs, argPats, patType)
1424+
if fn.symbol == defn.QuoteMatching_ExprMatch_unapply
1425+
|| fn.symbol == defn.QuoteMatching_TypeMatch_unapply
1426+
then QuotePatterns.decode(unapply)
1427+
else unapply
14231428
case REFINEDtpt =>
14241429
val refineCls = symAtAddr.getOrElse(start,
14251430
newRefinedClassSymbol(coordAt(start))).asClass

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,13 +734,21 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
734734
keywordStr("macro ") ~ toTextGlobal(call)
735735
case tree @ Quote(body, tags) =>
736736
val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty)
737-
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
737+
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tpd.bodyType(tree.asInstanceOf[tpd.Quote])) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
738738
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
739739
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
740740
keywordStr("'") ~ tagsText ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
741741
case Splice(expr) =>
742742
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
743743
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")
744+
case tree @ QuotePattern(bindings, body, quotes) =>
745+
val quotesText = (keywordStr("<") ~ toText(quotes) ~ keywordStr(">")).provided(printDebug)
746+
val bindingsText = bindings.map(binding => {
747+
keywordStr("type ") ~ toText(binding.symbol.name) ~ toText(binding.symbol.info) ~ "; "
748+
}).reduceLeft(_ ~~ _).provided(bindings.nonEmpty)
749+
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
750+
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
751+
keywordStr("'") ~ quotesText ~ open ~ bindingsText ~ toTextGlobal(body) ~ close
744752
case SplicePattern(pattern, args) =>
745753
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
746754
keywordStr("$") ~ spliceTypeText ~ {

0 commit comments

Comments
 (0)