Skip to content

Commit 4b0fbc8

Browse files
committed
Quoted pattern type variables without backticks
With this change we can support references to quote pattern type variables without backticks. ```scala case '{ type t; ... : F[t] } ``` SIP: https://github.com/scala/improvement-proposals/blob/main/content/quote-pattern-type-variable-syntax.md?plain=1#L66-L70 ```scala case '{ ... : F[t, t] } ``` SIP: https://github.com/scala/improvement-proposals/blob/main/content/quote-pattern-type-variable-syntax.md?plain=1#L72-L78
1 parent f128063 commit 4b0fbc8

File tree

4 files changed

+101
-18
lines changed

4 files changed

+101
-18
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,35 @@ object desugar {
363363
adaptToExpectedTpt(tree)
364364
}
365365

366+
/** Split out the quoted pattern type variable definition from the pattern.
367+
*
368+
* Type variable definitions are all the `type t` defined at the start of a quoted pattern.
369+
* Were name `t` is a pattern type variable name (i.e. lower case letters).
370+
*
371+
* ```
372+
* type t1; ...; type tn; <pattern>
373+
* ```
374+
* is split into
375+
* ```
376+
* (List(<type t1>; ...; <type tn>), <pattern>)
377+
* ```
378+
*/
379+
def quotedPatternTypeVariables(tree: untpd.Tree)(using Context): (List[untpd.TypeDef], untpd.Tree) =
380+
tree match
381+
case untpd.Block(stats, expr) =>
382+
val untpdTypeVariables = stats.takeWhile {
383+
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
384+
case _ => false
385+
}.asInstanceOf[List[untpd.TypeDef]]
386+
val otherStats = stats.dropWhile {
387+
case tdef @ untpd.TypeDef(name, _) => name.isVarPattern
388+
case _ => false
389+
}
390+
val pattern = if otherStats.isEmpty then expr else untpd.cpy.Block(tree)(otherStats, expr)
391+
(untpdTypeVariables, pattern)
392+
case _ =>
393+
(Nil, tree)
394+
366395
/** Add all evidence parameters in `params` as implicit parameters to `meth`.
367396
* If the parameters of `meth` end in an implicit parameter list or using clause,
368397
* evidence parameters are added in front of that list. Otherwise they are added

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

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,18 @@ import dotty.tools.dotc.staging.StagingLevel.*
2020
import dotty.tools.dotc.transform.SymUtils._
2121
import dotty.tools.dotc.typer.Implicits._
2222
import dotty.tools.dotc.typer.Inferencing._
23+
import dotty.tools.dotc.util.Property
2324
import dotty.tools.dotc.util.Spans._
2425
import dotty.tools.dotc.util.Stats.record
2526
import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative
2627
import scala.collection.mutable
2728

28-
2929
/** Type quotes `'{ ... }` and splices `${ ... }` */
3030
trait QuotesAndSplices {
3131
self: Typer =>
3232

33-
import tpd._
33+
import tpd.*
34+
import QuotesAndSplices.*
3435

3536
/** Translate `'{ e }` into `scala.quoted.Expr.apply(e)` and `'[T]` into `scala.quoted.Type.apply[T]`
3637
* while tracking the quotation level in the context.
@@ -150,19 +151,26 @@ trait QuotesAndSplices {
150151
* The resulting pattern is the split in `splitQuotePattern`.
151152
*/
152153
def typedQuotedTypeVar(tree: untpd.Ident, pt: Type)(using Context): Tree =
153-
def spliceOwner(ctx: Context): Symbol =
154-
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
155-
val name = tree.name.toTypeName
156-
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
157-
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
158154
val typeSymInfo = pt match
159155
case pt: TypeBounds => pt
160156
case _ => TypeBounds.empty
161-
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
162-
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
163-
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
164-
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
165-
pat.select(tpnme.Underlying)
157+
getQuotedPatternTypeVariable(tree.name.asTypeName) match
158+
case Some(typeSym) =>
159+
if !(typeSymInfo =:= TypeBounds.empty) then
160+
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym$typeSymInfo; ... }`", tree.srcPos)
161+
ref(typeSym)
162+
case None =>
163+
def spliceOwner(ctx: Context): Symbol =
164+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
165+
val name = tree.name.toTypeName
166+
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
167+
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
168+
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
169+
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
170+
addQuotedPatternTypeVariable(typeSym)
171+
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
172+
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
173+
pat.select(tpnme.Underlying)
166174

167175
def typedHole(tree: untpd.Hole, pt: Type)(using Context): Tree =
168176
val tpt = typedType(tree.tpt)
@@ -393,11 +401,24 @@ trait QuotesAndSplices {
393401
case Some(argPt: ValueType) => argPt // excludes TypeBounds
394402
case _ => defn.AnyType
395403
}
396-
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
397-
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
398-
val quoted1 =
399-
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx)
400-
else typedExpr(quoted0, WildcardType)(using quoteCtx)
404+
val (untpdTypeVariables, quoted0) = desugar.quotedPatternTypeVariables(desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt))))
405+
406+
val (typeTypeVariables, patternCtx) =
407+
val quoteCtx = quotePatternContext()
408+
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
409+
else typedBlockStats(untpdTypeVariables)(using quoteCtx)
410+
411+
val quoted1 = inContext(patternCtx) {
412+
for typeVariable <- typeTypeVariables do
413+
addQuotedPatternTypeVariable(typeVariable.symbol)
414+
415+
val pattern =
416+
if quoted.isType then typedType(quoted0, WildcardType)
417+
else typedExpr(quoted0, WildcardType)
418+
419+
if untpdTypeVariables.isEmpty then pattern
420+
else tpd.Block(typeTypeVariables, pattern)
421+
}
401422

402423
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
403424

@@ -454,3 +475,24 @@ trait QuotesAndSplices {
454475
proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
455476
}
456477
}
478+
479+
object QuotesAndSplices {
480+
import tpd._
481+
482+
/** Key for mapping from quoted pattern type variable names into their symbol */
483+
private val TypeVariableKey = new Property.Key[collection.mutable.Map[TypeName, Symbol]]
484+
485+
/** Get the symbol for the quoted pattern type variable if it exists */
486+
def getQuotedPatternTypeVariable(name: TypeName)(using Context): Option[Symbol] =
487+
ctx.property(TypeVariableKey).get.get(name)
488+
489+
/** Get the symbol for the quoted pattern type variable if it exists */
490+
def addQuotedPatternTypeVariable(sym: Symbol)(using Context): Unit =
491+
ctx.property(TypeVariableKey).get.update(sym.name.asTypeName, sym)
492+
493+
/** Context used to type the contents of a quoted */
494+
def quotePatternContext()(using Context): Context =
495+
quoteContext.fresh.setNewScope
496+
.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
497+
.setProperty(TypeVariableKey, collection.mutable.Map.empty)
498+
}

tests/neg-macros/quotedPatterns-5.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import scala.quoted.*
22
object Test {
33
def test(x: quoted.Expr[Int])(using Quotes): Unit = x match {
44
case '{ type t; 4 } => Type.of[t]
5-
case '{ type t; poly[t]($x); 4 } => // error: duplicate pattern variable: t
5+
case '{ type t; poly[t]($x); 4 } =>
66
case '{ type `t`; poly[`t`]($x); 4 } =>
77
Type.of[t] // error
88
case _ =>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.quoted.*
2+
3+
def foo[T: Type](expr: Expr[Any])(using Quotes): Any =
4+
expr match
5+
case '{ $x: Map[t, t] } =>
6+
case '{ type t; $x: Any } =>
7+
case '{ type t; $x: Map[t, t] } =>
8+
case '{ ($x: Set[t]).toSet[t] } =>
9+
10+
Type.of[T] match
11+
case '[Map[t, t]] =>
12+
case '[(t, t, t, t, t, t, t)] =>

0 commit comments

Comments
 (0)