Skip to content

Commit 1420d6a

Browse files
authored
Merge pull request #12912 from dotty-staging/fix-12722
Detect provisional superclasses and recompute in Typer
2 parents 135cb9b + 79b61fa commit 1420d6a

File tree

14 files changed

+122
-48
lines changed

14 files changed

+122
-48
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class CompilationUnit protected (val source: SourceFile) {
6666
def isSuspendable: Boolean = true
6767

6868
/** Suspends the compilation unit by thowing a SuspendException
69-
* and recoring the suspended compilation unit
69+
* and recording the suspended compilation unit
7070
*/
7171
def suspend()(using Context): Nothing =
7272
assert(isSuspendable)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,7 @@ class Definitions {
888888
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")
889889
@tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child")
890890
@tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount")
891+
@tu lazy val ProvisionalSuperClassAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ProvisionalSuperClass")
891892
@tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated")
892893
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")
893894
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")

compiler/src/dotty/tools/dotc/transform/PostTyper.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
359359
if (sym.isClass)
360360
VarianceChecker.check(tree)
361361
annotateExperimental(sym)
362+
tree.rhs match
363+
case impl: Template =>
364+
for parent <- impl.parents do
365+
Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos)
362366
// Add SourceFile annotation to top-level classes
363367
if sym.owner.is(Package)
364368
&& ctx.compilationUnit.source.exists

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,30 @@ object Checking {
191191
report.errorOrMigrationWarning(em"$tp is not a legal $what\nsince it${rstatus.msg}", pos)
192192
}
193193

194+
/** Given a parent `parent` of a class `cls`, if `parent` is a trait check that
195+
* the superclass of `cls` derived from the superclass of `parent`.
196+
*
197+
* An exception is made if `cls` extends `Any`, and `parent` is `java.io.Serializable`
198+
* or `java.lang.Comparable`. These two classes are treated by Scala as universal
199+
* traits. E.g. the following is OK:
200+
*
201+
* ... extends Any with java.io.Serializable
202+
*
203+
* The standard library relies on this idiom.
204+
*/
205+
def checkTraitInheritance(parent: Symbol, cls: ClassSymbol, pos: SrcPos)(using Context): Unit =
206+
parent match {
207+
case parent: ClassSymbol if parent.is(Trait) =>
208+
val psuper = parent.superClass
209+
val csuper = cls.superClass
210+
val ok = csuper.derivesFrom(psuper) ||
211+
parent.is(JavaDefined) && csuper == defn.AnyClass &&
212+
(parent == defn.JavaSerializableClass || parent == defn.ComparableClass)
213+
if (!ok)
214+
report.error(em"illegal trait inheritance: super$csuper does not derive from $parent's super$psuper", pos)
215+
case _ =>
216+
}
217+
194218
/** A type map which checks that the only cycles in a type are F-bounds
195219
* and that protects all F-bounded references by LazyRefs.
196220
*/

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

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,58 @@ class Namer { typer: Typer =>
412412
def isEnumConstant(vd: ValDef)(using Context): Boolean =
413413
vd.mods.isAllOf(JavaEnumValue)
414414

415+
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
416+
* If that's not already the case, add one. The added class type CT is determined as follows.
417+
* First, let C be the unique class such that
418+
* - there is a parent P_i such that P_i derives from C, and
419+
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
420+
* Then, let CT be the smallest type which
421+
* - has C as its class symbol, and
422+
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
423+
*
424+
* Tweak: It could be that at the point where the method is called, some superclass
425+
* is still missing its parents. Parents are set to Nil when completion starts and are
426+
* set to the actual parents later. If a superclass completes a subclass in one
427+
* of its parents, the parents of the superclass or some intervening class might
428+
* not yet be set. This situation can be detected by asking for the baseType of Any -
429+
* if that type does not exist, one of the base classes of this class misses its parents.
430+
* If this situation arises, the computation of the superclass might be imprecise.
431+
* For instance, in i12722.scala, the superclass of `IPersonalCoinOps` is computed
432+
* as `Object`, where `JsObject` would be correct. The problem cannot be solved locally,
433+
* but we detect the situaton and mark the superclass with a `@ProvisionalSuperClass`
434+
* annotation in this case. When typechecking the class, we then run ensureFirstIsClass
435+
* again and possibly improve the computed super class.
436+
* An alternatiev fix would compute superclasses at typer instead at completion. But
437+
* that breaks too many invariants. For instance, we rely on correct @Child annotations
438+
* after completion, and these in turn need the superclass.
439+
*/
440+
def ensureFirstIsClass(cls: ClassSymbol, parents: List[Type])(using Context): List[Type] =
441+
442+
def realClassParent(sym: Symbol): ClassSymbol =
443+
if !sym.isClass then defn.ObjectClass
444+
else if !sym.is(Trait) then sym.asClass
445+
else sym.info.parents match
446+
case parentRef :: _ => realClassParent(parentRef.typeSymbol)
447+
case nil => defn.ObjectClass
448+
449+
def improve(candidate: ClassSymbol, parent: Type): ClassSymbol =
450+
val pcls = realClassParent(parent.classSymbol)
451+
if (pcls derivesFrom candidate) pcls else candidate
452+
453+
parents match
454+
case p :: _ if p.classSymbol.isRealClass => parents
455+
case _ =>
456+
val pcls = parents.foldLeft(defn.ObjectClass)(improve)
457+
typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %")
458+
val bases = parents.map(_.baseType(pcls))
459+
var first = TypeComparer.glb(defn.ObjectType :: bases)
460+
val isProvisional = parents.exists(!_.baseType(defn.AnyClass).exists)
461+
if isProvisional then
462+
typr.println(i"provisional superclass $first for $cls")
463+
first = AnnotatedType(first, Annotation(defn.ProvisionalSuperClassAnnot))
464+
checkFeasibleParent(first, cls.srcPos, em" in inferred superclass $first") :: parents
465+
end ensureFirstIsClass
466+
415467
/** Add child annotation for `child` to annotations of `cls`. The annotation
416468
* is added at the correct insertion point, so that Child annotations appear
417469
* in reverse order of their start positions.
@@ -1260,37 +1312,6 @@ class Namer { typer: Typer =>
12601312
}
12611313
}
12621314

1263-
/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
1264-
* If that's not already the case, add one. The added class type CT is determined as follows.
1265-
* First, let C be the unique class such that
1266-
* - there is a parent P_i such that P_i derives from C, and
1267-
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
1268-
* Then, let CT be the smallest type which
1269-
* - has C as its class symbol, and
1270-
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
1271-
*/
1272-
def ensureFirstIsClass(parents: List[Type]): List[Type] =
1273-
1274-
def realClassParent(sym: Symbol): ClassSymbol =
1275-
if !sym.isClass then defn.ObjectClass
1276-
else if !sym.is(Trait) then sym.asClass
1277-
else sym.info.parents match
1278-
case parentRef :: _ => realClassParent(parentRef.typeSymbol)
1279-
case nil => defn.ObjectClass
1280-
1281-
def improve(candidate: ClassSymbol, parent: Type): ClassSymbol =
1282-
val pcls = realClassParent(parent.classSymbol)
1283-
if (pcls derivesFrom candidate) pcls else candidate
1284-
1285-
parents match
1286-
case p :: _ if p.classSymbol.isRealClass => parents
1287-
case _ =>
1288-
val pcls = parents.foldLeft(defn.ObjectClass)(improve)
1289-
typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %")
1290-
val first = TypeComparer.glb(defn.ObjectType :: parents.map(_.baseType(pcls)))
1291-
checkFeasibleParent(first, cls.srcPos, em" in inferred superclass $first") :: parents
1292-
end ensureFirstIsClass
1293-
12941315
/** If `parents` contains references to traits that have supertraits with implicit parameters
12951316
* add those supertraits in linearization order unless they are already covered by other
12961317
* parent types. For instance, in
@@ -1333,7 +1354,7 @@ class Namer { typer: Typer =>
13331354
val parentTypes = defn.adjustForTuple(cls, cls.typeParams,
13341355
defn.adjustForBoxedUnit(cls,
13351356
addUsingTraits(
1336-
ensureFirstIsClass(parents.map(checkedParentType(_)))
1357+
ensureFirstIsClass(cls, parents.map(checkedParentType(_)))
13371358
)
13381359
)
13391360
)

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2278,11 +2278,20 @@ class Typer extends Namer
22782278
result = maybeCall(result, psym)
22792279
}
22802280
else checkParentCall(result, cls)
2281-
checkTraitInheritance(psym, cls, tree.srcPos)
22822281
if (cls is Case) checkCaseInheritance(psym, cls, tree.srcPos)
22832282
result
22842283
}
22852284

2285+
def ensureCorrectSuperClass(): Unit =
2286+
val parents0 = cls.classInfo.declaredParents
2287+
parents0 match
2288+
case AnnotatedType(sc, ann) :: rest if ann.symbol == defn.ProvisionalSuperClassAnnot =>
2289+
val parents1 = ensureFirstIsClass(cls, rest)
2290+
if parents1.head ne sc then
2291+
typr.println(i"improved provisional superclass $sc to ${parents1.head}")
2292+
cls.info = cls.classInfo.derivedClassInfo(declaredParents = parents1)
2293+
case _ =>
2294+
22862295
/** Augment `ptrees` to have the same class symbols as `parents`. Generate TypeTrees
22872296
* or New trees to fill in any parents for which no tree exists yet.
22882297
*/
@@ -2322,6 +2331,7 @@ class Typer extends Namer
23222331
}
23232332
}
23242333

2334+
ensureCorrectSuperClass()
23252335
completeAnnotations(cdef, cls)
23262336
val constr1 = typed(constr).asInstanceOf[DefDef]
23272337
val parents0 = parentTrees(
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package scala.annotation
2+
package internal
3+
4+
/** An annotation to record a provisional super class */
5+
class ProvisionalSuperClass extends StaticAnnotation
6+

project/MiMaFilters.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ object MiMaFilters {
1818
exclude[MissingClassProblem]("scala.annotation.experimental"),
1919
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
2020
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
21+
exclude[MissingClassProblem]("scala.annotation.internal.ProvisionalSuperClass"),
2122
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes.valueOrAbort"),
2223
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#reportModule.errorAndAbort"),
2324
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"),

tests/neg/extend-matchable.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ class E1 extends AnyRef, M // OK
1212
class F1 extends Any, M // error: Any does not have a constructor
1313

1414
class C2 extends M0 // OK inferred base type is AnyRef
15-
class D2(x: Int) extends AnyVal, M0 // error: illegal trait inheritance
1615
class E2 extends AnyRef, M0 // OK
17-
class F2 extends Any, M0 // error: Any does not have a constructor // error: illegal trait inheritance
16+
class F2 extends Any, M0 // error: Any does not have a constructor
1817

1918

2019

tests/neg/i1501.scala

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,3 @@ object Test {
1616
println(new C().foo)
1717
}
1818
}
19-
20-
object Test2 {
21-
class A
22-
class SubA(x: Int) extends A
23-
trait TA extends A
24-
trait TSubA extends SubA(2) // error: trait TSubA may not call constructor of class SubA
25-
26-
27-
class Foo extends TA with TSubA // error: missing argument for parameter x of constructor SubA:
28-
}

0 commit comments

Comments
 (0)