@@ -599,7 +599,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
599599 // an inline def in every class that extends its owner. To avoid this we
600600 // could store the hash as an annotation when pickling an inline def
601601 // and retrieve it here instead of computing it on the fly.
602- val inlineBodyHash = treeHash(inlineBody)
602+ val inlineBodyHash = treeHash(inlineBody, inlineSym = s )
603603 annots += marker(inlineBodyHash.toString)
604604 }
605605
@@ -620,27 +620,129 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
620620 * it should stay the same across compiler runs, compiler instances,
621621 * JVMs, etc.
622622 */
623- def treeHash (tree : Tree ): Int =
623+ def treeHash (tree : Tree , inlineSym : Symbol ): Int =
624624 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
625704
626705 def positionedHash (p : ast.Positioned , initHash : Int ): Int =
706+ var h = initHash
707+
627708 p match
628709 case p : WithLazyField [? ] =>
629710 p.forceIfLazy
630711 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+
631734 // FIXME: If `p` is a tree we should probably take its type into account
632735 // when hashing it, but producing a stable hash for a type is not trivial
633736 // since the same type might have multiple representations, for method
634737 // signatures this is already handled by `computeType` and the machinery
635738 // in Zinc that generates hashes from that, if we can reliably produce
636739 // stable hashes for types ourselves then we could bypass all that and
637740 // send Zinc hashes directly.
638- val h = MurmurHash3 .mix(initHash , p.productPrefix.hashCode)
741+ h = MurmurHash3 .mix(h , p.productPrefix.hashCode)
639742 iteratorHash(p.productIterator, h)
640743 end positionedHash
641744
642745 def iteratorHash (it : Iterator [Any ], initHash : Int ): Int =
643- import core .Constants ._
644746 var h = initHash
645747 while it.hasNext do
646748 it.next() match
@@ -649,24 +751,9 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
649751 case xs : List [? ] =>
650752 h = iteratorHash(xs.iterator, h)
651753 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)
667755 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)
670757 case elem =>
671758 internalError(
672759 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 {
691778 // annotated @org.junit.Test).
692779 api.Annotation .of(
693780 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)))
695782 }
696783}
0 commit comments