Skip to content

Commit ae801c0

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 1821107 commit ae801c0

File tree

9 files changed

+84
-14
lines changed

9 files changed

+84
-14
lines changed

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,19 @@ object Parsers {
15851585
t
15861586
end typ
15871587

1588+
/** TypeBlock ::= {TypeBlockStat semi} Type
1589+
* TypeBlockStat ::= ‘type’ {nl} TypeDcl
1590+
*/
1591+
def typeBlock(): Tree =
1592+
val tDefs = new ListBuffer[Tree]
1593+
while in.token == TYPE do
1594+
val mods = defAnnotsMods(modifierTokens)
1595+
tDefs += typeDefOrDcl(in.offset, in.skipToken(mods))
1596+
acceptStatSep()
1597+
val tpt = typ()
1598+
if tDefs.isEmpty then tpt else Block(tDefs.toList, tpt)
1599+
1600+
15881601
private def makeKindProjectorTypeDef(name: TypeName): TypeDef = {
15891602
val isVarianceAnnotated = name.startsWith("+") || name.startsWith("-")
15901603
// We remove the variance marker from the name without passing along the specified variance at all
@@ -2447,7 +2460,7 @@ object Parsers {
24472460
atSpan(in.skipToken()) {
24482461
withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) {
24492462
Quote {
2450-
if (in.token == LBRACKET) inBrackets(typ())
2463+
if (in.token == LBRACKET) inBrackets(typeBlock())
24512464
else stagedBlock()
24522465
}
24532466
}
@@ -3080,8 +3093,8 @@ object Parsers {
30803093
/* -------- PARAMETERS ------------------------------------------- */
30813094

30823095
/** DefParamClauses ::= DefParamClause { DefParamClause } -- and two DefTypeParamClause cannot be adjacent
3083-
* DefParamClause ::= DefTypeParamClause
3084-
* | DefTermParamClause
3096+
* DefParamClause ::= DefTypeParamClause
3097+
* | DefTermParamClause
30853098
* | UsingParamClause
30863099
*/
30873100
def typeOrTermParamClauses(
@@ -3179,7 +3192,7 @@ object Parsers {
31793192
* UsingClsTermParamClause::= ‘(’ ‘using’ [‘erased’] (ClsParams | ContextTypes) ‘)’
31803193
* ClsParams ::= ClsParam {‘,’ ClsParam}
31813194
* ClsParam ::= {Annotation}
3182-
*
3195+
*
31833196
* TypelessClause ::= DefTermParamClause
31843197
* | UsingParamClause
31853198
*
@@ -3557,13 +3570,13 @@ object Parsers {
35573570
}
35583571
}
35593572

3560-
3573+
35613574

35623575
/** DefDef ::= DefSig [‘:’ Type] ‘=’ Expr
35633576
* | this TypelessClauses [DefImplicitClause] `=' ConstrExpr
35643577
* DefDcl ::= DefSig `:' Type
35653578
* DefSig ::= id [DefTypeParamClause] DefTermParamClauses
3566-
*
3579+
*
35673580
* if clauseInterleaving is enabled:
35683581
* DefSig ::= id [DefParamClauses] [DefImplicitClause]
35693582
*/
@@ -3602,8 +3615,8 @@ object Parsers {
36023615
val mods1 = addFlag(mods, Method)
36033616
val ident = termIdent()
36043617
var name = ident.name.asTermName
3605-
val paramss =
3606-
if in.featureEnabled(Feature.clauseInterleaving) then
3618+
val paramss =
3619+
if in.featureEnabled(Feature.clauseInterleaving) then
36073620
// If you are making interleaving stable manually, please refer to the PR introducing it instead, section "How to make non-experimental"
36083621
typeOrTermParamClauses(ParamOwner.Def, numLeadParams = numLeadParams)
36093622
else
@@ -3613,7 +3626,7 @@ object Parsers {
36133626
joinParams(tparams, vparamss)
36143627

36153628
var tpt = fromWithinReturnType { typedOpt() }
3616-
3629+
36173630
if (migrateTo3) newLineOptWhenFollowedBy(LBRACE)
36183631
val rhs =
36193632
if in.token == EQUALS then

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,11 +197,11 @@ trait QuotesAndSplices {
197197
* )
198198
* ```
199199
*/
200-
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
200+
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Tree], Tree, List[Tree]) = {
201201
val ctx0 = ctx
202202

203-
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
204-
def getBinding(sym: Symbol): Bind =
203+
val typeBindings: collection.mutable.Map[Symbol, Tree] = collection.mutable.Map.empty
204+
def getBinding(sym: Symbol): Tree =
205205
typeBindings.getOrElseUpdate(sym, {
206206
val bindingBounds = sym.info
207207
val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span)
@@ -395,7 +395,10 @@ trait QuotesAndSplices {
395395
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
396396
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
397397
val quoted1 =
398-
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx)
398+
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx) match
399+
case quoted1 @ Block(stats, Typed(tpt, _)) => cpy.Block(quoted1)(stats, tpt)
400+
case quoted1 => quoted1
401+
399402
else typedExpr(quoted0, WildcardType)(using quoteCtx)
400403

401404
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)

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

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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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 <: AnyKind; `f`] =>
14+
assert(Type.show[f] == "[K >: scala.Nothing <: scala.Any, V >: scala.Nothing <: scala.Any] => scala.collection.immutable.Map[K, V]", Type.show[f])
15+
'{}
16+
}
17+
18+
inline def run[T <: AnyKind] = ${ impl[T] }
19+
}

tests/pos-macros/i10864a/Test_2.scala

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

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)