@@ -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,129 @@ 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 TypeBounds (lo, hi) =>
653
+ h = MurmurHash3 .mix(h, apiType(lo).hashCode)
654
+ h = MurmurHash3 .mix(h, apiType(hi).hashCode)
655
+ case tp : Type =>
656
+ h = MurmurHash3 .mix(h, apiType(tp).hashCode)
657
+ h
658
+ end typeHash
659
+
660
+ def constantHash (c : Constant , initHash : Int ): Int =
661
+ var h = MurmurHash3 .mix(initHash, c.tag)
662
+ c.tag match
663
+ case NullTag =>
664
+ // No value to hash, the tag is enough.
665
+ case ClazzTag =>
666
+ h = typeHash(c.typeValue, h)
667
+ case _ =>
668
+ h = MurmurHash3 .mix(h, c.value.hashCode)
669
+ h
670
+ end constantHash
671
+
672
+ /** An inline method that calls another inline method will eventually inline the call
673
+ * at a non-inline callsite, in this case if the implementation of the nested call
674
+ * changes, then the callsite will have a different API, we should hash the definition
675
+ */
676
+ def inlineDefHash (ref : Symbol , initHash : Int ): Int =
677
+ var h = initHash
678
+
679
+ def paramssHash (paramss : List [List [Symbol ]], initHash : Int ): Int = paramss match
680
+ case Nil :: paramss1 =>
681
+ paramssHash(paramss1, MurmurHash3 .mix(initHash, EmptyParamHash ))
682
+ case params :: paramss1 =>
683
+ var h = initHash
684
+ val paramsIt = params.iterator
685
+ while paramsIt.hasNext do
686
+ val param = paramsIt.next
687
+ h = nameHash(param.name, h)
688
+ h = typeHash(param.info, h)
689
+ if param.is(Inline ) then
690
+ h = MurmurHash3 .mix(h, InlineParamHash ) // inline would change the generated code
691
+ if param.isType then
692
+ val variance = param.paramVarianceSign
693
+ if variance != 0 then
694
+ h = MurmurHash3 .mix(h, variance)
695
+ paramssHash(paramss1, h)
696
+ case Nil =>
697
+ initHash
698
+ end paramssHash
699
+
700
+ h = paramssHash(ref.paramSymss, h)
701
+ h = typeHash(ref.info.finalResultType, h)
702
+ positionedHash(Inliner .bodyToInline(ref), h)
703
+ end inlineDefHash
625
704
626
705
def positionedHash (p : ast.Positioned , initHash : Int ): Int =
706
+ var h = initHash
707
+
627
708
p match
628
709
case p : WithLazyField [? ] =>
629
710
p.forceIfLazy
630
711
case _ =>
712
+
713
+ p match
714
+ case ref : RefTree @ unchecked =>
715
+ val sym = ref.symbol
716
+ def err (): Int =
717
+ internalError(i " Don't know how to produce a stable hash for inline reference ` $ref` " , ref.sourcePos)
718
+ ref.name.toString.hashCode
719
+ if sym.is(Inline , butNot = Param ) && ! seenInlines.contains(sym) then
720
+ seenInlines += sym // dont re-enter hashing this ref
721
+ if sym.is(Method ) then // inline def
722
+ if Inliner .hasBodyToInline(sym) then
723
+ h = inlineDefHash(sym, h)
724
+ else
725
+ h = err()
726
+ else // inline val
727
+ sym.info.widenTermRefExpr.dealias.normalized match
728
+ case ConstantType (c) =>
729
+ h = constantHash(c, h)
730
+ case tpe =>
731
+ h = err()
732
+ case _ =>
733
+
631
734
// FIXME: If `p` is a tree we should probably take its type into account
632
735
// when hashing it, but producing a stable hash for a type is not trivial
633
736
// since the same type might have multiple representations, for method
634
737
// signatures this is already handled by `computeType` and the machinery
635
738
// in Zinc that generates hashes from that, if we can reliably produce
636
739
// stable hashes for types ourselves then we could bypass all that and
637
740
// send Zinc hashes directly.
638
- val h = MurmurHash3 .mix(initHash , p.productPrefix.hashCode)
741
+ h = MurmurHash3 .mix(h , p.productPrefix.hashCode)
639
742
iteratorHash(p.productIterator, h)
640
743
end positionedHash
641
744
642
745
def iteratorHash (it : Iterator [Any ], initHash : Int ): Int =
643
- import core .Constants ._
644
746
var h = initHash
645
747
while it.hasNext do
646
748
it.next() match
@@ -649,24 +751,9 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
649
751
case xs : List [? ] =>
650
752
h = iteratorHash(xs.iterator, h)
651
753
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)
754
+ h = constantHash(c, h)
667
755
case n : Name =>
668
- // The hashCode of the name itself is not stable across compiler instances
669
- h = MurmurHash3 .mix(h, n.toString.hashCode)
756
+ h = nameHash(n, h)
670
757
case elem =>
671
758
internalError(
672
759
i " Don't know how to produce a stable hash for ` $elem` of unknown class ${elem.getClass}" ,
@@ -691,6 +778,6 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
691
778
// annotated @org.junit.Test).
692
779
api.Annotation .of(
693
780
apiType(annot.tree.tpe), // Used by sbt to find tests to run
694
- Array (api.AnnotationArgument .of(" TREE_HASH" , treeHash(annot.tree).toString)))
781
+ Array (api.AnnotationArgument .of(" TREE_HASH" , treeHash(annot.tree, inlineSym = NoSymbol ).toString)))
695
782
}
696
783
}
0 commit comments