Skip to content

Commit ed9f73e

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 ec3cdad commit ed9f73e

File tree

6 files changed

+125
-25
lines changed

6 files changed

+125
-25
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: 63 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@ import dotty.tools.dotc.staging.StagingLevel.*
1919
import dotty.tools.dotc.transform.SymUtils._
2020
import dotty.tools.dotc.typer.Implicits._
2121
import dotty.tools.dotc.typer.Inferencing._
22+
import dotty.tools.dotc.util.Property
2223
import dotty.tools.dotc.util.Spans._
2324
import dotty.tools.dotc.util.Stats.record
2425
import dotty.tools.dotc.reporting.IllegalVariableInPatternAlternative
2526
import scala.collection.mutable
2627

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

32-
import tpd._
32+
import tpd.*
33+
import QuotesAndSplices.*
3334

3435
/** Translate `'{ e }` into `scala.quoted.Expr.apply(e)` and `'[T]` into `scala.quoted.Type.apply[T]`
3536
* while tracking the quotation level in the context.
@@ -157,19 +158,30 @@ trait QuotesAndSplices {
157158
* The resulting pattern is the split in `splitQuotePattern`.
158159
*/
159160
def typedQuotedTypeVar(tree: untpd.Ident, pt: Type)(using Context): Tree =
160-
def spliceOwner(ctx: Context): Symbol =
161-
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
162-
val name = tree.name.toTypeName
163-
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
164-
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
165161
val typeSymInfo = pt match
166162
case pt: TypeBounds => pt
167163
case _ => TypeBounds.empty
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-
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
171-
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
172-
pat.select(tpnme.Underlying)
164+
getQuotedPatternTypeVariable(tree.name.asTypeName) match
165+
case Some(typeSym) =>
166+
checkExperimentalFeature(
167+
"support for multiple references to the same type (without backticks) in quoted type patterns (SIP-53)",
168+
tree.srcPos,
169+
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
170+
if !(typeSymInfo =:= TypeBounds.empty) then
171+
report.warning(em"Ignored bound$typeSymInfo\n\nConsider defining bounds explicitly `'{ $typeSym$typeSymInfo; ... }`", tree.srcPos)
172+
ref(typeSym)
173+
case None =>
174+
def spliceOwner(ctx: Context): Symbol =
175+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
176+
val name = tree.name.toTypeName
177+
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
178+
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
179+
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
180+
typeSym.addAnnotation(Annotation(New(ref(defn.QuotedRuntimePatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
181+
addQuotedPatternTypeVariable(typeSym)
182+
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
183+
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
184+
pat.select(tpnme.Underlying)
173185

174186
private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit =
175187
if (level == 0 && !ctx.owner.ownersIterator.exists(_.isInlineMethod))
@@ -394,11 +406,24 @@ trait QuotesAndSplices {
394406
case Some(argPt: ValueType) => argPt // excludes TypeBounds
395407
case _ => defn.AnyType
396408
}
397-
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
398-
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
399-
val quoted1 =
400-
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx)
401-
else typedExpr(quoted0, WildcardType)(using quoteCtx)
409+
val (untpdTypeVariables, quoted0) = desugar.quotedPatternTypeVariables(desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt))))
410+
411+
val (typeTypeVariables, patternCtx) =
412+
val quoteCtx = quotePatternContext()
413+
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
414+
else typedBlockStats(untpdTypeVariables)(using quoteCtx)
415+
416+
val quoted1 = inContext(patternCtx) {
417+
for typeVariable <- typeTypeVariables do
418+
addQuotedPatternTypeVariable(typeVariable.symbol)
419+
420+
val pattern =
421+
if quoted.isType then typedType(quoted0, WildcardType)
422+
else typedExpr(quoted0, WildcardType)
423+
424+
if untpdTypeVariables.isEmpty then pattern
425+
else tpd.Block(typeTypeVariables, pattern)
426+
}
402427

403428
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
404429

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

docs/_docs/reference/metaprogramming/macros.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -504,18 +504,22 @@ def let(x: Expr[Any])(using Quotes): Expr[Any] =
504504
let('{1}) // will return a `Expr[Any]` that contains an `Expr[Int]]`
505505
```
506506

507+
It is also possible to refer to the same type variable multiple times in a pattern.
508+
509+
```scala
510+
case '{ $x: (t, t) } =>
511+
```
512+
507513
While we can define the type variable in the middle of the pattern, their normal form is to define them as a `type` with a lower case name at the start of the pattern.
508-
We use the Scala backquote `` `t` `` naming convention which interprets the string within the backquote as a literal name identifier.
509-
This is typically used when we have names that contain special characters that are not allowed for normal Scala identifiers.
510-
But we use it to explicitly state that this is a reference to that name and not the introduction of a new variable.
514+
511515
```scala
512-
case '{ type t; $x: `t` } =>
516+
case '{ type t; $x: t } =>
513517
```
514-
This is a bit more verbose but has some expressivity advantages such as allowing to define bounds on the variables and be able to refer to them several times in any scope of the pattern.
518+
519+
This is a bit more verbose but has some expressivity advantages such as allowing to define bounds on the variables.
515520

516521
```scala
517-
case '{ type t >: List[Int] <: Seq[Int]; $x: `t` } =>
518-
case '{ type t; $x: (`t`, `t`) } =>
522+
case '{ type t >: List[Int] <: Seq[Int]; $x: t } =>
519523
```
520524

521525

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.quoted.*
2+
3+
def foo(using Quotes): Unit =
4+
(??? : Type[?]) match
5+
case '[ (t, t, t) ] => // error // error
6+
'{ ??? : Any } match
7+
case '{ type u; $x: u } => // error
8+
case '{ type u; ($ls: List[u]).map($f: u => Int) } => // error // error
9+

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)