Skip to content

Commit 17bf639

Browse files
authored
Merge pull request #6875 from dotty-staging/fix-#6873
Fix #6875: Extract quotes and splices typing logic into QuotesAndSplices
2 parents ae4ee41 + 5687ac0 commit 17bf639

File tree

2 files changed

+360
-326
lines changed

2 files changed

+360
-326
lines changed
Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
package dotty.tools.dotc.typer
2+
3+
import dotty.tools.dotc.ast._
4+
import dotty.tools.dotc.ast.Trees._
5+
import dotty.tools.dotc.core._
6+
import dotty.tools.dotc.core.Annotations._
7+
import dotty.tools.dotc.core.Constants._
8+
import dotty.tools.dotc.core.Contexts._
9+
import dotty.tools.dotc.core.Decorators._
10+
import dotty.tools.dotc.core.Flags._
11+
import dotty.tools.dotc.core.NameKinds.UniqueName
12+
import dotty.tools.dotc.core.Names._
13+
import dotty.tools.dotc.core.StagingContext._
14+
import dotty.tools.dotc.core.StdNames._
15+
import dotty.tools.dotc.core.Symbols._
16+
import dotty.tools.dotc.core.Types._
17+
import dotty.tools.dotc.reporting._
18+
import dotty.tools.dotc.typer.Implicits._
19+
import dotty.tools.dotc.typer.Inferencing._
20+
import dotty.tools.dotc.util.Spans._
21+
import dotty.tools.dotc.util.Stats.track
22+
23+
import scala.collection.mutable
24+
25+
import scala.annotation.tailrec
26+
import scala.annotation.internal.sharable
27+
import scala.annotation.threadUnsafe
28+
29+
/** Type quotes `'{ ... }` and splices `${ ... }` */
30+
trait QuotesAndSplices { self: Typer =>
31+
32+
import tpd._
33+
34+
/** Translate `'{ t }` into `scala.quoted.Expr.apply(t)` and `'[T]` into `scala.quoted.Type.apply[T]`
35+
* while tracking the quotation level in the context.
36+
*/
37+
def typedQuote(tree: untpd.Quote, pt: Type)(implicit ctx: Context): Tree = track("typedQuote") {
38+
val qctx = inferImplicitArg(defn.QuoteContextType, tree.span)
39+
if (level == 0 && qctx.tpe.isInstanceOf[SearchFailureType])
40+
ctx.error(missingArgMsg(qctx, defn.QuoteContextType, ""), ctx.source.atSpan(tree.span))
41+
42+
tree.quoted match {
43+
case untpd.Splice(innerExpr) if tree.isTerm =>
44+
ctx.warning("Canceled splice directly inside a quote. '{ ${ XYZ } } is equivalent to XYZ.", tree.sourcePos)
45+
typed(innerExpr, pt)
46+
case untpd.TypSplice(innerType) if tree.isType =>
47+
ctx.warning("Canceled splice directly inside a quote. '[ ${ XYZ } ] is equivalent to XYZ.", tree.sourcePos)
48+
typed(innerType, pt)
49+
case quoted =>
50+
ctx.compilationUnit.needsStaging = true
51+
val tree1 =
52+
if (quoted.isType) typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuoteR), quoted :: Nil), pt)(quoteContext)
53+
else if (ctx.mode.is(Mode.Pattern) && level == 0) typedQuotePattern(quoted, pt, qctx)
54+
else typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuoteR), quoted), pt)(quoteContext)
55+
tree1.withSpan(tree.span)
56+
}
57+
}
58+
59+
/** Translate `${ t: Expr[T] }` into expression `t.splice` while tracking the quotation level in the context */
60+
def typedSplice(tree: untpd.Splice, pt: Type)(implicit ctx: Context): Tree = track("typedSplice") {
61+
checkSpliceOutsideQuote(tree)
62+
tree.expr match {
63+
case untpd.Quote(innerExpr) if innerExpr.isTerm =>
64+
ctx.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.sourcePos)
65+
typed(innerExpr, pt)
66+
case expr =>
67+
ctx.compilationUnit.needsStaging = true
68+
if (ctx.mode.is(Mode.QuotedPattern) && level == 1) {
69+
if (isFullyDefined(pt, ForceDegree.all)) {
70+
def spliceOwner(ctx: Context): Symbol =
71+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
72+
val pat = typedPattern(expr, defn.QuotedExprType.appliedTo(pt))(
73+
spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
74+
Splice(pat)
75+
} else {
76+
ctx.error(i"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", expr.sourcePos)
77+
tree.withType(UnspecifiedErrorType)
78+
}
79+
}
80+
else {
81+
if (StagingContext.level == 0) {
82+
// Mark the first inline method from the context as a macro
83+
def markAsMacro(c: Context): Unit =
84+
if (c.owner eq c.outer.owner) markAsMacro(c.outer)
85+
else if (c.owner.isInlineMethod) c.owner.setFlag(Macro)
86+
else if (!c.outer.owner.is(Package)) markAsMacro(c.outer)
87+
markAsMacro(ctx)
88+
}
89+
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprSpliceR), tree.expr), pt)(spliceContext).withSpan(tree.span)
90+
}
91+
}
92+
}
93+
94+
/** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */
95+
def typedTypSplice(tree: untpd.TypSplice, pt: Type)(implicit ctx: Context): Tree = track("typedTypSplice") {
96+
ctx.compilationUnit.needsStaging = true
97+
checkSpliceOutsideQuote(tree)
98+
tree.expr match {
99+
case untpd.Quote(innerType) if innerType.isType =>
100+
ctx.warning("Canceled quote directly inside a splice. ${ '[ XYZ ] } is equivalent to XYZ.", tree.sourcePos)
101+
typed(innerType, pt)
102+
case expr =>
103+
if (ctx.mode.is(Mode.QuotedPattern) && level == 1) {
104+
if (isFullyDefined(pt, ForceDegree.all)) {
105+
ctx.error(i"Spliced type pattern must not be fully defined. Consider using $pt directly", tree.expr.sourcePos)
106+
tree.withType(UnspecifiedErrorType)
107+
} else {
108+
def spliceOwner(ctx: Context): Symbol =
109+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
110+
val name = expr match {
111+
case Ident(name) => ("$" + name).toTypeName
112+
case Typed(Ident(name), _) => ("$" + name).toTypeName
113+
case Bind(name, _) => ("$" + name).toTypeName
114+
case _ => NameKinds.UniqueName.fresh("$".toTypeName)
115+
}
116+
val typeSym = ctx.newSymbol(spliceOwner(ctx), name, EmptyFlags, TypeBounds.empty, NoSymbol, expr.span)
117+
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(expr.span)))
118+
val pat = typedPattern(expr, defn.QuotedTypeType.appliedTo(typeSym.typeRef))(
119+
spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
120+
pat.select(tpnme.splice)
121+
}
122+
} else {
123+
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
124+
}
125+
}
126+
}
127+
128+
private def checkSpliceOutsideQuote(tree: untpd.Tree)(implicit ctx: Context): Unit = {
129+
if (level == 0 && !ctx.owner.ownersIterator.exists(_.is(Inline)))
130+
ctx.error("Splice ${...} outside quotes '{...} or inline method", tree.sourcePos)
131+
else if (level < 0)
132+
ctx.error(
133+
s"""Splice $${...} at level $level.
134+
|
135+
|Inline method may contain a splice at level 0 but the contents of this splice cannot have a splice.
136+
|""".stripMargin, tree.sourcePos
137+
)
138+
}
139+
140+
/** Split a typed quoted pattern is split into its type bindings, pattern expression and inner patterns.
141+
* Type definitions with `@patternBindHole` will be inserted in the pattern expression for each type binding.
142+
*
143+
* A quote pattern
144+
* ```
145+
* case '{ type ${given t: Type[$t @ _]}; ${ls: Expr[List[$t]]} } => ...
146+
* ```
147+
* will return
148+
* ```
149+
* (
150+
* Map(<$t>: Symbol -> <$t @ _>: Bind),
151+
* <'{
152+
* @scala.internal.Quoted.patternBindHole type $t
153+
* scala.internal.Quoted.patternHole[List[$t]]
154+
* }>: Tree,
155+
* List(<ls: Expr[List[$t]]>: Tree)
156+
* )
157+
* ```
158+
*/
159+
private def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
160+
val ctx0 = ctx
161+
162+
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
163+
def getBinding(sym: Symbol): Bind =
164+
typeBindings.getOrElseUpdate(sym, {
165+
val bindingBounds = sym.info
166+
val bsym = ctx.newPatternBoundSymbol(sym.name.toTypeName, bindingBounds, quoted.span)
167+
Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span)
168+
})
169+
170+
object splitter extends tpd.TreeMap {
171+
val patBuf = new mutable.ListBuffer[Tree]
172+
val freshTypePatBuf = new mutable.ListBuffer[Tree]
173+
val freshTypeBindingsBuff = new mutable.ListBuffer[Tree]
174+
val typePatBuf = new mutable.ListBuffer[Tree]
175+
override def transform(tree: Tree)(implicit ctx: Context) = tree match {
176+
case Typed(Splice(pat), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
177+
val tpt1 = transform(tpt) // Transform type bindings
178+
val exprTpt = AppliedTypeTree(TypeTree(defn.QuotedExprType), tpt1 :: Nil)
179+
transform(Splice(Typed(pat, exprTpt)))
180+
case Splice(pat) =>
181+
try ref(defn.InternalQuoted_patternHoleR).appliedToType(tree.tpe).withSpan(tree.span)
182+
finally {
183+
val patType = pat.tpe.widen
184+
val patType1 = patType.underlyingIfRepeated(isJava = false)
185+
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
186+
patBuf += pat1
187+
}
188+
case Select(pat, _) if tree.symbol == defn.QuotedType_splice =>
189+
val sym = tree.tpe.dealias.typeSymbol.asType
190+
val tdef = TypeDef(sym).withSpan(sym.span)
191+
freshTypeBindingsBuff += transformTypeBindingTypeDef(tdef, freshTypePatBuf)
192+
TypeTree(tree.tpe.dealias).withSpan(tree.span)
193+
194+
case ddef: ValOrDefDef =>
195+
if (ddef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot)) {
196+
val bindingType = ddef.symbol.info match {
197+
case t: ExprType => t.resType
198+
case t: MethodType => t.toFunctionType()
199+
case t: PolyType =>
200+
HKTypeLambda(t.paramNames)(
201+
x => t.paramInfos.mapConserve(_.subst(t, x).asInstanceOf[TypeBounds]),
202+
x => t.resType.subst(t, x).toFunctionType())
203+
case t => t
204+
}
205+
val bindingExprTpe = AppliedType(defn.QuotedMatchingBindingType, bindingType :: Nil)
206+
assert(ddef.name.startsWith("$"))
207+
val bindName = ddef.name.toString.stripPrefix("$").toTermName
208+
val sym = ctx0.newPatternBoundSymbol(bindName, bindingExprTpe, ddef.span)
209+
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingExprTpe)).withSpan(ddef.span)
210+
}
211+
super.transform(tree)
212+
case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
213+
transformTypeBindingTypeDef(tdef, typePatBuf)
214+
case _ =>
215+
super.transform(tree)
216+
}
217+
218+
def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]]): Tree = {
219+
val bindingType = getBinding(tdef.symbol).symbol.typeRef
220+
val bindingTypeTpe = AppliedType(defn.QuotedTypeType, bindingType :: Nil)
221+
assert(tdef.name.startsWith("$"))
222+
val bindName = tdef.name.toString.stripPrefix("$").toTermName
223+
val sym = ctx0.newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span, flags = ImplicitTerm)
224+
buff += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
225+
super.transform(tdef)
226+
}
227+
}
228+
val shape0 = splitter.transform(quoted)
229+
val patterns = (splitter.freshTypePatBuf.iterator ++ splitter.typePatBuf.iterator ++ splitter.patBuf.iterator).toList
230+
val freshTypeBindings = splitter.freshTypeBindingsBuff.result()
231+
232+
val shape1 = seq(
233+
freshTypeBindings,
234+
shape0
235+
)
236+
val shape2 = {
237+
if (freshTypeBindings.isEmpty) shape1
238+
else {
239+
val isFreshTypeBindings = freshTypeBindings.map(_.symbol).toSet
240+
val typeMap = new TypeMap() {
241+
def apply(tp: Type): Type = tp match {
242+
case tp: TypeRef if tp.typeSymbol == defn.QuotedType_splice =>
243+
val tp1 = tp.dealias
244+
if (isFreshTypeBindings(tp1.typeSymbol)) tp1
245+
else tp
246+
case tp => mapOver(tp)
247+
}
248+
}
249+
new TreeTypeMap(typeMap = typeMap).transform(shape1)
250+
}
251+
}
252+
253+
(typeBindings.toMap, shape2, patterns)
254+
}
255+
256+
/** Type a quote pattern `case '{ <quoted> } =>` qiven the a current prototype. Typing the pattern
257+
* will also transform it into a call to `scala.internal.quoted.Matcher.unapply`.
258+
*
259+
* Code directly inside the quote is typed as an expression using Mode.QuotedPattern. Splices
260+
* within the quotes become patterns again and typed acordingly.
261+
*
262+
* ```
263+
* case '{ ($ls: List[$t]) } =>
264+
* // `t` is of type `Type[T$1]` for some unknown T$1
265+
* // `t` is implicitly available
266+
* // `l` is of type `Expr[List[T$1]]`
267+
* '{ val h: $t = $ls.head }
268+
* ```
269+
*
270+
* For each type splice we will create a new type binding in the pattern match ($t @ _ in this case)
271+
* and a corresponding type in the quoted pattern as a hole (@patternBindHole type $t in this case).
272+
* All these generated types are inserted at the start of the quoted code.
273+
*
274+
* After typing the tree will resemble
275+
*
276+
* ```
277+
* case '{ type ${given t: Type[$t @ _]}; ${ls: Expr[List[$t]]} } => ...
278+
* ```
279+
*
280+
* Then the pattern is _split_ into the expression containd in the pattern replacing the splices by holes,
281+
* and the patterns in the splices. All these are recombined into a call to `Matcher.unapply`.
282+
*
283+
* ```
284+
* case scala.internal.quoted.Matcher.unapply[
285+
* Tuple1[$t @ _], // Type binging definition
286+
* Tuple2[Type[$t], Expr[List[$t]]] // Typing the result of the pattern match
287+
* ](
288+
* Tuple2.unapply
289+
* [Type[$t], Expr[List[$t]]] //Propagated from the tuple above
290+
* (implict t @ _, ls @ _: Expr[List[$t]]) // from the spliced patterns
291+
* )(
292+
* '{ // Runtime quote Matcher.unapply uses to mach against. Expression directly inside the quoted pattern without the splices
293+
* @scala.internal.Quoted.patternBindHole type $t
294+
* scala.internal.Quoted.patternHole[List[$t]]
295+
* },
296+
* true, // If there is at least one type splice. Used to instantiate the context with or without GADT constraints
297+
* x$2 // tasty.Reflection instance
298+
* ) => ...
299+
* ```
300+
*/
301+
private def typedQuotePattern(quoted: untpd.Tree, pt: Type, qctx: tpd.Tree)(implicit ctx: Context): Tree = {
302+
val exprPt = pt.baseType(defn.QuotedExprClass)
303+
val quotedPt = exprPt.argInfos.headOption match {
304+
case Some(argPt: ValueType) => argPt // excludes TypeBounds
305+
case _ => defn.AnyType
306+
}
307+
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
308+
val quoted1 = typedExpr(quoted0, WildcardType)(quoteContext.addMode(Mode.QuotedPattern))
309+
310+
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
311+
312+
class ReplaceBindings extends TypeMap() {
313+
override def apply(tp: Type): Type = tp match {
314+
case tp: TypeRef =>
315+
val tp1 = if (tp.typeSymbol == defn.QuotedType_splice) tp.dealias else tp
316+
typeBindings.get(tp1.typeSymbol).fold(tp)(_.symbol.typeRef)
317+
case tp => mapOver(tp)
318+
}
319+
}
320+
val replaceBindings = new ReplaceBindings
321+
val patType = defn.tupleType(splices.tpes.map(tpe => replaceBindings(tpe.widen)))
322+
323+
val typeBindingsTuple = tpd.tupleTypeTree(typeBindings.values.toList)
324+
325+
val replaceBindingsInTree = new TreeMap {
326+
private[this] var bindMap = Map.empty[Symbol, Symbol]
327+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
328+
tree match {
329+
case tree: Bind =>
330+
val sym = tree.symbol
331+
val newInfo = replaceBindings(sym.info)
332+
val newSym = ctx.newSymbol(sym.owner, sym.name, sym.flags, newInfo, sym.privateWithin, sym.coord)
333+
bindMap += sym -> newSym
334+
Bind(newSym, transform(tree.body)).withSpan(sym.span)
335+
case _ =>
336+
super.transform(tree).withType(replaceBindingsInType(tree.tpe))
337+
}
338+
}
339+
private[this] val replaceBindingsInType = new ReplaceBindings {
340+
override def apply(tp: Type): Type = tp match {
341+
case tp: TermRef => bindMap.get(tp.termSymbol).fold[Type](tp)(_.typeRef)
342+
case tp => super.apply(tp)
343+
}
344+
}
345+
}
346+
347+
val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)
348+
349+
UnApply(
350+
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
351+
implicits =
352+
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
353+
Literal(Constant(typeBindings.nonEmpty)) ::
354+
qctx :: Nil,
355+
patterns = splicePat :: Nil,
356+
proto = defn.QuotedExprType.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
357+
}
358+
359+
}

0 commit comments

Comments
 (0)