@@ -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,7 +76,11 @@ 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 ]
79+ val deps = rec.classDependencies.flatMap((k,vs) =>
80+ vs.iterator.flatMap((to, depCtxs) =>
81+ depCtxs.asScala.map(depCtx => s " ClassDependency( $k, $to, $depCtx) " )
82+ )
83+ ).toArray[Object ]
7884 val names = rec.usedNames.map { case (clazz, names) => s " $clazz: $names" }.toArray[Object ]
7985 Arrays .sort(deps)
8086 Arrays .sort(names)
@@ -265,7 +271,7 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
265271 // Avoid cycles by remembering both the types (testcase:
266272 // tests/run/enum-values.scala) and the symbols of named types (testcase:
267273 // tests/pos-java-interop/i13575) we've seen before.
268- val seen = new mutable .HashSet [Symbol | Type ]
274+ val seen = new util .HashSet [Symbol | Type ]( 64 )
269275 def traverse (tp : Type ): Unit = if (! seen.contains(tp)) {
270276 seen += tp
271277 tp match {
@@ -306,7 +312,15 @@ private class ExtractDependenciesCollector(rec: DependencyRecorder) extends tpd.
306312 }
307313}
308314
309- case class ClassDependency (fromClass : Symbol , toClass : Symbol , context : DependencyContext )
315+ class ClassDepsInClass :
316+ private val _classes = util.EqHashMap [Symbol , EnumSet [DependencyContext ]]()
317+
318+ def addDependency (fromClass : Symbol , context : DependencyContext ): Unit =
319+ val set = _classes.getOrElseUpdate(fromClass, EnumSet .noneOf(classOf [DependencyContext ]))
320+ set.add(context)
321+
322+ def iterator : Iterator [(Symbol , EnumSet [DependencyContext ])] =
323+ _classes.iterator
310324
311325/** Record dependencies using `addUsedName`/`addClassDependency` and inform Zinc using `sendToZinc()`.
312326 *
@@ -355,10 +369,9 @@ class DependencyRecorder {
355369 * safely.
356370 */
357371 def addUsedRawName (name : Name , includeSealedChildren : Boolean = false )(using Context ): Unit = {
358- val fromClass = resolveDependencySource
372+ val fromClass = resolveDependencyFromClass
359373 if (fromClass.exists) {
360- val usedName = _usedNames.getOrElseUpdate(fromClass, new UsedNamesInClass )
361- usedName.update(name, includeSealedChildren)
374+ lastUsedCache.update(name, includeSealedChildren)
362375 }
363376 }
364377
@@ -373,9 +386,9 @@ class DependencyRecorder {
373386 * of the associated value, see the documentation of parameter `includeSealedChildren`
374387 * of `addUsedRawName`.
375388 */
376- private val _names = new mutable .HashMap [Name , DefaultScopes .type | PatMatScopes .type ]
389+ private val _names = new util .HashMap [Name , DefaultScopes .type | PatMatScopes .type ]
377390
378- def names : collection. Map [ Name , EnumSet [UseScope ]] = _names
391+ def iterator : Iterator [( Name , EnumSet [UseScope ]) ] = _names.iterator
379392
380393 private [DependencyRecorder ] def update (name : Name , includeSealedChildren : Boolean ): Unit = {
381394 if (includeSealedChildren)
@@ -386,7 +399,7 @@ class DependencyRecorder {
386399
387400 override def toString (): String = {
388401 val builder = new StringBuilder
389- names .foreach { case (name, scopes) =>
402+ iterator .foreach { (name, scopes) =>
390403 builder.append(name.mangledString)
391404 builder.append(" in [" )
392405 scopes.forEach(scope => builder.append(scope.toString))
@@ -398,17 +411,17 @@ class DependencyRecorder {
398411 }
399412
400413
401- private val _classDependencies = new mutable.HashSet [ ClassDependency ]
414+ private val _classDependencies = new mutable.HashMap [ Symbol , ClassDepsInClass ]
402415
403- def classDependencies : Set [ ClassDependency ] = _classDependencies
416+ def classDependencies : collection. Map [ Symbol , ClassDepsInClass ] = _classDependencies
404417
405418 /** Record a dependency to the class `to` in a given `context`
406419 * from the current non-local enclosing class.
407420 */
408421 def addClassDependency (toClass : Symbol , context : DependencyContext )(using Context ): Unit =
409- val fromClass = resolveDependencySource
422+ val fromClass = resolveDependencyFromClass
410423 if (fromClass.exists)
411- _classDependencies += ClassDependency (fromClass, toClass, context)
424+ lastDepCache.addDependency( toClass, context)
412425
413426 private val _usedNames = new mutable.HashMap [Symbol , UsedNamesInClass ]
414427
@@ -418,11 +431,13 @@ class DependencyRecorder {
418431 usedNames.foreach:
419432 case (clazz, usedNames) =>
420433 val className = classNameAsString(clazz)
421- usedNames.names.foreach:
422- case (usedName, scopes) =>
423- cb.usedName(className, usedName.toString, scopes)
434+ usedNames.iterator.foreach: (usedName, scopes) =>
435+ cb.usedName(className, usedName.toString, scopes)
424436 val siblingClassfiles = new mutable.HashMap [PlainFile , Path ]
425- classDependencies.foreach(recordClassDependency(cb, _, siblingClassfiles))
437+ for (fromClass, partialDependencies) <- _classDependencies do
438+ for (toClass, deps) <- partialDependencies.iterator do
439+ for dep <- deps.asScala do
440+ recordClassDependency(cb, fromClass, toClass, dep, siblingClassfiles)
426441 clear()
427442
428443 /** Clear all state. */
@@ -431,15 +446,17 @@ class DependencyRecorder {
431446 _classDependencies.clear()
432447 lastOwner = NoSymbol
433448 lastDepSource = NoSymbol
449+ lastDepCache = null
450+ lastUsedCache = null
434451 _responsibleForImports = NoSymbol
435452
436453 /** Handles dependency on given symbol by trying to figure out if represents a term
437454 * that is coming from either source code (not necessarily compiled in this compilation
438455 * run) or from class file and calls respective callback method.
439456 */
440- private def recordClassDependency (cb : interfaces.IncrementalCallback , dep : ClassDependency ,
441- siblingClassfiles : mutable.Map [PlainFile , Path ])(using Context ): Unit = {
442- val fromClassName = classNameAsString(dep. fromClass)
457+ private def recordClassDependency (cb : interfaces.IncrementalCallback , fromClass : Symbol , toClass : Symbol ,
458+ depCtx : DependencyContext , siblingClassfiles : mutable.Map [PlainFile , Path ])(using Context ): Unit = {
459+ val fromClassName = classNameAsString(fromClass)
443460 val sourceFile = ctx.compilationUnit.source
444461
445462 /** For a `.tasty` file, constructs a sibling class to the `jpath`.
@@ -465,13 +482,13 @@ class DependencyRecorder {
465482 })
466483
467484 def binaryDependency (path : Path , binaryClassName : String ) =
468- cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, dep.context )
485+ cb.binaryDependency(path, binaryClassName, fromClassName, sourceFile, depCtx )
469486
470- val depClass = dep. toClass
487+ val depClass = toClass
471488 val depFile = depClass.associatedFile
472489 if depFile != null then {
473490 // Cannot ignore inheritance relationship coming from the same source (see sbt/zinc#417)
474- def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance
491+ def allowLocal = depCtx == DependencyByInheritance || depCtx == LocalDependencyByInheritance
475492 val isTasty = depFile.hasTastyExtension
476493
477494 def processExternalDependency () = {
@@ -485,7 +502,7 @@ class DependencyRecorder {
485502 case pf : PlainFile => // The dependency comes from a class file, Zinc handles JRT filesystem
486503 binaryDependency(if isTasty then cachedSiblingClass(pf) else pf.jpath, binaryClassName)
487504 case _ =>
488- internalError(s " Ignoring dependency $depFile of unknown class ${depFile.getClass}} " , dep. fromClass.srcPos)
505+ internalError(s " Ignoring dependency $depFile of unknown class ${depFile.getClass}} " , fromClass.srcPos)
489506 }
490507 }
491508
@@ -495,23 +512,30 @@ class DependencyRecorder {
495512 // We cannot ignore dependencies coming from the same source file because
496513 // the dependency info needs to propagate. See source-dependencies/trait-trait-211.
497514 val toClassName = classNameAsString(depClass)
498- cb.classDependency(toClassName, fromClassName, dep.context )
515+ cb.classDependency(toClassName, fromClassName, depCtx )
499516 }
500517 }
501518
502519 private var lastOwner : Symbol = _
503520 private var lastDepSource : Symbol = _
521+ private var lastDepCache : ClassDepsInClass | Null = _
522+ private var lastUsedCache : UsedNamesInClass | Null = _
504523
505524 /** The source of the dependency according to `nonLocalEnclosingClass`
506525 * if it exists, otherwise fall back to `responsibleForImports`.
507526 *
508527 * This is backed by a cache which is invalidated when `ctx.owner` changes.
509528 */
510- private def resolveDependencySource (using Context ): Symbol = {
529+ private def resolveDependencyFromClass (using Context ): Symbol = {
530+ import dotty .tools .uncheckedNN
511531 if (lastOwner != ctx.owner) {
512532 lastOwner = ctx.owner
513533 val source = nonLocalEnclosingClass
514- lastDepSource = if (source.is(PackageClass )) responsibleForImports else source
534+ val fromClass = if (source.is(PackageClass )) responsibleForImports else source
535+ if lastDepSource != fromClass then
536+ lastDepSource = fromClass
537+ lastDepCache = _classDependencies.getOrElseUpdate(fromClass, new ClassDepsInClass )
538+ lastUsedCache = _usedNames.getOrElseUpdate(fromClass, new UsedNamesInClass )
515539 }
516540
517541 lastDepSource
0 commit comments