let's consider step by step how withX(1).withY(2) is type checked:
with the following refactoring the error should become a bit clearer:
scala> trait HasZ extends Node with HasX with HasY {
| override type Self <: HasZ
|
| def withZ(value: String): Self = {
| val wx = withX(1)
| wx.withY(2)
| }
| }
<console>:15: error: type mismatch;
found : wx.Self
required: HasZ.this.Self
wx.withY(2)
^
wx and this are different values, so types that depend on them (wx.Self and this.Self) must be considered different types (there's no telling which concrete types you chose for them when creating instances)
to explain the actual error message you're getting:
since there is no such value wx in your code, the compiler has to invent one
it'll use an existential for that since it knows there has to be such a value with the given type,
but it doesn't know exactly what it is, so we get:
withX(1).withY(2) : (wx.Self forSome {val wx: this.Self}) // this: HasZ
the latter type (desugared, it's actually wx.type#Self forSome {val wx: this.Self}) can be written more compactly as this.Self#Self
your type arguments version is more strict than the version with type arguments, as it enforces the Self to be the same type in the whole chain of method calls
here's the equivalent version using type members:
trait Node {
type Self <: Node {type Self = Node.this.Self}