Skip to content

Commit 6a4f5fa

Browse files
committed
Fix #10050: Use lower pattern variable syntax for quoted patterns
In quoted pattern * Identify names lowered case names as type holes * `case '{ $x: t }`: `t` is type from within the expression * `case '{ $x: T }`/```case '{ $x: `t`}```: `T`/`t` type from outside the pattern * Old `$` prefixed syntax emits an error describing to new syntax
1 parent 7b5cdba commit 6a4f5fa

File tree

38 files changed

+205
-166
lines changed

38 files changed

+205
-166
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -908,21 +908,26 @@ object desugar {
908908

909909
/** Transforms
910910
*
911-
* <mods> type $T >: Low <: Hi
912-
*
911+
* <mods> type t >: Low <: Hi
913912
* to
914913
*
915914
* @patternType <mods> type $T >: Low <: Hi
916915
*
917-
* if the type is a type splice.
916+
* if the type has a pattern variable name
918917
*/
919918
def quotedPatternTypeDef(tree: TypeDef)(using Context): TypeDef = {
920919
assert(ctx.mode.is(Mode.QuotedPattern))
921-
if (tree.name.startsWith("$") && !tree.isBackquoted) {
920+
if tree.name.isVarPattern && !tree.isBackquoted then
922921
val patternTypeAnnot = New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)
923922
val mods = tree.mods.withAddedAnnotation(patternTypeAnnot)
924923
tree.withMods(mods)
925-
}
924+
else if tree.name.startsWith("$") && !tree.isBackquoted then
925+
report.error(
926+
"""Quoted pattern variable names starting with $ are not suported anymore.
927+
|Use lower cases type pattern name instead.
928+
|""".stripMargin,
929+
tree.srcPos)
930+
tree
926931
else tree
927932
}
928933

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +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 PatMatVarName: UniqueNameKind = new UniqueNameKind("ev$")
323+
val PatMatVarName: UniqueNameKind = new UniqueNameKind("$given")
324324

325325
val LocalOptInlineLocalObj: UniqueNameKind = new UniqueNameKind("ilo")
326326

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

Lines changed: 42 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ trait QuotesAndSplices {
3737

3838
import tpd._
3939

40-
/** Translate `'{ t }` into `scala.quoted.Expr.apply(t)` and `'[T]` into `scala.quoted.Type.apply[T]`
40+
/** Translate `'{ e }` into `scala.quoted.Expr.apply(e)` and `'[T]` into `scala.quoted.Type.apply[T]`
4141
* while tracking the quotation level in the context.
4242
*/
4343
def typedQuote(tree: untpd.Quote, pt: Type)(using Context): Tree = {
@@ -83,7 +83,7 @@ trait QuotesAndSplices {
8383
def spliceOwner(ctx: Context): Symbol =
8484
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
8585
val pat = typedPattern(tree.expr, defn.QuotedExprClass.typeRef.appliedTo(pt))(
86-
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
86+
using spliceContext.retractMode(Mode.QuotedPattern).addMode(Mode.Pattern).withOwner(spliceOwner(ctx)))
8787
val baseType = pat.tpe.baseType(defn.QuotedExprClass)
8888
val argType = if baseType != NoType then baseType.argTypesHi.head else defn.NothingType
8989
ref(defn.InternalQuoted_exprSplice).appliedToType(argType).appliedTo(pat)
@@ -158,25 +158,11 @@ trait QuotesAndSplices {
158158
}
159159

160160
if ctx.mode.is(Mode.QuotedPattern) && level == 1 then
161-
def spliceOwner(ctx: Context): Symbol =
162-
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
163-
val (name, expr) = tree.expr match {
164-
case Ident(name) =>
165-
val nameOfSyntheticGiven = PatMatVarName.fresh()
166-
(name.toTypeName, untpd.cpy.Ident(tree.expr)(nameOfSyntheticGiven))
167-
case expr =>
168-
report.error("expected a name binding", expr.srcPos)
169-
("$error".toTypeName, expr)
170-
}
171-
172-
val typeSymInfo = pt match
173-
case pt: TypeBounds => pt
174-
case _ => TypeBounds.empty
175-
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.expr.span)
176-
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.expr.span)))
177-
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
178-
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
179-
pat.select(tpnme.Underlying)
161+
report.error(
162+
"""`$` for quote pattern varable is not supported anymore.
163+
|Use lower cased variable name without the `$` instead.""".stripMargin,
164+
tree.srcPos)
165+
ref(defn.NothingType)
180166
else
181167
val tree1 = typedSelect(untpd.Select(tree.expr, tpnme.Underlying), pt)(using spliceContext).withSpan(tree.span)
182168
val msg = em"Consider using canonical type reference ${tree1.tpe} instead"
@@ -185,6 +171,21 @@ trait QuotesAndSplices {
185171
tree1
186172
}
187173

174+
def typedQuotedTypeVar(tree: untpd.Ident, pt: Type)(using Context): Tree =
175+
def spliceOwner(ctx: Context): Symbol =
176+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
177+
val name = tree.name.toTypeName
178+
val nameOfSyntheticGiven = PatMatVarName.fresh(tree.name.toTermName)
179+
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
180+
val typeSymInfo = pt match
181+
case pt: TypeBounds => pt
182+
case _ => TypeBounds.empty
183+
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
184+
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
185+
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
186+
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
187+
pat.select(tpnme.Underlying)
188+
188189
private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit =
189190
if (level == 0 && !ctx.owner.ownersIterator.exists(_.is(Inline)))
190191
report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos)
@@ -201,17 +202,17 @@ trait QuotesAndSplices {
201202
*
202203
* A quote pattern
203204
* ```
204-
* case '{ type ${given t: Type[$t @ _]}; ${ls: Expr[List[$t]]} } => ...
205+
* case '{ type ${given t$giveni: Type[t @ _]}; ${ls: Expr[List[t]]} } => ...
205206
* ```
206207
* will return
207208
* ```
208209
* (
209-
* Map(<$t>: Symbol -> <$t @ _>: Bind),
210+
* Map(<t$giveni>: Symbol -> <t @ _>: Bind),
210211
* <'{
211-
* @scala.internal.Quoted.patternType type $t
212-
* scala.internal.Quoted.patternHole[List[$t]]
212+
* @scala.internal.Quoted.patternType type t
213+
* scala.internal.Quoted.patternHole[List[t]]
213214
* }>: Tree,
214-
* List(<ls: Expr[List[$t]]>: Tree)
215+
* List(<ls: Expr[List[t]]>: Tree)
215216
* )
216217
* ```
217218
*/
@@ -278,7 +279,7 @@ trait QuotesAndSplices {
278279
tree
279280
case tdef: TypeDef =>
280281
if tdef.symbol.hasAnnotation(defn.InternalQuotedPatterns_patternTypeAnnot) then
281-
transformTypeBindingTypeDef(PatMatVarName.fresh(), tdef, typePatBuf)
282+
transformTypeBindingTypeDef(PatMatVarName.fresh(tdef.name.toTermName), tdef, typePatBuf)
282283
else if tdef.symbol.isClass then
283284
val kind = if tdef.symbol.is(Module) then "objects" else "classes"
284285
report.error("Implementation restriction: cannot match " + kind, tree.srcPos)
@@ -358,38 +359,38 @@ trait QuotesAndSplices {
358359
* within the quotes become patterns again and typed accordingly.
359360
*
360361
* ```
361-
* case '{ ($ls: List[$t]) } =>
362-
* // `t` is of type `Type[T$1]` for some unknown T$1
363-
* // `t` is implicitly available
364-
* // `l` is of type `Expr[List[T$1]]`
362+
* case '{ ($ls: List[t]) } =>
363+
* // `t$giveni` is of type `Type[t]` for some unknown `t`
364+
* // `t$giveni` is implicitly available
365+
* // `ls` is of type `Expr[List[t]]`
365366
* '{ val h: $t = $ls.head }
366367
* ```
367368
*
368-
* For each type splice we will create a new type binding in the pattern match ($t @ _ in this case)
369-
* and a corresponding type in the quoted pattern as a hole (@patternType type $t in this case).
369+
* For each type splice we will create a new type binding in the pattern match (`t @ _` in this case)
370+
* and a corresponding type in the quoted pattern as a hole (`@patternType type t` in this case).
370371
* All these generated types are inserted at the start of the quoted code.
371372
*
372373
* After typing the tree will resemble
373374
*
374375
* ```
375-
* case '{ type ${given t: Type[$t @ _]}; ${ls: Expr[List[$t]]} } => ...
376+
* case '{ type ${given t$giveni: Type[t @ _]}; ${ls: Expr[List[t]]} } => ...
376377
* ```
377378
*
378379
* Then the pattern is _split_ into the expression contained in the pattern replacing the splices by holes,
379380
* and the patterns in the splices. All these are recombined into a call to `Matcher.unapply`.
380381
*
381382
* ```
382383
* case scala.internal.quoted.Expr.unapply[
383-
* Tuple1[$t @ _], // Type binging definition
384-
* Tuple2[Type[$t], Expr[List[$t]]] // Typing the result of the pattern match
384+
* Tuple1[t @ _], // Type binging definition
385+
* Tuple2[Type[t], Expr[List[t]]] // Typing the result of the pattern match
385386
* ](
386387
* Tuple2.unapply
387-
* [Type[$t], Expr[List[$t]]] //Propagated from the tuple above
388-
* (implicit t @ _, ls @ _: Expr[List[$t]]) // from the spliced patterns
388+
* [Type[t], Expr[List[t]]] //Propagated from the tuple above
389+
* (given t$giveni @ _, ls @ _: Expr[List[t]]) // from the spliced patterns
389390
* )(
390391
* '{ // Runtime quote Matcher.unapply uses to mach against. Expression directly inside the quoted pattern without the splices
391-
* @scala.internal.Quoted.patternType type $t
392-
* scala.internal.Quoted.patternHole[List[$t]]
392+
* @scala.internal.Quoted.patternType type t
393+
* scala.internal.Quoted.patternHole[List[t]]
393394
* },
394395
* true, // If there is at least one type splice. Used to instantiate the context with or without GADT constraints
395396
* x$2 // tasty.Reflection instance
@@ -409,7 +410,7 @@ trait QuotesAndSplices {
409410
case _ => defn.AnyType
410411
}
411412
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
412-
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern)
413+
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
413414
val quoted1 =
414415
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx)
415416
else typedExpr(quoted0, WildcardType)(using quoteCtx)

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -438,12 +438,16 @@ class Typer extends Namer
438438
val name = tree.name
439439
def kind = if (name.isTermName) "" else "type "
440440
typr.println(s"typed ident $kind$name in ${ctx.owner}")
441-
if (ctx.mode is Mode.Pattern) {
442-
if (name == nme.WILDCARD)
441+
if ctx.mode.is(Mode.Pattern) then
442+
if name == nme.WILDCARD then
443443
return tree.withType(pt)
444-
if (untpd.isVarPattern(tree) && name.isTermName)
444+
if untpd.isVarPattern(tree) && name.isTermName then
445445
return typed(desugar.patternVar(tree), pt)
446-
}
446+
else if ctx.mode.is(Mode.QuotedPattern) then
447+
if untpd.isVarPattern(tree) && name.isTypeName then
448+
return typedQuotedTypeVar(tree, pt)
449+
end if
450+
447451
// Shortcut for the root package, this is not just a performance
448452
// optimization, it also avoids forcing imports thus potentially avoiding
449453
// cyclic references.

docs/docs/reference/metaprogramming/macros.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -703,11 +703,11 @@ Sometimes it is necessary to get a more precise type for an expression. This can
703703
```scala
704704
def f(exp: Expr[Any])(using QuoteContext) =
705705
expr match
706-
case '{ $x: $T } =>
707-
// If the pattern match succeeds, then there is some type `T` such that
708-
// - `x` is bound to a variable of type `Expr[T]`
709-
// - `T` is bound to a new type T and a given instance `Type[T]` is provided for it
710-
// That is, we have `x: Expr[T]` and `given Type[T]`, for some (unknown) type `T`.
706+
case '{ $x: t } =>
707+
// If the pattern match succeeds, then there is some type `t` such that
708+
// - `x` is bound to a variable of type `Expr[t]`
709+
// - `t` is bound to a new type `t` and a given instance `Type[t]` is provided for it
710+
// That is, we have `x: Expr[t]` and `given Type[t]`, for some (unknown) type `t`.
711711
```
712712

713713
This might be used to then perform an implicit search as in:
@@ -720,8 +720,8 @@ private def showMeExpr(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]])(using
720720
argsExpr match {
721721
case Varargs(argExprs) =>
722722
val argShowedExprs = argExprs.map {
723-
case '{ $arg: $T } =>
724-
Expr.summon[Show[T]] match {
723+
case '{ $arg: t } =>
724+
Expr.summon[Show[t]] match {
725725
case Some(showExpr) => '{ $showExpr.show($arg) }
726726
case None => Reporting.error(s"could not find implicit for ${showTp.show}", arg); '{???}
727727
}

0 commit comments

Comments
 (0)