Class-constrained protocols don't allow for class methods?

Protocols that're constrained to a class allow for static functions, but not class functions. Is this by design or a bug?

protocol MyProtocol: class {
    static func a() -> Self // Compiles.
    class func b() -> Self // Doesn't compile.  "Class methods are only allowed within classes; use 'static' to declare a static method"
}

FWIW Using static will still work fine, but maybe the error message could be more explicit.

protocol Proto : AnyObject {
  static func bar() -> Int
}

class Foo : Proto {
  class func bar() -> Int {
    return 1
  }
}

class Baz : Foo {
  override class func bar() -> Int{
    return 2
  }
}


print(Foo.bar()) // 1
print(Baz.bar()) // 2

That seems wrong, I thought that static on a reference type was equivalent to final class. It feels really odd to use it like this on a protocol that's class constrained.

No, it’s right. Protocols, even class-constrained ones, aren’t classes. There’s no such thing as a final func in a protocol, and naturally there’s no such thing as a class func, because there’s no inheritance.

Clearly the error message wasn't good enough if Dan was confused. :-) I don't have better wording off the top of my head, but it's probably still worth filing a bug.

1 Like

Created a pull request to clarify the error message specific to protocols & protocol extensions. Would be interested to know if this helps with the confusion that @Dan_Stenmark had. Better error message for 'class func/var' usage in protocols by Moximillian · Pull Request #16965 · apple/swift · GitHub

2 Likes

Nicely done. That error message improvement looks good to me.

I think there is a good enough reason to bring up a proposal to allow the class keyword on class-constrained protocol requirements. This introduces some flexibility: static would mean exactly static and class would mean exactly class.

What is your use case for such a design? And how would it work that constraining a protocol to AnyObject would change the meaning of its static requirements?

First of all, there won't be a need for the diagnostic. This will allow to be specific about whether the method is overridable or not, but we would also loose the ability to declare a type method that can be implemented as both. The latter is what worries me. However, we can't be general about properties either: they have to be either {get} or {get set}. Actually, more than this, I would support more flexible requirements, where we can be specific about certain semantics (class func) or not care at all about others (var foo: Int {get set})

But, to what end? Any specific use cases? You’re advocating for major changes to the language and I’m not certain I understand at all what the gain is to be had.

class func in protocols is quite a source breaking change, yes. I am rather sceptic about it after thinking for a while. The possibility to omit {get set}, however, isn't, nor I think it is a major change in terms of implementation. I don't have a concrete use case off the top of my head. But, do you think it really is that important at this point? To me, the fact that different conforming types might not want to have the same accessibility semantics for a requirement is enough to give it a try.

Here's my use-case where I stumbled upon this and was very surprised the language didn't support it:
Started with this extension, which is a common pattern I've seen:

extension UIView {
  class func loadFromNib() -> Self? {
    Bundle(for: Self.self).loadNibNamed(nibName(), owner: self)?.first as? Self
  }
}

However, this assumes the nib is in the same bundle as the executable with the class. SPM packages resources and executables in separate bundles, so my solution was:

protocol NibView: UIView {
  static var nibBundle: Bundle { get }
}

extension NibView {
  class func loadFromNib() -> Self? {
    nibBundle.loadNibNamed(nibName(), owner: self)?.first as? Self
  }
}

How is this not possible? NibView is constrained to a class...