@@ -26,6 +26,8 @@ import xsbti.UseScope
2626import xsbti .api .DependencyContext
2727import xsbti .api .DependencyContext ._
2828
29+ import scala .jdk .CollectionConverters .*
30+
2931import scala .collection .{Set , mutable }
3032
3133
@@ -74,8 +76,8 @@ class ExtractDependencies extends Phase {
7476 collector.traverse(unit.tpdTree)
7577
7678 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 ]
7981 Arrays .sort(deps)
8082 Arrays .sort(names)
8183
@@ -162,7 +164,7 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
162164
163165
164166 /** 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 `.
166168 */
167169 override def traverse (tree : Tree )(using Context ): Unit = try {
168170 tree match {
@@ -226,6 +228,13 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
226228 throw ex
227229 }
228230
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+
229238 /** Traverse a used type and record all the dependencies we need to keep track
230239 * of for incremental recompilation.
231240 *
@@ -262,17 +271,13 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
262271 private abstract class TypeDependencyTraverser (using Context ) extends TypeTraverser () {
263272 protected def addDependency (symbol : Symbol ): Unit
264273
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 {
271277 tp match {
272278 case tp : NamedType =>
273279 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
276281 addDependency(sym)
277282 if ! sym.isClass then traverse(tp.info)
278283 traverse(tp.prefix)
@@ -306,8 +311,6 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
306311 }
307312}
308313
309- case class ClassDependency (fromClass : Symbol , toClass : Symbol , context : DependencyContext )
310-
311314/** Record dependencies using `addUsedName`/`addClassDependency` and inform Zinc using `sendToZinc()`.
312315 *
313316 * 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
319322class DependencyRecorder {
320323 import ExtractDependencies .*
321324
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
323326 * names which are only defined and not referenced.
324327 */
325- def usedNames : collection. Map [Symbol , UsedNamesInClass ] = _usedNames
328+ def foundDeps : util. ReadOnlyMap [Symbol , FoundDepsInClass ] = _foundDeps
326329
327330 /** Record a reference to the name of `sym` from the current non-local
328331 * enclosing class.
@@ -355,10 +358,9 @@ class DependencyRecorder {
355358 * safely.
356359 */
357360 def addUsedRawName (name : Name , includeSealedChildren : Boolean = false )(using Context ): Unit = {
358- val fromClass = resolveDependencySource
361+ val fromClass = resolveDependencyFromClass
359362 if (fromClass.exists) {
360- val usedName = _usedNames.getOrElseUpdate(fromClass, new UsedNamesInClass )
361- usedName.update(name, includeSealedChildren)
363+ lastFoundCache.recordName(name, includeSealedChildren)
362364 }
363365 }
364366
@@ -367,24 +369,34 @@ class DependencyRecorder {
367369 private val DefaultScopes = EnumSet .of(UseScope .Default )
368370 private val PatMatScopes = EnumSet .of(UseScope .Default , UseScope .PatMatTarget )
369371
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 {
372374 /** Each key corresponds to a name used in the class. To understand the meaning
373375 * of the associated value, see the documentation of parameter `includeSealedChildren`
374376 * of `addUsedRawName`.
375377 */
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
377389
378- def names : collection. Map [ Name , EnumSet [UseScope ]] = _names
390+ def names : Iterator [( Name , EnumSet [UseScope ]) ] = _names.iterator
379391
380- private [DependencyRecorder ] def update (name : Name , includeSealedChildren : Boolean ): Unit = {
392+ private [DependencyRecorder ] def recordName (name : Name , includeSealedChildren : Boolean ): Unit = {
381393 if (includeSealedChildren)
382394 _names(name) = PatMatScopes
383395 else
384396 _names.getOrElseUpdate(name, DefaultScopes )
385397 }
386398
387- override def toString () : String = {
399+ def namesString : String = {
388400 val builder = new StringBuilder
389401 names.foreach { case (name, scopes) =>
390402 builder.append(name.mangledString)
@@ -395,51 +407,59 @@ class DependencyRecorder {
395407 }
396408 builder.toString()
397409 }
398- }
399-
400-
401- private val _classDependencies = new mutable.HashSet [ClassDependency ]
402410
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+ }
404423
405424 /** Record a dependency to the class `to` in a given `context`
406425 * from the current non-local enclosing class.
407426 */
408427 def addClassDependency (toClass : Symbol , context : DependencyContext )(using Context ): Unit =
409- val fromClass = resolveDependencySource
428+ val fromClass = resolveDependencyFromClass
410429 if (fromClass.exists)
411- _classDependencies += ClassDependency (fromClass, toClass, context)
430+ lastFoundCache.addDependency( toClass, context)
412431
413- private val _usedNames = new mutable. HashMap [Symbol , UsedNamesInClass ]
432+ private val _foundDeps = new util. EqHashMap [Symbol , FoundDepsInClass ]
414433
415434 /** Send the collected dependency information to Zinc and clear the local caches. */
416435 def sendToZinc ()(using Context ): Unit =
417436 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)
424437 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)
426446 clear()
427447
428448 /** Clear all state. */
429449 def clear (): Unit =
430- _usedNames.clear()
431- _classDependencies.clear()
450+ _foundDeps.clear()
432451 lastOwner = NoSymbol
433452 lastDepSource = NoSymbol
453+ lastFoundCache = null
434454 _responsibleForImports = NoSymbol
435455
436456 /** Handles dependency on given symbol by trying to figure out if represents a term
437457 * that is coming from either source code (not necessarily compiled in this compilation
438458 * run) or from class file and calls respective callback method.
439459 */
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)
443463 val sourceFile = ctx.compilationUnit.source
444464
445465 /** For a `.tasty` file, constructs a sibling class to the `jpath`.
@@ -465,13 +485,13 @@ class DependencyRecorder {
465485 })
466486
467487 def binaryDependency (path : Path , binaryClassName : String ) =
468- cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, dep.context )
488+ cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, depCtx )
469489
470- val depClass = dep. toClass
490+ val depClass = toClass
471491 val depFile = depClass.associatedFile
472492 if depFile != null then {
473493 // 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
475495 val isTasty = depFile.hasTastyExtension
476496
477497 def processExternalDependency () = {
@@ -485,7 +505,7 @@ class DependencyRecorder {
485505 case pf : PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem
486506 binaryDependency(if isTasty then cachedSiblingClass(pf) else pf.jpath, binaryClassName)
487507 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)
489509 }
490510 }
491511
@@ -495,23 +515,28 @@ class DependencyRecorder {
495515 // We cannot ignore dependencies coming from the same source file because
496516 // the dependency info needs to propagate. See source-dependencies/trait-trait-211.
497517 val toClassName = classNameAsString(depClass)
498- cb.classDependency(toClassName, fromClassName, dep.context )
518+ cb.classDependency(toClassName, fromClassName, depCtx )
499519 }
500520 }
501521
502522 private var lastOwner : Symbol = _
503523 private var lastDepSource : Symbol = _
524+ private var lastFoundCache : FoundDepsInClass | Null = _
504525
505526 /** The source of the dependency according to `nonLocalEnclosingClass`
506527 * if it exists, otherwise fall back to `responsibleForImports`.
507528 *
508529 * This is backed by a cache which is invalidated when `ctx.owner` changes.
509530 */
510- private def resolveDependencySource (using Context ): Symbol = {
531+ private def resolveDependencyFromClass (using Context ): Symbol = {
532+ import dotty .tools .uncheckedNN
511533 if (lastOwner != ctx.owner) {
512534 lastOwner = ctx.owner
513535 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 )
515540 }
516541
517542 lastDepSource
0 commit comments