Skip to content

Commit 486d552

Browse files
committed
Type quoted patterns with type splices
1 parent 00688b8 commit 486d552

File tree

29 files changed

+693
-75
lines changed

29 files changed

+693
-75
lines changed

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,25 @@ object desugar {
818818
}
819819
}
820820

821+
/** Transforms
822+
*
823+
* <mods> type $T >: Low <: Hi
824+
*
825+
* to
826+
*
827+
* @patternBindHole <mods> type $T >: Low <: Hi
828+
*
829+
* if the type is a type splice.
830+
*/
831+
def quotedPatternTypeDef(tree: TypeDef)(implicit ctx: Context): TypeDef = {
832+
assert(ctx.mode.is(Mode.QuotedPattern))
833+
if (tree.name.startsWith("$") && !tree.isBackquoted) {
834+
val patternBindHoleAnnot = New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(tree.span)
835+
val mods = tree.mods.withAddedAnnotation(patternBindHoleAnnot)
836+
tree.withMods(mods)
837+
} else tree
838+
}
839+
821840
/** The normalized name of `mdef`. This means
822841
* 1. Check that the name does not redefine a Scala core class.
823842
* If it does redefine, issue an error and return a mangled name instead of the original one.
@@ -1031,7 +1050,9 @@ object desugar {
10311050
checkModifiers(tree) match {
10321051
case tree: ValDef => valDef(tree)
10331052
case tree: TypeDef =>
1034-
if (tree.isClassDef) classDef(tree) else tree
1053+
if (tree.isClassDef) classDef(tree)
1054+
else if (ctx.mode.is(Mode.QuotedPattern)) quotedPatternTypeDef(tree)
1055+
else tree
10351056
case tree: DefDef =>
10361057
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
10371058
else defDef(tree)

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ class TreeMapWithImplicits extends tpd.TreeMap {
6868
nestedCtx
6969
}
7070

71+
private def patternScopeCtx(pattern: Tree)(implicit ctx: Context): Context = {
72+
val nestedCtx = ctx.fresh.setNewScope
73+
new TreeTraverser {
74+
def traverse(tree: Tree)(implicit ctx: Context): Unit = {
75+
tree match {
76+
case d: DefTree => nestedCtx.enter(d.symbol)
77+
case _ =>
78+
}
79+
traverseChildren(tree)
80+
}
81+
}.traverse(pattern)
82+
nestedCtx
83+
}
84+
7185
override def transform(tree: Tree)(implicit ctx: Context): Tree = {
7286
def localCtx =
7387
if (tree.hasType && tree.symbol.exists) ctx.withOwner(tree.symbol) else ctx
@@ -93,6 +107,13 @@ class TreeMapWithImplicits extends tpd.TreeMap {
93107
Nil,
94108
transformSelf(self),
95109
transformStats(impl.body, tree.symbol))
110+
case tree: CaseDef =>
111+
val patCtx = patternScopeCtx(tree.pat)(ctx)
112+
cpy.CaseDef(tree)(
113+
transform(tree.pat),
114+
transform(tree.guard)(patCtx),
115+
transform(tree.body)(patCtx)
116+
)
96117
case _ =>
97118
super.transform(tree)
98119
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,14 @@ object Trees {
768768

769769
/** Is this a definition of a class? */
770770
def isClassDef: Boolean = rhs.isInstanceOf[Template[_]]
771+
772+
def isBackquoted: Boolean = false
773+
}
774+
775+
class BackquotedTypeDef[-T >: Untyped] private[ast] (name: TypeName, rhs: Tree[T])(implicit @constructorOnly src: SourceFile)
776+
extends TypeDef[T](name, rhs) {
777+
override def isBackquoted: Boolean = true
778+
override def productPrefix: String = "BackquotedTypeDef"
771779
}
772780

773781
/** extends parents { self => body }
@@ -982,6 +990,7 @@ object Trees {
982990
type ValDef = Trees.ValDef[T]
983991
type DefDef = Trees.DefDef[T]
984992
type TypeDef = Trees.TypeDef[T]
993+
type BackquotedTypeDef = Trees.BackquotedTypeDef[T]
985994
type Template = Trees.Template[T]
986995
type Import = Trees.Import[T]
987996
type PackageDef = Trees.PackageDef[T]

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
12081208
/** An extractor for typed splices */
12091209
object Splice {
12101210
def apply(tree: Tree)(implicit ctx: Context): Tree = {
1211-
val baseType = tree.tpe.baseType(defn.QuotedExprClass)
1211+
val baseType = tree.tpe.baseType(defn.QuotedExprClass).orElse(tree.tpe.baseType(defn.QuotedTypeClass))
12121212
val argType =
12131213
if (baseType != NoType) baseType.argTypesHi.head
12141214
else defn.NothingType
@@ -1342,6 +1342,17 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
13421342
}
13431343
}
13441344

1345+
/** Creates the tuple type tree repesentation of the type trees in `ts` */
1346+
def tupleTypeTree(elems: List[Tree])(implicit ctx: Context): Tree = {
1347+
val arity = elems.length
1348+
if (arity <= Definitions.MaxTupleArity && defn.TupleType(arity) != null) AppliedTypeTree(TypeTree(defn.TupleType(arity)), elems)
1349+
else nestedPairsType(elems)
1350+
}
1351+
1352+
/** Creates the nested pairs type tree repesentation of the type trees in `ts` */
1353+
def nestedPairsType(ts: List[Tree])(implicit ctx: Context): Tree =
1354+
ts.foldRight[Tree](TypeTree(defn.UnitType))((x, acc) => AppliedTypeTree(TypeTree(defn.PairType), x :: acc :: Nil))
1355+
13451356
/** Replaces all positions in `tree` with zero-extent positions */
13461357
private def focusPositions(tree: Tree)(implicit ctx: Context): Tree = {
13471358
val transformer = new tpd.TreeMap {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
339339
def ValDef(name: TermName, tpt: Tree, rhs: LazyTree)(implicit src: SourceFile): ValDef = new ValDef(name, tpt, rhs)
340340
def DefDef(name: TermName, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree)(implicit src: SourceFile): DefDef = new DefDef(name, tparams, vparamss, tpt, rhs)
341341
def TypeDef(name: TypeName, rhs: Tree)(implicit src: SourceFile): TypeDef = new TypeDef(name, rhs)
342+
def BackquotedTypeDef(name: TypeName, rhs: Tree)(implicit src: SourceFile): TypeDef = new BackquotedTypeDef(name, rhs)
342343
def Template(constr: DefDef, parents: List[Tree], derived: List[Tree], self: ValDef, body: LazyTreeList)(implicit src: SourceFile): Template =
343344
if (derived.isEmpty) new Template(constr, parents, self, body)
344345
else new DerivingTemplate(constr, parents ++ derived, self, body, derived.length)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,8 @@ trait Symbols { this: Context =>
210210
Nil, decls)
211211

212212
/** Define a new symbol associated with a Bind or pattern wildcard and, by default, make it gadt narrowable. */
213-
def newPatternBoundSymbol(name: Name, info: Type, span: Span, addToGadt: Boolean = true): Symbol = {
214-
val sym = newSymbol(owner, name, Case, info, coord = span)
213+
def newPatternBoundSymbol(name: Name, info: Type, span: Span, addToGadt: Boolean = true, flags: FlagSet = EmptyFlags): Symbol = {
214+
val sym = newSymbol(owner, name, Case | flags, info, coord = span)
215215
if (addToGadt && name.isTypeName) gadt.addToConstraint(sym)
216216
sym
217217
}

compiler/src/dotty/tools/dotc/parsing/Parsers.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2643,10 +2643,15 @@ object Parsers {
26432643
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
26442644
newLinesOpt()
26452645
atSpan(start, nameStart) {
2646-
val name = ident().toTypeName
2646+
val nameIdent = typeIdent()
26472647
val tparams = typeParamClauseOpt(ParamOwner.Type)
2648-
def makeTypeDef(rhs: Tree): Tree =
2649-
finalizeDef(TypeDef(name, lambdaAbstract(tparams, rhs)), mods, start)
2648+
def makeTypeDef(rhs: Tree): Tree = {
2649+
val rhs1 = lambdaAbstract(tparams, rhs)
2650+
val tdef =
2651+
if (nameIdent.isBackquoted) BackquotedTypeDef(nameIdent.name.toTypeName, rhs1)
2652+
else TypeDef(nameIdent.name.toTypeName, rhs1)
2653+
finalizeDef(tdef, mods, start)
2654+
}
26502655
in.token match {
26512656
case EQUALS =>
26522657
in.nextToken()

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
479479
if (lo eq hi) optText(lo)(" = " ~ _)
480480
else optText(lo)(" >: " ~ _) ~ optText(hi)(" <: " ~ _)
481481
case Bind(name, body) =>
482+
("implict ": Text).provided(tree.symbol.is(Implicit) && !ctx.settings.YtestPickler.value) ~ // Used for scala.quoted.Type in quote patterns (not pickled)
482483
changePrec(InfixPrec) { toText(name) ~ " @ " ~ toText(body) }
483484
case Alternative(trees) =>
484485
changePrec(OrPrec) { toText(trees, " | ") }
@@ -610,6 +611,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
610611
else keywordStr("'{") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
611612
case Splice(tree) =>
612613
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
614+
case TypSplice(tree) =>
615+
keywordStr("${") ~ toTextGlobal(dropBlock(tree)) ~ keywordStr("}")
613616
case tree: Applications.IntegratedTypeArgs =>
614617
toText(tree.app) ~ Str("(with integrated type args)").provided(printDebug)
615618
case Thicket(trees) =>

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

Lines changed: 126 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,13 +1971,51 @@ class Typer extends Namer
19711971
val exprPt = pt.baseType(defn.QuotedExprClass)
19721972
val quotedPt = if (exprPt.exists) exprPt.argTypesHi.head else defn.AnyType
19731973
val quoted1 = typedExpr(quoted, quotedPt)(quoteContext.addMode(Mode.QuotedPattern))
1974-
val (shape, splices) = splitQuotePattern(quoted1)
1975-
val patType = defn.tupleType(splices.tpes.map(_.widen))
1976-
val splicePat = typed(untpd.Tuple(splices.map(untpd.TypedSplice(_))).withSpan(quoted.span), patType)
1974+
1975+
val (typeBindings, shape, splices) = splitQuotePattern(quoted1)
1976+
1977+
class ReplaceBindings extends TypeMap() {
1978+
override def apply(tp: Type): Type = tp match {
1979+
case tp: TypeRef =>
1980+
val tp1 = if (tp.typeSymbol == defn.QuotedType_splice) tp.dealias else tp
1981+
typeBindings.get(tp1.typeSymbol).fold(tp)(_.symbol.typeRef)
1982+
case tp => mapOver(tp)
1983+
}
1984+
}
1985+
val replaceBindings = new ReplaceBindings
1986+
val patType = defn.tupleType(splices.tpes.map(tpe => replaceBindings(tpe.widen)))
1987+
1988+
val typeBindingsTuple = tpd.tupleTypeTree(typeBindings.values.toList)
1989+
1990+
val replaceBindingsInTree = new TreeMap {
1991+
private[this] var bindMap = Map.empty[Symbol, Symbol]
1992+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
1993+
tree match {
1994+
case tree: Bind =>
1995+
val sym = tree.symbol
1996+
val newInfo = replaceBindings(sym.info)
1997+
val newSym = ctx.newSymbol(sym.owner, sym.name, sym.flags, newInfo, sym.privateWithin, sym.coord)
1998+
bindMap += sym -> newSym
1999+
Bind(newSym, transform(tree.body)).withSpan(sym.span)
2000+
case _ =>
2001+
super.transform(tree).withType(replaceBindingsInType(tree.tpe))
2002+
}
2003+
}
2004+
private[this] val replaceBindingsInType = new ReplaceBindings {
2005+
override def apply(tp: Type): Type = tp match {
2006+
case tp: TermRef => bindMap.get(tp.termSymbol).fold[Type](tp)(_.typeRef)
2007+
case tp => super.apply(tp)
2008+
}
2009+
}
2010+
}
2011+
2012+
val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)
2013+
19772014
UnApply(
1978-
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToType(patType),
2015+
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
19792016
implicits =
19802017
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
2018+
Literal(Constant(typeBindings.nonEmpty)) ::
19812019
implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil,
19822020
patterns = splicePat :: Nil,
19832021
proto = pt)
@@ -1987,13 +2025,26 @@ class Typer extends Namer
19872025
}
19882026
}
19892027

1990-
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
2028+
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
19912029
val ctx0 = ctx
2030+
2031+
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
2032+
def getBinding(sym: Symbol): Bind =
2033+
typeBindings.getOrElseUpdate(sym, {
2034+
val bindingBounds = sym.info
2035+
val bsym = ctx.newPatternBoundSymbol(sym.name.toTypeName, bindingBounds, quoted.span)
2036+
Bind(bsym, untpd.Ident(nme.WILDCARD).withType(bindingBounds)).withSpan(quoted.span)
2037+
})
2038+
19922039
object splitter extends tpd.TreeMap {
19932040
val patBuf = new mutable.ListBuffer[Tree]
2041+
val freshTypePatBuf = new mutable.ListBuffer[Tree]
2042+
val freshTypeBindingsBuff = new mutable.ListBuffer[Tree]
2043+
val typePatBuf = new mutable.ListBuffer[Tree]
19942044
override def transform(tree: Tree)(implicit ctx: Context) = tree match {
19952045
case Typed(Splice(pat), tpt) if !tpt.tpe.derivesFrom(defn.RepeatedParamClass) =>
1996-
val exprTpt = AppliedTypeTree(TypeTree(defn.QuotedExprType), tpt :: Nil)
2046+
val tpt1 = transform(tpt) // Transform type bindings
2047+
val exprTpt = AppliedTypeTree(TypeTree(defn.QuotedExprType), tpt1 :: Nil)
19972048
transform(Splice(Typed(pat, exprTpt)))
19982049
case Splice(pat) =>
19992050
try patternHole(tree)
@@ -2003,6 +2054,12 @@ class Typer extends Namer
20032054
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
20042055
patBuf += pat1
20052056
}
2057+
case Select(pat, _) if tree.symbol == defn.QuotedType_splice =>
2058+
val sym = tree.tpe.dealias.typeSymbol.asType
2059+
val tdef = TypeDef(sym).withSpan(sym.span)
2060+
freshTypeBindingsBuff += transformTypeBindingTypeDef(tdef, freshTypePatBuf)
2061+
TypeTree(tree.tpe.dealias).withSpan(tree.span)
2062+
20062063
case ddef: ValOrDefDef =>
20072064
if (ddef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot)) {
20082065
val bindingType = ddef.symbol.info match {
@@ -2021,17 +2078,55 @@ class Typer extends Namer
20212078
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingExprTpe)).withSpan(ddef.span)
20222079
}
20232080
super.transform(tree)
2081+
case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
2082+
transformTypeBindingTypeDef(tdef, typePatBuf)
20242083
case _ =>
20252084
super.transform(tree)
20262085
}
2086+
2087+
def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]]): Tree = {
2088+
val bindingType = getBinding(tdef.symbol).symbol.typeRef
2089+
val bindingTypeTpe = AppliedType(defn.QuotedTypeType, bindingType :: Nil)
2090+
assert(tdef.name.startsWith("$"))
2091+
val bindName = tdef.name.toString.stripPrefix("$").toTermName
2092+
val sym = ctx0.newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span, flags = ImplicitTerm)
2093+
buff += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
2094+
super.transform(tdef)
2095+
}
2096+
}
2097+
val shape0 = splitter.transform(quoted)
2098+
val patterns = (splitter.freshTypePatBuf.iterator ++ splitter.typePatBuf.iterator ++ splitter.patBuf.iterator).toList
2099+
val freshTypeBindings = splitter.freshTypeBindingsBuff.result()
2100+
2101+
val shape1 = seq(
2102+
freshTypeBindings,
2103+
shape0
2104+
)
2105+
val shape2 = {
2106+
if (freshTypeBindings.isEmpty) shape1
2107+
else {
2108+
val isFreshTypeBindings = freshTypeBindings.map(_.symbol).toSet
2109+
val typeMap = new TypeMap() {
2110+
def apply(tp: Type): Type = tp match {
2111+
case tp: TypeRef if tp.typeSymbol == defn.QuotedType_splice =>
2112+
val tp1 = tp.dealias
2113+
if (isFreshTypeBindings(tp1.typeSymbol)) tp1
2114+
else tp
2115+
case tp => mapOver(tp)
2116+
}
2117+
}
2118+
new TreeTypeMap(typeMap = typeMap).transform(shape1)
2119+
}
20272120
}
2028-
val result = splitter.transform(quoted)
2029-
(result, splitter.patBuf.toList)
2121+
2122+
(typeBindings.toMap, shape2, patterns)
20302123
}
20312124

20322125
/** A hole the shape pattern of a quoted.Matcher.unapply, representing a splice */
2033-
def patternHole(splice: Tree)(implicit ctx: Context): Tree =
2126+
def patternHole(splice: Tree)(implicit ctx: Context): Tree = {
2127+
val Splice(pat) = splice
20342128
ref(defn.InternalQuoted_patternHoleR).appliedToType(splice.tpe).withSpan(splice.span)
2129+
}
20352130

20362131
/** Translate `${ t: Expr[T] }` into expression `t.splice` while tracking the quotation level in the context */
20372132
def typedSplice(tree: untpd.Splice, pt: Type)(implicit ctx: Context): Tree = track("typedSplice") {
@@ -2077,7 +2172,28 @@ class Typer extends Namer
20772172
ctx.warning("Canceled quote directly inside a splice. ${ '[ XYZ ] } is equivalent to XYZ.", tree.sourcePos)
20782173
typed(innerType, pt)
20792174
case expr =>
2080-
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
2175+
if (ctx.mode.is(Mode.QuotedPattern) && level == 1) {
2176+
if (isFullyDefined(pt, ForceDegree.all)) {
2177+
ctx.error(i"Spliced type pattern must not be fully defined. Consider using $pt directly", tree.expr.sourcePos)
2178+
tree.withType(UnspecifiedErrorType)
2179+
} else {
2180+
def spliceOwner(ctx: Context): Symbol =
2181+
if (ctx.mode.is(Mode.QuotedPattern)) spliceOwner(ctx.outer) else ctx.owner
2182+
val name = expr match {
2183+
case Ident(name) => ("$" + name).toTypeName
2184+
case Typed(Ident(name), _) => ("$" + name).toTypeName
2185+
case Bind(name, _) => ("$" + name).toTypeName
2186+
case _ => NameKinds.UniqueName.fresh("$".toTypeName)
2187+
}
2188+
val typeSym = ctx.newSymbol(spliceOwner(ctx), name, EmptyFlags, TypeBounds.empty, NoSymbol, expr.span)
2189+
typeSym.addAnnotation(Annotation(New(ref(defn.InternalQuoted_patternBindHoleAnnot.typeRef)).withSpan(expr.span)))
2190+
val pat = typedPattern(expr, defn.QuotedTypeType.appliedTo(typeSym.typeRef))(
2191+
spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
2192+
pat.select(tpnme.splice)
2193+
}
2194+
} else {
2195+
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
2196+
}
20812197
}
20822198
}
20832199

0 commit comments

Comments
 (0)