Skip to content

Commit ccd2222

Browse files
committed
WIP Type quoted patterns with type splices
1 parent 39e021f commit ccd2222

File tree

13 files changed

+247
-13
lines changed

13 files changed

+247
-13
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,24 @@ object desugar {
837837
Thicket(aliasType :: companions.toList)
838838
}
839839

840+
/** Transforms
841+
*
842+
* <mods> type $T >: Low <: Hi
843+
*
844+
* to
845+
*
846+
* @patternBindHole <mods> type $T >: Low <: Hi
847+
*
848+
* if the type is a type splice.
849+
*/
850+
def quotedPatternTypeDef(tree: TypeDef)(implicit ctx: Context): TypeDef = {
851+
assert(ctx.mode.is(Mode.QuotedPattern))
852+
if (tree.name.startsWith("$") /* && !tree.isBackQuoted*/) { // TODO add backquoted TypeDef
853+
val mods = tree.mods.withAddedAnnotation(New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(tree.span))
854+
tree.withMods(mods)
855+
} else tree
856+
}
857+
840858
/** The normalized name of `mdef`. This means
841859
* 1. Check that the name does not redefine a Scala core class.
842860
* If it does redefine, issue an error and return a mangled name instead of the original one.
@@ -1003,6 +1021,7 @@ object desugar {
10031021
case tree: TypeDef =>
10041022
if (tree.isClassDef) classDef(tree)
10051023
else if (tree.mods.is(Opaque, butNot = Synthetic)) opaqueAlias(tree)
1024+
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
10061025
else tree
10071026
case tree: DefDef =>
10081027
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1181,7 +1181,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11811181
/** An extractor for typed splices */
11821182
object Splice {
11831183
def apply(tree: Tree)(implicit ctx: Context): Tree = {
1184-
val baseType = tree.tpe.baseType(defn.QuotedExprClass)
1184+
val baseType = tree.tpe.baseType(defn.QuotedExprClass).orElse(tree.tpe.baseType(defn.QuotedTypeClass))
11851185
val argType =
11861186
if (baseType != NoType) baseType.argTypesHi.head
11871187
else {
@@ -1318,6 +1318,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13181318
}
13191319
}
13201320

1321+
/** Creates the tuple type tree repesentation of the type trees in `ts` */
1322+
def tupleTypeTree(elems: List[Tree])(implicit ctx: Context): Tree = {
1323+
val arity = elems.length
1324+
if (arity <= Definitions.MaxTupleArity && defn.TupleType(arity) != null) AppliedTypeTree(TypeTree(defn.TupleType(arity)), elems)
1325+
else nestedPairsType(elems)
1326+
}
1327+
1328+
/** Creates the nested pairs type tree repesentation of the type trees in `ts` */
1329+
def nestedPairsType(ts: List[Tree])(implicit ctx: Context): Tree =
1330+
ts.foldRight[Tree](TypeTree(defn.UnitType))((x, acc) => AppliedTypeTree(TypeTree(defn.PairType), x :: acc :: Nil))
1331+
13211332
/** Replaces all positions in `tree` with zero-extent positions */
13221333
private def focusPositions(tree: Tree)(implicit ctx: Context): Tree = {
13231334
val transformer = new tpd.TreeMap {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,7 @@ class Definitions {
723723
lazy val InternalQuoted_patternHoleR: TermRef = InternalQuotedModule.requiredMethodRef("patternHole")
724724
def InternalQuoted_patternHole(implicit ctx: Context): Symbol = InternalQuoted_patternHoleR.symbol
725725
lazy val InternalQuoted_patternBindHoleAnnot: ClassSymbol = InternalQuotedModule.requiredClass("patternBindHole")
726+
lazy val InternalQuoted_patternTypeHole: Symbol = InternalQuotedModule.requiredType("patternTypeHole")
726727

727728
lazy val InternalQuotedMatcherModuleRef: TermRef = ctx.requiredModuleRef("scala.internal.quoted.Matcher")
728729
def InternalQuotedMatcherModule(implicit ctx: Context): Symbol = InternalQuotedMatcherModuleRef.symbol

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
584584
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
585585
case Splice(tree) =>
586586
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
587+
case TypSplice(tree) =>
588+
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
587589
case tree: Applications.IntegratedTypeArgs =>
588590
toText(tree.app) ~ Str("(with integrated type args)").provided(ctx.settings.YprintDebug.value)
589591
case Thicket(trees) =>

compiler/src/dotty/tools/dotc/transform/PCPCheckAndHeal.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ class PCPCheckAndHeal(@constructorOnly ictx: Context) extends TreeMapWithStages(
150150
/** Is a reference to a class but not `this.type` */
151151
def isClassRef = sym.isClass && !tp.isInstanceOf[ThisType]
152152

153-
if (sym.exists && !sym.isStaticOwner && !isClassRef && !levelOK(sym))
153+
if (sym.exists && !sym.isStaticOwner && !isClassRef && !levelOK(sym) &&
154+
!sym.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) // FIXME this is a workaround
155+
)
154156
tryHeal(sym, tp, pos)
155157
else
156158
None

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

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,11 +1921,27 @@ class Typer extends Namer
19211921
val exprPt = pt.baseType(defn.QuotedExprClass)
19221922
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
19231923
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
1924-
val (shape, splices) = splitQuotePattern(quoted1)
1924+
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
1925+
// val typeBindings = splices.collect {
1926+
// case t if t.tpe.derivesFrom(defn.QuotedTypeClass) =>
1927+
// t.tpe.widen.argTypesHi.head.typeSymbol
1928+
// }
1929+
// val inQuoteTypeBinding = typeBindings.map { sym =>
1930+
// ctx.newSymbol(sym.owner, (sym.name + "$$$").toTypeName, // TODO remove $$$, just there for debugging
1931+
// EmptyFlags, sym.info, coord = sym.coord)
1932+
// }
1933+
// val shape2 =
1934+
// seq(inQuoteTypeBinding.map(TypeDef), shape.subst(typeBindings, inQuoteTypeBinding))
1935+
1936+
19251937
val patType = defn.tupleType(splices.tpes.map(_.widen))
1938+
1939+
val typeBindingsTuple = tpd.tupleTypeTree(typeBindings)
1940+
19261941
val splicePat = typed(untpd.Tuple(splices.map(untpd.TypedSplice(_))).withSpan(quoted.span), patType)
1942+
19271943
UnApply(
1928-
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToType(patType),
1944+
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
19291945
implicits =
19301946
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
19311947
implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil,
@@ -1937,8 +1953,24 @@ class Typer extends Namer
19371953
}
19381954
}
19391955

1940-
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
1956+
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (List[Bind], Tree, List[Tree]) = {
19411957
val ctx0 = ctx
1958+
1959+
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
1960+
def getBinding(sym: Symbol): Bind =
1961+
typeBindings.getOrElseUpdate(sym, {
1962+
val bindingBounds = TypeBounds.apply(defn.NothingType, defn.AnyType) // TODO recover bounds
1963+
val bsym = ctx.newPatternBoundSymbol((sym.name + "$").toTypeName, bindingBounds, quoted.span)
1964+
Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span)
1965+
})
1966+
def replaceTypeBindings = new TypeMap {
1967+
def apply(tp: Type): Type = tp match {
1968+
case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
1969+
getBinding(tp.typeSymbol).symbol.typeRef
1970+
case _ => mapOver(tp)
1971+
}
1972+
}
1973+
19421974
object splitter extends tpd.TreeMap {
19431975
val patBuf = new mutable.ListBuffer[Tree]
19441976
override def transform(tree: Tree)(implicit ctx: Context) = tree match {
@@ -1971,17 +2003,29 @@ class Typer extends Namer
19712003
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingExprTpe)).withSpan(ddef.span)
19722004
}
19732005
super.transform(tree)
2006+
case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
2007+
val bindingType = getBinding(tdef.symbol).symbol.typeRef
2008+
val bindingTypeTpe = AppliedType(defn.QuotedTypeType, bindingType :: Nil)
2009+
assert(tdef.name.startsWith("$"))
2010+
val bindName = tdef.name.toString.stripPrefix("$").toTermName
2011+
val sym = ctx0.newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span)
2012+
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
2013+
super.transform(tree)
19742014
case _ =>
19752015
super.transform(tree)
19762016
}
19772017
}
19782018
val result = splitter.transform(quoted)
1979-
(result, splitter.patBuf.toList)
2019+
val patterns = splitter.patBuf.toList
2020+
(typeBindings.toList.map(_._2), result, patterns)
19802021
}
19812022

19822023
/** A hole the shape pattern of a quoted.Matcher.unapply, representing a splice */
1983-
def patternHole(splice: Tree)(implicit ctx: Context): Tree =
1984-
ref(defn.InternalQuoted_patternHoleR).appliedToType(splice.tpe).withSpan(splice.span)
2024+
def patternHole(splice: Tree)(implicit ctx: Context): Tree = {
2025+
val Splice(pat) = splice
2026+
if (pat.tpe.derivesFrom(defn.QuotedTypeClass)) AppliedTypeTree(ref(defn.InternalQuoted_patternTypeHole), TypeTree(splice.tpe) :: Nil).withSpan(splice.span)
2027+
else ref(defn.InternalQuoted_patternHoleR).appliedToType(splice.tpe).withSpan(splice.span)
2028+
}
19852029

19862030
/** Translate `${ t: Expr[T] }` into expression `t.splice` while tracking the quotation level in the context */
19872031
def typedSplice(tree: untpd.Splice, pt: Type)(implicit ctx: Context): Tree = track("typedSplice") {
@@ -2020,9 +2064,48 @@ class Typer extends Namer
20202064

20212065
/** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */
20222066
def typedTypSplice(tree: untpd.TypSplice, pt: Type)(implicit ctx: Context): Tree = track("typedTypSplice") {
2067+
// TODO factor out comon code with typedSplice
20232068
ctx.compilationUnit.needsStaging = true
20242069
checkSpliceOutsideQuote(tree)
2025-
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
2070+
tree.expr match {
2071+
case untpd.Quote(innerExpr) =>
2072+
ctx.warning("Canceled quote directly inside a splice. ${ '{ XYZ } } is equivalent to XYZ.", tree.sourcePos)
2073+
typed(innerExpr, pt)
2074+
case expr =>
2075+
if (ctx.mode.is(Mode.QuotedPattern) && level == 1) {
2076+
if (isFullyDefined(pt, ForceDegree.all)) {
2077+
// TODO is this error still relevant here? probably not
2078+
ctx.error(i"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.sourcePos)
2079+
tree.withType(UnspecifiedErrorType)
2080+
} else {
2081+
expr match {
2082+
case Ident(name) => typedIdent(untpd.Ident(("$" + name).toTypeName), pt)
2083+
}
2084+
2085+
// println()
2086+
// println(expr)
2087+
// println()
2088+
// println()
2089+
// val bindingBounds = TypeBounds.apply(defn.NothingType, defn.AnyType)
2090+
// def getName(tree: untpd.Tree): TypeName = tree match {
2091+
// case tree: RefTree => ("$" + tree.name).toTypeName
2092+
// case tree: Typed => getName(tree.expr)
2093+
// }
2094+
// val sym = ctx.newPatternBoundSymbol(getName(expr), bindingBounds, expr.span)
2095+
// val bind = Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(expr.span)
2096+
//
2097+
// def spliceOwner(ctx: Context): Symbol =
2098+
// if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
2099+
// val pat = typedPattern(tree.expr, defn.QuotedTypeType.appliedTo(sym.typeRef))(
2100+
// spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
2101+
// Splice(Typed(pat, AppliedTypeTree(TypeTree(defn.QuotedTypeType), bind :: Nil)))
2102+
2103+
}
2104+
2105+
} else {
2106+
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
2107+
}
2108+
}
20262109
}
20272110

20282111
private def checkSpliceOutsideQuote(tree: untpd.Tree)(implicit ctx: Context): Unit = {

library/src-2.x/scala/internal/quoted/Matcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import scala.tasty.Reflection
55

66
object Matcher {
77

8-
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] =
8+
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] =
99
throw new Exception("running on non bootstrapped library")
1010

1111
}

library/src-3.x/scala/internal/quoted/Matcher.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ object Matcher {
3030
* @param reflection instance of the reflection API (implicitly provided by the macro)
3131
* @return None if it did not match, `Some(tup)` if it matched where `tup` contains `Expr[Ti]``
3232
*/
33-
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = {
33+
def unapply[TypeBindings <: Tuple, Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup] = {
3434
// TODO improve performance
3535
import reflection.{Bind => BindPattern, _}
3636
import Matching._

tests/pos/quotedPatterns.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ object Test {
3030
case '{ def $ff[T](i: T): Int = $z; 2 } =>
3131
val a: quoted.matching.Bind[[T] => T => Int] = ff
3232
z
33+
// case '{ poly[$t]($x); 2 } => ???
34+
// case '{ val x: $t = $a; val y: `$t` = x; 1 } => ???
35+
case '{ type $t <: AnyRef; val x: $t = $a; val y: $t = x; 1 } => ???
3336
case _ => '{1}
3437
}
38+
39+
def poly[T](x: T): Unit = ()
3540
}

tests/run-macros/quote-matcher-runtime/quoted_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ object Macros {
1010
private def impl[A, B](a: Expr[A], b: Expr[B])(implicit reflect: Reflection): Expr[Unit] = {
1111
import reflect.{Bind => _, _}
1212

13-
val res = scala.internal.quoted.Matcher.unapply[Tuple](a)(b, reflect).map { tup =>
13+
val res = scala.internal.quoted.Matcher.unapply[Tuple, Tuple](a)(b, reflect).map { tup =>
1414
tup.toArray.toList.map {
1515
case r: Expr[_] =>
1616
s"Expr(${r.unseal.show})"

tests/run-macros/quote-matcher-runtime/quoted_2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Macros._
33

44
import scala.internal.quoted.Matcher._
55

6-
import scala.internal.Quoted.{patternHole, patternBindHole, patternType}
6+
import scala.internal.Quoted._
77

88
object Test {
99

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import scala.quoted._
2+
import scala.quoted.matching._
3+
4+
import scala.tasty.Reflection
5+
6+
import scala.internal.quoted.Matcher._
7+
import scala.internal.Quoted._
8+
9+
object Macros {
10+
11+
inline def swapFandG(x: => Unit): Unit = ${impl('x)}
12+
13+
private def impl(x: Expr[Unit])(implicit reflect: Reflection): Expr[Unit] = {
14+
15+
type TT // type binding
16+
object DSLf {
17+
def unapply(x: Expr[_])(implicit reflect: Reflection): Option[Tuple2[Type[_], Expr[Any]]] =
18+
scala.internal.quoted.Matcher.unapply[Tuple2[Type[_], Expr[Any]]](x)('{type t; DSL.f[Hole[t]](patternHole[t]) }, reflect)
19+
} // case '{ DSL.f[$t]($x) } =>
20+
21+
x match {
22+
// case '{ DSL.f[$t]($x) } =>
23+
// case scala.internal.quoted.Matcher.unapply[Tuple2[Type[tt @ _], Expr[tt]]](Tuple2(t, TypedExpr(x)(`t`, reflect))(/*implicits*/ '{ DSL.f[Hole[Nothing, Any]](hole[Any])] }, reflect) =>
24+
case DSLf((t: Type[tt], x: Expr[t2])) =>
25+
26+
implicit val tt = t
27+
'{ DSL.g[$t]($x) }
28+
29+
// case '{ DSL.f[$t]($x) } =>
30+
// case DSLg(t, x) =>
31+
// '{ DSL.f[$t]($x) }
32+
???
33+
case _ =>
34+
x
35+
}
36+
}
37+
38+
}
39+
40+
41+
object TypedExpr {
42+
def unapply[T](arg: Expr[_])(implicit t: Type[T], reflect: Reflection): Option[Expr[T]] = {
43+
import reflect._
44+
if (arg.unseal.tpe <:< t.unseal.tpe) Some(arg.asInstanceOf[Expr[T]])
45+
else None
46+
}
47+
}
48+
49+
50+
//
51+
// DSL in which the user write the code
52+
//
53+
54+
object DSL {
55+
def f[T](x: T): Unit = println("f: " + x.toString)
56+
def g[T](x: T): Unit = println("g: " + x.toString)
57+
}
58+
59+
//
60+
// Helper to abstract call to scala.internal.quoted.Matcher.unapply and setup an object with the unapply
61+
//
62+
63+
class ExprMatch[Tup <: Tuple](pattern: Expr[_]) {
64+
def unapply(x: Expr[_])(implicit reflect: Reflection): Option[Tup] =
65+
scala.internal.quoted.Matcher.unapply[Tup](x)(pattern, reflect)
66+
}
67+
68+
69+
//class a {
70+
//
71+
// def foo(x: Any) =
72+
//
73+
// new Foo { type T = Int; type Tup = (Int, List[Int]); val contents: Tup = (3, 5 :: Nil) } match {
74+
// case Bar(x, ls) =>
75+
// val l2 = x :: ls
76+
// l2
77+
// }
78+
// new Foo { val contents = ??? } match {
79+
// case Bar(x, ls) =>
80+
// val l2 = x :: ls
81+
// l2
82+
// }
83+
//}
84+
//
85+
//
86+
//
87+
//trait Foo {
88+
// type T
89+
// type Tup <: Tuple
90+
// val contents: Tup
91+
//}
92+
//
93+
//object Foo {
94+
// def unaplly[F <: Foo](arg: F): Option[arg.Tup] = Some(arg.contents)
95+
//}
96+
//
97+
//object Bar {
98+
// def unapply(arg: Foo { type Tup = (T, List[T]) }): Option[arg.Tup] =
99+
// Foo.unaplly[Foo { type Tup = (T, List[T]) }](arg)
100+
//}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import Macros._
2+
3+
4+
object Test {
5+
6+
def main(args: Array[String]): Unit = {
7+
swapFandG(DSL.f(5))
8+
// swapFandG(DSL.g("abc"))
9+
}
10+
11+
}

0 commit comments

Comments
 (0)