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 4, 2023
1 parent 8a055d6 commit 40d3e50
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 75 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 @@ -1989,7 +1989,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 @@ -732,6 +732,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 @@ -1144,6 +1160,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 @@ -1322,6 +1339,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 @@ -1563,6 +1584,8 @@ object Trees {
cpy.Quote(tree)(transform(body)(using quoteContext))
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(_, _, args, content, tpt) =>
cpy.Hole(tree)(args = transform(args), content = transform(content), tpt = transform(tpt))
case _ =>
Expand Down Expand Up @@ -1708,6 +1731,8 @@ object Trees {
this(x, body)(using quoteContext)
case Splice(expr) =>
this(x, expr)(using spliceContext)
case SplicePattern(body, args) =>
this(this(x, body)(using spliceContext), args)
case Hole(_, _, args, content, tpt) =>
this(this(this(x, args), content), tpt)
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)(implicit src: SourceFile): Quote = new Quote(body)
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 @@ -1745,10 +1745,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 @@ -1764,6 +1764,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 @@ -724,6 +724,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)
val open = Str(keywordStr("{")).provided(args.isEmpty)
val close = Str(keywordStr("}")).provided(args.isEmpty)
val argsText = ("(" ~ toTextGlobal(args, ", ") ~ ")").provided(args.nonEmpty)
keywordStr("$") ~ spliceTypeText ~ open ~ inPattern(toText(pattern)) ~ close ~ argsText
case Hole(isTermHole, idx, args, content, tpt) =>
val (prefix, postfix) = if isTermHole 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
128 changes: 58 additions & 70 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,44 +74,57 @@ 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) 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)
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)) {
def spliceOwner(ctx: Context): Symbol =
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
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(spliceOwner(ctx)))
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
untpd.cpy.SplicePattern(tree)(pat, typedArgs).withType(pt)
}
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
report.error(em"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.body.srcPos)
tree.withType(UnspecifiedErrorType)
}
}

Expand All @@ -128,29 +141,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 @@ -228,29 +229,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 @@ -3088,6 +3088,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 40d3e50

Please sign in to comment.