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 b95b7a2 commit 2d3136f
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 7 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
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,11 +206,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 @@ -413,7 +413,9 @@ trait QuotesAndSplices {
addQuotedPatternTypeVariable(typeVariable.symbol)

val pattern =
if quoted.isType then typedType(quoted0, WildcardType)
if quoted.isType then typedType(quoted0, WildcardType)(using patternCtx) match
case quoted1 @ Block(stats, Typed(tpt, _)) => cpy.Block(quoted1)(stats, tpt)
case quoted1 => quoted1
else typedExpr(quoted0, WildcardType)

if untpdTypeVariables.isEmpty then pattern
Expand Down
11 changes: 10 additions & 1 deletion docs/_docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -534,11 +534,20 @@ 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] }
...
```

`Type.of[T]` is used to summon the given instance of `Type[T]` in scope, it is equivalent to `summon[Type[T]]`.

It is possible to match against a higer-kinded type using appropriate type bounds on type variables.
```scala
def empty[K <: AnyKind : Type]: Type[AnyKind] =
Type.of[K] match
case '[type f[X]; f] => Type.of[f]
case '[type f[X <: Int, Y]; f] => Type.of[f]
case '[type k <: AnyKind; k ] => Type.of[k]
```

#### Type testing and casting
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.
These cases will issue a warning at compile-time, but if they are ignored, they can result in unexpected behavior.
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]
21 changes: 21 additions & 0 deletions tests/pos-macros/i10864a/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 <: Int]; f] =>
assert(Type.show[f] == "[T >: scala.Nothing <: scala.Int] => C[T]", Type.show[f])
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] }
}
8 changes: 8 additions & 0 deletions tests/pos-macros/i10864a/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@main
def run =
T.run[Int]
T.run[C]
T.run[List]
T.run[Map]

class C[T <: Int]
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 2d3136f

Please sign in to comment.