Skip to content

Add QuotePattern AST #17935

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 3 commits into from
Jun 28, 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
24 changes: 23 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -807,12 +807,19 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

/** The variables defined by a pattern, in reverse order of their appearance. */
def patVars(tree: Tree)(using Context): List[Symbol] = {
val acc = new TreeAccumulator[List[Symbol]] {
val acc = new TreeAccumulator[List[Symbol]] { outer =>
def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match {
case Bind(_, body) => apply(tree.symbol :: syms, body)
case Annotated(tree, id @ Ident(tpnme.BOUNDTYPE_ANNOT)) => apply(id.symbol :: syms, tree)
case QuotePattern(bindings, body, _) => quotePatVars(bindings.map(_.symbol) ::: syms, body)
case _ => foldOver(syms, tree)
}
private object quotePatVars extends TreeAccumulator[List[Symbol]] {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using a separate object, could we instead add a case SplitPattern(pat, _) in the original TreeAccumulator that checks the level?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could. I was trying to keep keep the cases of patVars to a minimum for the case where we do not have quoted patterns(most of them). Should I change this?

Copy link
Member

Choose a reason for hiding this comment

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

Your call, I would optimize for what you think is the most readable

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 tried with levels. While that one is more concise and simple to read, I believe the current version makes the distinction between the two different traversal modes clearer.

def apply(syms: List[Symbol], tree: Tree)(using Context) = tree match {
case SplicePattern(pat, _) => outer.apply(syms, pat)
case _ => foldOver(syms, tree)
}
}
}
acc(Nil, tree)
}
Expand Down Expand Up @@ -1048,6 +1055,21 @@ trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
if tree.symbol.isTypeSplice then Some(tree.qualifier) else None
}

extension (tree: tpd.Quote)
/** Type of the quoted expression as seen from outside the quote */
def bodyType(using Context): Type =
val quoteType = tree.tpe // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
exprType.argInfos.head // T
end extension

extension (tree: tpd.QuotePattern)
/** Type of the quoted pattern */
def bodyType(using Context): Type =
val quoteType = tree.tpe // `Expr[T]` or `Type[T]`
quoteType.argInfos.head // T
end extension

/** Extractor for not-null assertions.
* A not-null assertion for reference `x` has the form `x.$asInstanceOf$[x.type & T]`.
*/
Expand Down
40 changes: 30 additions & 10 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -686,9 +686,9 @@ object Trees {
* phases. After `pickleQuotes` phase, the only quotes that exist are in `inline`
* methods. These are dropped when we remove the inline method implementations.
*
* Type quotes `'[body]` from the parser are desugared into quote patterns (using a `Type.of[T]]`)
* when type checking. TASTy files will not contain type quotes. Type quotes are used again
* in the `staging` phase to represent the reification of `Type.of[T]]`.
* Type quotes `'[body]` from the parser are typed into `QuotePattern`s when type checking.
* TASTy files will not contain type quotes. Type quotes are used again in the `staging`
* phase to represent the reification of `Type.of[T]]`.
*
* Type tags `tags` are always empty before the `staging` phase. Tags for stage inconsistent
* types are added in the `staging` phase to level 0 quotes. Tags for types that refer to
Expand All @@ -704,12 +704,6 @@ object Trees {
/** Is this a type quote `'[tpe]' */
def isTypeQuote = body.isType

/** Type of the quoted expression as seen from outside the quote */
def bodyType(using Context): Type =
val quoteType = typeOpt // `Quotes ?=> Expr[T]` or `Quotes ?=> Type[T]`
val exprType = quoteType.argInfos.last // `Expr[T]` or `Type[T]`
exprType.argInfos.head // T

/** Set the type of the body of the quote */
def withBodyType(tpe: Type)(using Context): Quote[Type] =
val exprType = // `Expr[T]` or `Type[T]`
Expand Down Expand Up @@ -737,13 +731,30 @@ object Trees {
type ThisTree[+T <: Untyped] = Splice[T]
}

/** A tree representing a quote pattern `'{ type binding1; ...; body }` or `'[ type binding1; ...; body ]`.
* `QuotePattern`s are created the type checker when typing an `untpd.Quote` in a pattern context.
*
* `QuotePattern`s are checked are encoded into `unapply`s in the `staging` phase.
*
* The `bindings` contain the list of quote pattern type variable definitions (`Bind`s) in the oreder in
* which they are defined in the source.
*
* @param bindings Type variable definitions (`Bind` tree)
* @param body Quoted pattern (without type variable definitions)
* @param quotes A reference to the given `Quotes` instance in scope
Copy link
Member

Choose a reason for hiding this comment

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

Does this actually need to be stored in the tree or could we look for the given when we need it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In theory, we could look for it again. But I preferred to keep track of it to ensure we do not change the instance. Maybe a macro-generated quote pattern could go wrong. I need to analyze this in depth if we want to remove this instance. Would be nicer though.

*/
case class QuotePattern[+T <: Untyped] private[ast] (bindings: List[Tree[T]], body: Tree[T], quotes: Tree[T])(implicit @constructorOnly src: SourceFile)
extends PatternTree[T] {
type ThisTree[+T <: Untyped] = QuotePattern[T]
}

/** A tree representing a pattern splice `${ pattern }`, `$ident` or `$ident(args*)` in a quote pattern.
*
* Parser will only create `${ pattern }` and `$ident`, hence they will not have args.
* While typing, the `$ident(args*)` the args are identified and desugared into a `SplicePattern`
* containing them.
*
* SplicePattern are removed after typing the pattern and are not present in TASTy.
* `SplicePattern` can only be contained within a `QuotePattern`.
*
* @param body The tree that was spliced
* @param args The arguments of the splice (the HOAS arguments)
Expand Down Expand Up @@ -1163,6 +1174,7 @@ object Trees {
type Inlined = Trees.Inlined[T]
type Quote = Trees.Quote[T]
type Splice = Trees.Splice[T]
type QuotePattern = Trees.QuotePattern[T]
type SplicePattern = Trees.SplicePattern[T]
type TypeTree = Trees.TypeTree[T]
type InferredTypeTree = Trees.InferredTypeTree[T]
Expand Down Expand Up @@ -1341,6 +1353,10 @@ object Trees {
case tree: Splice if (expr eq tree.expr) => tree
case _ => finalize(tree, untpd.Splice(expr)(sourceFile(tree)))
}
def QuotePattern(tree: Tree)(bindings: List[Tree], body: Tree, quotes: Tree)(using Context): QuotePattern = tree match {
case tree: QuotePattern if (bindings eq tree.bindings) && (body eq tree.body) && (quotes eq tree.quotes) => tree
case _ => finalize(tree, untpd.QuotePattern(bindings, body, quotes)(sourceFile(tree)))
}
def SplicePattern(tree: Tree)(body: Tree, args: List[Tree])(using Context): SplicePattern = tree match {
case tree: SplicePattern if (body eq tree.body) && (args eq tree.args) => tree
case _ => finalize(tree, untpd.SplicePattern(body, args)(sourceFile(tree)))
Expand Down Expand Up @@ -1586,6 +1602,8 @@ object Trees {
cpy.Quote(tree)(transform(body)(using quoteContext), transform(tags))
case tree @ Splice(expr) =>
cpy.Splice(tree)(transform(expr)(using spliceContext))
case tree @ QuotePattern(bindings, body, quotes) =>
cpy.QuotePattern(tree)(transform(bindings), transform(body)(using quoteContext), transform(quotes))
case tree @ SplicePattern(body, args) =>
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
case tree @ Hole(isTerm, idx, args, content) =>
Expand Down Expand Up @@ -1733,6 +1751,8 @@ object Trees {
this(this(x, body)(using quoteContext), tags)
case Splice(expr) =>
this(x, expr)(using spliceContext)
case QuotePattern(bindings, body, quotes) =>
this(this(this(x, bindings), body)(using quoteContext), quotes)
case SplicePattern(body, args) =>
this(this(x, body)(using spliceContext), args)
case Hole(_, _, args, content) =>
Expand Down
3 changes: 3 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def Quote(body: Tree, tags: List[Tree])(using Context): Quote =
untpd.Quote(body, tags).withBodyType(body.tpe)

def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree, proto: Type)(using Context): QuotePattern =
ta.assignType(untpd.QuotePattern(bindings, body, quotes), proto)

def Splice(expr: Tree, tpe: Type)(using Context): Splice =
untpd.Splice(expr).withType(tpe)

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion)
def Quote(body: Tree, tags: List[Tree])(implicit src: SourceFile): Quote = new Quote(body, tags)
def Splice(expr: Tree)(implicit src: SourceFile): Splice = new Splice(expr)
def QuotePattern(bindings: List[Tree], body: Tree, quotes: Tree)(implicit src: SourceFile): QuotePattern = new QuotePattern(bindings, body, quotes)
def SplicePattern(body: Tree, args: List[Tree])(implicit src: SourceFile): SplicePattern = new SplicePattern(body, args)
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()
Expand Down
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -853,9 +853,9 @@ class Definitions {

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

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

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

Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/core/tasty/TreePickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import config.Config
import collection.mutable
import reporting.{Profile, NoProfile}
import dotty.tools.tasty.TastyFormat.ASTsSection
import quoted.QuotePatterns

object TreePickler:
class StackSizeExceeded(val mdef: tpd.MemberDef) extends Exception
Expand Down Expand Up @@ -685,6 +686,9 @@ class TreePickler(pickler: TastyPickler) {
.appliedTo(expr)
.withSpan(tree.span)
)
case tree: QuotePattern =>
// TODO: Add QUOTEPATTERN tag to TASTy
pickleTree(QuotePatterns.encode(tree))
case Hole(_, idx, args, _) =>
writeByte(HOLE)
withLength {
Expand Down
7 changes: 6 additions & 1 deletion compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import Trees._
import Decorators._
import transform.SymUtils._
import cc.{adaptFunctionTypeUnderPureFuns, adaptByNameArgUnderPureFuns}
import dotty.tools.dotc.quoted.QuotePatterns

import dotty.tools.tasty.{TastyBuffer, TastyReader}
import TastyBuffer._
Expand Down Expand Up @@ -1419,7 +1420,11 @@ class TreeUnpickler(reader: TastyReader,
}
val patType = readType()
val argPats = until(end)(readTree())
UnApply(fn, implicitArgs, argPats, patType)
val unapply = UnApply(fn, implicitArgs, argPats, patType)
if fn.symbol == defn.QuoteMatching_ExprMatch_unapply
|| fn.symbol == defn.QuoteMatching_TypeMatch_unapply
then QuotePatterns.decode(unapply)
else unapply
case REFINEDtpt =>
val refineCls = symAtAddr.getOrElse(start,
newRefinedClassSymbol(coordAt(start))).asClass
Expand Down
10 changes: 9 additions & 1 deletion compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -734,13 +734,21 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
keywordStr("macro ") ~ toTextGlobal(call)
case tree @ Quote(body, tags) =>
val tagsText = (keywordStr("<") ~ toTextGlobal(tags, ", ") ~ keywordStr(">")).provided(tree.tags.nonEmpty)
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tree.bodyType) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
val exprTypeText = (keywordStr("[") ~ toTextGlobal(tpd.bodyType(tree.asInstanceOf[tpd.Quote])) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
Copy link
Member

Choose a reason for hiding this comment

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

What happens when I pretty-print an untyped Quote node?

Copy link
Contributor Author

@nicolasstucki nicolasstucki Jun 28, 2023

Choose a reason for hiding this comment

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

The provided condition makes sure that the type exists. If it does not, the text and tpd.bodyType is not evaluated as it is a by-name prefix.

The other solution to this would be to create bodyTypeOpt. But this is the only use case for it.

Copy link
Member

Choose a reason for hiding this comment

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

Ah I missed the provided, ok.

val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
keywordStr("'") ~ tagsText ~ exprTypeText ~ open ~ toTextGlobal(body) ~ close
case Splice(expr) =>
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")
case tree @ QuotePattern(bindings, body, quotes) =>
val quotesText = (keywordStr("<") ~ toText(quotes) ~ keywordStr(">")).provided(printDebug)
val bindingsText = bindings.map(binding => {
keywordStr("type ") ~ toText(binding.symbol.name) ~ toText(binding.symbol.info) ~ "; "
}).reduceLeft(_ ~~ _).provided(bindings.nonEmpty)
val open = if (body.isTerm) keywordStr("{") else keywordStr("[")
val close = if (body.isTerm) keywordStr("}") else keywordStr("]")
keywordStr("'") ~ quotesText ~ open ~ bindingsText ~ toTextGlobal(body) ~ close
case SplicePattern(pattern, args) =>
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
keywordStr("$") ~ spliceTypeText ~ {
Expand Down
Loading