ExpressibleByStringLiteral taking precedence over specific operator overload

Given

static func == (x: Expr, y: Expr)     -> Bool { x.op == y.op && x.args == y.args }
static func == (x: Expr, str: String) -> Bool { x.asString == str }

where Expr is ExpressibleByStringLiteral

I expected expr == "y" to use func == (x: Expr, str: String) but see that it is calling func == (x: Expr, y: Expr) after constructing an Expr from the string literal instead.

Is there a way to make func == (x: Expr, str: String) be preferred (without explicitly writing expr == String("x"))?

Addendum: However, I now see that despite Expr conforming to ExpressibleByFloatLiteral, expr == 0 calls func == (x: Expr, num: Double) -> Bool as desired rather than func == (x: Expr, y: Expr) -> Bool as above.

What causes this difference between ExpressibleByFloatLiteral and ExpressibleByStringLiteral?

Swift prefers homogeneous operands. You could use the undocumented and unsupported feature @_disfavoredOverload for the first overload, but it cannot be recommended. Moreover, users may be surprised to find that you're messing with Swift's overload resolution rules in that way, particularly if (as you imply) it changes the result of a comparison.

I cannot reproduce the behavior you describe, suggesting likely that there is some other code you haven't detailed that is causing that discrepancy:

struct Expr: ExpressibleByStringLiteral, ExpressibleByFloatLiteral {
    init(floatLiteral value: Double) { }
    init(stringLiteral value: String) { }
    init() { }
    
    static func == (lhs: Expr, rhs: Expr) -> Bool { true }
    static func == (lhs: Expr, rhs: Double) -> Bool { false }
    static func == (lhs: Expr, rhs: String) -> Bool { false }
}

let expr = Expr()
print(expr == "string") // true
print(expr == 42.0)     // true
2 Likes

print(expr == 42) // false

Hmmm: expr == 42.0 calls func == (Expr, Expr) but expr == 42 calls func == (Expr, Double)

42 is an Integer literal. Expr is not ExpressibleByIntegerLiteral, but Double is.

2 Likes

Is preferring ==(Expr, Expr) over ==(Expr, String) another consequence of the "favoring hack" mentioned here Ambiguous use of operator in Swift 5.4?

I see. So, am I understanding correctly that 42 is an integer literal satisfyting ExpressibleByIntegerLiteral and not satisfying ExpressibleByFloatLiteral, but an integer literal can match a Double parameter?

Yes, Double conforms to ExpressibleByIntegerLiteral.

3 Likes