One possibility would be to use a special form of extension. For lack of a better idea, let’s use the protected
keyword for now. E.g.
class C extends A, protected Serializable, protected Product { ... }
class D extends A, protected Serializable, protected Product { ... }
We could then specify that upper bounds don’t take protected superclasses into account. So we’d
infer type A
for x
in
val x = if (???) C() else D()
instead of A & Serializable & Product
. But normal subtyping would not depend on protectedness. E.g. the following would work OK:
def f(p: Product) = ...
f(C())
The extension syntax is admittedly clunky, but that should be not a big issue since most of the problematic cases are auto-generated anyway from case classes and enum cases.
~~
A separate question is whether protected
inheritance should mean anything in addition to “omit from upper bounds”. One possible refinement could be to require that a protected
superclass only adds protected members to its subclass. I.e. the following would be OK
trait A { def a: Int }
trait B { def a: Int = ???; protected def b: Int = ??? }
class C extends A, protected B
Here, the members of C
are a
and b
, and the latter is protected. But if B
was defined like this
trait B { def a: Int = ???; def b: Int = ??? }
then the definition of C
would give a compile time error, since the public b
member comes from a protected
superclass.
The advantage of this rule is that it gives us a way to state and check that a trait such as IndexedSeqOptimized
is only inherited for performance.
Unlike in C++'s private inheritance, I do not propose to retroactively change the visibility of members of inherited classes. See also the discussion in Allow traits to be transparently or "invisibly" mixed in.