Skip to content

Commit

Permalink
Support type variable definitions in quoted patterns
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nicolasstucki committed Apr 28, 2023
1 parent f128063 commit bcb2c04
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 6 deletions.
15 changes: 14 additions & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1632,6 +1632,19 @@ object Parsers {
t
end typ

/** TypeBlock ::= {TypeBlockStat semi} Type
* TypeBlockStat ::= ‘type’ {nl} TypeDcl
*/
def typeBlock(): Tree =
val tDefs = new ListBuffer[Tree]
while in.token == TYPE do
val mods = defAnnotsMods(modifierTokens)
tDefs += typeDefOrDcl(in.offset, in.skipToken(mods))
acceptStatSep()
val tpt = typ()
if tDefs.isEmpty then tpt else Block(tDefs.toList, tpt)


private def makeKindProjectorTypeDef(name: TypeName): TypeDef = {
val isVarianceAnnotated = name.startsWith("+") || name.startsWith("-")
// We remove the variance marker from the name without passing along the specified variance at all
Expand Down Expand Up @@ -2495,7 +2508,7 @@ object Parsers {
atSpan(in.skipToken()) {
withinStaged(StageKind.Quoted | (if (location.inPattern) StageKind.QuotedPattern else 0)) {
Quote {
if (in.token == LBRACKET) inBrackets(typ())
if (in.token == LBRACKET) inBrackets(typeBlock())
else stagedBlock()
}
}
Expand Down
11 changes: 7 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,11 @@ trait QuotesAndSplices {
* )
* ```
*/
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
private def splitQuotePattern(quoted: Tree)(using Context): (Map[Symbol, Tree], Tree, List[Tree]) = {
val ctx0 = ctx

val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
def getBinding(sym: Symbol): Bind =
val typeBindings: collection.mutable.Map[Symbol, Tree] = collection.mutable.Map.empty
def getBinding(sym: Symbol): Tree =
typeBindings.getOrElseUpdate(sym, {
val bindingBounds = sym.info
val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span)
Expand Down Expand Up @@ -396,7 +396,10 @@ trait QuotesAndSplices {
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
val quoted1 =
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx)
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx) match
case quoted1 @ Block(stats, Typed(tpt, _)) => cpy.Block(quoted1)(stats, tpt)
case quoted1 => quoted1

else typedExpr(quoted0, WildcardType)(using quoteCtx)

val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
Expand Down
1 change: 1 addition & 0 deletions docs/_docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ def empty[T: Type]: Expr[T] =
Type.of[T] match
case '[String] => '{ "" }
case '[List[t]] => '{ List.empty[t] }
case '[type t <: Option[Int]; List[`t`]] => '{ List.empty[t] }
...
```

Expand Down
4 changes: 3 additions & 1 deletion docs/_docs/reference/syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ ColonArgument ::= colon [LambdaStart]
LambdaStart ::= FunParams (‘=>’ | ‘?=>’)
| HkTypeParamClause ‘=>’
Quoted ::= ‘'’ ‘{’ Block ‘}’
| ‘'’ ‘[’ Type ‘]’
| ‘'’ ‘[’ TypeBlock ‘]’
ExprSplice ::= spliceId -- if inside quoted block
| ‘$’ ‘{’ Block ‘}’ -- unless inside quoted pattern
| ‘$’ ‘{’ Pattern ‘}’ -- when inside quoted pattern
Expand All @@ -296,6 +296,8 @@ BlockStat ::= Import
| Extension
| Expr1
| EndMarker
TypeBlock ::= {TypeBlockStat semi} Type
TypeBlockStat ::= ‘type’ {nl} TypeDcl
ForExpr ::= ‘for’ ‘(’ Enumerators0 ‘)’ {nl} [‘do‘ | ‘yield’] Expr
| ‘for’ ‘{’ Enumerators0 ‘}’ {nl} [‘do‘ | ‘yield’] Expr
Expand Down
15 changes: 15 additions & 0 deletions tests/pos-macros/i10864/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import scala.quoted._

case class T(t: Type[_])

object T {
def impl[T <: AnyKind](using tt: Type[T])(using Quotes): Expr[Unit] = {
val t = T(tt)
t.t match
case '[type x <: AnyKind; `x`] => // ok
case _ => quotes.reflect.report.error("not ok :(")
'{}
}

inline def run[T <: AnyKind] = ${ impl[T] }
}
4 changes: 4 additions & 0 deletions tests/pos-macros/i10864/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def test =
T.run[List]
T.run[Map]
T.run[Tuple22]
19 changes: 19 additions & 0 deletions tests/pos-macros/i10864a/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import scala.quoted._

case class T(t: Type[_])

object T {
def impl[T <: AnyKind](using tt: Type[T])(using Quotes): Expr[Unit] = {
val t = T(tt)
t.t match
case '[type x; `x`] =>
assert(Type.show[x] == "scala.Int", Type.show[x])
case '[type f[X]; `f`] =>
assert(Type.show[f] == "[A >: scala.Nothing <: scala.Any] => scala.collection.immutable.List[A]", Type.show[f])
case '[type f <: AnyKind; `f`] =>
assert(Type.show[f] == "[K >: scala.Nothing <: scala.Any, V >: scala.Nothing <: scala.Any] => scala.collection.immutable.Map[K, V]", Type.show[f])
'{}
}

inline def run[T <: AnyKind] = ${ impl[T] }
}
5 changes: 5 additions & 0 deletions tests/pos-macros/i10864a/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@main
def run =
T.run[Int]
T.run[List]
T.run[Map]
8 changes: 8 additions & 0 deletions tests/pos-macros/i11738.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import scala.quoted.*

def blah[A](using Quotes, Type[A]): Expr[Unit] =
Type.of[A] match
case '[h *: t] => println(s"h = ${Type.show[h]}, t = ${Type.show[t]}") // ok
case '[type f[X]; `f`[a]] => println(s"f = ${Type.show[f]}, a = ${Type.show[a]}") // error
case _ =>
'{()}
1 change: 1 addition & 0 deletions tests/pos-macros/i7264.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ class Foo {
def f[T2](t: Type[T2])(using Quotes) = t match {
case '[ *:[Int, t2] ] =>
Type.of[ *:[Int, t2] ]
case '[ type t <: Tuple; *:[`t`, `t`] ] =>
}
}

0 comments on commit bcb2c04

Please sign in to comment.