Skip to content

Commit 2d3136f

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 scala#10864 Fixes scala#11738
1 parent b95b7a2 commit 2d3136f

File tree

10 files changed

+90
-7
lines changed

10 files changed

+90
-7
lines changed

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

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

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

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,11 @@ trait QuotesAndSplices {
206206
* )
207207
* ```
208208
*/
209-
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
209+
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Tree], Tree, List[Tree]) = {
210210
val ctx0 = ctx
211211

212-
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
213-
def getBinding(sym: Symbol): Bind =
212+
val typeBindings: collection.mutable.Map[Symbol, Tree] = collection.mutable.Map.empty
213+
def getBinding(sym: Symbol): Tree =
214214
typeBindings.getOrElseUpdate(sym, {
215215
val bindingBounds = sym.info
216216
val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span)
@@ -413,7 +413,9 @@ trait QuotesAndSplices {
413413
addQuotedPatternTypeVariable(typeVariable.symbol)
414414

415415
val pattern =
416-
if quoted.isType then typedType(quoted0, WildcardType)
416+
if quoted.isType then typedType(quoted0, WildcardType)(using patternCtx) match
417+
case quoted1 @ Block(stats, Typed(tpt, _)) => cpy.Block(quoted1)(stats, tpt)
418+
case quoted1 => quoted1
417419
else typedExpr(quoted0, WildcardType)
418420

419421
if untpdTypeVariables.isEmpty then pattern

docs/_docs/reference/metaprogramming/macros.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,11 +534,20 @@ def empty[T: Type]: 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]: Type[AnyKind] =
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
@@ -277,7 +277,7 @@ ColonArgument ::= colon [LambdaStart]
277277
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
278278
| HkTypeParamClause ‘=>’
279279
Quoted ::= ‘'’ ‘{’ Block ‘}’
280-
| ‘'’ ‘[’ Type ‘]’
280+
| ‘'’ ‘[’ TypeBlock ‘]’
281281
ExprSplice ::= spliceId -- if inside quoted block
282282
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
283283
| ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern
@@ -296,6 +296,8 @@ BlockStat ::= Import
296296
| Extension
297297
| Expr1
298298
| EndMarker
299+
TypeBlock ::= {TypeBlockStat semi} Type
300+
TypeBlockStat ::= ‘type’ {nl} TypeDcl
299301
300302
ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr
301303
| ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr

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 <: Int]; f] =>
12+
assert(Type.show[f] == "[T >: scala.Nothing <: scala.Int] => C[T]", Type.show[f])
13+
case '[type f[X]; f] =>
14+
assert(Type.show[f] == "[A >: scala.Nothing <: scala.Any] => scala.collection.immutable.List[A]", 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)