Skip to content

Fix #10050: Use lowercase pattern variable syntax for quoted patterns #10125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -908,21 +908,26 @@ object desugar {

/** Transforms
*
* <mods> type $T >: Low <: Hi
*
* <mods> type t >: Low <: Hi
* to
*
* @patternType <mods> type $T >: Low <: Hi
*
* if the type is a type splice.
* if the type has a pattern variable name
*/
def quotedPatternTypeDef(tree: TypeDef)(using Context): TypeDef = {
assert(ctx.mode.is(Mode.QuotedPattern))
if (tree.name.startsWith("$") && !tree.isBackquoted) {
if tree.name.isVarPattern && !tree.isBackquoted then
val patternTypeAnnot = New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)
val mods = tree.mods.withAddedAnnotation(patternTypeAnnot)
tree.withMods(mods)
}
else if tree.name.startsWith("$") && !tree.isBackquoted then
report.error(
"""Quoted pattern variable names starting with $ are not suported anymore.
|Use lower cases type pattern name instead.
|""".stripMargin,
tree.srcPos)
tree
else tree
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ object NameKinds {
val PatMatStdBinderName: UniqueNameKind = new UniqueNameKind("x")
val PatMatAltsName: UniqueNameKind = new UniqueNameKind("matchAlts")
val PatMatResultName: UniqueNameKind = new UniqueNameKind("matchResult")
val PatMatVarName: UniqueNameKind = new UniqueNameKind("ev$")
val PatMatGivenVarName: UniqueNameKind = new UniqueNameKind("$given")

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

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ object NameOps {
&& (n != true_)
&& (n != null_))
}
} || name.is(PatMatVarName)
} || name.is(PatMatGivenVarName)

def isOpAssignmentName: Boolean = name match {
case raw.NE | raw.LE | raw.GE | EMPTY =>
Expand Down
88 changes: 46 additions & 42 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import dotty.tools.dotc.core.Constants._
import dotty.tools.dotc.core.Contexts._
import dotty.tools.dotc.core.Decorators._
import dotty.tools.dotc.core.Flags._
import dotty.tools.dotc.core.NameKinds.{UniqueName, PatMatVarName}
import dotty.tools.dotc.core.NameKinds.{UniqueName, PatMatGivenVarName}
import dotty.tools.dotc.core.Names._
import dotty.tools.dotc.core.StagingContext._
import dotty.tools.dotc.core.StdNames._
Expand All @@ -37,7 +37,7 @@ trait QuotesAndSplices {

import tpd._

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

if ctx.mode.is(Mode.QuotedPattern) && level == 1 then
def spliceOwner(ctx: Context): Symbol =
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
val (name, expr) = tree.expr match {
case Ident(name) =>
val nameOfSyntheticGiven = PatMatVarName.fresh()
(name.toTypeName, untpd.cpy.Ident(tree.expr)(nameOfSyntheticGiven))
case expr =>
report.error("expected a name binding", expr.srcPos)
("$error".toTypeName, expr)
}

val typeSymInfo = pt match
case pt: TypeBounds => pt
case _ => TypeBounds.empty
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.expr.span)
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.expr.span)))
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
pat.select(tpnme.Underlying)
report.error(
"""`$` for quote pattern varable is not supported anymore.
|Use lower cased variable name without the `$` instead.""".stripMargin,
tree.srcPos)
ref(defn.NothingType)
else
val tree1 = typedSelect(untpd.Select(tree.expr, tpnme.Underlying), pt)(using spliceContext).withSpan(tree.span)
val msg = em"Consider using canonical type reference ${tree1.tpe} instead"
Expand All @@ -185,6 +171,24 @@ trait QuotesAndSplices {
tree1
}

/** Type a pattern variable name `t` in quote pattern as `${given t$giveni: Type[t @ _]}`.
* The resulting pattern is the split in `splitQuotePattern`.
*/
def typedQuotedTypeVar(tree: untpd.Ident, pt: Type)(using Context): Tree =
def spliceOwner(ctx: Context): Symbol =
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
val name = tree.name.toTypeName
val nameOfSyntheticGiven = PatMatGivenVarName.fresh(tree.name.toTermName)
val expr = untpd.cpy.Ident(tree)(nameOfSyntheticGiven)
val typeSymInfo = pt match
case pt: TypeBounds => pt
case _ => TypeBounds.empty
val typeSym = newSymbol(spliceOwner(ctx), name, EmptyFlags, typeSymInfo, NoSymbol, tree.span)
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_patternTypeAnnot.typeRef)).withSpan(tree.span)))
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
pat.select(tpnme.Underlying)

private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit =
if (level == 0 && !ctx.owner.ownersIterator.exists(_.is(Inline)))
report.error("Splice ${...} outside quotes '{...} or inline method", tree.srcPos)
Expand All @@ -201,17 +205,17 @@ trait QuotesAndSplices {
*
* A quote pattern
* ```
* case '{ type ${given t: Type[$t @ _]}; ${ls: Expr[List[$t]]} } => ...
* case '{ type ${given t$giveni: Type[t @ _]}; ${ls: Expr[List[t]]} } => ...
* ```
* will return
* ```
* (
* Map(<$t>: Symbol -> <$t @ _>: Bind),
* Map(<t$giveni>: Symbol -> <t @ _>: Bind),
* <'{
* @scala.internal.Quoted.patternType type $t
* scala.internal.Quoted.patternHole[List[$t]]
* @scala.internal.Quoted.patternType type t
* scala.internal.Quoted.patternHole[List[t]]
* }>: Tree,
* List(<ls: Expr[List[$t]]>: Tree)
* List(<ls: Expr[List[t]]>: Tree)
* )
* ```
*/
Expand Down Expand Up @@ -278,7 +282,7 @@ trait QuotesAndSplices {
tree
case tdef: TypeDef =>
if tdef.symbol.hasAnnotation(defn.InternalQuotedPatterns_patternTypeAnnot) then
transformTypeBindingTypeDef(PatMatVarName.fresh(), tdef, typePatBuf)
transformTypeBindingTypeDef(PatMatGivenVarName.fresh(tdef.name.toTermName), tdef, typePatBuf)
else if tdef.symbol.isClass then
val kind = if tdef.symbol.is(Module) then "objects" else "classes"
report.error("Implementation restriction: cannot match " + kind, tree.srcPos)
Expand Down Expand Up @@ -358,38 +362,38 @@ trait QuotesAndSplices {
* within the quotes become patterns again and typed accordingly.
*
* ```
* case '{ ($ls: List[$t]) } =>
* // `t` is of type `Type[T$1]` for some unknown T$1
* // `t` is implicitly available
* // `l` is of type `Expr[List[T$1]]`
* case '{ ($ls: List[t]) } =>
* // `t$giveni` is of type `Type[t]` for some unknown `t`
* // `t$giveni` is implicitly available
* // `ls` is of type `Expr[List[t]]`
* '{ val h: $t = $ls.head }
* ```
*
* For each type splice we will create a new type binding in the pattern match ($t @ _ in this case)
* and a corresponding type in the quoted pattern as a hole (@patternType type $t in this case).
* For each type splice we will create a new type binding in the pattern match (`t @ _` in this case)
* and a corresponding type in the quoted pattern as a hole (`@patternType type t` in this case).
* All these generated types are inserted at the start of the quoted code.
*
* After typing the tree will resemble
*
* ```
* case '{ type ${given t: Type[$t @ _]}; ${ls: Expr[List[$t]]} } => ...
* case '{ type ${given t$giveni: Type[t @ _]}; ${ls: Expr[List[t]]} } => ...
* ```
*
* Then the pattern is _split_ into the expression contained in the pattern replacing the splices by holes,
* and the patterns in the splices. All these are recombined into a call to `Matcher.unapply`.
*
* ```
* case scala.internal.quoted.Expr.unapply[
* Tuple1[$t @ _], // Type binging definition
* Tuple2[Type[$t], Expr[List[$t]]] // Typing the result of the pattern match
* Tuple1[t @ _], // Type binging definition
* Tuple2[Type[t], Expr[List[t]]] // Typing the result of the pattern match
* ](
* Tuple2.unapply
* [Type[$t], Expr[List[$t]]] //Propagated from the tuple above
* (implicit t @ _, ls @ _: Expr[List[$t]]) // from the spliced patterns
* [Type[t], Expr[List[t]]] //Propagated from the tuple above
* (given t$giveni @ _, ls @ _: Expr[List[t]]) // from the spliced patterns
* )(
* '{ // Runtime quote Matcher.unapply uses to mach against. Expression directly inside the quoted pattern without the splices
* @scala.internal.Quoted.patternType type $t
* scala.internal.Quoted.patternHole[List[$t]]
* @scala.internal.Quoted.patternType type t
* scala.internal.Quoted.patternHole[List[t]]
* },
* true, // If there is at least one type splice. Used to instantiate the context with or without GADT constraints
* x$2 // tasty.Reflection instance
Expand All @@ -409,7 +413,7 @@ trait QuotesAndSplices {
case _ => defn.AnyType
}
val quoted0 = desugar.quotedPattern(quoted, untpd.TypedSplice(TypeTree(quotedPt)))
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern)
val quoteCtx = quoteContext.addMode(Mode.QuotedPattern).retractMode(Mode.Pattern)
val quoted1 =
if quoted.isType then typedType(quoted0, WildcardType)(using quoteCtx)
else typedExpr(quoted0, WildcardType)(using quoteCtx)
Expand Down
12 changes: 8 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -431,12 +431,16 @@ class Typer extends Namer
val name = tree.name
def kind = if (name.isTermName) "" else "type "
typr.println(s"typed ident $kind$name in ${ctx.owner}")
if (ctx.mode is Mode.Pattern) {
if (name == nme.WILDCARD)
if ctx.mode.is(Mode.Pattern) then
if name == nme.WILDCARD then
return tree.withType(pt)
if (untpd.isVarPattern(tree) && name.isTermName)
if untpd.isVarPattern(tree) && name.isTermName then
return typed(desugar.patternVar(tree), pt)
}
else if ctx.mode.is(Mode.QuotedPattern) then
if untpd.isVarPattern(tree) && name.isTypeName then
return typedQuotedTypeVar(tree, pt)
end if

// Shortcut for the root package, this is not just a performance
// optimization, it also avoids forcing imports thus potentially avoiding
// cyclic references.
Expand Down
14 changes: 7 additions & 7 deletions docs/docs/reference/metaprogramming/macros.md
Original file line number Diff line number Diff line change
Expand Up @@ -703,11 +703,11 @@ Sometimes it is necessary to get a more precise type for an expression. This can
```scala
def f(exp: Expr[Any])(using QuoteContext) =
expr match
case '{ $x: $T } =>
// If the pattern match succeeds, then there is some type `T` such that
// - `x` is bound to a variable of type `Expr[T]`
// - `T` is bound to a new type T and a given instance `Type[T]` is provided for it
// That is, we have `x: Expr[T]` and `given Type[T]`, for some (unknown) type `T`.
case '{ $x: t } =>
// If the pattern match succeeds, then there is some type `t` such that
// - `x` is bound to a variable of type `Expr[t]`
// - `t` is bound to a new type `t` and a given instance `Type[t]` is provided for it
// That is, we have `x: Expr[t]` and `given Type[t]`, for some (unknown) type `t`.
```

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