“Ambiguous” operators when conditionally conforming to Numeric

I just got hit by a weird “ambiguous operator” error. It came up with a generic type which does not conform to Numeric, but nonetheless implements all the operators for it. Then, in a conditional extension, I added the conformance. Unfortunately, the operators cannot actually be *used* in the conditional extension, because of an alleged ambiguity.

I’ve cut it down to a small example. In this stripped-down version, there are ways to work around the issue, such as making the conformance unconditional. However in the actual place I encountered it, the conformance must be conditional.

• • •

Let’s start with a simple wrapper type:

struct Foo<T: Numeric> : Equatable {
    var x: T
}

extension Foo : ExpressibleByIntegerLiteral {
    init(integerLiteral value: T.IntegerLiteralType) {
        x = T(integerLiteral: value)
    }
}

Give it some basic operators:

extension Foo {
    static func += (lhs: inout Foo, rhs: Foo) { lhs.x += rhs.x }
    static func -= (lhs: inout Foo, rhs: Foo) { lhs.x -= rhs.x }
    static func *= (lhs: inout Foo, rhs: Foo) { lhs.x *= rhs.x }
    
    static func + (lhs: Foo, rhs: Foo) -> Foo {
        var result = lhs
        result += rhs
        return result
    }
    
    static func - (lhs: Foo, rhs: Foo) -> Foo {
        var result = lhs
        result -= rhs
        return result
    }
    
    static func * (lhs: Foo, rhs: Foo) -> Foo {
        var result = lhs
        result *= rhs
        return result
    }
}

And add a conditional conformance to Numeric:

extension Foo : Numeric where T : FloatingPoint {
    typealias Magnitude = T.Magnitude
    
    var magnitude: T.Magnitude { return x.magnitude }
    
    init?<U: BinaryInteger>(exactly n: U) {
        guard let x = T(exactly: n) else { return nil }
        self.x = x
    }
}

So far so good.

Now let’s try to use that conformance:

extension Foo where T : FloatingPoint {
    func foo() {
        _ = self + self
    }
}

…and we’re hit by an error: “ambiguous use of operator +”.

Interestingly, if we move all the operators down into the extension that declares Numeric conformance, then it compiles fine. But with unconditionally-available operators, we get a compiler error.

Is this a known bug?

• • •

The actual place this arose was when trying to implement Complex<T: SignedNumeric>, which cannot conform unconditionally to SignedNumeric because the magnitude requirement doesn’t work when T is an integer type.

So I implemented the arithmetic operators on Complex, but could only provide SignedNumeric conformance when T: FloatingPoint. Unfortunately, that made all the basic operators “ambiguous”.

Simplified the case a bit more:

protocol A { static func +(lhs: Self, rhs: Self) }

protocol B: A {}

struct Foo<T: A> {
    static func +(lhs: Foo, rhs: Foo) {}
}

extension Foo: A where T: B {}

extension Foo where T: B {

    func soo() { self + self }
}

Haven't come across it, but definitely a bug. It works fine if you substitute + with foo.

4 Likes

This definitely looks like a bug. If you haven't yet, please file it at bugs.swift.org.

1 Like

Filed as SR–7476, using @anthonylatsis’s minimal example.

2 Likes