1
1
package dotty .tools .dotc
2
2
package sbt
3
3
4
- import ast .{Trees , tpd }
4
+ import ast .{Positioned , Trees , tpd , untpd }
5
5
import core ._
6
6
import core .Decorators ._
7
7
import Annotations ._
@@ -11,6 +11,7 @@ import Phases._
11
11
import Trees ._
12
12
import Types ._
13
13
import Symbols ._
14
+ import Names ._
14
15
import NameOps ._
15
16
import NameKinds .DefaultGetterName
16
17
import typer .Inliner
@@ -582,13 +583,18 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
582
583
val annots = new mutable.ListBuffer [api.Annotation ]
583
584
val inlineBody = Inliner .bodyToInline(s)
584
585
if (! inlineBody.isEmpty) {
585
- // FIXME: If the body of an inlineable method changes, all the reverse
586
- // dependencies of this method need to be recompiled. sbt has no way
587
- // of tracking method bodies, so as a hack we include the printed
588
- // tree of the method as part of the signature we send to sbt.
589
- // To do this properly we would need a way to hash trees and types in
590
- // dotty itself.
591
- annots += marker(inlineBody.toString)
586
+ // If the body of an inline def changes, all the reverse dependencies of
587
+ // this method need to be recompiled. sbt has no way of tracking method
588
+ // bodies, so we include the hash of the body of the method as part of the
589
+ // signature we send to sbt.
590
+ //
591
+ // FIXME: The API of a class we send to Zinc includes the signatures of
592
+ // inherited methods, which means that we repeatedly compute the hash of
593
+ // an inline def in every class that extends its owner. To avoid this we
594
+ // could store the hash as an annotation when pickling an inline def
595
+ // and retrieve it here instead of computing it on the fly.
596
+ val inlineBodyHash = treeHash(inlineBody)
597
+ annots += marker(inlineBodyHash.toString)
592
598
}
593
599
594
600
// In the Scala2 ExtractAPI phase we only extract annotations that extend
@@ -602,16 +608,66 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
602
608
annots.toList
603
609
}
604
610
611
+ /** Produce a hash for a tree that is as stable as possible:
612
+ * it should stay the same across compiler runs, compiler instances,
613
+ * JVMs, etc.
614
+ */
615
+ def treeHash (tree : Tree ): Int =
616
+ import scala .util .hashing .MurmurHash3
617
+
618
+ def positionedHash (p : ast.Positioned , initHash : Int ): Int =
619
+ p match
620
+ case p : WithLazyField [? ] =>
621
+ p.forceIfLazy
622
+ case _ =>
623
+ // FIXME: If `p` is a tree we should probably take its type into account
624
+ // when hashing it, but producing a stable hash for a type is not trivial
625
+ // since the same type might have multiple representations, for method
626
+ // signatures this is already handled by `computeType` and the machinery
627
+ // in Zinc that generates hashes from that, if we can reliably produce
628
+ // stable hashes for types ourselves then we could bypass all that and
629
+ // send Zinc hashes directly.
630
+ val h = MurmurHash3 .mix(initHash, p.productPrefix.hashCode)
631
+ iteratorHash(p.productIterator, h)
632
+ end positionedHash
633
+
634
+ def iteratorHash (it : Iterator [Any ], initHash : Int ): Int =
635
+ var h = initHash
636
+ while it.hasNext do
637
+ it.next() match
638
+ case p : Positioned =>
639
+ h = positionedHash(p, h)
640
+ case xs : List [? ] =>
641
+ h = iteratorHash(xs.iterator, h)
642
+ case c : core.Constants .Constant =>
643
+ h = MurmurHash3 .mix(h, c.tag)
644
+ h = MurmurHash3 .mix(h, c.value.## ) // We can't use `value.hashCode` since value might be null
645
+ case n : Name =>
646
+ // The hashCode of the name itself is not stable across compiler instances
647
+ h = MurmurHash3 .mix(h, n.toString.hashCode)
648
+ case elem =>
649
+ report.warning(
650
+ i """ Internal error: Don't know how to produce a stable hash for ` $elem` of unknown class ${elem.getClass}
651
+ |Incremental compilation might not work correctly. """ , tree.sourcePos)
652
+ h = MurmurHash3 .mix(h, elem.toString.hashCode)
653
+ h
654
+ end iteratorHash
655
+
656
+ val seed = 4 // https://xkcd.com/221
657
+ val h = positionedHash(tree, seed)
658
+ MurmurHash3 .finalizeHash(h, 0 )
659
+ end treeHash
660
+
605
661
def apiAnnotation (annot : Annotation ): api.Annotation = {
606
- // FIXME: To faithfully extract an API we should extract the annotation tree,
607
- // sbt instead wants us to extract the annotation type and its arguments,
608
- // to do this properly we would need a way to hash trees and types in dotty itself,
609
- // instead we use the raw string representation of the annotation tree.
610
- // However, we still need to extract the annotation type in the way sbt expect
611
- // because sbt uses this information to find tests to run (for example
612
- // junit tests are annotated @org.junit.Test).
662
+ // Like with inline defs, the whole body of the annotation and not just its
663
+ // type is part of its API so we need to store its hash, but Zinc wants us
664
+ // to extract the annotation type and its arguments, so we use a dummy
665
+ // annotation argument to store the hash of the tree. We still need to
666
+ // extract the annotation type in the way Zinc expects because sbt uses this
667
+ // information to find tests to run (for example junit tests are
668
+ // annotated @org.junit.Test).
613
669
api.Annotation .of(
614
670
apiType(annot.tree.tpe), // Used by sbt to find tests to run
615
- Array (api.AnnotationArgument .of(" FULLTREE " , annot.tree.toString)))
671
+ Array (api.AnnotationArgument .of(" TREE_HASH " , treeHash( annot.tree) .toString)))
616
672
}
617
673
}
0 commit comments