Super call in non override function

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()
  }
}
1 Like

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? :slight_smile:

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.

Spoiler! (Intended Solution)
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).

5 Likes

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.

1 Like

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++.

2 Likes

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. :slight_smile:

1 Like

This is issue SR-103, tracked since at least 2015. It was (most recently?) discussed on these forums in 2019.

2 Likes