@@ -36,78 +36,27 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
36
36
}
37
37
38
38
private [this ] var myCtx : Context = myInitCtx
39
-
40
39
def currentCtx : Context = myCtx
41
40
41
+ private val compiler : Compiler = new InteractiveCompiler
42
+
42
43
private val myOpenedFiles = new mutable.LinkedHashMap [URI , SourceFile ] {
43
44
override def default (key : URI ) = NoSource
44
45
}
46
+ def openedFiles : Map [URI , SourceFile ] = myOpenedFiles
45
47
46
48
private val myOpenedTrees = new mutable.LinkedHashMap [URI , List [SourceTree ]] {
47
49
override def default (key : URI ) = Nil
48
50
}
51
+ def openedTrees : Map [URI , List [SourceTree ]] = myOpenedTrees
49
52
50
53
private val myCompilationUnits = new mutable.LinkedHashMap [URI , CompilationUnit ]
51
-
52
- def openedFiles : Map [URI , SourceFile ] = myOpenedFiles
53
- def openedTrees : Map [URI , List [SourceTree ]] = myOpenedTrees
54
54
def compilationUnits : Map [URI , CompilationUnit ] = myCompilationUnits
55
55
56
- def allTrees (implicit ctx : Context ): List [SourceTree ] = allTreesContaining(" " )
57
-
58
- def allTreesContaining (id : String )(implicit ctx : Context ): List [SourceTree ] = {
59
- val fromSource = openedTrees.values.flatten.toList
60
- val fromClassPath = (dirClassPathClasses ++ zipClassPathClasses).flatMap { cls =>
61
- val className = cls.toTypeName
62
- List (tree(className, id), tree(className.moduleClassName, id)).flatten
63
- }
64
- (fromSource ++ fromClassPath).distinct
65
- }
66
-
67
- private def tree (className : TypeName , id : String )(implicit ctx : Context ): Option [SourceTree ] = {
68
- val clsd = ctx.base.staticRef(className)
69
- clsd match {
70
- case clsd : ClassDenotation =>
71
- clsd.ensureCompleted()
72
- SourceTree .fromSymbol(clsd.symbol.asClass, id)
73
- case _ =>
74
- None
75
- }
76
- }
77
-
78
56
// Presence of a file with one of these suffixes indicates that the
79
57
// corresponding class has been pickled with TASTY.
80
58
private val tastySuffixes = List (" .hasTasty" , " .tasty" )
81
59
82
- private def classNames (cp : ClassPath , packageName : String ): List [String ] = {
83
- def className (classSegments : List [String ]) =
84
- classSegments.mkString(" ." ).stripSuffix(" .class" )
85
-
86
- val ClassPathEntries (pkgs, classReps) = cp.list(packageName)
87
-
88
- classReps
89
- .filter((classRep : ClassRepresentation ) => classRep.binary match {
90
- case None =>
91
- true
92
- case Some (binFile) =>
93
- val prefix =
94
- if (binFile.name.endsWith(" .class" ))
95
- binFile.name.stripSuffix(" .class" )
96
- else
97
- null
98
- prefix != null && {
99
- binFile match {
100
- case pf : PlainFile =>
101
- tastySuffixes.map(suffix => pf.givenPath.parent / (prefix + suffix)).exists(_.exists)
102
- case _ =>
103
- sys.error(s " Unhandled file type: $binFile [getClass = ${binFile.getClass}] " )
104
- }
105
- }
106
- })
107
- .map(classRep => (packageName ++ (if (packageName != " " ) " ." else " " ) ++ classRep.name)).toList ++
108
- pkgs.flatMap(pkg => classNames(cp, pkg.name))
109
- }
110
-
111
60
// FIXME: All the code doing classpath handling is very fragile and ugly,
112
61
// improving this requires changing the dotty classpath APIs to handle our usecases.
113
62
// We also need something like sbt server-mode to be informed of changes on
@@ -128,46 +77,173 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
128
77
}
129
78
130
79
// Like in `ZipArchiveFileLookup` we assume that zips are immutable
131
- private val zipClassPathClasses : Seq [String ] = zipClassPaths.flatMap { zipCp =>
132
- val zipFile = new ZipFile (zipCp.zipFile)
80
+ private val zipClassPathClasses : Seq [TypeName ] = {
81
+ val names = new mutable.ListBuffer [TypeName ]
82
+ zipClassPaths.foreach { zipCp =>
83
+ val zipFile = new ZipFile (zipCp.zipFile)
84
+ classesFromZip(zipFile, names)
85
+ }
86
+ names
87
+ }
88
+
89
+ initialize()
90
+
91
+ /**
92
+ * The trees for all the source files in this project.
93
+ *
94
+ * This includes the trees for the buffers that are presently open in the IDE, and the trees
95
+ * from the target directory.
96
+ */
97
+ def sourceTrees (implicit ctx : Context ): List [SourceTree ] = sourceTreesContaining(" " )
98
+
99
+ /**
100
+ * The trees for all the source files in this project that contain `id`.
101
+ *
102
+ * This includes the trees for the buffers that are presently open in the IDE, and the trees
103
+ * from the target directory.
104
+ */
105
+ def sourceTreesContaining (id : String )(implicit ctx : Context ): List [SourceTree ] = {
106
+ val fromBuffers = openedTrees.values.flatten.toList
107
+ val fromCompilationOutput = {
108
+ val classNames = new mutable.ListBuffer [TypeName ]
109
+ val output = ctx.settings.outputDir.value
110
+ if (output.isDirectory) {
111
+ classesFromDir(output.jpath, classNames)
112
+ } else {
113
+ val zipFile = new ZipFile (output.file)
114
+ classesFromZip(zipFile, classNames)
115
+ }
116
+ classNames.flatMap { cls =>
117
+ treesFromClassName(cls, id)
118
+ }
119
+ }
120
+ (fromBuffers ++ fromCompilationOutput).distinct
121
+ }
133
122
123
+ /**
124
+ * All the trees for this project.
125
+ *
126
+ * This includes the trees of the sources of this project, along with the trees that are found
127
+ * on this project's classpath.
128
+ */
129
+ def allTrees (implicit ctx : Context ): List [SourceTree ] = allTreesContaining(" " )
130
+
131
+ /**
132
+ * All the trees for this project that contain `id`.
133
+ *
134
+ * This includes the trees of the sources of this project, along with the trees that are found
135
+ * on this project's classpath.
136
+ */
137
+ def allTreesContaining (id : String )(implicit ctx : Context ): List [SourceTree ] = {
138
+ val fromSource = openedTrees.values.flatten.toList
139
+ val fromClassPath = (dirClassPathClasses ++ zipClassPathClasses).flatMap { cls =>
140
+ treesFromClassName(cls, id)
141
+ }
142
+ (fromSource ++ fromClassPath).distinct
143
+ }
144
+
145
+ def run (uri : URI , sourceCode : String ): List [MessageContainer ] = run(uri, toSource(uri, sourceCode))
146
+
147
+ def run (uri : URI , source : SourceFile ): List [MessageContainer ] = {
148
+ val previousCtx = myCtx
134
149
try {
135
- for {
136
- entry <- zipFile.stream.toArray((size : Int ) => new Array [ZipEntry ](size))
137
- name = entry.getName
138
- tastySuffix <- tastySuffixes.find(name.endsWith)
139
- } yield name.replace(" /" , " ." ).stripSuffix(tastySuffix)
150
+ val reporter =
151
+ new StoreReporter (null ) with UniqueMessagePositions with HideNonSensicalMessages
152
+
153
+ val run = compiler.newRun(myInitCtx.fresh.setReporter(reporter))
154
+ myCtx = run.runContext
155
+
156
+ implicit val ctx = myCtx
157
+
158
+ myOpenedFiles(uri) = source
159
+
160
+ run.compileSources(List (source))
161
+ run.printSummary()
162
+ val unit = ctx.run.units.head
163
+ val t = unit.tpdTree
164
+ cleanup(t)
165
+ myOpenedTrees(uri) = topLevelClassTrees(t, source)
166
+ myCompilationUnits(uri) = unit
167
+
168
+ reporter.removeBufferedMessages
169
+ }
170
+ catch {
171
+ case ex : FatalError =>
172
+ myCtx = previousCtx
173
+ close(uri)
174
+ Nil
175
+ }
176
+ }
177
+
178
+ def close (uri : URI ): Unit = {
179
+ myOpenedFiles.remove(uri)
180
+ myOpenedTrees.remove(uri)
181
+ myCompilationUnits.remove(uri)
182
+ }
183
+
184
+ /**
185
+ * The `SourceTree`s that define the class `className` and/or module `className`.
186
+ *
187
+ * @see SourceTree.fromSymbol
188
+ */
189
+ private def treesFromClassName (className : TypeName , id : String )(implicit ctx : Context ): List [SourceTree ] = {
190
+ def tree (className : TypeName , id : String ): Option [SourceTree ] = {
191
+ val clsd = ctx.base.staticRef(className)
192
+ clsd match {
193
+ case clsd : ClassDenotation =>
194
+ clsd.ensureCompleted()
195
+ SourceTree .fromSymbol(clsd.symbol.asClass, id)
196
+ case _ =>
197
+ None
198
+ }
140
199
}
141
- finally zipFile.close()
200
+ List (tree(className, id), tree(className.moduleClassName, id)).flatten
142
201
}
143
202
144
203
// FIXME: classfiles in directories may change at any point, so we retraverse
145
204
// the directories each time, if we knew when classfiles changed (sbt
146
205
// server-mode might help here), we could do cache invalidation instead.
147
- private def dirClassPathClasses : Seq [String ] = {
148
- val names = new mutable.ListBuffer [String ]
206
+ private def dirClassPathClasses : Seq [TypeName ] = {
207
+ val names = new mutable.ListBuffer [TypeName ]
149
208
dirClassPaths.foreach { dirCp =>
150
209
val root = dirCp.dir.toPath
151
- try
152
- Files .walkFileTree(root, new SimpleFileVisitor [Path ] {
153
- override def visitFile (path : Path , attrs : BasicFileAttributes ) = {
154
- if (! attrs.isDirectory) {
155
- val name = path.getFileName.toString
156
- for {
157
- tastySuffix <- tastySuffixes
158
- if name.endsWith(tastySuffix)
159
- } {
160
- names += root.relativize(path).toString.replace(" /" , " ." ).stripSuffix(tastySuffix)
161
- }
210
+ classesFromDir(root, names)
211
+ }
212
+ names
213
+ }
214
+
215
+ /** Adds the names of the classes that are defined in `zipFile` to `buffer`. */
216
+ private def classesFromZip (zipFile : ZipFile , buffer : mutable.ListBuffer [TypeName ]): Unit = {
217
+ try {
218
+ for {
219
+ entry <- zipFile.stream.toArray((size : Int ) => new Array [ZipEntry ](size))
220
+ name = entry.getName
221
+ tastySuffix <- tastySuffixes.find(name.endsWith)
222
+ } buffer += name.replace(" /" , " ." ).stripSuffix(tastySuffix).toTypeName
223
+ }
224
+ finally zipFile.close()
225
+ }
226
+
227
+ /** Adds the names of the classes that are defined in `dir` to `buffer`. */
228
+ private def classesFromDir (dir : Path , buffer : mutable.ListBuffer [TypeName ]): Unit = {
229
+ try
230
+ Files .walkFileTree(dir, new SimpleFileVisitor [Path ] {
231
+ override def visitFile (path : Path , attrs : BasicFileAttributes ) = {
232
+ if (! attrs.isDirectory) {
233
+ val name = path.getFileName.toString
234
+ for {
235
+ tastySuffix <- tastySuffixes
236
+ if name.endsWith(tastySuffix)
237
+ } {
238
+ buffer += dir.relativize(path).toString.replace(" /" , " ." ).stripSuffix(tastySuffix).toTypeName
162
239
}
163
- FileVisitResult .CONTINUE
164
240
}
165
- })
166
- catch {
167
- case _ : NoSuchFileException =>
168
- }
241
+ FileVisitResult .CONTINUE
242
+ }
243
+ })
244
+ catch {
245
+ case _ : NoSuchFileException =>
169
246
}
170
- names.toList
171
247
}
172
248
173
249
private def topLevelClassTrees (topTree : Tree , source : SourceFile ): List [SourceTree ] = {
@@ -185,8 +261,6 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
185
261
trees.toList
186
262
}
187
263
188
- private val compiler : Compiler = new InteractiveCompiler
189
-
190
264
/** Remove attachments and error out completers. The goal is to avoid
191
265
* having a completer hanging in a typed tree which can capture the context
192
266
* of a previous run. Note that typed trees can have untyped or partially
@@ -224,44 +298,20 @@ class InteractiveDriver(val settings: List[String]) extends Driver {
224
298
new SourceFile (virtualFile, Codec .UTF8 )
225
299
}
226
300
227
- def run (uri : URI , sourceCode : String ): List [MessageContainer ] = run(uri, toSource(uri, sourceCode))
228
-
229
- def run (uri : URI , source : SourceFile ): List [MessageContainer ] = {
230
- val previousCtx = myCtx
231
- try {
232
- val reporter =
233
- new StoreReporter (null ) with UniqueMessagePositions with HideNonSensicalMessages
234
-
235
- val run = compiler.newRun(myInitCtx.fresh.setReporter(reporter))
236
- myCtx = run.runContext
237
-
238
- implicit val ctx = myCtx
239
-
240
- myOpenedFiles(uri) = source
241
-
242
- run.compileSources(List (source))
243
- run.printSummary()
244
- val unit = ctx.run.units.head
245
- val t = unit.tpdTree
246
- cleanup(t)
247
- myOpenedTrees(uri) = topLevelClassTrees(t, source)
248
- myCompilationUnits(uri) = unit
249
-
250
- reporter.removeBufferedMessages
251
- }
252
- catch {
253
- case ex : FatalError =>
254
- myCtx = previousCtx
255
- close(uri)
256
- Nil
257
- }
301
+ /**
302
+ * Initialize this driver and compiler.
303
+ *
304
+ * This is necessary because an `InteractiveDriver` can be put to work without having
305
+ * compiled anything (for instance, resolving a symbol coming from a different compiler in
306
+ * this compiler). In those cases, an un-initialized compiler may crash (for instance if
307
+ * late-compilation is needed).
308
+ */
309
+ private [this ] def initialize (): Unit = {
310
+ val run = compiler.newRun(myInitCtx.fresh)
311
+ myCtx = run.runContext
312
+ run.compileUnits(Nil , myCtx)
258
313
}
259
314
260
- def close (uri : URI ): Unit = {
261
- myOpenedFiles.remove(uri)
262
- myOpenedTrees.remove(uri)
263
- myCompilationUnits.remove(uri)
264
- }
265
315
}
266
316
267
317
object InteractiveDriver {
0 commit comments