Skip to content

Make type of type hole available in quoted patterns #10045

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 3 commits into from
Oct 22, 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
2 changes: 1 addition & 1 deletion community-build/community-projects/utest
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>

/** Is tree a variable pattern? */
def isVarPattern(pat: Tree): Boolean = unsplice(pat) match {
case x: Ident => x.name.isVariableName && !isBackquoted(x)
case x: Ident => x.name.isVarPattern && !isBackquoted(x)
case _ => false
}

Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/NameKinds.scala
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +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 LocalOptInlineLocalObj: UniqueNameKind = new UniqueNameKind("ilo")

Expand Down
21 changes: 11 additions & 10 deletions compiler/src/dotty/tools/dotc/core/NameOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,17 @@ object NameOps {
case name: SimpleName => name.exists(isOperatorPart)
case _ => false

/** Is name a variable name? */
def isVariableName: Boolean = testSimple { n =>
n.length > 0 && {
val first = n.head
(((first.isLower && first.isLetter) || first == '_')
&& (n != false_)
&& (n != true_)
&& (n != null_))
}
}
/** Is name of a variable pattern? */
def isVarPattern: Boolean =
testSimple { n =>
n.length > 0 && {
val first = n.head
(((first.isLower && first.isLetter) || first == '_')
&& (n != false_)
&& (n != true_)
&& (n != null_))
}
} || name.is(PatMatVarName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The renaming makes everything much more clear 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. The scalability concern made me realize that it was just a misnomer.


def isOpAssignmentName: Boolean = name match {
case raw.NE | raw.LE | raw.GE | EMPTY =>
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/parsing/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2594,7 +2594,7 @@ object Parsers {
val givenMod = atSpan(in.skipToken())(Mod.Given())
atSpan(in.offset) {
in.token match {
case IDENTIFIER | USCORE if in.name.isVariableName =>
case IDENTIFIER | USCORE if in.name.isVarPattern =>
val name = in.name
in.nextToken()
accept(COLON)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ object PatternMatcher {
case Typed(_, tpt) if tpt.tpe.isRepeatedParam => true
case Bind(nme.WILDCARD, WildcardPattern()) => true // don't skip when binding an interesting symbol!
case t if isWildcardArg(t) => true
case x: Ident => x.name.isVariableName && !isBackquoted(x)
case x: Ident => x.name.isVarPattern && !isBackquoted(x)
case Alternative(ps) => ps.forall(unapply)
case EmptyTree => true
case _ => false
Expand Down
24 changes: 13 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,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
import dotty.tools.dotc.core.NameKinds.{UniqueName, PatMatVarName}
import dotty.tools.dotc.core.Names._
import dotty.tools.dotc.core.StagingContext._
import dotty.tools.dotc.core.StdNames._
Expand Down Expand Up @@ -156,19 +156,21 @@ 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 = tree.expr match {
case Ident(name) => ("$" + name).toTypeName
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
("$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(tree.expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
val pat = typedPattern(expr, defn.QuotedTypeClass.typeRef.appliedTo(typeSym.typeRef))(
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
pat.select(tpnme.spliceType)
else
Expand Down Expand Up @@ -214,7 +216,7 @@ trait QuotesAndSplices {
def getBinding(sym: Symbol): Bind =
typeBindings.getOrElseUpdate(sym, {
val bindingBounds = sym.info
val bsym = newPatternBoundSymbol(sym.name.toTypeName, bindingBounds, quoted.span)
val bsym = newPatternBoundSymbol(sym.name.toString.stripPrefix("$").toTypeName, bindingBounds, quoted.span)
Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span)
})

Expand Down Expand Up @@ -263,13 +265,14 @@ trait QuotesAndSplices {
val sym = tree.tpe.dealias.typeSymbol
if sym.exists then
val tdef = TypeDef(sym.asType).withSpan(sym.span)
freshTypeBindingsBuff += transformTypeBindingTypeDef(tdef, freshTypePatBuf)
val nameOfSyntheticGiven = pat.symbol.name.toTermName
freshTypeBindingsBuff += transformTypeBindingTypeDef(nameOfSyntheticGiven, tdef, freshTypePatBuf)
TypeTree(tree.tpe.dealias).withSpan(tree.span)
else
tree
case tdef: TypeDef =>
if tdef.symbol.hasAnnotation(defn.InternalQuotedPatterns_patternTypeAnnot) then
transformTypeBindingTypeDef(tdef, typePatBuf)
transformTypeBindingTypeDef(PatMatVarName.fresh(), 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 @@ -305,13 +308,12 @@ trait QuotesAndSplices {
super.transform(tree)
}

private def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(using Context): Tree = {
private def transformTypeBindingTypeDef(nameOfSyntheticGiven: TermName, tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(using Context): Tree = {
if (variance == -1)
tdef.symbol.addAnnotation(Annotation(New(ref(defn.InternalQuotedPatterns_fromAboveAnnot.typeRef)).withSpan(tdef.span)))
val bindingType = getBinding(tdef.symbol).symbol.typeRef
val bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil)
val bindName = tdef.name.toString.stripPrefix("$").toTermName
val sym = newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span, flags = ImplicitTerm)(using ctx0)
val sym = newPatternBoundSymbol(nameOfSyntheticGiven, bindingTypeTpe, tdef.span, flags = ImplicitTerm)(using ctx0)
buff += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
super.transform(tdef)
}
Expand Down
10 changes: 5 additions & 5 deletions tests/neg-macros/quotedPatterns-5.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import scala.quoted._
object Test {
def test(x: quoted.Expr[Int])(using QuoteContext) = x match {
case '{ type $t; poly[$t]($x); 4 } => ??? // error: duplicate pattern variable: $t
case '{ type `$t`; poly[`$t`]($x); 4 } =>
val tt: quoted.Type[_] = t // error
???
def test(x: quoted.Expr[Int])(using QuoteContext): Unit = x match {
case '{ type $T; 4 } => Type[T]
case '{ type $T; poly[$T]($x); 4 } => // error: duplicate pattern variable: T
case '{ type `$T`; poly[`$T`]($x); 4 } =>
Type[T] // error
case _ =>
}

Expand Down
4 changes: 2 additions & 2 deletions tests/pos-macros/i6997c.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ inline def mcr(x: => Any): Any = ${mcrImpl('x)}

def mcrImpl(body: Expr[Any])(using ctx: QuoteContext): Expr[Any] =
body match
case '{$x: $t} =>
case '{$x: $T} =>
'{
val tmp: $t = $x
val tmp: T = $x
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not as symmetric as before. I think meta-programmers will be at a loss about whether to use $ or not before a type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not symmetric because it is not intended to be as quoted types do not follow the same level symmetry that expression do. The syntax $ for types won't exist outside of patterns and hence they will have a single option and won't be able to get confused about this at the use site. The use of $ in the pattern is its own issue (see #10050) which we will address separately. Here we focus on the use sites and keep the changes to the patterns to a minimum.

println(tmp)
tmp
}
4 changes: 2 additions & 2 deletions tests/pos-macros/i7264.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import scala.quoted._
class Foo {
def f[T2](t: Type[T2])(using QuoteContext) = t match {
case '[ *:[Int, $t] ] =>
'[ *:[Int, $t] ]
case '[ *:[Int, $T] ] =>
'[ *:[Int, T] ]
}
}
2 changes: 1 addition & 1 deletion tests/pos-macros/i7264b.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ import scala.quoted._
class Foo {
def f[T2: Type](e: Expr[T2])(using QuoteContext) = e match {
case '{ $x: *:[Int, $t] } =>
'[ *:[Int, $t] ]
'[ *:[Int, t] ]
}
}
8 changes: 4 additions & 4 deletions tests/pos-macros/i7264c.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import scala.quoted._
class Foo {
def f[T2: Type](e: Expr[T2])(using QuoteContext) = e match {
case '{ $x: $t0 } =>
t0 match
case '[ *:[Int, $t] ] =>
'[ *:[Int, $t] ]
case '{ $x: $T0 } =>
Type[T0] match
case '[ *:[Int, $T] ] =>
'[ *:[Int, T] ]
}
}
10 changes: 5 additions & 5 deletions tests/pos-macros/tasty-constant-type/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ object Macro {

trait AddInt[A <: Int, B <: Int] { type Out <: Int }

transparent inline def ff[A <: Int, B <: Int](): AddInt[A, B] = ${ impl('[A], '[B]) }
transparent inline def ff[A <: Int, B <: Int](): AddInt[A, B] = ${ impl[A, B] }

def impl[A <: Int : Type, B <: Int : Type](a: Type[A], b: Type[B])(using qctx: QuoteContext) : Expr[AddInt[A, B]] = {
def impl[A <: Int : Type, B <: Int : Type](using qctx: QuoteContext) : Expr[AddInt[A, B]] = {
import qctx.reflect._

val ConstantType(Constant.Int(v1)) = a.unseal.tpe
val ConstantType(Constant.Int(v2)) = b.unseal.tpe
val ConstantType(Constant.Int(v1)) = Type.of[A]
val ConstantType(Constant.Int(v2)) = Type.of[B]

Literal(Constant.Int(v1 + v2)).tpe.seal match
case '[$t] => '{ null: AddInt[$a, $b] { type Out = $t } }
case '[$T] => '{ null: AddInt[A, B] { type Out = T } }
}
}
4 changes: 2 additions & 2 deletions tests/run-macros/flops-rewrite-3/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class CheckedTransformation(transform: PartialFunction[Expr[Any], Expr[Any]]) ex
def apply[T: Type](e: Expr[T])(using QuoteContext): Expr[T] = {
transform.applyOrElse(e, identity) match {
case '{ $e2: T } => e2
case '{ $e2: $t } =>
case '{ $e2: $T } =>
throw new Exception(
s"""Transformed
|${e.show}
Expand All @@ -69,7 +69,7 @@ class CheckedTransformation(transform: PartialFunction[Expr[Any], Expr[Any]]) ex
|Expected type to be
|${summon[Type[T]].show}
|but was
|${t.show}
|${Type[T].show}
""".stripMargin)
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/flops-rewrite/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private class Rewriter(preTransform: Expr[Any] => Expr[Any], postTransform: Expr
|Expected type to be
|${summon[Type[T]].show}
|but was
|${t.show}
|${Type[t].show}
""".stripMargin)
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/run-macros/i7987/Macros_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ object Macros {

def macroImpl[T]()(using qctx: QuoteContext): Expr[String] = {
Expr.summon[Mirror.Of[Some[Int]]] match
case Some('{ $_ : $t }) => Expr(t.show)
case Some('{ $_ : $T }) => Expr(Type[T].show)
}
}
10 changes: 5 additions & 5 deletions tests/run-macros/i8007/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import scala.quoted._

object Macro1 {

def mirrorFields[T](t: Type[T])(using qctx: QuoteContext): List[String] =
t match {
case '[$field *: $fields] => field.show :: mirrorFields(fields)
def mirrorFields[T: Type](using qctx: QuoteContext): List[String] =
Type[T] match {
case '[$Field *: $Fields] => Type[Field].show :: mirrorFields[Fields]
case '[EmptyTuple] => Nil
}

Expand All @@ -22,8 +22,8 @@ object Macro1 {
val mirrorTpe = '[Mirror.Of[T]]

Expr.summon(using mirrorTpe).get match {
case '{ $m: Mirror.ProductOf[T]{ type MirroredElemLabels = $t } } => {
Expr(mirrorFields(t))
case '{ $m: Mirror.ProductOf[T]{ type MirroredElemLabels = $Elems } } => {
Expr(mirrorFields[Elems])
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions tests/run-macros/i8007/Macro_2.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import scala.quoted._

object Macro2 {

def mirrorFields[T](t: Type[T])(using qctx: QuoteContext): List[String] =
def mirrorFields[T](using t: Type[T])(using qctx: QuoteContext): List[String] =
t match {
case '[$field *: $fields] => field.show.substring(1, field.show.length-1) :: mirrorFields(fields)
case '[$Field *: $Fields] => Type[Field].show.substring(1, Type[Field].show.length-1) :: mirrorFields[Fields]
case '[EmptyTuple] => Nil
}

Expand All @@ -24,8 +24,8 @@ object Macro2 {
import qctx.reflect._

val fields = ev match {
case '{ $m: Mirror.ProductOf[T] { type MirroredElemLabels = $t } } =>
mirrorFields(t)
case '{ $m: Mirror.ProductOf[T] { type MirroredElemLabels = $Labels } } =>
mirrorFields[Labels]
}

val body: Expr[T] => Expr[String] = elem =>
Expand Down
16 changes: 8 additions & 8 deletions tests/run-macros/i8007/Macro_3.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ object Eq {
def eqv(x: T, y: T): Boolean = body(x, y)
}

def summonAll[T](t: Type[T])(using qctx: QuoteContext): List[Expr[Eq[_]]] = t match {
case '[String *: $tpes] => '{ summon[Eq[String]] } :: summonAll(tpes)
case '[Int *: $tpes] => '{ summon[Eq[Int]] } :: summonAll(tpes)
case '[$tpe *: $tpes] => derived(using tpe, qctx) :: summonAll(tpes)
def summonAll[T](using t: Type[T])(using qctx: QuoteContext): List[Expr[Eq[_]]] = t match {
case '[String *: $Tpes] => '{ summon[Eq[String]] } :: summonAll[Tpes]
case '[Int *: $Tpes] => '{ summon[Eq[Int]] } :: summonAll[Tpes]
case '[$Tpe *: $Tpes] => derived[Tpe] :: summonAll[Tpes]
case '[EmptyTuple] => Nil
}

Expand All @@ -38,8 +38,8 @@ object Eq {
val ev: Expr[Mirror.Of[T]] = Expr.summon(using '[Mirror.Of[T]]).get

ev match {
case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $elementTypes }} =>
val elemInstances = summonAll(elementTypes)
case '{ $m: Mirror.ProductOf[T] { type MirroredElemTypes = $ElementTypes }} =>
val elemInstances = summonAll[ElementTypes]
val eqProductBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => {
elemInstances.zipWithIndex.foldLeft(Expr(true: Boolean)) {
case (acc, (elem, index)) =>
Expand All @@ -53,8 +53,8 @@ object Eq {
eqProduct((x: T, y: T) => ${eqProductBody('x, 'y)})
}

case '{ $m: Mirror.SumOf[T] { type MirroredElemTypes = $elementTypes }} =>
val elemInstances = summonAll(elementTypes)
case '{ $m: Mirror.SumOf[T] { type MirroredElemTypes = $ElementTypes }} =>
val elemInstances = summonAll[ElementTypes]
val eqSumBody: (Expr[T], Expr[T]) => Expr[Boolean] = (x, y) => {
val ordx = '{ $m.ordinal($x) }
val ordy = '{ $m.ordinal($y) }
Expand Down
14 changes: 7 additions & 7 deletions tests/run-macros/quote-matcher-symantics-3/quoted_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ object Macros {
object FromEnv {
def unapply[T](e: Expr[Any])(using env: Env): Option[Expr[R[T]]] =
e match
case '{envVar[$t](${Const(id)})} =>
case '{envVar[$_](${Const(id)})} =>
env.get(id).asInstanceOf[Option[Expr[R[T]]]] // We can only add binds that have the same type as the refs
case _ =>
None
Expand All @@ -39,11 +39,11 @@ object Macros {
case '{ ($x: Int) <= ($y: Int) } =>
'{ $sym.leq(${lift(x)}, ${lift(y)}).asInstanceOf[R[T]] }

case '{ ${f}($arg: $t): $u } =>
'{ $sym.app[$t, $u](${lift(f)}, ${lift(arg)}).asInstanceOf[R[T]] }
case '{ ${f}($arg: $A): $B } =>
'{ $sym.app[A, B](${lift(f)}, ${lift(arg)}).asInstanceOf[R[T]] }

case '{ (if ($cond) $thenp else $elsep): $t } =>
'{ $sym.ifThenElse[$t](${lift(cond)}, ${lift(thenp)}, ${lift(elsep)}) }.asInstanceOf[Expr[R[T]]]
case '{ (if ($cond) $thenp else $elsep): $A } =>
'{ $sym.ifThenElse[A](${lift(cond)}, ${lift(thenp)}, ${lift(elsep)}) }.asInstanceOf[Expr[R[T]]]

case '{ (x0: Int) => $bodyFn(x0): Any } =>
val (i, nEnvVar) = freshEnvVar[Int]()
Expand All @@ -60,8 +60,8 @@ object Macros {
val body2 = UnsafeExpr.open(bodyFn) { (body1, close) => close(body1)(nEnvVar) }
'{ $sym.lam((x: R[Int => Int]) => ${given Env = envWith(i, 'x)(using env); lift(body2)}).asInstanceOf[R[T]] }

case '{ Symantics.fix[$t, $u]($f) } =>
'{ $sym.fix[$t, $u]((x: R[$t => $u]) => $sym.app(${lift(f)}, x)).asInstanceOf[R[T]] }
case '{ Symantics.fix[$A, $B]($f) } =>
'{ $sym.fix[A, B]((x: R[A => B]) => $sym.app(${lift(f)}, x)).asInstanceOf[R[T]] }

case FromEnv(expr) => expr.asInstanceOf[Expr[R[T]]]

Expand Down
4 changes: 2 additions & 2 deletions tests/run-macros/quote-matcher-type-bind/Macro_1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ object Macros {

private def impl(x: Expr[Unit])(using QuoteContext): Expr[Unit] = {
x match {
case '{ DSL.f[$t]($x) } => '{ DSL.g[$t]($x) }
case '{ DSL.g[$t]($x) } => '{ DSL.f[$t]($x) }
case '{ DSL.f[$T]($x) } => '{ DSL.g[T]($x) }
case '{ DSL.g[$T]($x) } => '{ DSL.f[T]($x) }
case _ => x
}
}
Expand Down
Loading