Skip to content

Commit caec229

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

File tree

13 files changed

+275
-40
lines changed

13 files changed

+275
-40
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: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
11
object Test {
22

33
val x = '{1 + 2}
4-
5-
def f(x: Int) = x
6-
def g(x: Int, y: Int) = x * y
4+
//
5+
// def f(x: Int) = x
6+
// def g(x: Int, y: Int) = x * y
77

88
def res given tasty.Reflection: quoted.Expr[Int] = x match {
9-
case '{1 + 2} => '{0}
10-
case '{f($y)} => y
11-
case '{g($y, $z)} => '{$y * $z}
12-
case '{ ((a: Int) => 3)($y) } => y
13-
case '{ 1 + ($y: Int)} => y
14-
case '{ val a = 1 + ($y: Int); 3 } => y
15-
case '{ val $y: Int = $z; println(`$y`); 1 } =>
16-
val a: quoted.matching.Bind[Int] = y
17-
z
18-
case '{ (($y: Int) => 1 + `$y` + ($z: Int))(2) } =>
19-
val a: quoted.matching.Bind[Int] = y
20-
z
21-
case '{ def $ff: Int = $z; `$ff` } =>
22-
val a: quoted.matching.Bind[Int] = ff
23-
z
24-
case '{ def $ff(i: Int): Int = $z; 2 } =>
25-
val a: quoted.matching.Bind[Int => Int] = ff
26-
z
27-
case '{ def $ff(i: Int)(j: Int): Int = $z; 2 } =>
28-
val a: quoted.matching.Bind[Int => Int => Int] = ff
29-
z
30-
case '{ def $ff[T](i: T): Int = $z; 2 } =>
31-
val a: quoted.matching.Bind[[T] => T => Int] = ff
32-
z
9+
// case '{1 + 2} => '{0}
10+
// case '{f($y)} => y
11+
// case '{g($y, $z)} => '{$y * $z}
12+
// case '{ ((a: Int) => 3)($y) } => y
13+
// case '{ 1 + ($y: Int)} => y
14+
// case '{ val a = 1 + ($y: Int); 3 } => y
15+
// case '{ val $y: Int = $z; println(`$y`); 1 } =>
16+
// val a: quoted.matching.Bind[Int] = y
17+
// z
18+
// case '{ (($y: Int) => 1 + `$y` + ($z: Int))(2) } =>
19+
// val a: quoted.matching.Bind[Int] = y
20+
// z
21+
// case '{ def $ff: Int = $z; `$ff` } =>
22+
// val a: quoted.matching.Bind[Int] = ff
23+
// z
24+
// case '{ def $ff(i: Int): Int = $z; 2 } =>
25+
// val a: quoted.matching.Bind[Int => Int] = ff
26+
// z
27+
// case '{ def $ff(i: Int)(j: Int): Int = $z; 2 } =>
28+
// val a: quoted.matching.Bind[Int => Int => Int] = ff
29+
// z
30+
// case '{ def $ff[T](i: T): Int = $z; 2 } =>
31+
// val a: quoted.matching.Bind[[T] => T => Int] = ff
32+
// z
33+
// case '{ poly[$t]($x); 2 } => ???
34+
// case '{ val x: $t = $a; val y: `$t` = x; 1 } => ???
35+
case '{ type $t; val x: $t = $a; val y: $t = x; 1 } => ???
36+
// case '{ type $t; val x: $t = $a; val y: $t = x; 1 } => ???
3337
case _ => '{1}
3438
}
39+
40+
def poly[T](x: T): Unit = ()
3541
}

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

0 commit comments

Comments
 (0)