Skip to content

Commit 02ee27f

Browse files
committed
Type quoted patterns with type splices
1 parent 6c5de07 commit 02ee27f

File tree

19 files changed

+334
-75
lines changed

19 files changed

+334
-75
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) {
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/Trees.scala

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

773773
/** Is this a definition of a class? */
774774
def isClassDef: Boolean = rhs.isInstanceOf[Template[_]]
775+
776+
def isBackquoted: Boolean = false
777+
}
778+
779+
class BackquotedTypeDef[-T >: Untyped] private[ast] (name: TypeName, rhs: Tree[T])(implicit @constructorOnly src: SourceFile)
780+
extends TypeDef[T](name, rhs) {
781+
override def isBackquoted: Boolean = true
782+
override def productPrefix: String = "BackquotedTypeDef"
775783
}
776784

777785
/** extends parents { self => body }
@@ -985,6 +993,7 @@ object Trees {
985993
type DefDef = Trees.DefDef[T]
986994
type BackquotedDefDef = Trees.BackquotedDefDef[T]
987995
type TypeDef = Trees.TypeDef[T]
996+
type BackquotedTypeDef = Trees.BackquotedTypeDef[T]
988997
type Template = Trees.Template[T]
989998
type Import = Trees.Import[T]
990999
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
@@ -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/ast/untpd.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
334334
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)
335335
def BackquotedDefDef(name: TermName, tparams: List[TypeDef], vparamss: List[List[ValDef]], tpt: Tree, rhs: LazyTree)(implicit src: SourceFile): DefDef = new BackquotedDefDef(name, tparams, vparamss, tpt, rhs)
336336
def TypeDef(name: TypeName, rhs: Tree)(implicit src: SourceFile): TypeDef = new TypeDef(name, rhs)
337+
def BackquotedTypeDef(name: TypeName, rhs: Tree)(implicit src: SourceFile): TypeDef = new BackquotedTypeDef(name, rhs)
337338
def Template(constr: DefDef, parents: List[Tree], derived: List[Tree], self: ValDef, body: LazyTreeList)(implicit src: SourceFile): Template =
338339
if (derived.isEmpty) new Template(constr, parents, self, body)
339340
else new DerivingTemplate(constr, parents ++ derived, self, body, derived.length)

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2556,10 +2556,15 @@ object Parsers {
25562556
def typeDefOrDcl(start: Offset, mods: Modifiers): Tree = {
25572557
newLinesOpt()
25582558
atSpan(start, nameStart) {
2559-
val name = ident().toTypeName
2559+
val nameIdent = typeIdent()
25602560
val tparams = typeParamClauseOpt(ParamOwner.Type)
2561-
def makeTypeDef(rhs: Tree): Tree =
2562-
finalizeDef(TypeDef(name, lambdaAbstract(tparams, rhs)), mods, start)
2561+
def makeTypeDef(rhs: Tree): Tree = {
2562+
val rhs1 = lambdaAbstract(tparams, rhs)
2563+
val tdef =
2564+
if (nameIdent.isBackquoted) BackquotedTypeDef(nameIdent.name.toTypeName, rhs1)
2565+
else TypeDef(nameIdent.name.toTypeName, rhs1)
2566+
finalizeDef(tdef, mods, start)
2567+
}
25632568
in.token match {
25642569
case EQUALS =>
25652570
in.nextToken()

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: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,13 +1964,50 @@ 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+
val tp1 = if (tp.typeSymbol == defn.QuotedType_splice) tp.dealias else tp
1973+
typeBindings.get(tp1.typeSymbol).fold(tp)(_.symbol.typeRef)
1974+
case tp => mapOver(tp)
1975+
}
1976+
}
1977+
val replaceBindings = new ReplaceBindings
1978+
val patType = defn.tupleType(splices.tpes.map(tpe => replaceBindings(tpe.widen)))
1979+
1980+
val typeBindingsTuple = tpd.tupleTypeTree(typeBindings.values.toList)
1981+
1982+
val replaceBindingsInTree = new TreeMap {
1983+
private[this] var bindMap = Map.empty[Symbol, Symbol]
1984+
override def transform(tree: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
1985+
tree match {
1986+
case tree: Bind =>
1987+
val sym = tree.symbol
1988+
val newInfo = replaceBindings(sym.info)
1989+
val newSym = ctx.newSymbol(sym.owner, sym.name, sym.flags, newInfo, sym.privateWithin, sym.coord)
1990+
bindMap += sym -> newSym
1991+
Bind(newSym, transform(tree.body)).withSpan(sym.span)
1992+
case _ =>
1993+
super.transform(tree).withType(replaceBindingsInType(tree.tpe))
1994+
}
1995+
}
1996+
private[this] val replaceBindingsInType = new ReplaceBindings {
1997+
override def apply(tp: Type): Type = tp match {
1998+
case tp: TermRef => bindMap.get(tp.termSymbol).fold(tp)(_.typeRef)
1999+
case tp => super.apply(tp)
2000+
}
2001+
}
2002+
}
2003+
2004+
val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)
2005+
19702006
UnApply(
1971-
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToType(patType),
2007+
fun = ref(defn.InternalQuotedMatcher_unapplyR).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
19722008
implicits =
19732009
ref(defn.InternalQuoted_exprQuoteR).appliedToType(shape.tpe).appliedTo(shape) ::
2010+
Literal(Constant(typeBindings.nonEmpty)) ::
19742011
implicitArgTree(defn.TastyReflectionType, tree.span) :: Nil,
19752012
patterns = splicePat :: Nil,
19762013
proto = pt)
@@ -1980,8 +2017,25 @@ class Typer extends Namer
19802017
}
19812018
}
19822019

1983-
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Tree, List[Tree]) = {
2020+
def splitQuotePattern(quoted: Tree)(implicit ctx: Context): (Map[Symbol, Bind], Tree, List[Tree]) = {
19842021
val ctx0 = ctx
2022+
2023+
val typeBindings: collection.mutable.Map[Symbol, Bind] = collection.mutable.Map.empty
2024+
val freshTypeBindingsBuff = List.newBuilder[Tree]
2025+
def getBinding(sym: Symbol): Bind =
2026+
typeBindings.getOrElseUpdate(sym, {
2027+
val bindingBounds = sym.info
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 {
@@ -1996,6 +2050,12 @@ class Typer extends Namer
19962050
val pat1 = if (patType eq patType1) pat else pat.withType(patType1)
19972051
patBuf += pat1
19982052
}
2053+
case Select(pat, _) if tree.symbol == defn.QuotedType_splice =>
2054+
val sym = tree.tpe.dealias.typeSymbol.asType
2055+
val tdef = TypeDef(sym).withSpan(sym.span)
2056+
freshTypeBindingsBuff += transformTypeBindingTypeDef(tdef)
2057+
TypeTree(tree.tpe.dealias).withSpan(tree.span)
2058+
19992059
case ddef: ValOrDefDef =>
20002060
if (ddef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot)) {
20012061
val bindingType = ddef.symbol.info match {
@@ -2014,17 +2074,54 @@ class Typer extends Namer
20142074
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingExprTpe)).withSpan(ddef.span)
20152075
}
20162076
super.transform(tree)
2077+
case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
2078+
transformTypeBindingTypeDef(tdef)
20172079
case _ =>
20182080
super.transform(tree)
20192081
}
2082+
2083+
def transformTypeBindingTypeDef(tdef: TypeDef): Tree = {
2084+
val bindingType = getBinding(tdef.symbol).symbol.typeRef
2085+
val bindingTypeTpe = AppliedType(defn.QuotedTypeType, bindingType :: Nil)
2086+
assert(tdef.name.startsWith("$"))
2087+
val bindName = tdef.name.toString.stripPrefix("$").toTermName
2088+
val sym = ctx0.newPatternBoundSymbol(bindName, bindingTypeTpe, tdef.span)
2089+
patBuf += Bind(sym, untpd.Ident(nme.WILDCARD).withType(bindingTypeTpe)).withSpan(tdef.span)
2090+
super.transform(tdef)
2091+
}
20202092
}
2021-
val result = splitter.transform(quoted)
2022-
(result, splitter.patBuf.toList)
2093+
val shape0 = splitter.transform(quoted)
2094+
val patterns = splitter.patBuf.toList
2095+
val freshTypeBindings = freshTypeBindingsBuff.result()
2096+
2097+
val shape1 = seq(
2098+
freshTypeBindings,
2099+
shape0
2100+
)
2101+
val shape2 = {
2102+
if (freshTypeBindings.isEmpty) shape1
2103+
else {
2104+
val isFreshTypeBindings = freshTypeBindings.map(_.symbol).toSet
2105+
new TreeTypeMap(
2106+
typeMap = {
2107+
case tp: TypeRef if tp.typeSymbol == defn.QuotedType_splice =>
2108+
val tp1 = tp.dealias
2109+
if (isFreshTypeBindings(tp1.typeSymbol)) tp1
2110+
else tp
2111+
case tp => tp
2112+
}
2113+
).transform(shape1)
2114+
}
2115+
}
2116+
2117+
(typeBindings.toMap, shape2, patterns)
20232118
}
20242119

20252120
/** A hole the shape pattern of a quoted.Matcher.unapply, representing a splice */
2026-
def patternHole(splice: Tree)(implicit ctx: Context): Tree =
2121+
def patternHole(splice: Tree)(implicit ctx: Context): Tree = {
2122+
val Splice(pat) = splice
20272123
ref(defn.InternalQuoted_patternHoleR).appliedToType(splice.tpe).withSpan(splice.span)
2124+
}
20282125

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

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
}

0 commit comments

Comments
 (0)