@@ -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,121 @@ 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
+ * Ths `ReplState` attachment indicates Repl wrapping is required.
240
+ *
241
+ * This enables -Vprint:repl so that users can see how their code snippet was wrapped.
242
+ */
243
+ class ReplPhase extends Phase :
244
+ def phaseName : String = " repl"
245
+
246
+ def run (using Context ): Unit =
247
+ ctx.compilationUnit.untpdTree match
248
+ case pkg @ PackageDef (_, stats) =>
249
+ pkg.getAttachment(ReplCompiler .ReplState ).foreach {
250
+ case given State =>
251
+ val defs = definitions(stats)
252
+ val res = wrapped(defs, Span (0 , stats.last.span.end))
253
+ res.putAttachment(ReplCompiler .ReplState , defs.state)
254
+ ctx.compilationUnit.untpdTree = res
255
+ }
256
+ case _ =>
257
+ end run
258
+
259
+ private case class Definitions (stats : List [untpd.Tree ], state : State )
260
+
261
+ private def definitions (trees : List [untpd.Tree ])(using Context , State ): Definitions =
262
+ import untpd .*
263
+
264
+ // If trees is of the form `{ def1; def2; def3 }` then `List(def1, def2, def3)`
265
+ val flattened = trees match {
266
+ case List (Block (stats, expr)) =>
267
+ if (expr eq EmptyTree ) stats // happens when expr is not an expression
268
+ else stats :+ expr
269
+ case _ =>
270
+ trees
271
+ }
272
+
273
+ val state = summon[State ]
274
+ var valIdx = state.valIndex
275
+ val defs = mutable.ListBuffer .empty[Tree ]
276
+
277
+ /** If the user inputs a definition whose name is of the form REPL_RES_PREFIX and a number,
278
+ * such as `val res9 = 1`, we bump `valIdx` to avoid name clashes. lampepfl/dotty#3536 */
279
+ def maybeBumpValIdx (tree : Tree ): Unit = tree match
280
+ case apply : Apply => for a <- apply.args do maybeBumpValIdx(a)
281
+ case tuple : Tuple => for t <- tuple.trees do maybeBumpValIdx(t)
282
+ case patDef : PatDef => for p <- patDef.pats do maybeBumpValIdx(p)
283
+ case tree : NameTree => tree.name.show.stripPrefix(str.REPL_RES_PREFIX ).toIntOption match
284
+ case Some (n) if n >= valIdx => valIdx = n + 1
285
+ case _ =>
286
+ case _ =>
287
+
288
+ flattened.foreach {
289
+ case expr @ Assign (id : Ident , _) =>
290
+ // special case simple reassignment (e.g. x = 3)
291
+ // in order to print the new value in the REPL
292
+ val assignName = (id.name ++ str.REPL_ASSIGN_SUFFIX ).toTermName
293
+ val assign = ValDef (assignName, TypeTree (), id).withSpan(expr.span)
294
+ defs += expr += assign
295
+ case expr if expr.isTerm =>
296
+ val resName = (str.REPL_RES_PREFIX + valIdx).toTermName
297
+ valIdx += 1
298
+ val vd = ValDef (resName, TypeTree (), expr).withSpan(expr.span)
299
+ defs += vd
300
+ case other =>
301
+ maybeBumpValIdx(other)
302
+ defs += other
303
+ }
304
+
305
+ Definitions (defs.toList, state.copy(objectIndex = state.objectIndex + 1 , valIndex = valIdx))
306
+ end definitions
307
+
308
+ /** Wrap trees in an object and add imports from the previous compilations.
309
+ *
310
+ * The resulting structure is something like:
311
+ *
312
+ * ```
313
+ * package <none> {
314
+ * object rs$line$nextId {
315
+ * import rs$line${i <- 0 until nextId}.*
316
+ *
317
+ * <trees>
318
+ * }
319
+ * }
320
+ * ```
321
+ */
322
+ private def wrapped (defs : Definitions , span : Span )(using Context ): untpd.PackageDef =
323
+ import untpd .*
324
+
325
+ val objectName = ctx.source.file.toString
326
+ assert(objectName.startsWith(str.REPL_SESSION_LINE ))
327
+ assert(objectName.endsWith(defs.state.objectIndex.toString))
328
+ val objectTermName = objectName.toTermName
329
+ ReplCompiler .objectNames.update(defs.state.objectIndex, objectTermName)
330
+
331
+ val tmpl = Template (emptyConstructor, Nil , Nil , EmptyValDef , defs.stats)
332
+ val module = ModuleDef (objectTermName, tmpl).withSpan(span)
333
+
334
+ PackageDef (Ident (nme.EMPTY_PACKAGE ), List (module))
335
+ end ReplPhase
0 commit comments