Skip to content

Commit e2371ab

Browse files
committed
Encode and decode quote patterns
1 parent 294d544 commit e2371ab

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
package dotty.tools.dotc
2+
package quoted
3+
4+
import dotty.tools.dotc.ast.TreeTypeMap
5+
import dotty.tools.dotc.ast.Trees.*
6+
import dotty.tools.dotc.ast.tpd
7+
import dotty.tools.dotc.ast.untpd
8+
import dotty.tools.dotc.core.*
9+
import dotty.tools.dotc.core.Annotations.*
10+
import dotty.tools.dotc.core.Constants.*
11+
import dotty.tools.dotc.core.Contexts.*
12+
import dotty.tools.dotc.core.Decorators.*
13+
import dotty.tools.dotc.core.Flags.*
14+
import dotty.tools.dotc.core.NameKinds.PatMatGivenVarName
15+
import dotty.tools.dotc.core.Names.*
16+
import dotty.tools.dotc.core.StdNames.*
17+
import dotty.tools.dotc.core.Symbols.*
18+
import dotty.tools.dotc.core.Types.*
19+
import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative
20+
import dotty.tools.dotc.transform.SymUtils._
21+
22+
import scala.collection.mutable
23+
24+
object QuotePatterns:
25+
import tpd._
26+
27+
/** TODO */
28+
def checkPattern(quotePattern: QuotePattern)(using Context): Unit = new tpd.TreeTraverser {
29+
def traverse(tree: Tree)(using Context): Unit = tree match {
30+
case _: SplicePattern =>
31+
case tdef: TypeDef if tdef.symbol.isClass =>
32+
val kind = if tdef.symbol.is(Module) then "objects" else "classes"
33+
report.error(em"Implementation restriction: cannot match $kind", tree.srcPos)
34+
case tree: NamedDefTree =>
35+
if tree.name.is(NameKinds.WildcardParamName) then
36+
report.warning(
37+
"Use of `_` for lambda in quoted pattern. Use explicit lambda instead or use `$_` to match any term.",
38+
tree.srcPos)
39+
if tree.name.isTermName && !tree.nameSpan.isSynthetic && tree.name != nme.ANON_FUN && tree.name.startsWith("$") then
40+
report.error("Names cannot start with $ quote pattern", tree.namePos)
41+
traverseChildren(tree)
42+
case _: Match =>
43+
report.error("Implementation restriction: cannot match `match` expressions", tree.srcPos)
44+
case _: Try =>
45+
report.error("Implementation restriction: cannot match `try` expressions", tree.srcPos)
46+
case _: Return =>
47+
report.error("Implementation restriction: cannot match `return` statements", tree.srcPos)
48+
case _ =>
49+
traverseChildren(tree)
50+
}
51+
}.traverse(quotePattern.body)
52+
53+
54+
def encode(quotePattern: QuotePattern)(using Context): Tree =
55+
val quoteClass = if (quotePattern.body.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass
56+
57+
val matchModule = if quotePattern.body.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch
58+
val unapplyFun = quotePattern.quotes.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(nme.unapply)
59+
60+
val typeBindingsTuple = tpd.hkNestedPairsTypeTree(quotePattern.bindings)
61+
62+
val (splicePatterns, shape0) = splitQuotePattern(quotePattern.body)
63+
64+
val shape1 =
65+
if quotePattern.bindings.isEmpty then shape0
66+
else
67+
val patternTypes = quotePattern.bindings.map { binding =>
68+
val sym = binding.symbol
69+
val typeSym = newSymbol(ctx.owner, sym.name ++ "$inPattern" /* TODO remove $inPattern */, EmptyFlags, sym.info, NoSymbol, binding.span)
70+
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(binding.span)))
71+
TypeDef(typeSym.asType).withSpan(binding.span)
72+
}
73+
new TreeTypeMap(
74+
substFrom = quotePattern.bindings.map(_.symbol),
75+
substTo = patternTypes.map(_.symbol)
76+
).transform(Block(patternTypes, shape0))
77+
78+
79+
val quotedShape =
80+
if (quotePattern.body.isTerm) tpd.Quote(shape1, Nil).select(nme.apply).appliedTo(quotePattern.quotes)
81+
else ref(defn.QuotedTypeModule_of.termRef).appliedToTypeTree(shape1).appliedTo(quotePattern.quotes)
82+
83+
val givenTypes = quotePattern.bindings.map { binding =>
84+
val name = binding.symbol.name.toTypeName
85+
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(name.toTermName)
86+
val tpe = defn.QuotedTypeClass.typeRef.appliedTo(binding.symbol.typeRef)
87+
val givenTypeSym = newPatternBoundSymbol(nameOfSyntheticGiven, tpe, binding.span, flags = Given)
88+
Bind(givenTypeSym, untpd.Ident(nme.WILDCARD).withType(tpe)).withSpan(binding.span)
89+
}
90+
91+
val patterns = givenTypes ::: splicePatterns
92+
val patternTypes = patterns.map(_.tpe.widenTermRefExpr)
93+
94+
val splicePat =
95+
if patterns.isEmpty then ref(defn.EmptyTupleModule.termRef)
96+
else if patterns.size <= Definitions.MaxTupleArity then
97+
val tupleNUnapply =
98+
ref(defn.TupleType(patterns.size).nn.typeSymbol.companionModule)
99+
.select(nme.unapply)
100+
.appliedToTypes(patternTypes)
101+
UnApply(tupleNUnapply, Nil, patterns, defn.tupleType(patternTypes))
102+
else ???
103+
104+
val patType =
105+
val quotedTypes =
106+
quotePattern.bindings.map(givenType => defn.QuotedTypeClass.typeRef.appliedTo(givenType.symbol.typeRef))
107+
val quotedExprs =
108+
splicePatterns.map(_.tpe.widenTermRefExpr)
109+
defn.tupleType(quotedTypes :::quotedExprs)
110+
111+
UnApply(
112+
fun = unapplyFun.appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
113+
implicits = quotedShape :: Nil,
114+
patterns = splicePat :: Nil,
115+
quotePattern.tpe)
116+
117+
/** TODO update
118+
*
119+
* Split a typed quoted pattern is split into its type bindings, pattern expression and inner patterns.
120+
* Type definitions with `@patternType` will be inserted in the pattern expression for each type binding.
121+
*
122+
* A quote pattern
123+
* ```
124+
* case '{ type ${given t$giveni: Type[t @ _]}; ${ls: Expr[List[t]]} } => ...
125+
* ```
126+
* will return
127+
* ```
128+
* (
129+
* Map(<t$giveni>: Symbol -> <t @ _>: Bind),
130+
* <'{
131+
* @scala.internal.Quoted.patternType type t
132+
* scala.internal.Quoted.patternHole[List[t]]
133+
* }>: Tree,
134+
* List(<ls: Expr[List[t]]>: Tree)
135+
* )
136+
* ```
137+
*/
138+
private def splitQuotePattern(body: Tree)(using Context): (List[Tree], Tree) = {
139+
val patBuf = new mutable.ListBuffer[Tree]
140+
val shape = new tpd.TreeMap {
141+
override def transform(tree: Tree)(using Context) = tree match {
142+
case Typed(splice @ SplicePattern(pat, Nil), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
143+
transform(tpt) // Collect type bindings
144+
transform(splice)
145+
case SplicePattern(pat, args) =>
146+
val patType = pat.tpe.widen
147+
val patType1 = patType.translateFromRepeated(toArray = false)
148+
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
149+
patBuf += pat1
150+
if args.isEmpty then ref(defn.QuotedRuntimePatterns_patternHole.termRef).appliedToType(tree.tpe).withSpan(tree.span)
151+
else ref(defn.QuotedRuntimePatterns_higherOrderHole.termRef).appliedToType(tree.tpe).appliedTo(SeqLiteral(args, TypeTree(defn.AnyType))).withSpan(tree.span)
152+
case _ =>
153+
super.transform(tree)
154+
}
155+
}.transform(body)
156+
(patBuf.toList, shape)
157+
}
158+
159+
160+
def decode(tree: UnApply)(using Context): Option[QuotePattern] =
161+
if ctx.reporter.hasErrors then
162+
return None
163+
val (fun, implicits, patternTuple) = tree match
164+
case UnApply(fun, implicits, patternTuple :: Nil) => (fun, implicits, patternTuple)
165+
case _ => return None
166+
val patterns = patternTuple match
167+
case _: Ident => Nil // EmptyTuple
168+
case UnApply(_, _, patterns) => patterns // TupleN
169+
// TODO: `*:`
170+
case _ => return None
171+
val shape = implicits match
172+
case Apply(Select(Quote(shape, _), _), _) :: Nil => shape
173+
case List(Apply(TypeApply(_, shape :: Nil), _)) => shape
174+
case _ => return None
175+
fun match
176+
// <quotes>.asInstanceOf[QuoteMatching].{ExprMatch,TypeMatch}.unapply[<typeBindings>, <resTypes>]
177+
case TypeApply(Select(Select(TypeApply(Select(quotes, _), _), _), _), typeBindings :: resTypes :: Nil) =>
178+
val bindings = kListUnroll(typeBindings)
179+
val addPattenSplice = new TreeMap {
180+
private val patternIterator = patterns.iterator.filter {
181+
case pat: Bind => !pat.symbol.name.is(PatMatGivenVarName)
182+
case _ => true
183+
}
184+
override def transform(tree: tpd.Tree)(using Context): tpd.Tree = tree match
185+
case TypeApply(patternHole, _) if patternHole.symbol == defn.QuotedRuntimePatterns_patternHole =>
186+
cpy.SplicePattern(tree)(patternIterator.next(), Nil)
187+
case Apply(patternHole, SeqLiteral(args, _) :: Nil) if patternHole.symbol == defn.QuotedRuntimePatterns_higherOrderHole =>
188+
cpy.SplicePattern(tree)(patternIterator.next(), args)
189+
case _ => super.transform(tree)
190+
}
191+
val body = addPattenSplice.transform(shape) match
192+
case block @ Block((tdef: TypeDef) :: rest, expr) if tdef.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot) =>
193+
val (tdefs, stats) = rest.span {
194+
case tdef: TypeDef => tdef.symbol.hasAnnotation(defn.QuotedRuntimePatterns_patternTypeAnnot)
195+
case _ => false
196+
}
197+
val shapeBindingSyms = tdef.symbol :: tdefs.map(_.symbol)
198+
val body1 = if stats.isEmpty then expr else cpy.Block(block)(stats, expr)
199+
body1.subst(shapeBindingSyms, bindings.map(_.symbol))
200+
case body => body
201+
val quotePattern = cpy.QuotePattern(tree)(bindings, body, quotes)
202+
Some(quotePattern)
203+
case _ => None
204+
205+
private def kListUnroll(tree: Tree): List[Tree] = tree match
206+
case AppliedTypeTree(kCons, head :: tail :: Nil) => head :: kListUnroll(tail)
207+
case kNil => Nil

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import dotty.tools.dotc.core.StdNames._
1515
import dotty.tools.dotc.core.Symbols._
1616
import dotty.tools.dotc.core.Types._
1717
import dotty.tools.dotc.inlines.PrepareInlineable
18+
import dotty.tools.dotc.quoted.QuotePatterns
1819
import dotty.tools.dotc.staging.StagingLevel.*
1920
import dotty.tools.dotc.transform.SymUtils._
2021
import dotty.tools.dotc.typer.ErrorReporting.errorTree
@@ -439,10 +440,15 @@ trait QuotesAndSplices {
439440
val matchModule = if quoted.isTerm then defn.QuoteMatching_ExprMatch else defn.QuoteMatching_TypeMatch
440441
val unapplyFun = quotes.asInstance(defn.QuoteMatchingClass.typeRef).select(matchModule).select(nme.unapply)
441442

442-
UnApply(
443+
val res = UnApply(
443444
fun = unapplyFun.appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
444445
implicits = quotedPattern :: Nil,
445446
patterns = splicePat :: Nil,
446447
proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe)))
448+
449+
val decoded = QuotePatterns.decode(res)
450+
val encoded = decoded.map(QuotePatterns.encode).getOrElse(res)
451+
452+
res
447453
}
448454
}

0 commit comments

Comments
 (0)