Static callAsFunction doesn't work

Looks like it's not possible to meaningfully use "static func callAsFuntcion":

struct Test {
    func foo(_ v: Int) -> Int { v }
    subscript(_ v: Int) -> Int { v }
    func callAsFunction(_ v: Int) -> Int { v }
    static func foo(_ v: Int) -> Int { v }
    static subscript(_ v: Int) -> Int { v } // ✅ ok since Swift 5.1
    static func callAsFunction(_ v: Int) -> Int { v } // ✅ ok since Swift 5.1! but...
}
let test = Test()
_ = test.foo(0) // ✅
_ = test[0]     // ✅
_ = test(0)     // ✅
_ = Test.foo(0) // ✅
_ = Test[0]     // ✅
_ = Test(0)     // 🛑 Argument passed to call that takes no arguments
1 Like

This should be flagged as an error at the declaration of the static function; it was explicitly omitted by the original proposal since it overlaps with initializer calling syntax. I'd consider it a bug that the compiler allows it.

(It can be explicitly called using Test.callAsFunction(0), but of course, that's missing the entire point of callAsFunction.)

5 Likes

But that's a collision only when it actually collides with the constructor, no?

e.g. if constructor is Test(x: Int) and callAsFunction is Test(y: Int) there is no collision.

By the same logic I could have a global function func Test(z: Int) which should be fine, and there would be an issue only when it actually collides with the corresponding constructor of Test.

Note that it was an error in Swift 5.0.

1 Like

It should be at least a warning at this point. If not, an issue was desired.

Technically true, but I think I agree with SE-0253's reasoning that it is likely confusing to humans.

But, demonstration of a real use case that would benefit from this could persuade that it's worth that trade-off. Right now I'm struggling to think of why I'd really need a static callAsFunction. Once could always produce the same result using a singleton global constant, right?

Personally I don't think I've ever used callAsFunction (or any near analogy) in Swift, even though I've use e.g. __call__ in Python quite a lot. Now that I think about it, I'm not sure why that is.

2 Likes

It would actually be useful specifically to mimic initializer syntax on types that don’t support being extended with convenience initializers:

extension CGColor {
    static func callAsFunction(h: CGFloat, s: CGFloat, v: CGFloat) -> CGColor {
        // ...
    }
}
1 Like

Good find. As this was explicitly considered and ruled out – the relevant change in Swift 5.1 is a bug / regression.


Also an interesting thought. We could still do this via a global function, right?

Note that compared to normal inits either method won't allow CGColor.init(h: ..., ) notation with explicit ".init" in the name. Not a huge deal but worth to remember.


BTW, what exactly is the thing that prevents convenience inits in CGColor? Could Swift be extended to actually allow it, or is it too much of a trouble?

Right - this feels like it should be addressed by allowing convenience inits here, rather than faking them with static callAsFunction. You want the compiler to ensure your convenience inits are actually calling a designated initialiser.

2 Likes