Skip to content

Commit 103ed7c

Browse files
committed
Merge branch 'master' into overloaded-with-retry-path
2 parents 1fdacc7 + a79145d commit 103ed7c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+456
-66
lines changed

compiler/src/dotty/tools/dotc/Driver.scala

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,24 +89,23 @@ class Driver {
8989
// Resolve classpath and class names of tasty files
9090
val (classPaths, classNames) = fileNames0.flatMap { name =>
9191
val path = Paths.get(name)
92-
if (name.endsWith(".jar"))
92+
if !name.endsWith(".jar") && !name.endsWith(".tasty") then // is class name
93+
("", name) :: Nil // TODO remove this case. We cannot rely on an expected tasty file beeing loaded.
94+
else if !Files.exists(path) then
95+
report.error(s"File does not exist: $name")
96+
Nil
97+
else if name.endsWith(".jar") then
9398
new dotty.tools.io.Jar(File(name)).toList.collect {
9499
case e if e.getName.endsWith(".tasty") =>
95100
(name, e.getName.stripSuffix(".tasty").replace("/", "."))
96101
}
97-
else if (!name.endsWith(".tasty"))
98-
("", name) :: Nil
99-
else if (Files.exists(path))
100-
TastyFileUtil.getClassName(path) match {
101-
case Some(res) => res:: Nil
102+
else
103+
assert(name.endsWith(".tasty"))
104+
TastyFileUtil.getClassName(path) match
105+
case Some(res) => res :: Nil
102106
case _ =>
103-
report.error(s"Could not load classname from $name.")
104-
("", name) :: Nil
105-
}
106-
else {
107-
report.error(s"File $name does not exist.")
108-
("", name) :: Nil
109-
}
107+
report.error(s"Could not load classname from: $name")
108+
Nil
110109
}.unzip
111110
val ctx1 = ctx0.fresh
112111
val classPaths1 = classPaths.distinct.filter(_ != "")

compiler/src/dotty/tools/dotc/core/Symbols.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ object Symbols {
250250

251251
/** The source or class file from which this class or
252252
* the class containing this symbol was generated, null if not applicable.
253+
* Note that this the returned classfile might be the top-level class
254+
* containing this symbol instead of the directly enclosing class.
253255
* Overridden in ClassSymbol
254256
*/
255257
def associatedFile(using Context): AbstractFile =

compiler/src/dotty/tools/dotc/reporting/ErrorMessageID.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ enum ErrorMessageID extends java.lang.Enum[ErrorMessageID] {
167167
ModifierNotAllowedForDefinitionID,
168168
CannotExtendJavaEnumID,
169169
InvalidReferenceInImplicitNotFoundAnnotationID,
170-
TraitMayNotDefineNativeMethodID
170+
TraitMayNotDefineNativeMethodID,
171+
JavaEnumParentArgsID
171172

172173
def errorNumber = ordinal - 2
173174
}

compiler/src/dotty/tools/dotc/reporting/messages.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,12 @@ import transform.SymUtils._
15281528
def explain = ""
15291529
}
15301530

1531+
class JavaEnumParentArgs(parent: Type)(using Context)
1532+
extends TypeMsg(JavaEnumParentArgsID) {
1533+
def msg = em"""not enough arguments for constructor Enum: ${hl("(name: String, ordinal: Int)")}: ${hl(parent.show)}"""
1534+
def explain = ""
1535+
}
1536+
15311537
class CannotHaveSameNameAs(sym: Symbol, cls: Symbol, reason: CannotHaveSameNameAs.Reason)(using Context)
15321538
extends SyntaxMsg(CannotHaveSameNameAsID) {
15331539
import CannotHaveSameNameAs._

compiler/src/dotty/tools/dotc/sbt/ExtractAPI.scala

Lines changed: 94 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package dotty.tools.dotc
22
package sbt
33

4-
import ast.{Trees, tpd}
4+
import ExtractDependencies.internalError
5+
import ast.{Positioned, Trees, tpd, untpd}
56
import core._
67
import core.Decorators._
78
import Annotations._
@@ -11,6 +12,7 @@ import Phases._
1112
import Trees._
1213
import Types._
1314
import Symbols._
15+
import Names._
1416
import NameOps._
1517
import NameKinds.DefaultGetterName
1618
import typer.Inliner
@@ -29,7 +31,7 @@ import scala.collection.mutable
2931
*
3032
* See the documentation of `ExtractAPICollector`, `ExtractDependencies`,
3133
* `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
3335
* information on incremental recompilation.
3436
*
3537
* The following flags affect this phase:
@@ -171,6 +173,7 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
171173
private val orMarker = marker("Or")
172174
private val byNameMarker = marker("ByName")
173175
private val matchMarker = marker("Match")
176+
private val superMarker = marker("Super")
174177

175178
/** Extract the API representation of a source file */
176179
def apiSource(tree: Tree): Seq[api.ClassLike] = {
@@ -513,8 +516,11 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
513516
apiType(tp.ref)
514517
case tp: TypeVar =>
515518
apiType(tp.underlying)
519+
case SuperType(thistpe, supertpe) =>
520+
val s = combineApiTypes(apiType(thistpe), apiType(supertpe))
521+
withMarker(s, superMarker)
516522
case _ => {
517-
report.warning(i"sbt-api: Unhandled type ${tp.getClass} : $tp")
523+
internalError(i"Unhandled type $tp of class ${tp.getClass}")
518524
Constants.emptyType
519525
}
520526
}
@@ -582,13 +588,18 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
582588
val annots = new mutable.ListBuffer[api.Annotation]
583589
val inlineBody = Inliner.bodyToInline(s)
584590
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)
592603
}
593604

594605
// In the Scala2 ExtractAPI phase we only extract annotations that extend
@@ -602,16 +613,81 @@ private class ExtractAPICollector(using Context) extends ThunkHolder {
602613
annots.toList
603614
}
604615

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+
605681
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).
613689
api.Annotation.of(
614690
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)))
616692
}
617693
}

compiler/src/dotty/tools/dotc/sbt/ExtractDependencies.scala

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import dotty.tools.dotc.core.StdNames._
1616
import dotty.tools.dotc.core.Symbols._
1717
import dotty.tools.dotc.core.Types._
1818
import dotty.tools.dotc.transform.SymUtils._
19+
import dotty.tools.dotc.util.{SrcPos, NoSourcePosition}
1920
import dotty.tools.io
2021
import dotty.tools.io.{AbstractFile, PlainFile, ZipArchive}
2122
import xsbti.UseScope
@@ -112,34 +113,24 @@ class ExtractDependencies extends Phase {
112113
def binaryDependency(file: File, binaryClassName: String) =
113114
ctx.sbtCallback.binaryDependency(file, binaryClassName, fromClassName, sourceFile, dep.context)
114115

115-
def processExternalDependency(depFile: AbstractFile) = {
116-
def binaryClassName(classSegments: List[String]) =
117-
classSegments.mkString(".").stripSuffix(".class")
118-
116+
def processExternalDependency(depFile: AbstractFile, binaryClassName: String) = {
119117
depFile match {
120118
case ze: ZipArchive#Entry => // The dependency comes from a JAR
121-
for (zip <- ze.underlyingSource; zipFile <- Option(zip.file)) {
122-
val classSegments = io.File(ze.path).segments
123-
binaryDependency(zipFile, binaryClassName(classSegments))
124-
}
125-
119+
ze.underlyingSource match
120+
case Some(zip) if zip.file != null =>
121+
binaryDependency(zip.file, binaryClassName)
122+
case _ =>
126123
case pf: PlainFile => // The dependency comes from a class file
127-
val packages = dep.to.ownersIterator
128-
.count(x => x.is(PackageClass) && !x.isEffectiveRoot)
129-
// We can recover the fully qualified name of a classfile from
130-
// its path
131-
val classSegments = pf.givenPath.segments.takeRight(packages + 1)
132124
// FIXME: pf.file is null for classfiles coming from the modulepath
133125
// (handled by JrtClassPath) because they cannot be represented as
134126
// java.io.File, since the `binaryDependency` callback must take a
135127
// java.io.File, this means that we cannot record dependencies coming
136128
// from the modulepath. For now this isn't a big deal since we only
137129
// support having the standard Java library on the modulepath.
138-
if (pf.file != null)
139-
binaryDependency(pf.file, binaryClassName(classSegments))
140-
130+
if pf.file != null then
131+
binaryDependency(pf.file, binaryClassName)
141132
case _ =>
142-
report.warning(s"sbt-deps: Ignoring dependency $depFile of class ${depFile.getClass}}")
133+
internalError(s"Ignoring dependency $depFile of unknown class ${depFile.getClass}}", dep.from.srcPos)
143134
}
144135
}
145136

@@ -149,7 +140,25 @@ class ExtractDependencies extends Phase {
149140
def allowLocal = dep.context == DependencyByInheritance || dep.context == LocalDependencyByInheritance
150141
if (depFile.extension == "class") {
151142
// Dependency is external -- source is undefined
152-
processExternalDependency(depFile)
143+
144+
// The fully qualified name on the JVM of the class corresponding to `dep.to`
145+
val binaryClassName = {
146+
val builder = new StringBuilder
147+
val pkg = dep.to.enclosingPackageClass
148+
if (!pkg.isEffectiveRoot) {
149+
builder.append(pkg.fullName.mangledString)
150+
builder.append(".")
151+
}
152+
val flatName = dep.to.flatName
153+
// We create fake companion object symbols to hold the static members
154+
// of Java classes, make sure to use the name of the actual Java class
155+
// here.
156+
val clsFlatName = if (dep.to.is(JavaDefined)) flatName.stripModuleClassSuffix else flatName
157+
builder.append(clsFlatName.mangledString)
158+
builder.toString
159+
}
160+
161+
processExternalDependency(depFile, binaryClassName)
153162
} else if (allowLocal || depFile.file != sourceFile) {
154163
// We cannot ignore dependencies coming from the same source file because
155164
// the dependency info needs to propagate. See source-dependencies/trait-trait-211.
@@ -163,6 +172,10 @@ class ExtractDependencies extends Phase {
163172
object ExtractDependencies {
164173
def classNameAsString(sym: Symbol)(using Context): String =
165174
sym.fullName.stripModuleClassSuffix.toString
175+
176+
/** Report an internal error in incremental compilation. */
177+
def internalError(msg: => String, pos: SrcPos = NoSourcePosition)(using Context): Unit =
178+
report.error(s"Internal error in the incremental compiler while compiling ${ctx.compilationUnit.source}: $msg", pos)
166179
}
167180

168181
private case class ClassDependency(from: Symbol, to: Symbol, context: DependencyContext)

compiler/src/dotty/tools/dotc/typer/QuotesAndSplices.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ trait QuotesAndSplices {
5757
val tree1 =
5858
if ctx.mode.is(Mode.Pattern) then
5959
typedQuotePattern(tree, pt, qctx)
60-
else if (tree.quoted.isType)
60+
else if tree.quoted.isType then
61+
report.warning(em"Consider using canonical type constructor scala.quoted.Type[${tree.quoted}] instead", tree.srcPos)
6162
typedTypeApply(untpd.TypeApply(untpd.ref(defn.QuotedTypeModule_apply.termRef), tree.quoted :: Nil), pt)(using quoteContext).select(nme.apply).appliedTo(qctx)
6263
else
6364
typedApply(untpd.Apply(untpd.ref(defn.InternalQuoted_exprQuote.termRef), tree.quoted), pt)(using pushQuoteContext(qctx)).select(nme.apply).appliedTo(qctx)
@@ -171,7 +172,9 @@ trait QuotesAndSplices {
171172
using spliceContext.retractMode(Mode.QuotedPattern).withOwner(spliceOwner(ctx)))
172173
pat.select(tpnme.spliceType)
173174
else
174-
typedSelect(untpd.Select(tree.expr, tpnme.spliceType), pt)(using spliceContext).withSpan(tree.span)
175+
val tree1 = typedSelect(untpd.Select(tree.expr, tpnme.spliceType), pt)(using spliceContext).withSpan(tree.span)
176+
report.warning(em"Consider using canonical type reference ${tree1.tpe.show} instead", tree.srcPos)
177+
tree1
175178
}
176179

177180
private def checkSpliceOutsideQuote(tree: untpd.Tree)(using Context): Unit =

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ object RefChecks {
9595
* and required classes. Also check that only `enum` constructs extend
9696
* `java.lang.Enum`.
9797
*/
98-
private def checkParents(cls: Symbol)(using Context): Unit = cls.info match {
98+
private def checkParents(cls: Symbol, parentTrees: List[Tree])(using Context): Unit = cls.info match {
9999
case cinfo: ClassInfo =>
100100
def checkSelfConforms(other: ClassSymbol, category: String, relation: String) = {
101101
val otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType)
@@ -109,12 +109,26 @@ object RefChecks {
109109
for (reqd <- cinfo.cls.givenSelfType.classSymbols)
110110
checkSelfConforms(reqd, "missing requirement", "required")
111111

112+
def isClassExtendingJavaEnum =
113+
!cls.isOneOf(Enum | Trait) && parents.exists(_.classSymbol == defn.JavaEnumClass)
114+
112115
// Prevent wrong `extends` of java.lang.Enum
113-
if !migrateTo3 &&
114-
!cls.isOneOf(Enum | Trait) &&
115-
parents.exists(_.classSymbol == defn.JavaEnumClass)
116-
then
117-
report.error(CannotExtendJavaEnum(cls), cls.sourcePos)
116+
if isClassExtendingJavaEnum then
117+
if !migrateTo3 then // always error, only traits or enum-syntax is possible under scala 3.x
118+
report.error(CannotExtendJavaEnum(cls), cls.sourcePos)
119+
else
120+
// conditionally error, we allow classes to extend java.lang.Enum in scala 2 migration mode,
121+
// however the no-arg constructor is forbidden, we must look at the parent trees to see
122+
// which overload is called.
123+
val javaEnumCtor = defn.JavaEnumClass.primaryConstructor
124+
parentTrees.exists {
125+
case parent @ tpd.Apply(tpd.TypeApply(fn, _), _) if fn.tpe.termSymbol eq javaEnumCtor =>
126+
// here we are simulating the error for missing arguments to a constructor.
127+
report.error(JavaEnumParentArgs(parent.tpe), cls.sourcePos)
128+
true
129+
case _ =>
130+
false
131+
}
118132

119133
case _ =>
120134
}
@@ -1089,7 +1103,7 @@ class RefChecks extends MiniPhase { thisPhase =>
10891103
override def transformTemplate(tree: Template)(using Context): Tree = try {
10901104
val cls = ctx.owner.asClass
10911105
checkOverloadedRestrictions(cls)
1092-
checkParents(cls)
1106+
checkParents(cls, tree.parents)
10931107
if (cls.is(Trait)) tree.parents.foreach(checkParentPrefix(cls, _))
10941108
checkCompanionNameClashes(cls)
10951109
checkAllOverrides(cls)

0 commit comments

Comments
 (0)