The reason for this behavior is Scala linearization of classes, which is used to eliminate the ambiguities and semantics of abstract override . But first, first.
Class linearization
Whenever you have an instance of a type a and you call the method on it a.foobar() , the compiler must figure out where to find the definition of foobar . Since a can extend any other class and feature set, there can be several definitions for the foobar function. To resolve these ambiguities, Scala linearizes your class a with all its superclasses and traits. Linearization will produce the order in which the various types are checked to determine foobar . The first match will be the function being performed.
The Scala specification defines linearization as follows:
Definition 5.1.2. Let C be a class with pattern C1 s ... with Cn {stats}. The linearization of C, L (C) is defined as follows: L (C) = C, L (Cn) +: ... +: L (C1)
Here +: denotes a concatenation where elements of the right operand replace identical elements of the left operand.
Since every theory is gray, take a look at an example:
trait T1 { def foobar() = 1 } trait T2 { def foobar() = 2 } class B extends T2 { override def foobar() = 42 } class A extends B with T1 with T2 { override def foobar() = super.foobar() }
First of all, we have to redefine the foobar method in class a , because there are several competing definitions for it. However, the question now is which method definition is called by super.foobar . To find out, we must calculate the linearization of a .
L(A) = A, L(T2) +: L(T1) +: L(B) L(B) = B, L(T2) L(T2) = T2 L(T1) = T1 L(A) = A, T2 +: (T1, B, T2) L(A) = A, T1, B, T2
Thus, super.foobar will call a definition in T1 that returns 1 .
Abstract redefinition
The abstract override modifier for the method basically says that there must be a class / trait I that implements this method, which appears after the attribute with the abstract override modifier in the linearization of the class of your class instance. This is necessary for executing super.foobar() , because super.foobar() entails that in the linearization, a further search is made for the definition of foobar .
When you now look at the definition of class C , you will see that it has the following linearization
C, B, A
Therefore, it cannot compile, because starting with B you will not find the test implementation.
When we now look at examples that work, then we will understand why they really work. In the case of C extends A with new C with B you basically create an anonymous class Z extends C with B The linearization of Z is
Z, B, C, A
There you see that B can find the test implementation in C Thus, the code can be compiled. The same is true for the class D example.