1
1
package dotty .tools .dotc
2
2
package sbt
3
3
4
- import ast .{Trees , tpd }
4
+ import ExtractDependencies .internalError
5
+ import ast .{Positioned , Trees , tpd , untpd }
5
6
import core ._
6
7
import core .Decorators ._
7
8
import Annotations ._
@@ -11,6 +12,7 @@ import Phases._
11
12
import Trees ._
12
13
import Types ._
13
14
import Symbols ._
15
+ import Names ._
14
16
import NameOps ._
15
17
import NameKinds .DefaultGetterName
16
18
import typer .Inliner
@@ -29,7 +31,7 @@ import scala.collection.mutable
29
31
*
30
32
* See the documentation of `ExtractAPICollector`, `ExtractDependencies`,
31
33
* `ExtractDependenciesCollector` and
32
- * http://www.scala-sbt.org/0.13 /docs/Understanding-Recompilation.html for more
34
+ * http://www.scala-sbt.org/1.x /docs/Understanding-Recompilation.html for more
33
35
* information on incremental recompilation.
34
36
*
35
37
* The following flags affect this phase:
@@ -171,6 +173,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
171
173
private val orMarker = marker(" Or" )
172
174
private val byNameMarker = marker(" ByName" )
173
175
private val matchMarker = marker(" Match" )
176
+ private val superMarker = marker(" Super" )
174
177
175
178
/** Extract the API representation of a source file */
176
179
def apiSource (tree : Tree ): Seq [api.ClassLike ] = {
@@ -513,8 +516,11 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
513
516
apiType(tp.ref)
514
517
case tp : TypeVar =>
515
518
apiType(tp.underlying)
519
+ case SuperType (thistpe, supertpe) =>
520
+ val s = combineApiTypes(apiType(thistpe), apiType(supertpe))
521
+ withMarker(s, superMarker)
516
522
case _ => {
517
- report.warning (i " sbt-api: Unhandled type ${tp.getClass} : $tp " )
523
+ internalError (i " Unhandled type $tp of class ${tp.getClass} " )
518
524
Constants .emptyType
519
525
}
520
526
}
@@ -582,13 +588,18 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
582
588
val annots = new mutable.ListBuffer [api.Annotation ]
583
589
val inlineBody = Inliner .bodyToInline(s)
584
590
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)
591
+ // If the body of an inline def changes, all the reverse dependencies of
592
+ // this method need to be recompiled. sbt has no way of tracking method
593
+ // bodies, so we include the hash of the body of the method as part of the
594
+ // signature we send to sbt.
595
+ //
596
+ // FIXME: The API of a class we send to Zinc includes the signatures of
597
+ // inherited methods, which means that we repeatedly compute the hash of
598
+ // an inline def in every class that extends its owner. To avoid this we
599
+ // could store the hash as an annotation when pickling an inline def
600
+ // and retrieve it here instead of computing it on the fly.
601
+ val inlineBodyHash = treeHash(inlineBody)
602
+ annots += marker(inlineBodyHash.toString)
592
603
}
593
604
594
605
// In the Scala2 ExtractAPI phase we only extract annotations that extend
@@ -602,16 +613,81 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
602
613
annots.toList
603
614
}
604
615
616
+ /** Produce a hash for a tree that is as stable as possible:
617
+ * it should stay the same across compiler runs, compiler instances,
618
+ * JVMs, etc.
619
+ */
620
+ def treeHash (tree : Tree ): Int =
621
+ import scala .util .hashing .MurmurHash3
622
+
623
+ def positionedHash (p : ast.Positioned , initHash : Int ): Int =
624
+ p match
625
+ case p : WithLazyField [? ] =>
626
+ p.forceIfLazy
627
+ case _ =>
628
+ // FIXME: If `p` is a tree we should probably take its type into account
629
+ // when hashing it, but producing a stable hash for a type is not trivial
630
+ // since the same type might have multiple representations, for method
631
+ // signatures this is already handled by `computeType` and the machinery
632
+ // in Zinc that generates hashes from that, if we can reliably produce
633
+ // stable hashes for types ourselves then we could bypass all that and
634
+ // send Zinc hashes directly.
635
+ val h = MurmurHash3 .mix(initHash, p.productPrefix.hashCode)
636
+ iteratorHash(p.productIterator, h)
637
+ end positionedHash
638
+
639
+ def iteratorHash (it : Iterator [Any ], initHash : Int ): Int =
640
+ import core .Constants ._
641
+ var h = initHash
642
+ while it.hasNext do
643
+ it.next() match
644
+ case p : Positioned =>
645
+ h = positionedHash(p, h)
646
+ case xs : List [? ] =>
647
+ h = iteratorHash(xs.iterator, h)
648
+ case c : Constant =>
649
+ h = MurmurHash3 .mix(h, c.tag)
650
+ c.tag match
651
+ case NullTag =>
652
+ // No value to hash, the tag is enough.
653
+ case ClazzTag =>
654
+ // Go through `apiType` to get a value with a stable hash, it'd
655
+ // be better to use Murmur here too instead of relying on
656
+ // `hashCode`, but that would essentially mean duplicating
657
+ // https://github.com/sbt/zinc/blob/develop/internal/zinc-apiinfo/src/main/scala/xsbt/api/HashAPI.scala
658
+ // and at that point we might as well do type hashing on our own
659
+ // representation.
660
+ val apiValue = apiType(c.typeValue)
661
+ h = MurmurHash3 .mix(h, apiValue.hashCode)
662
+ case _ =>
663
+ h = MurmurHash3 .mix(h, c.value.hashCode)
664
+ case n : Name =>
665
+ // The hashCode of the name itself is not stable across compiler instances
666
+ h = MurmurHash3 .mix(h, n.toString.hashCode)
667
+ case elem =>
668
+ internalError(
669
+ i " Don't know how to produce a stable hash for ` $elem` of unknown class ${elem.getClass}" ,
670
+ tree.sourcePos)
671
+
672
+ h = MurmurHash3 .mix(h, elem.toString.hashCode)
673
+ h
674
+ end iteratorHash
675
+
676
+ val seed = 4 // https://xkcd.com/221
677
+ val h = positionedHash(tree, seed)
678
+ MurmurHash3 .finalizeHash(h, 0 )
679
+ end treeHash
680
+
605
681
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).
682
+ // Like with inline defs, the whole body of the annotation and not just its
683
+ // type is part of its API so we need to store its hash, but Zinc wants us
684
+ // to extract the annotation type and its arguments, so we use a dummy
685
+ // annotation argument to store the hash of the tree. We still need to
686
+ // extract the annotation type in the way Zinc expects because sbt uses this
687
+ // information to find tests to run (for example junit tests are
688
+ // annotated @org.junit.Test).
613
689
api.Annotation .of(
614
690
apiType(annot.tree.tpe), // Used by sbt to find tests to run
615
- Array (api.AnnotationArgument .of(" FULLTREE " , annot.tree.toString)))
691
+ Array (api.AnnotationArgument .of(" TREE_HASH " , treeHash( annot.tree) .toString)))
616
692
}
617
693
}
0 commit comments