Is it well known, that there are cases where
func foo() {
super.foo()
}
is a valid member of a class.
Is it well known, that there are cases where
func foo() {
super.foo()
}
is a valid member of a class.
Do you mean you have encountered a case where the above code compiles?
Otherwise, the declaration of func foo ()
is missing the override
keyword.
override func foo() {
super.foo()
}
class PrintsTo10 {
func print () {
for i in 0...10 {
Swift.print (i)
}
}
}
class PrintsTo15: PrintsTo10 {
override func print () {
super.print ()
for i in 11...15 {
Swift.print (i)
}
}
}
Yes, there are in fact cases where this is legal code.
You mean something like this?
class PrintsTo10 {
func print () -> Int {
for i in 0...10 {
Swift.print (i)
}
return 0
}
}
class PrintsTo15: PrintsTo10 {
func print () {
super.print () // Result of call to 'print()' is unused
for i in 11...15 {
Swift.print (i)
}
}
}
// PrintsTo15().print () // Error: Ambiguous use of 'print()'
But, if you try to call the function:
PrintsTo15().print () // Error: Ambiguous use of 'print()'
Is this a riddle? Iām going to guess something involving protocol default implementations. Maybe:
class C {
func foo()
}
class S: C { }
protocol P {
func foo()
}
extension P where Self: S {
func foo() {
super.foo()
}
}
Yes, it's a riddle. This errors with super cannot be called outside of class members
(At least on swift 5).
Then, it must be this?
Oh, I misunderstood, I thought you meant it as an example of what doesn't work. You're right, this does compile, even though print needs to be disambiguated. The intended solution does not require disambiguation.
protocol A {
func foo()
}
extension A {
func foo() {}
}
class B: A {}
class C: B {
func foo() {
super.foo()
}
}
func test() {
C().foo()
}
I stumbled on this pattern while coding an iOS app, and thought it was interesting (and cursed).
This may have something to do with protocols. I am anxiously waiting for @Slava_Pestov to comment on the matter.
This is a nice catch!
The general rule I know is: pure Swift methods in extensions cannot be overriden.
However, it gets complicated when the situation involves both protocol extension and inheritance.
Note that in your code, C.foo
does not witness A.foo
(of course, nor does it override B.foo
which is just A.foo
):
protocol A {
func foo()
}
extension A {
func foo() {
print("A")
}
}
class B: A {}
class C: B {
func foo() {
print("C")
}
}
func test() {
(C() as A).foo() // does not participate in dynamic dispatches of protocols
(C() as B).foo() // does not participate in dynamic dispatches of class inheritance
}
test()
prints "A" instead of "C", this may or may not be what one wants.
I did notice that while testing. It seems that the conformance table for A
in B
does not refer to a dynamically dispatchable method, so it will always call A.foo
, unless you specifically call foo on a variable typed C
, which forces me to write a redundant copy of foo
in B
for intended behavior. Sad! Probably any non final
class conforming to a protocol should not use default implementations of it's requirements, which feels like a strong language limitation.
I somewhat agree.
In my personal practice, I always try to avoid retroactively conforming an already existed class hierarchy to a new protocol. When I have to, I must tell my self to remember re-stating every method from the protocol, even some methods have their default implementations in a protocol extension.
I often resort to this tedious structure:
protocol P {
func foo()
func bar()
}
extension P {
func foo_default() { /* default impl */ }
func foo() { foo_default() }
func bar_default() { /* default impl */ }
func bar() { bar_default() }
}
class Base: P {
func foo() { foo_default() }
func bar() { bar_default() }
}
class Derived: Base {}
The mild good side of it, is I can reset overriding when I want:
class FarDescendant: Derived {
override func foo() { foo_default() }
}
this compensates for the missing ::
syntax from C++.
In a scenario like this, it would be good if the compiler emitted a warning or error when it counters a func foo ()
invoking super.foo ()
without override
in its decl.
It's less of a complication and more of a hole in the Swift language. There is a protocol inheritance chain running through a conformance hierarchy, and a class inheritance chain running through a class hierarchy, but the language doesn't really define how to connect the two when used together.
The statically-dispatched protocol extension method "feature" just sits in the middle of this hole, but it's an accidental overlap rather than a (partial) solution.
I would state the general "rule" as: don't attempt to mix protocol and class inheritance for a function signature. Whatever you'd be hoping for, probably isn't going to be the result.