diff --git a/compiler/src/dotty/tools/dotc/CompilationUnit.scala b/compiler/src/dotty/tools/dotc/CompilationUnit.scala index e858e01efc48..0feac75eef32 100644 --- a/compiler/src/dotty/tools/dotc/CompilationUnit.scala +++ b/compiler/src/dotty/tools/dotc/CompilationUnit.scala @@ -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) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 547ceb292055..44df9d3f413f 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -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") diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index ac6543ab94e3..da386525511a 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -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 diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 6abc4ccfd090..a8bcf3b52b0d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -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. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 0f6f7e46a39a..4a5cde3ae308 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -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. @@ -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 @@ -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(_))) ) ) ) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 536a80626380..c26aef946f05 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -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. */ @@ -2338,6 +2347,7 @@ class Typer extends Namer } } + ensureCorrectSuperClass() completeAnnotations(cdef, cls) val constr1 = typed(constr).asInstanceOf[DefDef] val parents0 = parentTrees( diff --git a/library/src/scala/annotation/internal/ProvisionalSuperClass.scala b/library/src/scala/annotation/internal/ProvisionalSuperClass.scala new file mode 100644 index 000000000000..147b7b087c5a --- /dev/null +++ b/library/src/scala/annotation/internal/ProvisionalSuperClass.scala @@ -0,0 +1,6 @@ +package scala.annotation +package internal + +/** An annotation to record a provisional super class */ +class ProvisionalSuperClass extends StaticAnnotation + diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index fd833d95e249..edc17457f5b1 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -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"), exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes.valueOrAbort"), exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#reportModule.errorAndAbort"), exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#SymbolMethods.fieldMember"), diff --git a/tests/neg/extend-matchable.scala b/tests/neg/extend-matchable.scala index 18dee7a1fb53..031c12f21d3e 100644 --- a/tests/neg/extend-matchable.scala +++ b/tests/neg/extend-matchable.scala @@ -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 diff --git a/tests/neg/i1501.scala b/tests/neg/i1501.scala index 68556640383a..045f2be1de2f 100644 --- a/tests/neg/i1501.scala +++ b/tests/neg/i1501.scala @@ -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: -} diff --git a/tests/neg/i1501a.scala b/tests/neg/i1501a.scala new file mode 100644 index 000000000000..4568c637f3de --- /dev/null +++ b/tests/neg/i1501a.scala @@ -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: +} diff --git a/tests/neg/i1653.scala b/tests/neg/i1653.scala index f21fc7d54d96..ef122b1a76b1 100644 --- a/tests/neg/i1653.scala +++ b/tests/neg/i1653.scala @@ -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 } diff --git a/tests/neg/i5005.scala b/tests/neg/i5005.scala index 1734f79d934c..93ca0900fbb0 100644 --- a/tests/neg/i5005.scala +++ b/tests/neg/i5005.scala @@ -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 diff --git a/tests/pos/i12722.scala b/tests/pos/i12722.scala new file mode 100644 index 000000000000..3740229dd947 --- /dev/null +++ b/tests/pos/i12722.scala @@ -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 \ No newline at end of file