Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/CompilationUnit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class CompilationUnit protected (val source: SourceFile) {
def isSuspendable: Boolean = true

/** Suspends the compilation unit by thowing a SuspendException
* and recoring the suspended compilation unit
* and recording the suspended compilation unit
*/
def suspend()(using Context): Nothing =
assert(isSuspendable)
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -887,6 +887,7 @@ class Definitions {
@tu lazy val BodyAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Body")
@tu lazy val ChildAnnot: ClassSymbol = requiredClass("scala.annotation.internal.Child")
@tu lazy val ContextResultCountAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ContextResultCount")
@tu lazy val ProvisionalSuperClassAnnot: ClassSymbol = requiredClass("scala.annotation.internal.ProvisionalSuperClass")
@tu lazy val DeprecatedAnnot: ClassSymbol = requiredClass("scala.deprecated")
@tu lazy val ImplicitAmbiguousAnnot: ClassSymbol = requiredClass("scala.annotation.implicitAmbiguous")
@tu lazy val ImplicitNotFoundAnnot: ClassSymbol = requiredClass("scala.annotation.implicitNotFound")
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase
if (sym.isClass)
VarianceChecker.check(tree)
annotateExperimental(sym)
tree.rhs match
case impl: Template =>
for parent <- impl.parents do
Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos)
// Add SourceFile annotation to top-level classes
if sym.owner.is(Package)
&& ctx.compilationUnit.source.exists
Expand Down
24 changes: 24 additions & 0 deletions compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,30 @@ object Checking {
report.errorOrMigrationWarning(em"$tp is not a legal $what\nsince it${rstatus.msg}", pos)
}

/** Given a parent `parent` of a class `cls`, if `parent` is a trait check that
* the superclass of `cls` derived from the superclass of `parent`.
*
* An exception is made if `cls` extends `Any`, and `parent` is `java.io.Serializable`
* or `java.lang.Comparable`. These two classes are treated by Scala as universal
* traits. E.g. the following is OK:
*
* ... extends Any with java.io.Serializable
*
* The standard library relies on this idiom.
*/
def checkTraitInheritance(parent: Symbol, cls: ClassSymbol, pos: SrcPos)(using Context): Unit =
parent match {
case parent: ClassSymbol if parent.is(Trait) =>
val psuper = parent.superClass
val csuper = cls.superClass
val ok = csuper.derivesFrom(psuper) ||
parent.is(JavaDefined) && csuper == defn.AnyClass &&
(parent == defn.JavaSerializableClass || parent == defn.ComparableClass)
if (!ok)
report.error(em"illegal trait inheritance: super$csuper does not derive from $parent's super$psuper", pos)
case _ =>
}

/** A type map which checks that the only cycles in a type are F-bounds
* and that protects all F-bounded references by LazyRefs.
*/
Expand Down
85 changes: 53 additions & 32 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,58 @@ class Namer { typer: Typer =>
def isEnumConstant(vd: ValDef)(using Context): Boolean =
vd.mods.isAllOf(JavaEnumValue)

/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
* If that's not already the case, add one. The added class type CT is determined as follows.
* First, let C be the unique class such that
* - there is a parent P_i such that P_i derives from C, and
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
* Then, let CT be the smallest type which
* - has C as its class symbol, and
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
*
* Tweak: It could be that at the point where the method is called, some superclass
* is still missing its parents. Parents are set to Nil when completion starts and are
* set to the actual parents later. If a superclass completes a subclass in one
* of its parents, the parents of the superclass or some intervening class might
* not yet be set. This situation can be detected by asking for the baseType of Any -
* if that type does not exist, one of the base classes of this class misses its parents.
* If this situation arises, the computation of the superclass might be imprecise.
* For instance, in i12722.scala, the superclass of `IPersonalCoinOps` is computed
* as `Object`, where `JsObject` would be correct. The problem cannot be solved locally,
* but we detect the situaton and mark the superclass with a `@ProvisionalSuperClass`
* annotation in this case. When typechecking the class, we then run ensureFirstIsClass
* again and possibly improve the computed super class.
* An alternatiev fix would compute superclasses at typer instead at completion. But
* that breaks too many invariants. For instance, we rely on correct @Child annotations
* after completion, and these in turn need the superclass.
*/
def ensureFirstIsClass(cls: ClassSymbol, parents: List[Type])(using Context): List[Type] =

def realClassParent(sym: Symbol): ClassSymbol =
if !sym.isClass then defn.ObjectClass
else if !sym.is(Trait) then sym.asClass
else sym.info.parents match
case parentRef :: _ => realClassParent(parentRef.typeSymbol)
case nil => defn.ObjectClass

def improve(candidate: ClassSymbol, parent: Type): ClassSymbol =
val pcls = realClassParent(parent.classSymbol)
if (pcls derivesFrom candidate) pcls else candidate

parents match
case p :: _ if p.classSymbol.isRealClass => parents
case _ =>
val pcls = parents.foldLeft(defn.ObjectClass)(improve)
typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %")
val bases = parents.map(_.baseType(pcls))
var first = TypeComparer.glb(defn.ObjectType :: bases)
val isProvisional = parents.exists(!_.baseType(defn.AnyClass).exists)
if isProvisional then
typr.println(i"provisional superclass $first for $cls")
first = AnnotatedType(first, Annotation(defn.ProvisionalSuperClassAnnot))
checkFeasibleParent(first, cls.srcPos, em" in inferred superclass $first") :: parents
end ensureFirstIsClass

/** Add child annotation for `child` to annotations of `cls`. The annotation
* is added at the correct insertion point, so that Child annotations appear
* in reverse order of their start positions.
Expand Down Expand Up @@ -1251,37 +1303,6 @@ class Namer { typer: Typer =>
}
}

/** Ensure that the first type in a list of parent types Ps points to a non-trait class.
* If that's not already the case, add one. The added class type CT is determined as follows.
* First, let C be the unique class such that
* - there is a parent P_i such that P_i derives from C, and
* - for every class D: If some parent P_j, j <= i derives from D, then C derives from D.
* Then, let CT be the smallest type which
* - has C as its class symbol, and
* - for all parents P_i: If P_i derives from C then P_i <:< CT.
*/
def ensureFirstIsClass(parents: List[Type]): List[Type] =

def realClassParent(sym: Symbol): ClassSymbol =
if !sym.isClass then defn.ObjectClass
else if !sym.is(Trait) then sym.asClass
else sym.info.parents match
case parentRef :: _ => realClassParent(parentRef.typeSymbol)
case nil => defn.ObjectClass

def improve(candidate: ClassSymbol, parent: Type): ClassSymbol =
val pcls = realClassParent(parent.classSymbol)
if (pcls derivesFrom candidate) pcls else candidate

parents match
case p :: _ if p.classSymbol.isRealClass => parents
case _ =>
val pcls = parents.foldLeft(defn.ObjectClass)(improve)
typr.println(i"ensure first is class $parents%, % --> ${parents map (_ baseType pcls)}%, %")
val first = TypeComparer.glb(defn.ObjectType :: parents.map(_.baseType(pcls)))
checkFeasibleParent(first, cls.srcPos, em" in inferred superclass $first") :: parents
end ensureFirstIsClass

/** If `parents` contains references to traits that have supertraits with implicit parameters
* add those supertraits in linearization order unless they are already covered by other
* parent types. For instance, in
Expand Down Expand Up @@ -1324,7 +1345,7 @@ class Namer { typer: Typer =>
val parentTypes = defn.adjustForTuple(cls, cls.typeParams,
defn.adjustForBoxedUnit(cls,
addUsingTraits(
ensureFirstIsClass(parents.map(checkedParentType(_)))
ensureFirstIsClass(cls, parents.map(checkedParentType(_)))
)
)
)
Expand Down
12 changes: 11 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2294,11 +2294,20 @@ class Typer extends Namer
result = maybeCall(result, psym)
}
else checkParentCall(result, cls)
checkTraitInheritance(psym, cls, tree.srcPos)
if (cls is Case) checkCaseInheritance(psym, cls, tree.srcPos)
result
}

def ensureCorrectSuperClass(): Unit =
val parents0 = cls.classInfo.declaredParents
parents0 match
case AnnotatedType(sc, ann) :: rest if ann.symbol == defn.ProvisionalSuperClassAnnot =>
val parents1 = ensureFirstIsClass(cls, rest)
if parents1.head ne sc then
typr.println(i"improved provisional superclass $sc to ${parents1.head}")
cls.info = cls.classInfo.derivedClassInfo(declaredParents = parents1)
case _ =>

/** Augment `ptrees` to have the same class symbols as `parents`. Generate TypeTrees
* or New trees to fill in any parents for which no tree exists yet.
*/
Expand Down Expand Up @@ -2338,6 +2347,7 @@ class Typer extends Namer
}
}

ensureCorrectSuperClass()
completeAnnotations(cdef, cls)
val constr1 = typed(constr).asInstanceOf[DefDef]
val parents0 = parentTrees(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package scala.annotation
package internal

/** An annotation to record a provisional super class */
class ProvisionalSuperClass extends StaticAnnotation

1 change: 1 addition & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ object MiMaFilters {
exclude[MissingClassProblem]("scala.annotation.experimental"),
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
exclude[MissingClassProblem]("scala.annotation.internal.ErasedParam"),
exclude[MissingClassProblem]("scala.annotation.internal.ProvisionalSuperClass"),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw there's glob support in mima's exclusions so you can use scala.annotation.internal.* here next time.

exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes.valueOrAbort"),
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#reportModule.errorAndAbort"),
exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"),
Expand Down
3 changes: 1 addition & 2 deletions tests/neg/extend-matchable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ class E1 extends AnyRef, M // OK
class F1 extends Any, M // error: Any does not have a constructor

class C2 extends M0 // OK inferred base type is AnyRef
class D2(x: Int) extends AnyVal, M0 // error: illegal trait inheritance
class E2 extends AnyRef, M0 // OK
class F2 extends Any, M0 // error: Any does not have a constructor // error: illegal trait inheritance
class F2 extends Any, M0 // error: Any does not have a constructor



Expand Down
10 changes: 0 additions & 10 deletions tests/neg/i1501.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,3 @@ object Test {
println(new C().foo)
}
}

object Test2 {
class A
class SubA(x: Int) extends A
trait TA extends A
trait TSubA extends SubA(2) // error: trait TSubA may not call constructor of class SubA


class Foo extends TA with TSubA // error: missing argument for parameter x of constructor SubA:
}
10 changes: 10 additions & 0 deletions tests/neg/i1501a.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@

object Test2 {
class A
class SubA(x: Int) extends A
trait TA extends A
trait TSubA extends SubA(2) // error: trait TSubA may not call constructor of class SubA


class Foo extends TA with TSubA // error: missing argument for parameter x of constructor SubA:
}
2 changes: 1 addition & 1 deletion tests/neg/i1653.scala
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
trait Foo {
def foo() = new Unit with Foo // error: cannot extend final class Unit // error: illegal trait inheritance
def foo() = new Unit with Foo // error: cannot extend final class Unit
}
2 changes: 1 addition & 1 deletion tests/neg/i5005.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ case class i0 (i0: i1) extends AnyVal // error
trait i1 extends i0 // error

trait F[x] extends AnyVal // error
case class G[x](a: F[x]) extends F[x] // error // error
case class G[x](a: F[x]) extends F[x] // error
8 changes: 8 additions & 0 deletions tests/pos/i12722.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
trait JsAny extends AnyRef
class JsObject extends JsAny

trait HTMLAttributes[T] extends JsObject
trait Component[P] extends JsObject
trait IPersonaSharedProps extends HTMLAttributes[PersonaCoinBase]
trait PersonaCoinBase extends Component[IPersonaCoinProps]
trait IPersonaCoinProps extends IPersonaSharedProps