Scala: Trait Mixin with Abstract Base Class

My understanding is that while the error message may be confusing, the behaviour is correct. foo is declared as abstract override in StackingTrait, and thus in any concrete class that mixes StackingTrait there must be a concrete (not marked as abstract) implementation of foo before StackingTrait (relative to the linearization order). This is because super refers to the trait just before in the linearization order, so there definitely needs to be a concrete implementation of foo before StackingTrait is mixed in, or super.foo would be nonsensical.

When you do this:

class Impl extends Base with StackingTrait {
  def foo {}
}

the linearization order is Base <- StackingTrait <- Impl. The only trait before StackingTrait is Base and Base does not define a concrete implementation of foo.

But when you do this:

traitImplHelper extends Base {
  def foo {}
}
class Impl extends ImplHelper with StackingTrait

The linearization order becomes: Base <- ImplHelper <- StackingTrait <- Impl Here ImplHelper contains a concrete definition of foo, and is definitly before StackingTrait.

For what is worth, if you had mixed ImplHelper after StackingTrait (as in class Impl extends StackingTrait with ImplHelper) you would again have the same problem and it would fail to compile.

So, this look fairly consistent to me. I am not aware of a way to make it compile as you intended to. However if you are more concerned about making it easier to write Impl (and being able to define foo right there without a need for a separate class/trait) than making it easy to write Base or StackingTrait, you can still do this:

trait Base {
  protected def fooImpl
  def foo { fooImpl } 
}
trait StackingTrait extends Base {
  abstract override def foo { super.foo }
}

class Impl extends Base with StackingTrait {
  protected def fooImpl {}
}

Just like in the original version you force each concrete class to implement foo (in the form of fooImpl) and this time it does compile. The downside here is that while fooImpl must not call super.foo (it makes no sense and will go into an infinite loop), the compiler won't warn you about it.