@@ -26,6 +26,8 @@ import xsbti.UseScope
26
26
import xsbti .api .DependencyContext
27
27
import xsbti .api .DependencyContext ._
28
28
29
+ import scala .jdk .CollectionConverters .*
30
+
29
31
import scala .collection .{Set , mutable }
30
32
31
33
@@ -74,8 +76,8 @@ class ExtractDependencies extends Phase {
74
76
collector.traverse(unit.tpdTree)
75
77
76
78
if (ctx.settings.YdumpSbtInc .value) {
77
- val deps = rec.classDependencies. map(_.toString) .toArray[Object ]
78
- val names = rec.usedNames. map { case (clazz, names ) => s " $clazz: $names " }.toArray[Object ]
79
+ val deps = rec.foundDeps.iterator. map { case (clazz, found) => s " $clazz : ${found.classesString} " } .toArray[Object ]
80
+ val names = rec.foundDeps.iterator. map { case (clazz, found ) => s " $clazz: ${found.namesString} " }.toArray[Object ]
79
81
Arrays .sort(deps)
80
82
Arrays .sort(names)
81
83
@@ -162,7 +164,7 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
162
164
163
165
164
166
/** Traverse the tree of a source file and record the dependencies and used names which
165
- * can be retrieved using `dependencies` and`usedNames `.
167
+ * can be retrieved using `foundDeps `.
166
168
*/
167
169
override def traverse (tree : Tree )(using Context ): Unit = try {
168
170
tree match {
@@ -226,6 +228,13 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
226
228
throw ex
227
229
}
228
230
231
+ /** Reused EqHashSet, safe to use as each TypeDependencyTraverser is used atomically
232
+ * Avoid cycles by remembering both the types (testcase:
233
+ * tests/run/enum-values.scala) and the symbols of named types (testcase:
234
+ * tests/pos-java-interop/i13575) we've seen before.
235
+ */
236
+ private val scratchSeen = new util.EqHashSet [Symbol | Type ](128 )
237
+
229
238
/** Traverse a used type and record all the dependencies we need to keep track
230
239
* of for incremental recompilation.
231
240
*
@@ -262,17 +271,13 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
262
271
private abstract class TypeDependencyTraverser (using Context ) extends TypeTraverser () {
263
272
protected def addDependency (symbol : Symbol ): Unit
264
273
265
- // Avoid cycles by remembering both the types (testcase:
266
- // tests/run/enum-values.scala) and the symbols of named types (testcase:
267
- // tests/pos-java-interop/i13575) we've seen before.
268
- val seen = new mutable.HashSet [Symbol | Type ]
269
- def traverse (tp : Type ): Unit = if (! seen.contains(tp)) {
270
- seen += tp
274
+ scratchSeen.clear(resetToInitial = false )
275
+
276
+ def traverse (tp : Type ): Unit = if scratchSeen.add(tp) then {
271
277
tp match {
272
278
case tp : NamedType =>
273
279
val sym = tp.symbol
274
- if ! seen.contains(sym) && ! sym.is(Package ) then
275
- seen += sym
280
+ if ! sym.is(Package ) && scratchSeen.add(sym) then
276
281
addDependency(sym)
277
282
if ! sym.isClass then traverse(tp.info)
278
283
traverse(tp.prefix)
@@ -306,8 +311,6 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
306
311
}
307
312
}
308
313
309
- case class ClassDependency (fromClass : Symbol , toClass : Symbol , context : DependencyContext )
310
-
311
314
/** Record dependencies using `addUsedName`/`addClassDependency` and inform Zinc using `sendToZinc()`.
312
315
*
313
316
* Note: As an alternative design choice, we could directly call the appropriate
@@ -319,10 +322,10 @@ case class ClassDependency(fromClass: Symbol, toClass: Symbol, context: Dependen
319
322
class DependencyRecorder {
320
323
import ExtractDependencies .*
321
324
322
- /** A map from a non-local class to the names it uses, this does not include
325
+ /** A map from a non-local class to the names and classes it uses, this does not include
323
326
* names which are only defined and not referenced.
324
327
*/
325
- def usedNames : collection. Map [Symbol , UsedNamesInClass ] = _usedNames
328
+ def foundDeps : util. ReadOnlyMap [Symbol , FoundDepsInClass ] = _foundDeps
326
329
327
330
/** Record a reference to the name of `sym` from the current non-local
328
331
* enclosing class.
@@ -355,10 +358,9 @@ class DependencyRecorder {
355
358
* safely.
356
359
*/
357
360
def addUsedRawName (name : Name , includeSealedChildren : Boolean = false )(using Context ): Unit = {
358
- val fromClass = resolveDependencySource
361
+ val fromClass = resolveDependencyFromClass
359
362
if (fromClass.exists) {
360
- val usedName = _usedNames.getOrElseUpdate(fromClass, new UsedNamesInClass )
361
- usedName.update(name, includeSealedChildren)
363
+ lastFoundCache.recordName(name, includeSealedChildren)
362
364
}
363
365
}
364
366
@@ -367,24 +369,34 @@ class DependencyRecorder {
367
369
private val DefaultScopes = EnumSet .of(UseScope .Default )
368
370
private val PatMatScopes = EnumSet .of(UseScope .Default , UseScope .PatMatTarget )
369
371
370
- /** An object that maintain the set of used names from within a class */
371
- final class UsedNamesInClass {
372
+ /** An object that maintain the set of used names and class dependencies from within a class */
373
+ final class FoundDepsInClass {
372
374
/** Each key corresponds to a name used in the class. To understand the meaning
373
375
* of the associated value, see the documentation of parameter `includeSealedChildren`
374
376
* of `addUsedRawName`.
375
377
*/
376
- private val _names = new mutable.HashMap [Name , DefaultScopes .type | PatMatScopes .type ]
378
+ private val _names = new util.HashMap [Name , DefaultScopes .type | PatMatScopes .type ]
379
+
380
+ /** Each key corresponds to a class dependency used in the class.
381
+ */
382
+ private val _classes = util.EqHashMap [Symbol , EnumSet [DependencyContext ]]()
383
+
384
+ def addDependency (fromClass : Symbol , context : DependencyContext ): Unit =
385
+ val set = _classes.getOrElseUpdate(fromClass, EnumSet .noneOf(classOf [DependencyContext ]))
386
+ set.add(context)
387
+
388
+ def classes : Iterator [(Symbol , EnumSet [DependencyContext ])] = _classes.iterator
377
389
378
- def names : collection. Map [ Name , EnumSet [UseScope ]] = _names
390
+ def names : Iterator [( Name , EnumSet [UseScope ]) ] = _names.iterator
379
391
380
- private [DependencyRecorder ] def update (name : Name , includeSealedChildren : Boolean ): Unit = {
392
+ private [DependencyRecorder ] def recordName (name : Name , includeSealedChildren : Boolean ): Unit = {
381
393
if (includeSealedChildren)
382
394
_names(name) = PatMatScopes
383
395
else
384
396
_names.getOrElseUpdate(name, DefaultScopes )
385
397
}
386
398
387
- override def toString () : String = {
399
+ def namesString : String = {
388
400
val builder = new StringBuilder
389
401
names.foreach { case (name, scopes) =>
390
402
builder.append(name.mangledString)
@@ -395,51 +407,59 @@ class DependencyRecorder {
395
407
}
396
408
builder.toString()
397
409
}
398
- }
399
-
400
-
401
- private val _classDependencies = new mutable.HashSet [ClassDependency ]
402
410
403
- def classDependencies : Set [ClassDependency ] = _classDependencies
411
+ def classesString : String = {
412
+ val builder = new StringBuilder
413
+ classes.foreach { case (clazz, scopes) =>
414
+ builder.append(clazz.toString)
415
+ builder.append(" in [" )
416
+ scopes.forEach(scope => builder.append(scope.toString))
417
+ builder.append(" ]" )
418
+ builder.append(" , " )
419
+ }
420
+ builder.toString()
421
+ }
422
+ }
404
423
405
424
/** Record a dependency to the class `to` in a given `context`
406
425
* from the current non-local enclosing class.
407
426
*/
408
427
def addClassDependency (toClass : Symbol , context : DependencyContext )(using Context ): Unit =
409
- val fromClass = resolveDependencySource
428
+ val fromClass = resolveDependencyFromClass
410
429
if (fromClass.exists)
411
- _classDependencies += ClassDependency (fromClass, toClass, context)
430
+ lastFoundCache.addDependency( toClass, context)
412
431
413
- private val _usedNames = new mutable. HashMap [Symbol , UsedNamesInClass ]
432
+ private val _foundDeps = new util. EqHashMap [Symbol , FoundDepsInClass ]
414
433
415
434
/** Send the collected dependency information to Zinc and clear the local caches. */
416
435
def sendToZinc ()(using Context ): Unit =
417
436
ctx.withIncCallback: cb =>
418
- usedNames.foreach:
419
- case (clazz, usedNames) =>
420
- val className = classNameAsString(clazz)
421
- usedNames.names.foreach:
422
- case (usedName, scopes) =>
423
- cb.usedName(className, usedName.toString, scopes)
424
437
val siblingClassfiles = new mutable.HashMap [PlainFile , Path ]
425
- classDependencies.foreach(recordClassDependency(cb, _, siblingClassfiles))
438
+ _foundDeps.iterator.foreach:
439
+ case (clazz, foundDeps) =>
440
+ val className = classNameAsString(clazz)
441
+ foundDeps.names.foreach: (usedName, scopes) =>
442
+ cb.usedName(className, usedName.toString, scopes)
443
+ for (toClass, deps) <- foundDeps.classes do
444
+ for dep <- deps.asScala do
445
+ recordClassDependency(cb, clazz, toClass, dep, siblingClassfiles)
426
446
clear()
427
447
428
448
/** Clear all state. */
429
449
def clear (): Unit =
430
- _usedNames.clear()
431
- _classDependencies.clear()
450
+ _foundDeps.clear()
432
451
lastOwner = NoSymbol
433
452
lastDepSource = NoSymbol
453
+ lastFoundCache = null
434
454
_responsibleForImports = NoSymbol
435
455
436
456
/** Handles dependency on given symbol by trying to figure out if represents a term
437
457
* that is coming from either source code (not necessarily compiled in this compilation
438
458
* run) or from class file and calls respective callback method.
439
459
*/
440
- private def recordClassDependency (cb : interfaces.IncrementalCallback , dep : ClassDependency ,
441
- siblingClassfiles : mutable.Map [PlainFile , Path ])(using Context ): Unit = {
442
- val fromClassName = classNameAsString(dep. fromClass)
460
+ private def recordClassDependency (cb : interfaces.IncrementalCallback , fromClass : Symbol , toClass : Symbol ,
461
+ depCtx : DependencyContext , siblingClassfiles : mutable.Map [PlainFile , Path ])(using Context ): Unit = {
462
+ val fromClassName = classNameAsString(fromClass)
443
463
val sourceFile = ctx.compilationUnit.source
444
464
445
465
/** For a `.tasty` file, constructs a sibling class to the `jpath`.
@@ -465,13 +485,13 @@ class DependencyRecorder {
465
485
})
466
486
467
487
def binaryDependency (path : Path , binaryClassName : String ) =
468
- cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, dep.context )
488
+ cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, depCtx )
469
489
470
- val depClass = dep. toClass
490
+ val depClass = toClass
471
491
val depFile = depClass.associatedFile
472
492
if depFile != null then {
473
493
// Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417)
474
- def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance
494
+ def allowLocal = depCtx == DependencyByInheritance || depCtx == LocalDependencyByInheritance
475
495
val isTasty = depFile.hasTastyExtension
476
496
477
497
def processExternalDependency () = {
@@ -485,7 +505,7 @@ class DependencyRecorder {
485
505
case pf : PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem
486
506
binaryDependency(if isTasty then cachedSiblingClass(pf) else pf.jpath, binaryClassName)
487
507
case _ =>
488
- internalError(s " Ignoring dependency $depFile of unknown class ${depFile.getClass}} " , dep. fromClass.srcPos)
508
+ internalError(s " Ignoring dependency $depFile of unknown class ${depFile.getClass}} " , fromClass.srcPos)
489
509
}
490
510
}
491
511
@@ -495,23 +515,28 @@ class DependencyRecorder {
495
515
// We cannot ignore dependencies coming from the same source file because
496
516
// the dependency info needs to propagate. See source-dependencies/trait-trait-211.
497
517
val toClassName = classNameAsString(depClass)
498
- cb.classDependency(toClassName, fromClassName, dep.context )
518
+ cb.classDependency(toClassName, fromClassName, depCtx )
499
519
}
500
520
}
501
521
502
522
private var lastOwner : Symbol = _
503
523
private var lastDepSource : Symbol = _
524
+ private var lastFoundCache : FoundDepsInClass | Null = _
504
525
505
526
/** The source of the dependency according to `nonLocalEnclosingClass`
506
527
* if it exists, otherwise fall back to `responsibleForImports`.
507
528
*
508
529
* This is backed by a cache which is invalidated when `ctx.owner` changes.
509
530
*/
510
- private def resolveDependencySource (using Context ): Symbol = {
531
+ private def resolveDependencyFromClass (using Context ): Symbol = {
532
+ import dotty .tools .uncheckedNN
511
533
if (lastOwner != ctx.owner) {
512
534
lastOwner = ctx.owner
513
535
val source = nonLocalEnclosingClass
514
- lastDepSource = if (source.is(PackageClass )) responsibleForImports else source
536
+ val fromClass = if (source.is(PackageClass )) responsibleForImports else source
537
+ if lastDepSource != fromClass then
538
+ lastDepSource = fromClass
539
+ lastFoundCache = _foundDeps.getOrElseUpdate(fromClass, new FoundDepsInClass )
515
540
}
516
541
517
542
lastDepSource
0 commit comments