@@ -15,11 +15,12 @@ import dotty.tools.dotc.transform.PostTyper
15
15
import dotty .tools .dotc .typer .ImportInfo .{withRootImports , RootRef }
16
16
import dotty .tools .dotc .typer .TyperPhase
17
17
import dotty .tools .dotc .util .Spans ._
18
- import dotty .tools .dotc .util .{ParsedComment , SourceFile }
18
+ import dotty .tools .dotc .util .{ParsedComment , Property , SourceFile }
19
19
import dotty .tools .dotc .{CompilationUnit , Compiler , Run }
20
20
import dotty .tools .repl .results ._
21
21
22
22
import scala .collection .mutable
23
+ import scala .util .chaining .given
23
24
24
25
/** This subclass of `Compiler` is adapted for use in the REPL.
25
26
*
@@ -29,12 +30,14 @@ import scala.collection.mutable
29
30
* - provides utility to query the type of an expression
30
31
* - provides utility to query the documentation of an expression
31
32
*/
32
- class ReplCompiler extends Compiler {
33
+ class ReplCompiler extends Compiler :
33
34
34
35
override protected def frontendPhases : List [List [Phase ]] = List (
35
- List (new TyperPhase (addRootImports = false )),
36
- List (new CollectTopLevelImports ),
37
- List (new PostTyper ),
36
+ List (Parser ()),
37
+ List (ReplPhase ()),
38
+ List (TyperPhase (addRootImports = false )),
39
+ List (CollectTopLevelImports ()),
40
+ List (PostTyper ()),
38
41
)
39
42
40
43
def newRun (initCtx : Context , state : State ): Run =
@@ -46,7 +49,7 @@ class ReplCompiler extends Compiler {
46
49
47
50
def importPreviousRun (id : Int )(using Context ) = {
48
51
// we first import the wrapper object id
49
- val path = nme.EMPTY_PACKAGE ++ " ." ++ objectNames(id)
52
+ val path = nme.EMPTY_PACKAGE ++ " ." ++ ReplCompiler . objectNames(id)
50
53
val ctx0 = ctx.fresh
51
54
.setNewScope
52
55
.withRootImports(RootRef (() => requiredModuleRef(path)) :: Nil )
@@ -67,117 +70,29 @@ class ReplCompiler extends Compiler {
67
70
}
68
71
run.suppressions.initSuspendedMessages(state.context.run)
69
72
run
73
+ end newRun
70
74
71
- private val objectNames = mutable.Map .empty[Int , TermName ]
75
+ private def packaged (stats : List [untpd.Tree ])(using Context ): untpd.PackageDef =
76
+ import untpd .*
77
+ PackageDef (Ident (nme.EMPTY_PACKAGE ), stats)
72
78
73
- private case class Definitions (stats : List [untpd.Tree ], state : State )
74
-
75
- private def definitions (trees : List [untpd.Tree ], state : State ): Definitions = inContext(state.context) {
76
- import untpd ._
77
-
78
- // If trees is of the form `{ def1; def2; def3 }` then `List(def1, def2, def3)`
79
- val flattened = trees match {
80
- case List (Block (stats, expr)) =>
81
- if (expr eq EmptyTree ) stats // happens when expr is not an expression
82
- else stats :+ expr
83
- case _ =>
84
- trees
85
- }
86
-
87
- var valIdx = state.valIndex
88
- val defs = new mutable.ListBuffer [Tree ]
89
-
90
- /** If the user inputs a definition whose name is of the form REPL_RES_PREFIX and a number,
91
- * such as `val res9 = 1`, we bump `valIdx` to avoid name clashes. lampepfl/dotty#3536 */
92
- def maybeBumpValIdx (tree : Tree ): Unit = tree match
93
- case apply : Apply => for a <- apply.args do maybeBumpValIdx(a)
94
- case tuple : Tuple => for t <- tuple.trees do maybeBumpValIdx(t)
95
- case patDef : PatDef => for p <- patDef.pats do maybeBumpValIdx(p)
96
- case tree : NameTree => tree.name.show.stripPrefix(str.REPL_RES_PREFIX ).toIntOption match
97
- case Some (n) if n >= valIdx => valIdx = n + 1
98
- case _ =>
99
- case _ =>
100
-
101
- flattened.foreach {
102
- case expr @ Assign (id : Ident , _) =>
103
- // special case simple reassignment (e.g. x = 3)
104
- // in order to print the new value in the REPL
105
- val assignName = (id.name ++ str.REPL_ASSIGN_SUFFIX ).toTermName
106
- val assign = ValDef (assignName, TypeTree (), id).withSpan(expr.span)
107
- defs += expr += assign
108
- case expr if expr.isTerm =>
109
- val resName = (str.REPL_RES_PREFIX + valIdx).toTermName
110
- valIdx += 1
111
- val vd = ValDef (resName, TypeTree (), expr).withSpan(expr.span)
112
- defs += vd
113
- case other =>
114
- maybeBumpValIdx(other)
115
- defs += other
116
- }
117
-
118
- Definitions (
119
- defs.toList,
120
- state.copy(
121
- objectIndex = state.objectIndex + 1 ,
122
- valIndex = valIdx
123
- )
124
- )
125
- }
126
-
127
- /** Wrap trees in an object and add imports from the previous compilations
128
- *
129
- * The resulting structure is something like:
130
- *
131
- * ```
132
- * package <none> {
133
- * object rs$line$nextId {
134
- * import rs$line${i <- 0 until nextId}._
135
- *
136
- * <trees>
137
- * }
138
- * }
139
- * ```
140
- */
141
- private def wrapped (defs : Definitions , objectTermName : TermName , span : Span ): untpd.PackageDef =
142
- inContext(defs.state.context) {
143
- import untpd ._
144
-
145
- val tmpl = Template (emptyConstructor, Nil , Nil , EmptyValDef , defs.stats)
146
- val module = ModuleDef (objectTermName, tmpl)
147
- .withSpan(span)
79
+ final def compile (parsed : Parsed )(using state : State ): Result [(CompilationUnit , State )] =
80
+ assert(! parsed.trees.isEmpty)
148
81
149
- PackageDef (Ident (nme.EMPTY_PACKAGE ), List (module))
82
+ given Context = state.context
83
+ val unit = ReplCompilationUnit (ctx.source).tap { unit =>
84
+ unit.untpdTree = packaged(parsed.trees)
85
+ unit.untpdTree.putAttachment(ReplCompiler .ReplState , state)
150
86
}
151
-
152
- private def createUnit (defs : Definitions , span : Span )(using Context ): CompilationUnit = {
153
- val objectName = ctx.source.file.toString
154
- assert(objectName.startsWith(str.REPL_SESSION_LINE ))
155
- assert(objectName.endsWith(defs.state.objectIndex.toString))
156
- val objectTermName = ctx.source.file.toString.toTermName
157
- objectNames.update(defs.state.objectIndex, objectTermName)
158
-
159
- val unit = new ReplCompilationUnit (ctx.source)
160
- unit.untpdTree = wrapped(defs, objectTermName, span)
161
- unit
162
- }
163
-
164
- private def runCompilationUnit (unit : CompilationUnit , state : State ): Result [(CompilationUnit , State )] = {
165
- val ctx = state.context
166
87
ctx.run.nn.compileUnits(unit :: Nil )
167
- ctx.run.nn.printSummary() // this outputs "2 errors found" like normal - but we might decide that's needlessly noisy for the REPL
168
-
169
- if (! ctx.reporter.hasErrors) (unit, state).result
170
- else ctx.reporter.removeBufferedMessages(using ctx).errors
171
- }
88
+ ctx.run.nn.printSummary() // "2 errors found"
172
89
173
- final def compile (parsed : Parsed )(implicit state : State ): Result [(CompilationUnit , State )] = {
174
- assert(! parsed.trees.isEmpty)
175
- val defs = definitions(parsed.trees, state)
176
- val unit = createUnit(defs, Span (0 , parsed.trees.last.span.end))(using state.context)
177
- runCompilationUnit(unit, defs.state)
178
- }
90
+ val newState = unit.tpdTree.getAttachment(ReplCompiler .ReplState ).get
91
+ if ! ctx.reporter.hasErrors then (unit, newState).result
92
+ else ctx.reporter.removeBufferedMessages.errors
93
+ end compile
179
94
180
- final def typeOf (expr : String )(implicit state : State ): Result [String ] =
95
+ final def typeOf (expr : String )(using state : State ): Result [String ] =
181
96
typeCheck(expr).map { tree =>
182
97
given Context = state.context
183
98
tree.rhs match {
@@ -190,7 +105,7 @@ class ReplCompiler extends Compiler {
190
105
}
191
106
}
192
107
193
- def docOf (expr : String )(implicit state : State ): Result [String ] = inContext(state.context) {
108
+ def docOf (expr : String )(using state : State ): Result [String ] = inContext(state.context) {
194
109
195
110
/** Extract the "selected" symbol from `tree`.
196
111
*
@@ -237,7 +152,7 @@ class ReplCompiler extends Compiler {
237
152
}
238
153
}
239
154
240
- final def typeCheck (expr : String , errorsAllowed : Boolean = false )(implicit state : State ): Result [tpd.ValDef ] = {
155
+ final def typeCheck (expr : String , errorsAllowed : Boolean = false )(using state : State ): Result [tpd.ValDef ] = {
241
156
242
157
def wrapped (expr : String , sourceFile : SourceFile , state : State )(using Context ): Result [untpd.PackageDef ] = {
243
158
def wrap (trees : List [untpd.Tree ]): untpd.PackageDef = {
@@ -300,4 +215,117 @@ class ReplCompiler extends Compiler {
300
215
}
301
216
}
302
217
}
303
- }
218
+ object ReplCompiler :
219
+ val ReplState : Property .StickyKey [State ] = Property .StickyKey ()
220
+ val objectNames = mutable.Map .empty[Int , TermName ]
221
+ end ReplCompiler
222
+
223
+ class ReplCompilationUnit (source : SourceFile ) extends CompilationUnit (source):
224
+ override def isSuspendable : Boolean = false
225
+
226
+ /** A placeholder phase that receives parse trees..
227
+ *
228
+ * It is called "parser" for the convenience of collective muscle memory.
229
+ *
230
+ * This enables -Vprint:parser.
231
+ */
232
+ class Parser extends Phase :
233
+ def phaseName : String = " parser"
234
+ def run (using Context ): Unit = ()
235
+ end Parser
236
+
237
+ /** A phase that assembles wrapped parse trees from user input.
238
+ *
239
+ * This enables -Vprint:repl so that users can see how their code snippet was wrapped.
240
+ */
241
+ class ReplPhase extends Phase :
242
+ def phaseName : String = " repl"
243
+
244
+ def run (using Context ): Unit =
245
+ ctx.compilationUnit.untpdTree match
246
+ case pkg @ PackageDef (_, stats) =>
247
+ given State = pkg.getAttachment(ReplCompiler .ReplState ).get
248
+ val defs = definitions(stats)
249
+ val res = wrapped(defs, Span (0 , stats.last.span.end))
250
+ res.putAttachment(ReplCompiler .ReplState , defs.state)
251
+ ctx.compilationUnit.untpdTree = res
252
+ case _ =>
253
+ end run
254
+
255
+ private case class Definitions (stats : List [untpd.Tree ], state : State )
256
+
257
+ private def definitions (trees : List [untpd.Tree ])(using Context , State ): Definitions =
258
+ import untpd .*
259
+
260
+ // If trees is of the form `{ def1; def2; def3 }` then `List(def1, def2, def3)`
261
+ val flattened = trees match {
262
+ case List (Block (stats, expr)) =>
263
+ if (expr eq EmptyTree ) stats // happens when expr is not an expression
264
+ else stats :+ expr
265
+ case _ =>
266
+ trees
267
+ }
268
+
269
+ val state = summon[State ]
270
+ var valIdx = state.valIndex
271
+ val defs = mutable.ListBuffer .empty[Tree ]
272
+
273
+ /** If the user inputs a definition whose name is of the form REPL_RES_PREFIX and a number,
274
+ * such as `val res9 = 1`, we bump `valIdx` to avoid name clashes. lampepfl/dotty#3536 */
275
+ def maybeBumpValIdx (tree : Tree ): Unit = tree match
276
+ case apply : Apply => for a <- apply.args do maybeBumpValIdx(a)
277
+ case tuple : Tuple => for t <- tuple.trees do maybeBumpValIdx(t)
278
+ case patDef : PatDef => for p <- patDef.pats do maybeBumpValIdx(p)
279
+ case tree : NameTree => tree.name.show.stripPrefix(str.REPL_RES_PREFIX ).toIntOption match
280
+ case Some (n) if n >= valIdx => valIdx = n + 1
281
+ case _ =>
282
+ case _ =>
283
+
284
+ flattened.foreach {
285
+ case expr @ Assign (id : Ident , _) =>
286
+ // special case simple reassignment (e.g. x = 3)
287
+ // in order to print the new value in the REPL
288
+ val assignName = (id.name ++ str.REPL_ASSIGN_SUFFIX ).toTermName
289
+ val assign = ValDef (assignName, TypeTree (), id).withSpan(expr.span)
290
+ defs += expr += assign
291
+ case expr if expr.isTerm =>
292
+ val resName = (str.REPL_RES_PREFIX + valIdx).toTermName
293
+ valIdx += 1
294
+ val vd = ValDef (resName, TypeTree (), expr).withSpan(expr.span)
295
+ defs += vd
296
+ case other =>
297
+ maybeBumpValIdx(other)
298
+ defs += other
299
+ }
300
+
301
+ Definitions (defs.toList, state.copy(objectIndex = state.objectIndex + 1 , valIndex = valIdx))
302
+ end definitions
303
+
304
+ /** Wrap trees in an object and add imports from the previous compilations.
305
+ *
306
+ * The resulting structure is something like:
307
+ *
308
+ * ```
309
+ * package <none> {
310
+ * object rs$line$nextId {
311
+ * import rs$line${i <- 0 until nextId}.*
312
+ *
313
+ * <trees>
314
+ * }
315
+ * }
316
+ * ```
317
+ */
318
+ private def wrapped (defs : Definitions , span : Span )(using Context ): untpd.PackageDef =
319
+ import untpd .*
320
+
321
+ val objectName = ctx.source.file.toString
322
+ assert(objectName.startsWith(str.REPL_SESSION_LINE ))
323
+ assert(objectName.endsWith(defs.state.objectIndex.toString))
324
+ val objectTermName = objectName.toTermName
325
+ ReplCompiler .objectNames.update(defs.state.objectIndex, objectTermName)
326
+
327
+ val tmpl = Template (emptyConstructor, Nil , Nil , EmptyValDef , defs.stats)
328
+ val module = ModuleDef (objectTermName, tmpl).withSpan(span)
329
+
330
+ PackageDef (Ident (nme.EMPTY_PACKAGE ), List (module))
331
+ end ReplPhase
0 commit comments