This protocol is impossible to implement

Is the following a bug?

The static methods causes this protocol to be impossible to implement while code with only this protocol does compile.

protocol Vec2
{
    var x: Double { get }
    var y: Double { get }
    
    static func + (lhs: Vec2, rhs: Vec2) -> Vec2
}

What exactly is the problem you are experiencing?

When I try to implement this protocol, swift tells me one of the arguments needs to be the Type that is implementing this protocol. When I do exactly that, the implementation of this static method does not conform to the protocol. And if I try to implement the static method, swift compiler tells me one of the arguments needs to be the Type that is implementing this protocol......and so on

were you trying to do

protocol Vec2
{
    var x: Double { get }
    var y: Double { get }
    
+    static func + (lhs: Self, rhs: Self) -> Self
}

?

2 Likes

Yes that's what I did. But the code I am showing is just some experiments I tried because I am curious how Swift compiler reacts.

1 Like

The unhelpful diagnostic can be considered a bug. The behavior is not.

Your protocol requires a static method with two parameters of existential type. A binary operator can only be implemented as a static method of a type when at least one of its parameters is of the containing type. Therefore, this requirement can only be fulfilled by a default implementation on extension Vec2.

It would not make sense to make this requirement implementable by concrete types, since every conforming type would then be required to provide an identical ambiguous overload with no way to disambiguate them, making this operator unusable.

2 Likes

I tried your suggestion of default implementation on extension Vec2. From what I did, it seems that this does not work either because compiler cannot infer which Self to call the static method from. Is that considered as bug, since swift compiled and even ran code that contains unusable code.

protocol Vec2
{
    init(x: Double, y: Double)
    
    var x: Double { get }
    var y: Double { get }
    
    static func + (lhs: any Vec2, rhs: any Vec2) -> any Vec2
}

extension Vec2
{
    static func + (lhs: any Vec2, rhs: any Vec2) -> any Vec2
    {
        if
            let p = lhs as? Point,
            let a = rhs as? Area
        {
            return Point(x: p.x + a.x, y: p.y + a.y)
        }
        
        return Point(x: 0, y: 0)
    }
}


struct Point: Vec2
{
    var x: Double
    var y: Double
    
    init(x: Double, y: Double)
    {
        self.x = x
        self.y = y
    }
}

struct Area: Vec2
{
    var x: Double
    var y: Double
    
    init(x: Double, y: Double)
    {
        self.x = x
        self.y = y
    }
}

let p = Point(x: 1, y: 1)
let a = Area(x: 5, y: 5)
// Everything compiles as long as I do not use the static method
// let r = p + a

As far as I remember for operators you just define them as global functions like:

func + (lhs: Vec2, rhs: Vec2) -> Vec2 {
    if
        let p = lhs as? Point,
        let a = rhs as? Area
    {
        return Point(x: p.x + a.x, y: p.y + a.y)
    }
    
    return Point(x: 0, y: 0)
}

And the compiler bug think is relevant Generic parameter 'Self' could not be inferred, cause static func + as extension will be defined for exact types and won't understand Point + Area.
Like, what does this mean?
let r: WhatType? = p + a

Your protocol declaration itself looks recursive. I think it's tricky for Swift to unwind it.

Not sure what exactly you want to achieve and why; this, however, seems to be close to that:

protocol Vec2 {
    var x: Double { get }
    var y: Double { get }
    func add(_ other: Vec2) -> Self
}

func + (lhs: Vec2, rhs: Vec2) -> Vec2 {
    func openExistential<T: Vec2>(_ lhs: T, _ rhs: Vec2) -> Vec2 {
        lhs.add(rhs)
    }
    return openExistential(lhs, rhs)
}
struct S: Vec2 {
    var x: Double
    var y: Double
    func add(_ other: Vec2) -> Self {
        S(x: x + other.x, y: y + other.y)
    }
}

struct R: Vec2 {
    var x: Double
    var y: Double
    func add(_ other: Vec2) -> Self {
        R(x: x + other.x, y: y + other.y)
    }
}

var s = S(x: 1, y: 2)
var r = R(x: 100, y: 200)
print(s + s) // S(x: 2.0, y: 4.0)
print(r + r) // R(x: 200.0, y: 400.0)
print(s + r) // S(x: 101.0, y: 202.0)
print(r + s) // R(x: 101.0, y: 202.0)

I suppose the question is not that this is something to achieve, rather Swift allowing to declare a protocol that apparently impossible to satisfy correctly (even via extension). I'm not sure if there is possibility to indicate this at the language level though...

2 Likes

Yes, we should reject this protocol declaration because the type signature of + does not give us any way to bind the Self generic parameter for the call.

This should be entirely analogous to the following which we diagnose already:

func foo<T>(_: any Vec2, _: Int) {} // whatever

We can't call foo() as written above because there's no way to infer T from the type signature. Do you mind filing an issue so we can add a diagnostic for this case?

7 Likes

Hi, I just saw your message. By "filling an issue", I suppose you mean on GitHub right? Sorry, I new to this whole process here.

1 Like

Yes! Issues · apple/swift · GitHub is the place to file.

3 Likes
2 Likes

Thank you for the bug report. Here's my attempt at a fix: Sema: Ban uncallable protocol member operators by slavapestov · Pull Request #73204 · apple/swift · GitHub

It looks like this incorrect behavior was implemented all the way back in 2016 with SE-0091. For a concrete nominal type, we would check that the operator's interface type contained at least one reference to the enclosing nominal. For a protocol, we would look for either the Self type, or an existential for the enclosing protocol, but the latter case was actually unsatisfiable, exactly as you observed.

4 Likes