@@ -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,130 @@ 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 inlineDefHash (ref : Symbol , 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
+ if param.isType then
694
+ val variance = param.paramVarianceSign
695
+ if variance != 0 then
696
+ h = MurmurHash3 .mix(h, variance)
697
+ paramssHash(paramss1, h)
698
+ case Nil =>
699
+ initHash
700
+ end paramssHash
701
+
702
+ h = paramssHash(ref.paramSymss, h)
703
+ h = typeHash(ref.info.finalResultType, h)
704
+ positionedHash(Inliner .bodyToInline(ref), h)
705
+ end inlineDefHash
706
+
707
+ def err (what : String , elem : Any , pos : Positioned , initHash : Int ): Int =
708
+ internalError(i " Don't know how to produce a stable hash for $what" , pos.sourcePos)
709
+ MurmurHash3 .mix(initHash, elem.toString.hashCode)
625
710
626
711
def positionedHash (p : ast.Positioned , initHash : Int ): Int =
712
+ var h = initHash
713
+
627
714
p match
628
715
case p : WithLazyField [? ] =>
629
716
p.forceIfLazy
630
717
case _ =>
718
+
719
+ p match
720
+ case ref : RefTree @ unchecked =>
721
+ val sym = ref.symbol
722
+ if sym.is(Inline , butNot = Param ) && ! seenInlines.contains(sym) then
723
+ seenInlines += sym // dont re-enter hashing this ref
724
+ if sym.is(Method ) then // inline def
725
+ if Inliner .hasBodyToInline(sym) then
726
+ h = inlineDefHash(sym, h)
727
+ else
728
+ h = err(i " inline method reference ` ${ref.name}` " , ref.name, ref, h)
729
+ else // inline val
730
+ // before inlining, inline val can be any expression/type, after inlining it should be constant
731
+ val tp = sym.info.widenTermRefExpr.dealias.normalized // copied from `constToLiteral`
732
+ h = typeHash(tp, h)
733
+ case _ =>
734
+
631
735
// FIXME: If `p` is a tree we should probably take its type into account
632
736
// when hashing it, but producing a stable hash for a type is not trivial
633
737
// since the same type might have multiple representations, for method
634
738
// signatures this is already handled by `computeType` and the machinery
635
739
// in Zinc that generates hashes from that, if we can reliably produce
636
740
// stable hashes for types ourselves then we could bypass all that and
637
741
// send Zinc hashes directly.
638
- val h = MurmurHash3 .mix(initHash , p.productPrefix.hashCode)
742
+ h = MurmurHash3 .mix(h , p.productPrefix.hashCode)
639
743
iteratorHash(p.productIterator, h)
640
744
end positionedHash
641
745
642
746
def iteratorHash (it : Iterator [Any ], initHash : Int ): Int =
643
- import core .Constants ._
644
747
var h = initHash
645
748
while it.hasNext do
646
749
it.next() match
@@ -649,30 +752,11 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
649
752
case xs : List [? ] =>
650
753
h = iteratorHash(xs.iterator, h)
651
754
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)
755
+ h = constantHash(c, h)
667
756
case n : Name =>
668
- // The hashCode of the name itself is not stable across compiler instances
669
- h = MurmurHash3 .mix(h, n.toString.hashCode)
757
+ h = nameHash(n, h)
670
758
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)
759
+ h = err(i " ` $elem` of unknown class ${elem.getClass}" , elem, tree, h)
676
760
h
677
761
end iteratorHash
678
762
@@ -691,6 +775,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
691
775
// annotated @org.junit.Test).
692
776
api.Annotation .of(
693
777
apiType(annot.tree.tpe), // Used by sbt to find tests to run
694
- Array (api.AnnotationArgument .of(" TREE_HASH" , treeHash(annot.tree).toString)))
778
+ Array (api.AnnotationArgument .of(" TREE_HASH" , treeHash(annot.tree, inlineSym = NoSymbol ).toString)))
695
779
}
696
780
}
0 commit comments