Skip to content

Add quoted type patterns #6930

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -640,9 +640,14 @@ class Definitions {
@tu lazy val InternalQuoted_patternHole: Symbol = InternalQuotedModule.requiredMethod("patternHole")
@tu lazy val InternalQuoted_patternBindHoleAnnot: ClassSymbol = InternalQuotedModule.requiredClass("patternBindHole")
@tu lazy val InternalQuoted_QuoteTypeTagAnnot: ClassSymbol = InternalQuotedModule.requiredClass("quoteTypeTag")
@tu lazy val InternalQuoted_fromAboveAnnot: ClassSymbol = InternalQuotedModule.requiredClass("fromAbove")

@tu lazy val InternalQuotedMatcherModule: Symbol = ctx.requiredModule("scala.internal.quoted.Matcher")
@tu lazy val InternalQuotedMatcher_unapply: Symbol = InternalQuotedMatcherModule.requiredMethod(nme.unapply)

@tu lazy val InternalQuotedExprModule: Symbol = ctx.requiredModule("scala.internal.quoted.Expr")
@tu lazy val InternalQuotedExpr_unapply: Symbol = InternalQuotedExprModule.requiredMethod(nme.unapply)

@tu lazy val InternalQuotedTypeModule: Symbol = ctx.requiredModule("scala.internal.quoted.Type")
@tu lazy val InternalQuotedType_unapply: Symbol = InternalQuotedTypeModule.requiredMethod(nme.unapply)

@tu lazy val QuotedTypeClass: ClassSymbol = ctx.requiredClass("scala.quoted.Type")
@tu lazy val QuotedType_splice: Symbol = QuotedTypeClass.requiredType(tpnme.splice)
Expand Down
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,13 @@ object PickledQuotes {
val unpickled = unpickle(tastyBytes, args, isType = true)(ctx.addMode(Mode.ReadPositions))
val tpt = unpickled match {
case Block(aliases, tpt) =>
// `@quoteTypeTag type` aliasses are not required after unpickling
tpt
// `@quoteTypeTag type` aliases are not required after unpickling.
// Type definitions are placeholders for type holes in the pickled quote, at this point
// those holes have been filled. As we already dealias al references to them in `dealiasTypeTags`
// there is no need to keep their definitions in the tree. As artifacts of quote reification
// they also do not have a meaningful position in the source.
val aliases1 = aliases.filter(!_.symbol.hasAnnotation(defn.InternalQuoted_QuoteTypeTagAnnot))
seq(aliases1, tpt)
case tpt => tpt
}
tpt.withType(dealiasTypeTags(tpt.tpe))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1830,6 +1830,7 @@ class ReflectionCompilerInterface(val rootContext: core.Contexts.Context) extend

def Definitions_InternalQuoted_patternHole: Symbol = defn.InternalQuoted_patternHole
def Definitions_InternalQuoted_patternBindHoleAnnot: Symbol = defn.InternalQuoted_patternBindHoleAnnot
def Definitions_InternalQuoted_fromAboveAnnot: Symbol = defn.InternalQuoted_fromAboveAnnot

// Types

Expand Down
44 changes: 33 additions & 11 deletions compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ trait QuotesAndSplices {
case _ =>
}
val tree1 =
if (tree.quoted.isType) typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(quoteContext)
else if (ctx.mode.is(Mode.Pattern) && level == 0) typedQuotePattern(tree, pt)
if (ctx.mode.is(Mode.Pattern) && level == 0) typedQuotePattern(tree, pt)
else if (tree.quoted.isType) typedTypeApply(untpd.TypeApply(untpd.ref(defn.InternalQuoted_typeQuote.termRef), tree.quoted :: Nil), pt)(quoteContext)
else typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuote.termRef), tree.quoted), pt)(quoteContext)
tree1.withSpan(tree.span)
}
Expand Down Expand Up @@ -163,6 +163,16 @@ trait QuotesAndSplices {
})

object splitter extends tpd.TreeMap {
private var variance: Int = 1

@forceInline private def atVariance[T](v: Int)(op: => T): T = {
val saved = variance
variance = v
val res = op
variance = saved
res
}

val patBuf = new mutable.ListBuffer[Tree]
val freshTypePatBuf = new mutable.ListBuffer[Tree]
val freshTypeBindingsBuff = new mutable.ListBuffer[Tree]
Expand Down Expand Up @@ -206,11 +216,21 @@ trait QuotesAndSplices {
super.transform(tree)
case tdef: TypeDef if tdef.symbol.hasAnnotation(defn.InternalQuoted_patternBindHoleAnnot) =>
transformTypeBindingTypeDef(tdef, typePatBuf)
case tree @ AppliedTypeTree(tpt, args) =>
val args1: List[Tree] = args.zipWithConserve(tpt.tpe.typeParams.map(_.paramVariance)) { (arg, v) =>
arg.tpe match {
case _: TypeBounds => transform(arg)
case _ => atVariance(variance * v)(transform(arg))
}
}
cpy.AppliedTypeTree(tree)(transform(tpt), args1)
case _ =>
super.transform(tree)
}

def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]]): Tree = {
private def transformTypeBindingTypeDef(tdef: TypeDef, buff: mutable.Builder[Tree, List[Tree]])(implicit ctx: Context): Tree = {
if (variance == -1)
tdef.symbol.addAnnotation(Annotation(New(ref(defn.InternalQuoted_fromAboveAnnot.typeRef)).withSpan(tdef.span)))
val bindingType = getBinding(tdef.symbol).symbol.typeRef
val bindingTypeTpe = AppliedType(defn.QuotedTypeClass.typeRef, bindingType :: Nil)
assert(tdef.name.startsWith("$"))
Expand Down Expand Up @@ -248,7 +268,7 @@ trait QuotesAndSplices {
}

/** Type a quote pattern `case '{ <quoted> } =>` qiven the a current prototype. Typing the pattern
* will also transform it into a call to `scala.internal.quoted.Matcher.unapply`.
* will also transform it into a call to `scala.internal.quoted.Expr.unapply`.
*
* Code directly inside the quote is typed as an expression using Mode.QuotedPattern. Splices
* within the quotes become patterns again and typed acordingly.
Expand All @@ -275,7 +295,7 @@ trait QuotesAndSplices {
* and the patterns in the splices. All these are recombined into a call to `Matcher.unapply`.
*
* ```
* case scala.internal.quoted.Matcher.unapply[
* case scala.internal.quoted.Expr.unapply[
* Tuple1[$t @ _], // Type binging definition
* Tuple2[Type[$t], Expr[List[$t]]] // Typing the result of the pattern match
* ](
Expand Down Expand Up @@ -344,14 +364,16 @@ trait QuotesAndSplices {

val splicePat = typed(untpd.Tuple(splices.map(x => untpd.TypedSplice(replaceBindingsInTree.transform(x)))).withSpan(quoted.span), patType)

val unapplySym = if (tree.quoted.isTerm) defn.InternalQuotedExpr_unapply else defn.InternalQuotedType_unapply
val quoteClass = if (tree.quoted.isTerm) defn.QuotedExprClass else defn.QuotedTypeClass
val quotedPattern =
if (tree.quoted.isTerm) ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx)
else ref(defn.InternalQuoted_typeQuote.termRef).appliedToTypeTrees(shape :: Nil)
UnApply(
fun = ref(defn.InternalQuotedMatcher_unapply.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
implicits =
ref(defn.InternalQuoted_exprQuote.termRef).appliedToType(defn.AnyType).appliedTo(shape).select(nme.apply).appliedTo(qctx) ::
Literal(Constant(typeBindings.nonEmpty)) ::
qctx :: Nil,
fun = ref(unapplySym.termRef).appliedToTypeTrees(typeBindingsTuple :: TypeTree(patType) :: Nil),
implicits = quotedPattern :: Literal(Constant(typeBindings.nonEmpty)) :: qctx :: Nil,
patterns = splicePat :: Nil,
proto = defn.QuotedExprClass.typeRef.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
proto = quoteClass.typeRef.appliedTo(replaceBindings(quoted1.tpe) & quotedPt))
}
}

8 changes: 4 additions & 4 deletions docs/docs/reference/other-new-features/quoted-pattern-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match {
```
In the example above we have `$a` which provides a `Bind[Int]`, `$x` which provides an `Expr[Int]` and `${Bind(`a`)}` which probides an `Expr[Int]` that is pattern matched against `Bind(`a`)` to check that it is a reference to `a`.

Quoted patterns are transformed during typer to a call of `scala.internal.quoted.Matcher.unapply` which splits the quoted code into the patterns and a reifiable quote that will be used as witnesses at runtime.
Quoted patterns are transformed during typer to a call of `scala.internal.quoted.Expr.unapply` which splits the quoted code into the patterns and a reifiable quote that will be used as witnesses at runtime.

```scala
def foo(x: Expr[Int]) given tasty.Reflect: Expr[Int] = x match {
case scala.internal.quoted.Matcher.unapply[Tuple3[Bind[Int], Expr[Int], Expr[Int]]](Tuple3(a, x, Bind(`a`), y))('{ @patternBindHole val a: Int = patternHole[Int]; patternHole[Int] + 1 }) =>
case scala.internal.quoted.Expr.unapply[Tuple3[Bind[Int], Expr[Int], Expr[Int]]](Tuple3(a, x, Bind(`a`), y))('{ @patternBindHole val a: Int = patternHole[Int]; patternHole[Int] + 1 }) =>
}
```


## Runtime semantics

At runtime to a `quoted.Expr` can be matched to another using `scala.internal.quoted.Matcher.unapply`.
At runtime to a `quoted.Expr` can be matched to another using `scala.internal.quoted.Expr.unapply`.

```scala
def unapply[Tup <: Tuple](scrutineeExpr: Expr[_])(implicit patternExpr: Expr[_], reflection: Reflection): Option[Tup]
Expand All @@ -49,7 +49,7 @@ def matched[T](x: T) = Some(Tuple1(x))
def (x: Matching) && (y: Matching) = if (x == None || y == None) None else Some(x.get ++ y.get)
def fold[T](m: Mattching*) given Env: Matching = m.fold(matched)(_ && _)

// `a =#= b` stands for `a` matches `b`
// `a =#= b` stands for `a` matches `b`
def (scrutinee: Tree) =#= pattern: Tree) given Env: Matching // described by cases in the tables below

def envWith(equiv: (Symbol, Symbol)*) given Env: Env // Adds to the current environment the fact that s1 from the scrutinee is equivalent to s2 in the pattern
Expand Down
Loading