@@ -599,7 +599,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
599
599
// an inline def in every class that extends its owner. To avoid this we
600
600
// could store the hash as an annotation when pickling an inline def
601
601
// and retrieve it here instead of computing it on the fly.
602
- val inlineBodyHash = treeHash(inlineBody)
602
+ val inlineBodyHash = treeHash(inlineBody, inlineSym = s )
603
603
annots += marker(inlineBodyHash.toString)
604
604
}
605
605
@@ -620,27 +620,122 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
620
620
* it should stay the same across compiler runs, compiler instances,
621
621
* JVMs, etc.
622
622
*/
623
- def treeHash (tree : Tree ): Int =
623
+ def treeHash (tree : Tree , inlineSym : Symbol ): Int =
624
624
import scala .util .hashing .MurmurHash3
625
+ import core .Constants .*
626
+
627
+ val seenInlines = mutable.HashSet .empty[Symbol ]
628
+
629
+ if inlineSym ne NoSymbol then
630
+ seenInlines += inlineSym // do not hash twice a recursive def
631
+
632
+ def nameHash (n : Name , initHash : Int ): Int =
633
+ val h =
634
+ if n.isTermName then
635
+ MurmurHash3 .mix(initHash, TermNameHash )
636
+ else
637
+ MurmurHash3 .mix(initHash, TypeNameHash )
638
+
639
+ // The hashCode of the name itself is not stable across compiler instances
640
+ MurmurHash3 .mix(h, n.toString.hashCode)
641
+ end nameHash
642
+
643
+ def typeHash (tp : Type , initHash : Int ): Int =
644
+ // Go through `apiType` to get a value with a stable hash, it'd
645
+ // be better to use Murmur here too instead of relying on
646
+ // `hashCode`, but that would essentially mean duplicating
647
+ // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
648
+ // and at that point we might as well do type hashing on our own
649
+ // representation.
650
+ var h = initHash
651
+ tp match
652
+ case ConstantType (c) =>
653
+ h = constantHash(c, h)
654
+ case TypeBounds (lo, hi) =>
655
+ h = MurmurHash3 .mix(h, apiType(lo).hashCode)
656
+ h = MurmurHash3 .mix(h, apiType(hi).hashCode)
657
+ case tp =>
658
+ h = MurmurHash3 .mix(h, apiType(tp).hashCode)
659
+ h
660
+ end typeHash
661
+
662
+ def constantHash (c : Constant , initHash : Int ): Int =
663
+ var h = MurmurHash3 .mix(initHash, c.tag)
664
+ c.tag match
665
+ case NullTag =>
666
+ // No value to hash, the tag is enough.
667
+ case ClazzTag =>
668
+ h = typeHash(c.typeValue, h)
669
+ case _ =>
670
+ h = MurmurHash3 .mix(h, c.value.hashCode)
671
+ h
672
+ end constantHash
673
+
674
+ /** An inline method that calls another inline method will eventually inline the call
675
+ * at a non-inline callsite, in this case if the implementation of the nested call
676
+ * changes, then the callsite will have a different API, we should hash the definition
677
+ */
678
+ def inlineReferenceHash (ref : Symbol , rhs : Tree , initHash : Int ): Int =
679
+ var h = initHash
680
+
681
+ def paramssHash (paramss : List [List [Symbol ]], initHash : Int ): Int = paramss match
682
+ case Nil :: paramss1 =>
683
+ paramssHash(paramss1, MurmurHash3 .mix(initHash, EmptyParamHash ))
684
+ case params :: paramss1 =>
685
+ var h = initHash
686
+ val paramsIt = params.iterator
687
+ while paramsIt.hasNext do
688
+ val param = paramsIt.next
689
+ h = nameHash(param.name, h)
690
+ h = typeHash(param.info, h)
691
+ if param.is(Inline ) then
692
+ h = MurmurHash3 .mix(h, InlineParamHash ) // inline would change the generated code
693
+ paramssHash(paramss1, h)
694
+ case Nil =>
695
+ initHash
696
+ end paramssHash
697
+
698
+ h = paramssHash(ref.paramSymss, h)
699
+ h = typeHash(ref.info.finalResultType, h)
700
+ positionedHash(rhs, h)
701
+ end inlineReferenceHash
702
+
703
+ def err (what : String , elem : Any , pos : Positioned , initHash : Int ): Int =
704
+ internalError(i " Don't know how to produce a stable hash for $what" , pos.sourcePos)
705
+ MurmurHash3 .mix(initHash, elem.toString.hashCode)
625
706
626
707
def positionedHash (p : ast.Positioned , initHash : Int ): Int =
708
+ var h = initHash
709
+
627
710
p match
628
711
case p : WithLazyField [? ] =>
629
712
p.forceIfLazy
630
713
case _ =>
714
+
715
+ p match
716
+ case ref : RefTree @ unchecked =>
717
+ val sym = ref.symbol
718
+ if sym.is(Inline , butNot = Param ) && ! seenInlines.contains(sym) then
719
+ seenInlines += sym // dont re-enter hashing this ref
720
+ sym.defTree match
721
+ case defTree : ValOrDefDef =>
722
+ h = inlineReferenceHash(sym, defTree.rhs, h)
723
+ case _ =>
724
+ h = err(i " inline method reference ` ${ref.name}` " , ref.name, ref, h)
725
+ case _ =>
726
+
631
727
// FIXME: If `p` is a tree we should probably take its type into account
632
728
// when hashing it, but producing a stable hash for a type is not trivial
633
729
// since the same type might have multiple representations, for method
634
730
// signatures this is already handled by `computeType` and the machinery
635
731
// in Zinc that generates hashes from that, if we can reliably produce
636
732
// stable hashes for types ourselves then we could bypass all that and
637
733
// send Zinc hashes directly.
638
- val h = MurmurHash3 .mix(initHash , p.productPrefix.hashCode)
734
+ h = MurmurHash3 .mix(h , p.productPrefix.hashCode)
639
735
iteratorHash(p.productIterator, h)
640
736
end positionedHash
641
737
642
738
def iteratorHash (it : Iterator [Any ], initHash : Int ): Int =
643
- import core .Constants ._
644
739
var h = initHash
645
740
while it.hasNext do
646
741
it.next() match
@@ -649,30 +744,11 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
649
744
case xs : List [? ] =>
650
745
h = iteratorHash(xs.iterator, h)
651
746
case c : Constant =>
652
- h = MurmurHash3 .mix(h, c.tag)
653
- c.tag match
654
- case NullTag =>
655
- // No value to hash, the tag is enough.
656
- case ClazzTag =>
657
- // Go through `apiType` to get a value with a stable hash, it'd
658
- // be better to use Murmur here too instead of relying on
659
- // `hashCode`, but that would essentially mean duplicating
660
- // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
661
- // and at that point we might as well do type hashing on our own
662
- // representation.
663
- val apiValue = apiType(c.typeValue)
664
- h = MurmurHash3 .mix(h, apiValue.hashCode)
665
- case _ =>
666
- h = MurmurHash3 .mix(h, c.value.hashCode)
747
+ h = constantHash(c, h)
667
748
case n : Name =>
668
- // The hashCode of the name itself is not stable across compiler instances
669
- h = MurmurHash3 .mix(h, n.toString.hashCode)
749
+ h = nameHash(n, h)
670
750
case elem =>
671
- internalError(
672
- i " Don't know how to produce a stable hash for ` $elem` of unknown class ${elem.getClass}" ,
673
- tree.sourcePos)
674
-
675
- h = MurmurHash3 .mix(h, elem.toString.hashCode)
751
+ h = err(i " ` $elem` of unknown class ${elem.getClass}" , elem, tree, h)
676
752
h
677
753
end iteratorHash
678
754
@@ -691,6 +767,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
691
767
// annotated @org.junit.Test).
692
768
api.Annotation .of(
693
769
apiType(annot.tree.tpe), // Used by sbt to find tests to run
694
- Array (api.AnnotationArgument .of(" TREE_HASH" , treeHash(annot.tree).toString)))
770
+ Array (api.AnnotationArgument .of(" TREE_HASH" , treeHash(annot.tree, inlineSym = NoSymbol ).toString)))
695
771
}
696
772
}
0 commit comments