@@ -25,6 +25,7 @@ import xsbti.api.DefinitionType
25
25
26
26
import scala .collection .mutable
27
27
import scala .util .hashing .MurmurHash3
28
+ import scala .util .chaining .*
28
29
29
30
/** This phase sends a representation of the API of classes to sbt via callbacks.
30
31
*
@@ -141,14 +142,17 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
141
142
/** This cache is necessary to avoid unstable name hashing when `typeCache` is present,
142
143
* see the comment in the `RefinedType` case in `computeType`
143
144
* The cache key is (api of RefinedType#parent, api of RefinedType#refinedInfo).
144
- */
145
+ */
145
146
private val refinedTypeCache = new mutable.HashMap [(api.Type , api.Definition ), api.Structure ]
146
147
147
- /** This cache is necessary to avoid infinite loops when hashing the body of inline definitions.
148
- * Its keys represent the root inline definitions, and its values are seen inline references within
149
- * the rhs of the key. If a symbol is present in the value set, then do not hash its signature or inline body.
148
+ /** This cache is necessary to avoid infinite loops when hashing an inline "Body" annotation.
149
+ * Its values are transitively seen inline references within a call chain starting from a single "origin" inline
150
+ * definition. Avoid hashing an inline "Body" annotation if its associated definition is already in the cache.
151
+ * Precondition: the cache is empty whenever we hash a new "origin" inline "Body" annotation.
150
152
*/
151
- private val seenInlineCache = mutable.HashMap .empty[Symbol , mutable.HashSet [Symbol ]]
153
+ private val seenInlineCache = mutable.HashSet .empty[Symbol ]
154
+
155
+ /** This cache is optional, it avoids recomputing hashes of inline "Body" annotations. */
152
156
private val inlineBodyCache = mutable.HashMap .empty[Symbol , Int ]
153
157
154
158
private val allNonLocalClassesInSrc = new mutable.HashSet [xsbti.api.ClassLike ]
@@ -357,15 +361,18 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
357
361
*/
358
362
def apiDef (sym : TermSymbol , inlineOrigin : Symbol ): api.Def = {
359
363
360
- val inlineExtras = new mutable.ListBuffer [Int => Int ]
364
+ var seenInlineExtras = false
365
+ var inlineExtras = 41
361
366
362
367
def mixInlineParam (p : Symbol ): Unit =
363
368
if inlineOrigin.exists && p.is(Inline ) then
364
- inlineExtras += hashInlineParam(p)
369
+ seenInlineExtras = true
370
+ inlineExtras = hashInlineParam(p, inlineExtras)
365
371
366
372
def inlineExtrasAnnot : Option [api.Annotation ] =
367
- Option .when(inlineOrigin.exists && inlineExtras.nonEmpty) {
368
- marker(s " ${hashList(inlineExtras.toList)(" inlineExtras" .hashCode)}" )
373
+ val h = inlineExtras
374
+ Option .when(seenInlineExtras) {
375
+ marker(s " ${MurmurHash3 .finalizeHash(h, " inlineExtras" .hashCode)}" )
369
376
}
370
377
371
378
def tparamList (pt : TypeLambda ): List [api.TypeParameter ] =
@@ -654,25 +661,15 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
654
661
// could store the hash as an annotation when pickling an inline def
655
662
// and retrieve it here instead of computing it on the fly.
656
663
657
- def registerInlineHash (inlineBodyHash : Int ): Unit =
658
- annots += marker(inlineBodyHash.toString)
659
-
660
- def nestedHash (root : Symbol ): Unit =
661
- if ! seenInlineCache(root).contains(s) then
662
- seenInlineCache(root) += s
663
- registerInlineHash(treeHash(inlineBody, inlineOrigin = root))
664
+ def hash [U ](inlineOrigin : Symbol ): Int =
665
+ assert(seenInlineCache.add(s)) // will fail if already seen, guarded by treeHash
666
+ treeHash(inlineBody, inlineOrigin)
664
667
665
- def originHash (root : Symbol ): Unit =
666
- def computeHash (): Int =
667
- assert(! seenInlineCache.contains(root))
668
- seenInlineCache.put(root, mutable.HashSet (root))
669
- val res = treeHash(inlineBody, inlineOrigin = root)
670
- seenInlineCache.remove(root)
671
- res
672
- registerInlineHash(inlineBodyCache.getOrElseUpdate(root, computeHash()))
668
+ val inlineHash =
669
+ if inlineOrigin.exists then hash(inlineOrigin)
670
+ else inlineBodyCache.getOrElseUpdate(s, hash(inlineOrigin = s).tap(_ => seenInlineCache.clear()))
673
671
674
- if inlineOrigin.exists then nestedHash(root = inlineOrigin)
675
- else originHash(root = s)
672
+ annots += marker(inlineHash.toString)
676
673
677
674
end if
678
675
@@ -712,32 +709,19 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
712
709
MurmurHash3 .mix(h, n.toString.hashCode)
713
710
end nameHash
714
711
715
- def typeHash (tp : Type , initHash : Int ): Int =
716
- // Go through `apiType` to get a value with a stable hash, it'd
717
- // be better to use Murmur here too instead of relying on
718
- // `hashCode`, but that would essentially mean duplicating
719
- // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
720
- // and at that point we might as well do type hashing on our own
721
- // representation.
722
- var h = initHash
723
- tp match
724
- case ConstantType (c) =>
725
- h = constantHash(c, h)
726
- case TypeBounds (lo, hi) => // TODO when does this happen?
727
- h = MurmurHash3 .mix(h, apiType(lo).hashCode)
728
- h = MurmurHash3 .mix(h, apiType(hi).hashCode)
729
- case tp =>
730
- h = MurmurHash3 .mix(h, apiType(tp).hashCode)
731
- h
732
- end typeHash
733
-
734
712
def constantHash (c : Constant , initHash : Int ): Int =
735
713
var h = MurmurHash3 .mix(initHash, c.tag)
736
714
c.tag match
737
715
case NullTag =>
738
716
// No value to hash, the tag is enough.
739
717
case ClazzTag =>
740
- h = typeHash(c.typeValue, h)
718
+ // Go through `apiType` to get a value with a stable hash, it'd
719
+ // be better to use Murmur here too instead of relying on
720
+ // `hashCode`, but that would essentially mean duplicating
721
+ // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
722
+ // and at that point we might as well do type hashing on our own
723
+ // representation.
724
+ h = MurmurHash3 .mix(h, apiType(c.typeValue).hashCode)
741
725
case _ =>
742
726
h = MurmurHash3 .mix(h, c.value.hashCode)
743
727
h
@@ -758,7 +742,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
758
742
p match
759
743
case ref : RefTree @ unchecked =>
760
744
val sym = ref.symbol
761
- if sym.is(Inline , butNot = Param ) && ! seenInlineCache(inlineOrigin) .contains(sym) then
745
+ if sym.is(Inline , butNot = Param ) && ! seenInlineCache.contains(sym) then
762
746
// An inline method that calls another inline method will eventually inline the call
763
747
// at a non-inline callsite, in this case if the implementation of the nested call
764
748
// changes, then the callsite will have a different API, we should hash the definition
@@ -824,20 +808,10 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
824
808
MurmurHash3 .finalizeHash(h, len)
825
809
end hashTparamsExtras
826
810
827
- private def hashList (extraHashes : List [Int => Int ])(initHash : Int ): Int =
828
- var h = initHash
829
- var fs = extraHashes
830
- var len = 0
831
- while fs.nonEmpty do
832
- h = fs.head(h)
833
- fs = fs.tail
834
- len += 1
835
- MurmurHash3 .finalizeHash(h, len)
836
-
837
811
/** Mix in the name hash also because otherwise switching which
838
812
* parameter is inline will not affect the hash.
839
813
*/
840
- private def hashInlineParam (p : Symbol )( h : Int ) =
814
+ private def hashInlineParam (p : Symbol , h : Int ) =
841
815
MurmurHash3 .mix(p.name.toString.hashCode, MurmurHash3 .mix(h, InlineParamHash ))
842
816
843
817
def apiAnnotation (annot : Annotation ): api.Annotation = {
0 commit comments