Skip to content

Commit 14cf508

Browse files
committed
Support type variable definitions in quoted patterns
Support explicit type variable definition in quoted patterns. This allows users to set explicit bounds or use the binding twice. Previously this was only possible on quoted expression patterns case '{ ... }. ```scala case '[type x; x] => case '[type x; Map[x, x]] => case '[type x <: List[Any]; x] => case '[type f[X]; f] => case '[type f <: AnyKind; f] => ``` Fixes #10864 Fixes #11738
1 parent ed9f73e commit 14cf508

File tree

11 files changed

+103
-8
lines changed

11 files changed

+103
-8
lines changed

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,6 +1633,19 @@ object Parsers {
16331633
t
16341634
end typ
16351635

1636+
/** TypeBlock ::= {TypeBlockStat semi} Type
1637+
* TypeBlockStat ::= ‘type’ {nl} TypeDcl
1638+
*/
1639+
def typeBlock(): Tree =
1640+
val tDefs = new ListBuffer[Tree]
1641+
while in.token == TYPE do
1642+
val mods = defAnnotsMods(modifierTokens)
1643+
tDefs += typeDefOrDcl(in.offset, in.skipToken(mods))
1644+
acceptStatSep()
1645+
val tpt = typ()
1646+
if tDefs.isEmpty then tpt else Block(tDefs.toList, tpt)
1647+
1648+
16361649
private def makeKindProjectorTypeDef(name: TypeName): TypeDef = {
16371650
val isVarianceAnnotated = name.startsWith("+") || name.startsWith("-")
16381651
// We remove the variance marker from the name without passing along the specified variance at all
@@ -2496,7 +2509,7 @@ object Parsers {
24962509
atSpan(in.skipToken()) {
24972510
withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) {
24982511
val body =
2499-
if (in.token == LBRACKET) inBrackets(typ())
2512+
if (in.token == LBRACKET) inBrackets(typeBlock())
25002513
else stagedBlock()
25012514
Quote(body)
25022515
}

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,11 @@ trait QuotesAndSplices {
213213
* )
214214
* ```
215215
*/
216-
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
216+
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Tree], Tree, List[Tree]) = {
217217
val ctx0 = ctx
218218

219-
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
220-
def getBinding(sym: Symbol): Bind =
219+
val typeBindings: collection.mutable.Map[Symbol, Tree] = collection.mutable.Map.empty
220+
def getBinding(sym: Symbol): Tree =
221221
typeBindings.getOrElseUpdate(sym, {
222222
val bindingBounds = sym.info
223223
val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span)
@@ -408,6 +408,12 @@ trait QuotesAndSplices {
408408
}
409409
val (untpdTypeVariables, quoted0) = desugar.quotedPatternTypeVariables(desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt))))
410410

411+
if quoted.isType && untpdTypeVariables.nonEmpty then
412+
checkExperimentalFeature(
413+
"explicit type variable declarations quoted type patterns (SIP-53)",
414+
untpdTypeVariables.head.srcPos,
415+
"\n\nSIP-53: https://docs.scala-lang.org/sips/quote-pattern-type-variable-syntax.html")
416+
411417
val (typeTypeVariables, patternCtx) =
412418
val quoteCtx = quotePatternContext()
413419
if untpdTypeVariables.isEmpty then (Nil, quoteCtx)
@@ -418,7 +424,7 @@ trait QuotesAndSplices {
418424
addQuotedPatternTypeVariable(typeVariable.symbol)
419425

420426
val pattern =
421-
if quoted.isType then typedType(quoted0, WildcardType)
427+
if quoted.isType then typedType(quoted0, WildcardType)(using patternCtx)
422428
else typedExpr(quoted0, WildcardType)
423429

424430
if untpdTypeVariables.isEmpty then pattern

docs/_docs/reference/metaprogramming/macros.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -530,15 +530,24 @@ It works the same way as a quoted pattern but is restricted to contain a type.
530530
Type variables can be used in quoted type patterns to extract a type.
531531

532532
```scala
533-
def empty[T: Type]: Expr[T] =
533+
def empty[T: Type](using Quotes): Expr[T] =
534534
Type.of[T] match
535535
case '[String] => '{ "" }
536536
case '[List[t]] => '{ List.empty[t] }
537+
case '[type t <: Option[Int]; List[t]] => '{ List.empty[t] }
537538
...
538539
```
539-
540540
`Type.of[T]` is used to summon the given instance of `Type[T]` in scope, it is equivalent to `summon[Type[T]]`.
541541

542+
It is possible to match against a higer-kinded type using appropriate type bounds on type variables.
543+
```scala
544+
def empty[K <: AnyKind : Type](using Quotes): Type[?] =
545+
Type.of[K] match
546+
case '[type f[X]; f] => Type.of[f]
547+
case '[type f[X <: Int, Y]; f] => Type.of[f]
548+
case '[type k <: AnyKind; k ] => Type.of[k]
549+
```
550+
542551
#### Type testing and casting
543552
It is important to note that instance checks and casts on `Expr`, such as `isInstanceOf[Expr[T]]` and `asInstanceOf[Expr[T]]`, will only check if the instance is of the class `Expr` but will not be able to check the `T` argument.
544553
These cases will issue a warning at compile-time, but if they are ignored, they can result in unexpected behavior.

docs/_docs/reference/syntax.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ ColonArgument ::= colon [LambdaStart]
274274
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
275275
| HkTypeParamClause ‘=>’
276276
Quoted ::= ‘'’ ‘{’ Block ‘}’
277-
| ‘'’ ‘[’ Type ‘]’
277+
| ‘'’ ‘[’ TypeBlock ‘]’
278278
ExprSplice ::= spliceId -- if inside quoted block
279279
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
280280
| ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern
@@ -293,6 +293,8 @@ BlockStat ::= Import
293293
| Extension
294294
| Expr1
295295
| EndMarker
296+
TypeBlock ::= {TypeBlockStat semi} Type
297+
TypeBlockStat ::= ‘type’ {nl} TypeDcl
296298
297299
ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr
298300
| ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted.*
2+
3+
def empty[K <: AnyKind : Type](using Quotes): Type[?] =
4+
Type.of[K] match
5+
case '[type t; `t`] => Type.of[t] // error
6+
case '[type f[X]; `f`] => Type.of[f] // error
7+
case '[type f[X <: Int, Y]; `f`] => Type.of[f] // error
8+
case '[type k <: AnyKind; `k` ] => Type.of[k] // error

tests/pos-macros/i10864/Macro_1.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import scala.quoted._
2+
3+
case class T(t: Type[_])
4+
5+
object T {
6+
def impl[T <: AnyKind](using tt: Type[T])(using Quotes): Expr[Unit] = {
7+
val t = T(tt)
8+
t.t match
9+
case '[type x <: AnyKind; x] => // ok
10+
case _ => quotes.reflect.report.error("not ok :(")
11+
'{}
12+
}
13+
14+
inline def run[T <: AnyKind] = ${ impl[T] }
15+
}

tests/pos-macros/i10864/Test_2.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test =
2+
T.run[List]
3+
T.run[Map]
4+
T.run[Tuple22]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import scala.quoted._
2+
3+
case class T(t: Type[_])
4+
5+
object T {
6+
def impl[T <: AnyKind](using tt: Type[T])(using Quotes): Expr[Unit] = {
7+
val t = T(tt)
8+
t.t match
9+
case '[type x; x] =>
10+
assert(Type.show[x] == "scala.Int", Type.show[x])
11+
case '[type f[X]; f] =>
12+
assert(Type.show[f] == "[A >: scala.Nothing <: scala.Any] => scala.collection.immutable.List[A]", Type.show[f])
13+
case '[type f[X <: Int]; f] =>
14+
assert(Type.show[f] == "[T >: scala.Nothing <: scala.Int] => C[T]", Type.show[f])
15+
case '[type f <: AnyKind; f] =>
16+
assert(Type.show[f] == "[K >: scala.Nothing <: scala.Any, V >: scala.Nothing <: scala.Any] => scala.collection.immutable.Map[K, V]", Type.show[f])
17+
'{}
18+
}
19+
20+
inline def run[T <: AnyKind] = ${ impl[T] }
21+
}

tests/pos-macros/i10864a/Test_2.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@main
2+
def run =
3+
T.run[Int]
4+
T.run[C]
5+
T.run[List]
6+
T.run[Map]
7+
8+
class C[T <: Int]

tests/pos-macros/i11738.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted.*
2+
3+
def blah[A](using Quotes, Type[A]): Expr[Unit] =
4+
Type.of[A] match
5+
case '[h *: t] => println(s"h = ${Type.show[h]}, t = ${Type.show[t]}") // ok
6+
case '[type f[X]; f[a]] => println(s"f = ${Type.show[f]}, a = ${Type.show[a]}") // error
7+
case _ =>
8+
'{()}

tests/pos-macros/i7264.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@ class Foo {
33
def f[T2](t: Type[T2])(using Quotes) = t match {
44
case '[ *:[Int, t2] ] =>
55
Type.of[ *:[Int, t2] ]
6+
case '[ type t <: Tuple; *:[t, t] ] =>
67
}
78
}

0 commit comments

Comments
 (0)