summaryrefslogtreecommitdiff
path: root/src/compiler/scala/reflect/quasiquotes/Placeholders.scala
blob: bc4f9542751931d378bc5d7104fe0b58d3ef5ba4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package scala.reflect
package quasiquotes

import java.util.UUID.randomUUID
import scala.collection.mutable

/** Emulates hole support (see Holes.scala) in the quasiquote parser (see Parsers.scala).
 *  A principled solution to splicing into Scala syntax would be a parser that natively supports holes.
 *  Unfortunately, that's outside of our reach in Scala 2.11, so we have to emulate.
 *  This trait stores knowledge of how to represent the holes as something understandable by the parser
 *  and how to recover holes from the results of parsing the produced representation.
 */
trait Placeholders { self: Quasiquotes =>
  import global._
  import Rank._
  import universeTypes._

  // Step 1: Transform Scala source with holes into vanilla Scala source

  lazy val posMap = mutable.LinkedHashMap[Position, (Int, Int)]()
  lazy val code = {
    val sb = new StringBuilder()
    val sessionSuffix = randomUUID().toString.replace("-", "").substring(0, 8) + "$"

    def appendPart(value: String, pos: Position) = {
      val start = sb.length
      sb.append(value)
      val end = sb.length
      posMap += pos -> ((start, end))
    }

    def appendHole(tree: Tree, rank: Rank) = {
      val placeholderName = c.freshName(TermName(nme.QUASIQUOTE_PREFIX + sessionSuffix))
      sb.append(placeholderName)
      val holeTree =
        if (method != nme.unapply) tree
        else Bind(placeholderName, tree)
      holeMap(placeholderName) = Hole(rank, holeTree)
    }

    val iargs = method match {
      case nme.apply   => args
      case nme.unapply => internal.subpatterns(args.head).get
      case _           => global.abort("unreachable")
    }

    foreach2(iargs, parts.init) { case (tree, (p, pos)) =>
      val (part, rank) = parseDots(p)
      appendPart(part, pos)
      appendHole(tree, rank)
    }
    val (p, pos) = parts.last
    appendPart(p, pos)

    sb.toString
  }

  object holeMap {
    private val underlying = mutable.LinkedHashMap.empty[String, Hole]
    private val accessed   = mutable.Set.empty[String]
    def unused: Set[Name] = (underlying.keys.toSet -- accessed).map(TermName(_))
    def contains(key: Name): Boolean = underlying.contains(key.toString)
    def apply(key: Name): Hole = {
      val skey = key.toString
      val value = underlying(skey)
      accessed += skey
      value
    }
    def update(key: Name, hole: Hole) =
      underlying += key.toString -> hole
    def get(key: Name): Option[Hole] = {
      val skey = key.toString
      underlying.get(skey).map { v =>
        accessed += skey
        v
      }
    }
    def keysIterator: Iterator[TermName] = underlying.keysIterator.map(TermName(_))
  }

  // Step 2: Transform vanilla Scala AST into an AST with holes

  trait HolePlaceholder {
    def matching: PartialFunction[Any, Name]
    def unapply(scrutinee: Any): Option[Hole] = {
      val name = matching.lift(scrutinee)
      name.flatMap { holeMap.get(_) }
    }
  }

  object Placeholder extends HolePlaceholder {
    def matching = {
      case name: Name => name
      case Ident(name) => name
      case Bind(name, Ident(nme.WILDCARD)) => name
      case TypeDef(_, name, List(), TypeBoundsTree(EmptyTree, EmptyTree)) => name
    }
  }

  object ModsPlaceholder extends HolePlaceholder {
    def apply(name: Name) =
      Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(name.toString))))
    def matching = {
      case Apply(Select(New(Ident(tpnme.QUASIQUOTE_MODS)), nme.CONSTRUCTOR), List(Literal(Constant(s: String)))) => TermName(s)
    }
  }

  object AnnotPlaceholder extends HolePlaceholder {
    def matching = {
      case Apply(Select(New(Ident(name)), nme.CONSTRUCTOR), Nil) => name
    }
  }

  object ParamPlaceholder extends HolePlaceholder {
    def apply(flags: FlagSet, name: Name) =
      ValDef(Modifiers(flags), nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree)
    def matching = {
      case ValDef(_, nme.QUASIQUOTE_PARAM, Ident(name), EmptyTree) => name
    }
  }

  object TuplePlaceholder {
    def apply(args: List[Tree]) =
      Apply(Ident(nme.QUASIQUOTE_TUPLE), args)
    def unapply(tree: Tree): Option[List[Tree]] = tree match {
      case Apply(Ident(nme.QUASIQUOTE_TUPLE), args) => Some(args)
      case _ => None
    }
  }

  object TupleTypePlaceholder {
    def apply(args: List[Tree]) =
      AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args)
    def unapply(tree: Tree): Option[List[Tree]] = tree match {
      case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_TUPLE), args) => Some(args)
      case _ => None
    }
  }

  object FunctionTypePlaceholder {
    def apply(args: List[Tree], res: Tree) =
      AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res)
    def unapply(tree: Tree): Option[(List[Tree], Tree)] = tree match {
      case AppliedTypeTree(Ident(tpnme.QUASIQUOTE_FUNCTION), args :+ res) => Some((args, res))
      case _ => None
    }
  }

  object SymbolPlaceholder {
    def unapply(scrutinee: Any): Option[Hole] = scrutinee match {
      case Placeholder(hole: ApplyHole) if hole.tpe <:< symbolType => Some(hole)
      case _ => None
    }
  }

  object CasePlaceholder {
    def apply(name: Name) =
      CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), Ident(name) :: Nil), EmptyTree, EmptyTree)
    def unapply(tree: Tree): Option[Hole] = tree match {
      case CaseDef(Apply(Ident(nme.QUASIQUOTE_CASE), List(Placeholder(hole))), EmptyTree, EmptyTree) => Some(hole)
      case _ => None
    }
  }

  object RefineStatPlaceholder {
    def apply(name: Name) =
      ValDef(NoMods, nme.QUASIQUOTE_REFINE_STAT, Ident(name), EmptyTree)
    def unapply(tree: Tree): Option[Hole] = tree match {
      case ValDef(_, nme.QUASIQUOTE_REFINE_STAT, Ident(Placeholder(hole)), _) => Some(hole)
      case _ => None
    }
  }

  object EarlyDefPlaceholder {
    def apply(name: Name) =
      ValDef(Modifiers(Flag.PRESUPER), nme.QUASIQUOTE_EARLY_DEF, Ident(name), EmptyTree)
    def unapply(tree: Tree): Option[Hole] = tree match {
      case ValDef(_, nme.QUASIQUOTE_EARLY_DEF, Ident(Placeholder(hole)), _) => Some(hole)
      case _ => None
    }
  }

  object PackageStatPlaceholder {
    def apply(name: Name) =
      ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(name), EmptyTree)
    def unapply(tree: Tree): Option[Hole] = tree match {
      case ValDef(NoMods, nme.QUASIQUOTE_PACKAGE_STAT, Ident(Placeholder(hole)), EmptyTree) => Some(hole)
      case _ => None
    }
  }

  object ForEnumPlaceholder {
    def apply(name: Name) =
      build.SyntacticValFrom(Bind(name, Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM))
    def unapply(tree: Tree): Option[Hole] = tree match {
      case build.SyntacticValFrom(Bind(Placeholder(hole), Ident(nme.WILDCARD)), Ident(nme.QUASIQUOTE_FOR_ENUM)) =>
        Some(hole)
      case _ => None
    }
  }
}