Skip to content

Commit 5501e37

Browse files
committed
WIP Type quoted patterns with type splices
1 parent 0a3b1e8 commit 5501e37

File tree

15 files changed

+266
-67
lines changed

15 files changed

+266
-67
lines changed

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,25 @@ object desugar {
859859
Thicket(aliasType :: companions.toList)
860860
}
861861

862+
/** Transforms
863+
*
864+
* <mods> type $T >: Low <: Hi
865+
*
866+
* to
867+
*
868+
* @patternBindHole <mods> type $T >: Low <: Hi
869+
*
870+
* if the type is a type splice.
871+
*/
872+
def quotedPatternTypeDef(tree: TypeDef)(implicit ctx: Context): TypeDef = {
873+
assert(ctx.mode.is(Mode.QuotedPattern))
874+
if (tree.name.startsWith("$") /* && !tree.isBackQuoted*/) { // TODO add backquoted TypeDef
875+
val patternBindHoleAnnot = New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(tree.span)
876+
val mods = tree.mods.withAddedAnnotation(patternBindHoleAnnot)
877+
tree.withMods(mods)
878+
} else tree
879+
}
880+
862881
/** The normalized name of `mdef`. This means
863882
* 1. Check that the name does not redefine a Scala core class.
864883
* If it does redefine, issue an error and return a mangled name instead of the original one.
@@ -1040,6 +1059,7 @@ object desugar {
10401059
case tree: TypeDef =>
10411060
if (tree.isClassDef) classDef(tree)
10421061
else if (tree.mods.is(Opaque, butNot = Synthetic)) opaqueAlias(tree)
1062+
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
10431063
else tree
10441064
case tree: DefDef =>
10451065
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
@@ -1184,7 +1184,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
11841184
/** An extractor for typed splices */
11851185
object Splice {
11861186
def apply(tree: Tree)(implicit ctx: Context): Tree = {
1187-
val baseType = tree.tpe.baseType(defn.QuotedExprClass)
1187+
val baseType = tree.tpe.baseType(defn.QuotedExprClass).orElse(tree.tpe.baseType(defn.QuotedTypeClass))
11881188
val argType =
11891189
if (baseType != NoType) baseType.argTypesHi.head
11901190
else defn.NothingType
@@ -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/printing/RefinedPrinter.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
596596
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
597597
case Splice(tree) =>
598598
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
599+
case TypSplice(tree) =>
600+
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
599601
case tree: Applications.IntegratedTypeArgs =>
600602
toText(tree.app) ~ Str("(with integrated type args)").provided(ctx.settings.YprintDebug.value)
601603
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: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,11 +1964,49 @@ class Typer extends Namer
19641964
val exprPt = pt.baseType(defn.QuotedExprClass)
19651965
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
19661966
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
1967-
val (shape, splices) = splitQuotePattern(quoted1)
1968-
val patType = defn.tupleType(splices.tpes.map(_.widen))
1969-
val splicePat = typed(untpd.Tuple(splices.map(untpd.TypedSplice(_))).withSpan(quoted.span), patType)
1967+
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
1968+
1969+
class ReplaceBindings extends TypeMap() {
1970+
override def apply(tp: Type): Type = tp match {
1971+
case tp: TypeRef =>
1972+
typeBindings.get(tp.typeSymbol).fold(tp)(_.symbol.typeRef)
1973+
case tp => mapOver(tp)
1974+
}
1975+
}
1976+
val replaceBindings = new ReplaceBindings
1977+
val patType = defn.tupleType(splices.tpes.map(tpe => replaceBindings(tpe.widen)))
1978+
1979+
val typeBindingsTuple = tpd.tupleTypeTree(typeBindings.values.toList)
1980+
1981+
val replaceBindingsInTree = new TreeMap {
1982+
private[this] var bindMap = Map.empty[Symbol, Symbol]
1983+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
1984+
tree match {
1985+
case tree: Bind =>
1986+
val sym = tree.symbol
1987+
val newInfo = replaceBindings(sym.info)
1988+
if (sym.info =:= newInfo) super.transform(tree)
1989+
else {
1990+
val newSym = ctx.newSymbol(sym.owner, sym.name, sym.flags, newInfo, sym.privateWithin, sym.coord)
1991+
bindMap += sym -> newSym
1992+
Bind(newSym, super.transform(tree.body))
1993+
}
1994+
case _ =>
1995+
val replaceBindingsInType = new ReplaceBindings {
1996+
override def apply(tp: Type): Type = tp match {
1997+
case tp: TermRef => bindMap.get(tp.termSymbol).fold(tp)(_.typeRef)
1998+
case tp => super.apply(tp)
1999+
}
2000+
}
2001+
super.transform(tree).withType(replaceBindingsInType(tree.tpe))
2002+
}
2003+
}
2004+
}
2005+
2006+
val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)
2007+
19702008
UnApply(
1971-
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToType(patType),
2009+
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
19722010
implicits =
19732011
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
19742012
implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil,
@@ -1980,8 +2018,24 @@ class Typer extends Namer
19802018
}
19812019
}
19822020

1983-
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
2021+
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
19842022
val ctx0 = ctx
2023+
2024+
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
2025+
def getBinding(sym: Symbol): Bind =
2026+
typeBindings.getOrElseUpdate(sym, {
2027+
val bindingBounds = TypeBounds.apply(defn.NothingType, defn.AnyType) // TODO recover bounds
2028+
val bsym = ctx.newPatternBoundSymbol((sym.name + "$").toTypeName, bindingBounds, quoted.span)
2029+
Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span)
2030+
})
2031+
def replaceTypeBindings = new TypeMap {
2032+
def apply(tp: Type): Type = tp match {
2033+
case tp: TypeRef if tp.typeSymbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
2034+
getBinding(tp.typeSymbol).symbol.typeRef
2035+
case _ => mapOver(tp)
2036+
}
2037+
}
2038+
19852039
object splitter extends tpd.TreeMap {
19862040
val patBuf = new mutable.ListBuffer[Tree]
19872041
override def transform(tree: Tree)(implicit ctx: Context) = tree match {
@@ -2014,17 +2068,28 @@ class Typer extends Namer
20142068
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingExprTpe)).withSpan(ddef.span)
20152069
}
20162070
super.transform(tree)
2071+
case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
2072+
val bindingType = getBinding(tdef.symbol).symbol.typeRef
2073+
val bindingTypeTpe = AppliedType(defn.QuotedTypeType, bindingType :: Nil)
2074+
assert(tdef.name.startsWith("$"))
2075+
val bindName = tdef.name.toString.stripPrefix("$").toTermName
2076+
val sym = ctx0.newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span)
2077+
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
2078+
super.transform(tree)
20172079
case _ =>
20182080
super.transform(tree)
20192081
}
20202082
}
20212083
val result = splitter.transform(quoted)
2022-
(result, splitter.patBuf.toList)
2084+
val patterns = splitter.patBuf.toList
2085+
(typeBindings.toMap, result, patterns)
20232086
}
20242087

20252088
/** A hole the shape pattern of a quoted.Matcher.unapply, representing a splice */
2026-
def patternHole(splice: Tree)(implicit ctx: Context): Tree =
2089+
def patternHole(splice: Tree)(implicit ctx: Context): Tree = {
2090+
val Splice(pat) = splice
20272091
ref(defn.InternalQuoted_patternHoleR).appliedToType(splice.tpe).withSpan(splice.span)
2092+
}
20282093

20292094
/** Translate `${ t: Expr[T] }` into expression `t.splice` while tracking the quotation level in the context */
20302095
def typedSplice(tree: untpd.Splice, pt: Type)(implicit ctx: Context): Tree = track("typedSplice") {
@@ -2063,14 +2128,47 @@ class Typer extends Namer
20632128

20642129
/** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */
20652130
def typedTypSplice(tree: untpd.TypSplice, pt: Type)(implicit ctx: Context): Tree = track("typedTypSplice") {
2131+
// TODO factor out comon code with typedSplice
20662132
ctx.compilationUnit.needsStaging = true
20672133
checkSpliceOutsideQuote(tree)
20682134
tree.expr match {
20692135
case untpd.Quote(innerType) if innerType.isType =>
20702136
ctx.warning("Canceled quote directly inside a splice. ${ '[ XYZ ] } is equivalent to XYZ.", tree.sourcePos)
20712137
typed(innerType, pt)
20722138
case expr =>
2073-
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
2139+
if (ctx.mode.is(Mode.QuotedPattern) && level == 1) {
2140+
if (isFullyDefined(pt, ForceDegree.all)) {
2141+
// TODO is this error still relevant here? probably not
2142+
ctx.error(i"Type must be fully defined.\nConsider annotating the splice using a type ascription:\n ($tree: XYZ).", tree.expr.sourcePos)
2143+
tree.withType(UnspecifiedErrorType)
2144+
} else {
2145+
expr match {
2146+
case Ident(name) => typedIdent(untpd.Ident(("$" + name).toTypeName), pt)
2147+
}
2148+
2149+
// println()
2150+
// println(expr)
2151+
// println()
2152+
// println()
2153+
// val bindingBounds = TypeBounds.apply(defn.NothingType, defn.AnyType)
2154+
// def getName(tree: untpd.Tree): TypeName = tree match {
2155+
// case tree: RefTree => ("$" + tree.name).toTypeName
2156+
// case tree: Typed => getName(tree.expr)
2157+
// }
2158+
// val sym = ctx.newPatternBoundSymbol(getName(expr), bindingBounds, expr.span)
2159+
// val bind = Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(expr.span)
2160+
//
2161+
// def spliceOwner(ctx: Context): Symbol =
2162+
// if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
2163+
// val pat = typedPattern(tree.expr, defn.QuotedTypeType.appliedTo(sym.typeRef))(
2164+
// spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
2165+
// Splice(Typed(pat, AppliedTypeTree(TypeTree(defn.QuotedTypeType), bind :: Nil)))
2166+
2167+
}
2168+
2169+
} else {
2170+
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
2171+
}
20742172
}
20752173
}
20762174

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.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,4 @@ object Quoted {
2424
/** A splice of a name in a quoted pattern is desugared by wrapping getting this annotation */
2525
class patternBindHole extends Annotation
2626

27-
class patternType extends Annotation
28-
2927
}

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

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,28 @@ 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._
3737

3838
type Env = Set[(Symbol, Symbol)]
3939

40+
class SymBinding(val sym: Symbol)
41+
4042
inline def withEnv[T](env: Env)(body: => given Env => T): T = body given env
4143

44+
def hasBindTypeAnnotation(tpt: TypeTree): Boolean = tpt match {
45+
case Annotated(tpt2, Apply(Select(New(TypeIdent("patternBindHole")), "<init>"), Nil)) => true
46+
case Annotated(tpt2, _) => hasBindTypeAnnotation(tpt2)
47+
case _ => false
48+
}
49+
50+
def hasBindAnnotation(sym: Symbol) = sym.annots.exists {
51+
case New(tpt) => tpt.symbol == kernel.Definitions_InternalQuoted_patternBindHoleAnnot
52+
case annot => annot.symbol.owner == kernel.Definitions_InternalQuoted_patternBindHoleAnnot
53+
}
54+
4255
/** Check that all trees match with `mtch` and concatenate the results with && */
4356
def matchLists[T](l1: List[T], l2: List[T])(mtch: (T, T) => Matching): Matching = (l1, l2) match {
4457
case (x :: xs, y :: ys) => mtch(x, y) && matchLists(xs, ys)(mtch)
@@ -63,6 +76,7 @@ object Matcher {
6376
/** Normalieze the tree */
6477
def normalize(tree: Tree): Tree = tree match {
6578
case Block(Nil, expr) => normalize(expr)
79+
case Block(stats1, Block(stats2, expr)) => normalize(Block(stats1 ::: stats2, expr))
6680
case Inlined(_, Nil, expr) => normalize(expr)
6781
case _ => tree
6882
}
@@ -81,15 +95,6 @@ object Matcher {
8195
def bindingMatch(sym: Symbol) =
8296
matched(new Bind(sym.name, sym))
8397

84-
def hasBindTypeAnnotation(tpt: TypeTree): Boolean = tpt match {
85-
case Annotated(tpt2, Apply(Select(New(TypeIdent("patternBindHole")), "<init>"), Nil)) => true
86-
case Annotated(tpt2, _) => hasBindTypeAnnotation(tpt2)
87-
case _ => false
88-
}
89-
90-
def hasBindAnnotation(sym: Symbol) =
91-
sym.annots.exists { case Apply(Select(New(TypeIdent("patternBindHole")),"<init>"),List()) => true; case _ => true }
92-
9398
(scrutinee, pattern) match {
9499

95100
// Match a scala.internal.Quoted.patternHole typed as a repeated argument and return the scrutinee tree
@@ -133,11 +138,20 @@ object Matcher {
133138
case (TypeApply(fn1, args1), TypeApply(fn2, args2)) if fn1.symbol == fn2.symbol =>
134139
fn1 =#= fn2 && args1 =##= args2
135140

136-
case (Block(stats1, expr1), Block(stats2, expr2)) =>
137-
withEnv(the[Env] ++ stats1.map(_.symbol).zip(stats2.map(_.symbol))) {
138-
stats1 =##= stats2 && expr1 =#= expr2
141+
case (Block(stats1, expr1), Block(binding :: stats2, expr2)) if isTypeBinding(binding) =>
142+
reflection.kernel.Context_GADT_addToConstraint(the[Context])(binding.symbol :: Nil)
143+
matched(new SymBinding(binding.symbol)) && Block(stats1, expr1) =#= Block(stats2, expr2)
144+
145+
case (Block(stat1 :: stats1, expr1), Block(stat2 :: stats2, expr2)) =>
146+
withEnv(the[Env] + (stat1.symbol -> stat2.symbol)) {
147+
stat1 =#= stat2 && Block(stats1, expr1) =#= Block(stats2, expr2)
139148
}
140149

150+
case (scrutinee, Block(typeBindings, expr2)) if typeBindings.forall(isTypeBinding) =>
151+
val bindingSymbols = typeBindings.map(_.symbol)
152+
reflection.kernel.Context_GADT_addToConstraint(the[Context])(bindingSymbols)
153+
bindingSymbols.foldRight(scrutinee =#= expr2)((x, acc) => matched(new SymBinding(x)) && acc)
154+
141155
case (If(cond1, thenp1, elsep1), If(cond2, thenp2, elsep2)) =>
142156
cond1 =#= cond2 && thenp1 =#= thenp2 && elsep1 =#= elsep2
143157

@@ -315,24 +329,24 @@ object Matcher {
315329
}
316330

317331
def isTypeBinding(tree: Tree): Boolean = tree match {
318-
case IsTypeDef(tree) =>
319-
tree.symbol.annots.exists(_.symbol.owner.fullName == "scala.internal.Quoted$.patternType")
332+
case IsTypeDef(tree) => hasBindAnnotation(tree.symbol)
320333
case _ => false
321334
}
322335

323336
implied for Env = Set.empty
324-
val res = patternExpr.unseal.underlyingArgument match {
325-
case Block(typeBindings, pattern) if typeBindings.forall(isTypeBinding) =>
326-
implicit val ctx2 = reflection.kernel.Context_GADT_setFreshGADTBounds(rootContext)
327-
val bindingSymbols = typeBindings.map(_.symbol(ctx2))
328-
reflection.kernel.Context_GADT_addToConstraint(ctx2)(bindingSymbols)
329-
val matchings = scrutineeExpr.unseal.underlyingArgument =#= pattern
330-
val constainedTypes = bindingSymbols.map(s => reflection.kernel.Context_GADT_approximation(ctx2)(s, true))
331-
constainedTypes.foldRight(matchings)((x, acc) => matched(x.seal) && acc)
332-
case pattern =>
333-
scrutineeExpr.unseal.underlyingArgument =#= pattern
337+
338+
{
339+
// TODO only set GADT context when needed
340+
implicit val ctx2 = reflection.kernel.Context_GADT_setFreshGADTBounds(rootContext)
341+
val matchings = scrutineeExpr.unseal.underlyingArgument =#= patternExpr.unseal.underlyingArgument
342+
val res = matchings.asOptionOfTuple.map { tup =>
343+
Tuple.fromArray(tup.toArray.map { // TODO improve code
344+
case x: SymBinding => reflection.kernel.Context_GADT_approximation(ctx2)(x.sym, true).seal
345+
case x => x
346+
})
347+
}
348+
res.asInstanceOf[Option[Tup]]
334349
}
335-
res.asOptionOfTuple.asInstanceOf[Option[Tup]]
336350
}
337351

338352
/** Result of matching a part of an expression */

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
}

0 commit comments

Comments
 (0)