-
Notifications
You must be signed in to change notification settings - Fork 1k
/
SymbolLoaders.scala
436 lines (384 loc) · 17.5 KB
/
SymbolLoaders.scala
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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
package dotty.tools
package dotc
package core
import java.io.{IOException, File}
import java.nio.channels.ClosedByInterruptException
import scala.compat.Platform.currentTime
import dotty.tools.io.{ ClassPath, ClassRepresentation, AbstractFile }
import config.Config
import Contexts._, Symbols._, Flags._, SymDenotations._, Types._, Scopes._, Names._
import NameOps._
import StdNames.str
import Decorators._
import classfile.ClassfileParser
import util.Stats
import Decorators._
import scala.util.control.NonFatal
import ast.Trees._
import parsing.JavaParsers.OutlineJavaParser
import parsing.Parsers.OutlineParser
import reporting.trace
import ast.desugar
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
object SymbolLoaders {
import ast.untpd._
/** A marker trait for a completer that replaces the original
* Symbol loader for an unpickled root.
*/
trait SecondCompleter
private def enterNew(
owner: Symbol, member: Symbol,
completer: SymbolLoader, scope: Scope = EmptyScope)(using Context): Symbol = {
val comesFromScan =
completer.isInstanceOf[SourcefileLoader]
assert(comesFromScan || scope.lookup(member.name) == NoSymbol,
s"${owner.fullName}.${member.name} already has a symbol")
owner.asClass.enter(member, scope)
member
}
/** Enter class with given `name` into scope of `owner`.
*/
def enterClass(
owner: Symbol, name: PreName, completer: SymbolLoader,
flags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(using Context): Symbol = {
val cls = newClassSymbol(owner, name.toTypeName.unmangleClassName.decode, flags, completer, assocFile = completer.sourceFileOrNull)
enterNew(owner, cls, completer, scope)
}
/** Enter module with given `name` into scope of `owner`.
*/
def enterModule(
owner: Symbol, name: PreName, completer: SymbolLoader,
modFlags: FlagSet = EmptyFlags, clsFlags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(using Context): Symbol = {
val module = newModuleSymbol(
owner, name.toTermName.decode, modFlags, clsFlags,
(module, _) => completer.proxy.withDecls(newScope).withSourceModule(module),
assocFile = completer.sourceFileOrNull)
enterNew(owner, module, completer, scope)
enterNew(owner, module.moduleClass, completer, scope)
}
/** Enter package with given `name` into scope of `owner`
* and give them `completer` as type.
*/
def enterPackage(owner: Symbol, pname: TermName, completer: (TermSymbol, ClassSymbol) => PackageLoader)(using Context): Symbol = {
val preExisting = owner.info.decls lookup pname
if (preExisting != NoSymbol)
// Some jars (often, obfuscated ones) include a package and
// object with the same name. Rather than render them unusable,
// offer a setting to resolve the conflict one way or the other.
// This was motivated by the desire to use YourKit probes, which
// require yjp.jar at runtime. See SI-2089.
if (ctx.settings.YtermConflict.value == "package" || ctx.mode.is(Mode.Interactive)) {
report.warning(
s"Resolving package/object name conflict in favor of package ${preExisting.fullName}. The object will be inaccessible.")
owner.asClass.delete(preExisting)
}
else if (ctx.settings.YtermConflict.value == "object") {
report.warning(
s"Resolving package/object name conflict in favor of object ${preExisting.fullName}. The package will be inaccessible.")
return NoSymbol
}
else
throw new TypeError(
i"""$owner contains object and package with same name: $pname
|one of them needs to be removed from classpath""")
newModuleSymbol(owner, pname, PackageCreationFlags, PackageCreationFlags,
completer).entered
}
/** Enter class and module with given `name` into scope of `owner`
* and give them `completer` as type.
*/
def enterClassAndModule(
owner: Symbol, name: PreName, completer: SymbolLoader,
flags: FlagSet = EmptyFlags, scope: Scope = EmptyScope)(using Context): Unit = {
val clazz = enterClass(owner, name, completer, flags, scope)
val module = enterModule(
owner, name, completer,
modFlags = flags.toTermFlags & RetainedModuleValFlags,
clsFlags = flags.toTypeFlags & RetainedModuleClassFlags,
scope = scope)
}
/** Enter all toplevel classes and objects in file `src` into package `owner`, provided
* they are in the right package. Issue a warning if a class or object is in the wrong
* package, i.e. if the file path differs from the declared package clause.
*
* All entered symbols are given a source completer of `src` as info.
*/
def enterToplevelsFromSource(
owner: Symbol, name: PreName, src: AbstractFile,
scope: Scope = EmptyScope)(using Context): Unit =
if src.exists && !src.isDirectory
val completer = new SourcefileLoader(src)
val filePath = owner.ownersIterator.takeWhile(!_.isRoot).map(_.name.toTermName).toList
def addPrefix(pid: RefTree, path: List[TermName]): List[TermName] = pid match {
case Ident(name: TermName) => name :: path
case Select(qual: RefTree, name: TermName) => name :: addPrefix(qual, path)
case _ => path
}
def enterScanned(unit: CompilationUnit)(using Context) = {
def checkPathMatches(path: List[TermName], what: String, tree: NameTree): Boolean = {
val ok = filePath == path
if (!ok)
report.warning(i"""$what ${tree.name} is in the wrong directory.
|It was declared to be in package ${path.reverse.mkString(".")}
|But it is found in directory ${filePath.reverse.mkString(File.separator)}""",
tree.srcPos.focus)
ok
}
/** Run the subset of desugaring necessary to record the correct symbols */
def simpleDesugar(tree: Tree): Tree = tree match
case tree: PackageDef =>
desugar.packageDef(tree)
case tree: ModuleDef =>
desugar.packageModuleDef(tree)
case _ =>
tree
def traverse(tree: Tree, path: List[TermName]): Unit = simpleDesugar(tree) match {
case tree @ PackageDef(pid, body) =>
val path1 = addPrefix(pid, path)
for (stat <- body) traverse(stat, path1)
case tree: TypeDef if tree.isClassDef =>
if (checkPathMatches(path, "class", tree))
// It might be a case class or implicit class,
// so enter class and module to be on the safe side
enterClassAndModule(owner, tree.name, completer, scope = scope)
case tree: ModuleDef =>
if (checkPathMatches(path, "object", tree))
enterModule(owner, tree.name, completer, scope = scope)
case _ =>
}
traverse(
if (unit.isJava) new OutlineJavaParser(unit.source).parse()
else new OutlineParser(unit.source).parse(),
Nil)
}
val unit = CompilationUnit(ctx.getSource(src.path))
enterScanned(unit)(using ctx.fresh.setCompilationUnit(unit))
/** The package objects of scala and scala.reflect should always
* be loaded in binary if classfiles are available, even if sourcefiles
* are newer. Late-compiling these objects from source leads to compilation
* order issues.
* Note: We do a name-base comparison here because the method is called before we even
* have ReflectPackage defined.
*/
def binaryOnly(owner: Symbol, name: String)(using Context): Boolean =
name == "package" &&
(owner.fullName.toString == "scala" || owner.fullName.toString == "scala.reflect")
/** Initialize toplevel class and module symbols in `owner` from class path representation `classRep`
*/
def initializeFromClassPath(owner: Symbol, classRep: ClassRepresentation)(using Context): Unit =
((classRep.binary, classRep.source): @unchecked) match {
case (Some(bin), Some(src)) if needCompile(bin, src) && !binaryOnly(owner, classRep.name) =>
if (ctx.settings.verbose.value) report.inform("[symloader] picked up newer source file for " + src.path)
enterToplevelsFromSource(owner, classRep.name, src)
case (None, Some(src)) =>
if (ctx.settings.verbose.value) report.inform("[symloader] no class, picked up source file for " + src.path)
enterToplevelsFromSource(owner, classRep.name, src)
case (Some(bin), _) =>
enterClassAndModule(owner, classRep.name, ctx.platform.newClassLoader(bin))
}
def needCompile(bin: AbstractFile, src: AbstractFile): Boolean =
src.lastModified >= bin.lastModified
/** Load contents of a package
*/
class PackageLoader(_sourceModule: TermSymbol, classPath: ClassPath)
extends SymbolLoader {
override def sourceFileOrNull: AbstractFile = null
override def sourceModule(using Context): TermSymbol = _sourceModule
def description(using Context): String = "package loader " + sourceModule.fullName
private var enterFlatClasses: Option[() => Context ?=> Unit] = None
Stats.record("package scopes")
/** The scope of a package. This is different from a normal scope
* in two aspects:
*
* 1. Names of scope entries are kept in mangled form.
* 2. Some function types in the `scala` package are synthesized.
*/
final class PackageScope extends MutableScope {
override def newScopeEntry(name: Name, sym: Symbol)(using Context): ScopeEntry =
super.newScopeEntry(name.mangled, sym)
override def lookupEntry(name: Name)(using Context): ScopeEntry = {
val mangled = name.mangled
val e = super.lookupEntry(mangled)
if (e != null) e
else if (isFlatName(mangled.toSimpleName) && enterFlatClasses.isDefined) {
Stats.record("package scopes with flatnames entered")
enterFlatClasses.get()
lookupEntry(name)
}
else e
}
override def ensureComplete()(using Context): Unit =
for (enter <- enterFlatClasses) enter()
override def newScopeLikeThis(): PackageScope = new PackageScope
}
private[core] val currentDecls: MutableScope = new PackageScope()
private def isFlatName(name: SimpleName): Boolean = {
val idx = name.lastIndexOf('$', name.length - 2)
idx >= 0 &&
(idx + str.TOPLEVEL_SUFFIX.length + 1 != name.length || !name.endsWith(str.TOPLEVEL_SUFFIX))
}
/** Name of class contains `$`, excepted names ending in `$package` */
def hasFlatName(classRep: ClassRepresentation): Boolean = {
val name = classRep.name
val idx = name.lastIndexOf('$', name.length - 2)
idx >= 0 &&
(idx + str.TOPLEVEL_SUFFIX.length + 1 != name.length || !name.endsWith(str.TOPLEVEL_SUFFIX))
}
def maybeModuleClass(classRep: ClassRepresentation): Boolean = classRep.name.last == '$'
private def enterClasses(root: SymDenotation, packageName: String, flat: Boolean)(using Context) = {
def isAbsent(classRep: ClassRepresentation) =
!root.unforcedDecls.lookup(classRep.name.toTypeName).exists
if (!root.isRoot) {
val classReps = classPath.list(packageName).classesAndSources
for (classRep <- classReps)
if (!maybeModuleClass(classRep) && hasFlatName(classRep) == flat &&
(!flat || isAbsent(classRep))) // on 2nd enter of flat names, check that the name has not been entered before
initializeFromClassPath(root.symbol, classRep)
for (classRep <- classReps)
if (maybeModuleClass(classRep) && hasFlatName(classRep) == flat &&
isAbsent(classRep))
initializeFromClassPath(root.symbol, classRep)
}
}
def doComplete(root: SymDenotation)(using Context): Unit = {
assert(root is PackageClass, root)
val pre = root.owner.thisType
root.info = ClassInfo(pre, root.symbol.asClass, Nil, currentDecls, pre select sourceModule)
if (!sourceModule.isCompleted)
sourceModule.completer.complete(sourceModule)
val packageName = if (root.isEffectiveRoot) "" else root.symbol.javaClassName
enterFlatClasses = Some { () =>
enterFlatClasses = None
inContext(ctx){enterClasses(root, packageName, flat = true)}
}
enterClasses(root, packageName, flat = false)
if (!root.isEmptyPackage)
for (pkg <- classPath.packages(packageName)) {
val fullName = pkg.name
val name =
if (packageName.isEmpty) fullName
else fullName.substring(packageName.length + 1)
enterPackage(root.symbol, name.toTermName,
(module, modcls) => new PackageLoader(module, classPath))
}
}
}
}
/** A lazy type that completes itself by calling parameter doComplete.
* Any linked modules/classes or module classes are also initialized.
*/
abstract class SymbolLoader extends LazyType { self =>
/** Load source or class file for `root`, return */
def doComplete(root: SymDenotation)(using Context): Unit
def sourceFileOrNull: AbstractFile
/** Description of the resource (ClassPath, AbstractFile)
* being processed by this loader
*/
def description(using Context): String
/** A proxy to this loader that keeps the doComplete operation
* but provides fresh slots for scope/sourceModule/moduleClass
*/
def proxy: SymbolLoader = new SymbolLoader {
export self.{doComplete, sourceFileOrNull}
def description(using Context): String = "proxy to ${self.description}"
}
override def complete(root: SymDenotation)(using Context): Unit = {
def signalError(ex: Exception): Unit = {
if (ctx.debug) ex.printStackTrace()
val msg = ex.getMessage()
report.error(
if (msg eq null) "i/o error while loading " + root.name
else "error while loading " + root.name + ",\n" + msg)
}
try {
val start = System.currentTimeMillis
if (Config.tracingEnabled && ctx.settings.YdebugTrace.value)
trace(s">>>> loading ${root.debugString}", _ => s"<<<< loaded ${root.debugString}") {
doComplete(root)
}
else
doComplete(root)
report.informTime("loaded " + description, start)
}
catch {
case ex: InterruptedException =>
throw ex
case ex: ClosedByInterruptException =>
throw new InterruptedException
case ex: IOException =>
signalError(ex)
case NonFatal(ex: TypeError) =>
println(s"exception caught when loading $root: ${ex.toMessage}")
throw ex
case NonFatal(ex) =>
println(s"exception caught when loading $root: $ex")
throw ex
}
finally {
def postProcess(denot: SymDenotation) =
if (!denot.isCompleted &&
!denot.completer.isInstanceOf[SymbolLoaders.SecondCompleter])
denot.markAbsent()
postProcess(root)
if (!root.isRoot)
postProcess(root.scalacLinkedClass.denot)
}
}
protected def rootDenots(rootDenot: ClassDenotation)(using Context): (ClassDenotation, ClassDenotation) = {
val linkedDenot = rootDenot.scalacLinkedClass.denot match {
case d: ClassDenotation => d
case d =>
// this can happen if the companion if shadowed by a val or type
// in a package object; in this case, we make up some dummy denotation
// as a stand in for loading.
// An example for this situation is scala.reflect.Manifest, which exists
// as a class in scala.reflect and as a val in scala.reflect.package.
if (rootDenot.is(ModuleClass))
newClassSymbol(
rootDenot.owner, rootDenot.name.stripModuleClassSuffix.asTypeName, Synthetic,
_ => NoType).classDenot
else
newModuleSymbol(
rootDenot.owner, rootDenot.name.toTermName, Synthetic, Synthetic,
(module, _) => NoLoader().withDecls(newScope).withSourceModule(module))
.moduleClass.denot.asClass
}
if (rootDenot.is(ModuleClass)) (linkedDenot, rootDenot)
else (rootDenot, linkedDenot)
}
}
class ClassfileLoader(val classfile: AbstractFile) extends SymbolLoader {
override def sourceFileOrNull: AbstractFile = classfile
def description(using Context): String = "class file " + classfile.toString
override def doComplete(root: SymDenotation)(using Context): Unit =
load(root)
def load(root: SymDenotation)(using Context): Unit = {
val (classRoot, moduleRoot) = rootDenots(root.asClass)
val classfileParser = new ClassfileParser(classfile, classRoot, moduleRoot)(ctx)
val result = classfileParser.run()
if (mayLoadTreesFromTasty)
result match {
case Some(unpickler: tasty.DottyUnpickler) =>
classRoot.classSymbol.rootTreeOrProvider = unpickler
moduleRoot.classSymbol.rootTreeOrProvider = unpickler
case _ =>
}
}
private def mayLoadTreesFromTasty(using Context): Boolean =
ctx.settings.YretainTrees.value || ctx.settings.fromTasty.value
}
class SourcefileLoader(val srcfile: AbstractFile) extends SymbolLoader {
def description(using Context): String = "source file " + srcfile.toString
override def sourceFileOrNull: AbstractFile = srcfile
def doComplete(root: SymDenotation)(using Context): Unit =
ctx.run.lateCompile(srcfile, typeCheck = ctx.settings.YretainTrees.value)
}
/** A NoCompleter which is also a SymbolLoader. */
class NoLoader extends SymbolLoader with NoCompleter {
def description(using Context): String = "NoLoader"
override def sourceFileOrNull: AbstractFile = null
override def complete(root: SymDenotation)(using Context): Unit =
super[NoCompleter].complete(root)
def doComplete(root: SymDenotation)(using Context): Unit =
unsupported("doComplete")
}