Skip to content

Commit 12764e2

Browse files
committed
Make type of type hole available quoted patterns
In general, instances of `scala.quoted.Type` are and should not be used explicitly in quotes. ```diff + def f[T](x: T)(using Type[T])(using QuoteContext) = + '{ val y: T = $x; ... } // good code - def f[T](x: T)(using t: Type[T])(using QuoteContext) = - '{ val y: t.T = $x; ... } // bad code - def f[T](x: T)(using t: Type[T])(using QuoteContext) = - '{ val y: $t = $x; ... } // bad code (syntax will be removed) ``` The only exception is on types extracted using quoted patterns as these hide tier type inside an instance of `Type[T]` which must be accessed using it's path. ```diff ??? match case '{ $x: $t } => - '{ val y: t.T = $x; ... } // bad code - '{ val y: $t = $x; ... } // bad code (syntax will be removed) ``` Here `t` is provided as a `given` as if we refer to its type we must be able to summon it. The change is to provide the name of the type that is extracted rather than the `given Type[T]`. `Type[T]` can be summoned were needed as in all other uses of abstract types in quotes. ```diff ??? match case '{ $x: $T } => + '{ val y: T = $x; ... } // good code - val t: Type[T] = summon[Type[T]] - '{ val y: t.T = $x; ... } // bad code - '{ val y: $t = $x; ... } // bad code (syntax will be removed) ``` The pattern will make `T` and a `given Type[T]` available on the right-hand side of the pattern. The `T` can be used the same way it could be used in the implementation of a method with signature: ```scala def f[T](x: T)(using Type[T])(using QuoteContext) = '{ val y: T = $x; ... } ``` ```scala case '{ $x: $T } => '{ val y: T = $x; ... } ``` It is also possible to use lower cases for the type splices `case '{ $x: $t } => '{ val y: t = $x; ... }`. This is similar to the use of type variables in normal patterns `case Some[t](x) =>`. It is preferable to use upper cases as it makes it clearer that this is a type and renders better on the uses in the right-hand side of the pattern.
1 parent 3107214 commit 12764e2

File tree

24 files changed

+97
-93
lines changed

24 files changed

+97
-93
lines changed

compiler/src/dotty/tools/dotc/core/NameKinds.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ object NameKinds {
320320
val PatMatStdBinderName: UniqueNameKind = new UniqueNameKind("x")
321321
val PatMatAltsName: UniqueNameKind = new UniqueNameKind("matchAlts")
322322
val PatMatResultName: UniqueNameKind = new UniqueNameKind("matchResult")
323+
val PatMatQuoteTypeEv: UniqueNameKind = new UniqueNameKind("tpev$")
323324

324325
val LocalOptInlineLocalObj: UniqueNameKind = new UniqueNameKind("ilo")
325326

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,16 @@ object NameOps {
8080
case _ => false
8181

8282
/** Is name a variable name? */
83-
def isVariableName: Boolean = testSimple { n =>
84-
n.length > 0 && {
85-
val first = n.head
86-
(((first.isLower && first.isLetter) || first == '_')
87-
&& (n != false_)
88-
&& (n != true_)
89-
&& (n != null_))
90-
}
91-
}
83+
def isVariableName: Boolean =
84+
testSimple { n =>
85+
n.length > 0 && {
86+
val first = n.head
87+
(((first.isLower && first.isLetter) || first == '_')
88+
&& (n != false_)
89+
&& (n != true_)
90+
&& (n != null_))
91+
}
92+
} || name.is(PatMatQuoteTypeEv)
9293

9394
def isOpAssignmentName: Boolean = name match {
9495
case raw.NE | raw.LE | raw.GE | EMPTY =>

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import dotty.tools.dotc.core.Constants._
99
import dotty.tools.dotc.core.Contexts._
1010
import dotty.tools.dotc.core.Decorators._
1111
import dotty.tools.dotc.core.Flags._
12-
import dotty.tools.dotc.core.NameKinds.UniqueName
12+
import dotty.tools.dotc.core.NameKinds.{UniqueName, PatMatQuoteTypeEv}
1313
import dotty.tools.dotc.core.Names._
1414
import dotty.tools.dotc.core.StagingContext._
1515
import dotty.tools.dotc.core.StdNames._
@@ -156,19 +156,21 @@ trait QuotesAndSplices {
156156
if ctx.mode.is(Mode.QuotedPattern) && level == 1 then
157157
def spliceOwner(ctx: Context): Symbol =
158158
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
159-
val name = tree.expr match {
160-
case Ident(name) => ("$" + name).toTypeName
159+
val (name, expr) = tree.expr match {
160+
case Ident(name) =>
161+
val nameOfSyntheticGiven = PatMatQuoteTypeEv.fresh()
162+
(name.toTypeName, untpd.cpy.Ident(tree.expr)(nameOfSyntheticGiven))
161163
case expr =>
162164
report.error("expected a name binding", expr.srcPos)
163-
"$error".toTypeName
165+
("$error".toTypeName, expr)
164166
}
165167

166168
val typeSymInfo = pt match
167169
case pt: TypeBounds => pt
168170
case _ => TypeBounds.empty
169171
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.expr.span)
170172
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.expr.span)))
171-
val pat = typedPattern(tree.expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
173+
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
172174
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
173175
pat.select(tpnme.spliceType)
174176
else
@@ -214,7 +216,7 @@ trait QuotesAndSplices {
214216
def getBinding(sym: Symbol): Bind =
215217
typeBindings.getOrElseUpdate(sym, {
216218
val bindingBounds = sym.info
217-
val bsym = newPatternBoundSymbol(sym.name.toTypeName, bindingBounds, quoted.span)
219+
val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span)
218220
Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span)
219221
})
220222

@@ -263,13 +265,14 @@ trait QuotesAndSplices {
263265
val sym = tree.tpe.dealias.typeSymbol
264266
if sym.exists then
265267
val tdef = TypeDef(sym.asType).withSpan(sym.span)
266-
freshTypeBindingsBuff += transformTypeBindingTypeDef(tdef, freshTypePatBuf)
268+
val nameOfSyntheticGiven = pat.symbol.name.toTermName
269+
freshTypeBindingsBuff += transformTypeBindingTypeDef(nameOfSyntheticGiven, tdef, freshTypePatBuf)
267270
TypeTree(tree.tpe.dealias).withSpan(tree.span)
268271
else
269272
tree
270273
case tdef: TypeDef =>
271274
if tdef.symbol.hasAnnotation(defn.InternalQuotedPatterns_patternTypeAnnot) then
272-
transformTypeBindingTypeDef(tdef, typePatBuf)
275+
transformTypeBindingTypeDef(PatMatQuoteTypeEv.fresh(), tdef, typePatBuf)
273276
else if tdef.symbol.isClass then
274277
val kind = if tdef.symbol.is(Module) then "objects" else "classes"
275278
report.error("Implementation restriction: cannot match " + kind, tree.srcPos)
@@ -305,13 +308,12 @@ trait QuotesAndSplices {
305308
super.transform(tree)
306309
}
307310

308-
private def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(using Context): Tree = {
311+
private def transformTypeBindingTypeDef(nameOfSyntheticGiven: TermName, tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(using Context): Tree = {
309312
if (variance == -1)
310313
tdef.symbol.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_fromAboveAnnot.typeRef)).withSpan(tdef.span)))
311314
val bindingType = getBinding(tdef.symbol).symbol.typeRef
312315
val bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil)
313-
val bindName = tdef.name.toString.stripPrefix("$").toTermName
314-
val sym = newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span, flags = ImplicitTerm)(using ctx0)
316+
val sym = newPatternBoundSymbol(nameOfSyntheticGiven, bindingTypeTpe, tdef.span, flags = ImplicitTerm)(using ctx0)
315317
buff += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
316318
super.transform(tdef)
317319
}

tests/neg-macros/quotedPatterns-5.scala

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import scala.quoted._
22
object Test {
3-
def test(x: quoted.Expr[Int])(using QuoteContext) = x match {
4-
case '{ type $t; poly[$t]($x); 4 } => ??? // error: duplicate pattern variable: $t
5-
case '{ type `$t`; poly[`$t`]($x); 4 } =>
6-
val tt: quoted.Type[_] = t // error
7-
???
3+
def test(x: quoted.Expr[Int])(using QuoteContext): Unit = x match {
4+
case '{ type $T; 4 } => Type[T]
5+
case '{ type $T; poly[$T]($x); 4 } => // error: duplicate pattern variable: T
6+
case '{ type `$T`; poly[`$T`]($x); 4 } =>
7+
Type[T] // error
88
case _ =>
99
}
1010

tests/pos-macros/i6997c.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ inline def mcr(x: => Any): Any = ${mcrImpl('x)}
66

77
def mcrImpl(body: Expr[Any])(using ctx: QuoteContext): Expr[Any] =
88
body match
9-
case '{$x: $t} =>
9+
case '{$x: $T} =>
1010
'{
11-
val tmp: $t = $x
11+
val tmp: T = $x
1212
println(tmp)
1313
tmp
1414
}

tests/pos-macros/i7264.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import scala.quoted._
22
class Foo {
33
def f[T2](t: Type[T2])(using QuoteContext) = t match {
4-
case '[ *:[Int, $t] ] =>
5-
'[ *:[Int, $t] ]
4+
case '[ *:[Int, $T] ] =>
5+
'[ *:[Int, T] ]
66
}
77
}

tests/pos-macros/i7264b.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ import scala.quoted._
22
class Foo {
33
def f[T2: Type](e: Expr[T2])(using QuoteContext) = e match {
44
case '{ $x: *:[Int, $t] } =>
5-
'[ *:[Int, $t] ]
5+
'[ *:[Int, t] ]
66
}
77
}

0 commit comments

Comments
 (0)