Skip to content

Commit

Permalink
Add SplicePattern AST to parse and type quote pattern splices
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasstucki committed May 16, 2023
1 parent cba5c9a commit a0bdfe8
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 77 deletions.
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -338,9 +338,9 @@ object desugar {
def quotedPattern(tree: untpd.Tree, expectedTpt: untpd.Tree)(using Context): untpd.Tree = {
def adaptToExpectedTpt(tree: untpd.Tree): untpd.Tree = tree match {
// Add the expected type as an ascription
case _: untpd.Splice =>
case _: untpd.SplicePattern =>
untpd.Typed(tree, expectedTpt).withSpan(tree.span)
case Typed(expr: untpd.Splice, tpt) =>
case Typed(expr: untpd.SplicePattern, tpt) =>
cpy.Typed(tree)(expr, untpd.makeAndType(tpt, expectedTpt).withSpan(tpt.span))

// Propagate down the expected type to the leafs of the expression
Expand Down Expand Up @@ -1979,7 +1979,7 @@ object desugar {
case Quote(body, _) =>
new UntypedTreeTraverser {
def traverse(tree: untpd.Tree)(using Context): Unit = tree match {
case Splice(expr) => collect(expr)
case SplicePattern(body, _) => collect(body)
case _ => traverseChildren(tree)
}
}.traverse(body)
Expand Down
25 changes: 25 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,22 @@ object Trees {
type ThisTree[+T <: Untyped] = Splice[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.
*
* @param body The tree that was spliced
* @param args The arguments of the splice (the HOAS arguments)
*/
case class SplicePattern[+T <: Untyped] private[ast] (body: Tree[T], args: List[Tree[T]])(implicit @constructorOnly src: SourceFile)
extends TermTree[T] {
type ThisTree[+T <: Untyped] = SplicePattern[T]
}

/** A type tree that represents an existing or inferred type */
case class TypeTree[+T <: Untyped]()(implicit @constructorOnly src: SourceFile)
extends DenotingTree[T] with TypTree[T] {
Expand Down Expand Up @@ -1147,6 +1163,7 @@ object Trees {
type Inlined = Trees.Inlined[T]
type Quote = Trees.Quote[T]
type Splice = Trees.Splice[T]
type SplicePattern = Trees.SplicePattern[T]
type TypeTree = Trees.TypeTree[T]
type InferredTypeTree = Trees.InferredTypeTree[T]
type SingletonTypeTree = Trees.SingletonTypeTree[T]
Expand Down Expand Up @@ -1325,6 +1342,10 @@ object Trees {
case tree: Splice if (expr eq tree.expr) => tree
case _ => finalize(tree, untpd.Splice(expr)(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)))
}
def SingletonTypeTree(tree: Tree)(ref: Tree)(using Context): SingletonTypeTree = tree match {
case tree: SingletonTypeTree if (ref eq tree.ref) => tree
case _ => finalize(tree, untpd.SingletonTypeTree(ref)(sourceFile(tree)))
Expand Down Expand Up @@ -1566,6 +1587,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 @ SplicePattern(body, args) =>
cpy.SplicePattern(tree)(transform(body)(using spliceContext), transform(args))
case tree @ Hole(isTerm, idx, args, content) =>
cpy.Hole(tree)(isTerm, idx, transform(args), transform(content))
case _ =>
Expand Down Expand Up @@ -1711,6 +1734,8 @@ object Trees {
this(this(x, body)(using quoteContext), tags)
case Splice(expr) =>
this(x, expr)(using spliceContext)
case SplicePattern(body, args) =>
this(this(x, body)(using spliceContext), args)
case Hole(_, _, args, content) =>
this(this(x, args), content)
case _ =>
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 @@ -399,6 +399,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 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()
def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref)
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1750,10 +1750,10 @@ object Parsers {
def splice(isType: Boolean): Tree =
val start = in.offset
atSpan(in.offset) {
val inPattern = (staged & StageKind.QuotedPattern) != 0
val expr =
if (in.name.length == 1) {
in.nextToken()
val inPattern = (staged & StageKind.QuotedPattern) != 0
withinStaged(StageKind.Spliced)(if (inPattern) inBraces(pattern()) else stagedBlock())
}
else atSpan(in.offset + 1) {
Expand All @@ -1769,6 +1769,8 @@ object Parsers {
else "To use a given Type[T] in a quote just write T directly"
syntaxError(em"$msg\n\nHint: $hint", Span(start, in.lastOffset))
Ident(nme.ERROR.toTypeName)
else if inPattern then
SplicePattern(expr, Nil)
else
Splice(expr)
}
Expand Down
6 changes: 6 additions & 0 deletions compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
case Splice(expr) =>
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
keywordStr("$") ~ spliceTypeText ~ keywordStr("{") ~ toTextGlobal(expr) ~ keywordStr("}")
case SplicePattern(pattern, args) =>
val spliceTypeText = (keywordStr("[") ~ toTextGlobal(tree.typeOpt) ~ keywordStr("]")).provided(printDebug && tree.typeOpt.exists)
keywordStr("$") ~ spliceTypeText ~ {
if args.isEmpty then keywordStr("{") ~ inPattern(toText(pattern)) ~ keywordStr("}")
else toText(pattern.symbol.name) ~ "(" ~ toTextGlobal(args, ", ") ~ ")"
}
case Hole(isTerm, idx, args, content) =>
val (prefix, postfix) = if isTerm then ("{{{", "}}}") else ("[[[", "]]]")
val argsText = toTextGlobal(args, ", ")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Applications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1097,7 +1097,7 @@ trait Applications extends Compatibility {
}
else {
val app = tree.fun match
case _: untpd.Splice if ctx.mode.is(Mode.QuotedPattern) => typedAppliedSplice(tree, pt)
case _: untpd.SplicePattern => typedAppliedSplice(tree, pt)
case _ => realApply
app match {
case Apply(fn @ Select(left, _), right :: Nil) if fn.hasType =>
Expand Down
130 changes: 58 additions & 72 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import dotty.tools.dotc.core.Types._
import dotty.tools.dotc.inlines.PrepareInlineable
import dotty.tools.dotc.staging.StagingLevel.*
import dotty.tools.dotc.transform.SymUtils._
import dotty.tools.dotc.typer.ErrorReporting.errorTree
import dotty.tools.dotc.typer.Implicits._
import dotty.tools.dotc.typer.Inferencing._
import dotty.tools.dotc.util.Spans._
Expand Down Expand Up @@ -74,45 +75,55 @@ trait QuotesAndSplices {
def typedSplice(tree: untpd.Splice, pt: Type)(using Context): Tree = {
record("typedSplice")
checkSpliceOutsideQuote(tree)
assert(!ctx.mode.is(Mode.QuotedPattern))
tree.expr match {
case untpd.Quote(innerExpr, Nil) if innerExpr.isTerm =>
report.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.srcPos)
return typed(innerExpr, pt)
case _ =>
}
if (ctx.mode.is(Mode.QuotedPattern))
if (isFullyDefined(pt, ForceDegree.flipBottom)) {
def spliceOwner(ctx: Context): Symbol =
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
val pat = typedPattern(tree.expr, defn.QuotedExprClass.typeRef.appliedTo(pt))(
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(spliceOwner(ctx)))
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
Splice(pat, argType).withSpan(tree.span)
}
else {
report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.srcPos)
tree.withType(UnspecifiedErrorType)
}
else {
if (level == 0) {
// Mark the first inline method from the context as a macro
def markAsMacro(c: Context): Unit =
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro
markAsMacro(ctx)
}

// TODO typecheck directly (without `exprSplice`)
val internalSplice =
untpd.Apply(untpd.ref(defn.QuotedRuntime_exprSplice.termRef), tree.expr)
typedApply(internalSplice, pt)(using spliceContext).withSpan(tree.span) match
case tree @ Apply(TypeApply(_, tpt :: Nil), spliced :: Nil) if tree.symbol == defn.QuotedRuntime_exprSplice =>
cpy.Splice(tree)(spliced)
case tree => tree
if (level == 0) {
// Mark the first inline method from the context as a macro
def markAsMacro(c: Context): Unit =
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
else assert(ctx.reporter.hasErrors) // Did not find inline def to mark as macro
markAsMacro(ctx)
}

// TODO typecheck directly (without `exprSplice`)
val internalSplice =
untpd.Apply(untpd.ref(defn.QuotedRuntime_exprSplice.termRef), tree.expr)
typedApply(internalSplice, pt)(using spliceContext).withSpan(tree.span) match
case tree @ Apply(TypeApply(_, tpt :: Nil), spliced :: Nil) if tree.symbol == defn.QuotedRuntime_exprSplice =>
cpy.Splice(tree)(spliced)
case tree => tree
}

def typedSplicePattern(tree: untpd.SplicePattern, pt: Type)(using Context): Tree = {
record("typedSplicePattern")
if isFullyDefined(pt, ForceDegree.flipBottom) then
def patternOuterContext(ctx: Context): Context =
if (ctx.mode.is(Mode.QuotedPattern)) patternOuterContext(ctx.outer) else ctx
val typedArgs = tree.args.map {
case arg: untpd.Ident =>
typedExpr(arg)
case arg =>
report.error("Open pattern expected an identifier", arg.srcPos)
EmptyTree
}
for arg <- typedArgs if arg.symbol.is(Mutable) do // TODO support these patterns. Possibly using scala.quoted.util.Var
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
val patType = if tree.args.isEmpty then pt else defn.FunctionOf(argTypes, pt)
val pat = typedPattern(tree.body, defn.QuotedExprClass.typeRef.appliedTo(patType))(
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(patternOuterContext(ctx).owner))
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
val argType = if baseType.exists then baseType.argTypesHi.head else defn.NothingType
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
else
errorTree(tree, em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.body.srcPos)
}

def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree =
Expand All @@ -127,29 +138,17 @@ trait QuotesAndSplices {
*/
def typedAppliedSplice(tree: untpd.Apply, pt: Type)(using Context): Tree = {
assert(ctx.mode.is(Mode.QuotedPattern))
val untpd.Apply(splice: untpd.Splice, args) = tree: @unchecked
def isInBraces: Boolean = splice.span.end != splice.expr.span.end
if !isFullyDefined(pt, ForceDegree.flipBottom) then
report.error(em"Type must be fully defined.", splice.srcPos)
tree.withType(UnspecifiedErrorType)
else if isInBraces then // ${x}(...) match an application
val untpd.Apply(splice: untpd.SplicePattern, args) = tree: @unchecked
def isInBraces: Boolean = splice.span.end != splice.body.span.end
if isInBraces then // ${x}(...) match an application
val typedArgs = args.map(arg => typedExpr(arg))
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
val splice1 = typedSplice(splice, defn.FunctionOf(argTypes, pt))
Apply(splice1.select(nme.apply), typedArgs).withType(pt).withSpan(tree.span)
val splice1 = typedSplicePattern(splice, defn.FunctionOf(argTypes, pt))
untpd.cpy.Apply(tree)(splice1.select(nme.apply), typedArgs).withType(pt)
else // $x(...) higher-order quasipattern
val typedArgs = args.map {
case arg: untpd.Ident =>
typedExpr(arg)
case arg =>
report.error("Open pattern expected an identifier", arg.srcPos)
EmptyTree
}
if args.isEmpty then
report.error("Missing arguments for open pattern", tree.srcPos)
val argTypes = typedArgs.map(_.tpe.widenTermRefExpr)
val typedPat = typedSplice(splice, defn.FunctionOf(argTypes, pt))
ref(defn.QuotedRuntimePatterns_patternHigherOrderHole).appliedToType(pt).appliedTo(typedPat, SeqLiteral(typedArgs, TypeTree(defn.AnyType)))
report.error("Missing arguments for open pattern", tree.srcPos)
typedSplicePattern(untpd.cpy.SplicePattern(tree)(splice.body, args), pt)
}

/** Type a pattern variable name `t` in quote pattern as `${given t$giveni: Type[t @ _]}`.
Expand Down Expand Up @@ -227,29 +226,16 @@ trait QuotesAndSplices {
val freshTypeBindingsBuff = new mutable.ListBuffer[Tree]
val typePatBuf = new mutable.ListBuffer[Tree]
override def transform(tree: Tree)(using Context) = tree match {
case Typed(splice: Splice, tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
case Typed(splice @ SplicePattern(pat, Nil), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
transform(tpt) // Collect type bindings
transform(splice)
case Apply(TypeApply(fn, targs), Splice(pat) :: args :: Nil) if fn.symbol == defn.QuotedRuntimePatterns_patternHigherOrderHole =>
args match // TODO support these patterns. Possibly using scala.quoted.util.Var
case SeqLiteral(args, _) =>
for arg <- args; if arg.symbol.is(Mutable) do
report.error("References to `var`s cannot be used in higher-order pattern", arg.srcPos)
try ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToTypeTrees(targs).appliedTo(args).withSpan(tree.span)
finally {
val patType = pat.tpe.widen
val patType1 = patType.translateFromRepeated(toArray = false)
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
patBuf += pat1
}
case Splice(pat) =>
try ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span)
finally {
val patType = pat.tpe.widen
val patType1 = patType.translateFromRepeated(toArray = false)
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
patBuf += pat1
}
case SplicePattern(pat, args) =>
val patType = pat.tpe.widen
val patType1 = patType.translateFromRepeated(toArray = false)
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
patBuf += pat1
if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span)
else ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))).withSpan(tree.span)
case Select(pat: Bind, _) if tree.symbol.isTypeSplice =>
val sym = tree.tpe.dealias.typeSymbol
if sym.exists then
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3096,6 +3096,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
case untpd.EmptyTree => tpd.EmptyTree
case tree: untpd.Quote => typedQuote(tree, pt)
case tree: untpd.Splice => typedSplice(tree, pt)
case tree: untpd.SplicePattern => typedSplicePattern(tree, pt)
case tree: untpd.MacroTree => report.error("Unexpected macro", tree.srcPos); tpd.nullLiteral // ill-formed code may reach here
case tree: untpd.Hole => typedHole(tree, pt)
case _ => typedUnadapted(desugar(tree, pt), pt, locked)
Expand Down

0 comments on commit a0bdfe8

Please sign in to comment.