The curious case of operator assignment

Fire up a playground or skeleton project, add this code, and attempt to compile it.

let lessThan = <

It won’t compile, and your error should read something along the lines of “unary operator cannot be separated from its operand”. Jared Sinclair discovered this issue the other day when trying to work indirectly with comparison operators.

It’s an odd error message given that the default declaration of  < is infix, not prefix. And he and I spent a bit of time the other day tracking down why this error happened and how to work around it.

To give a larger context, take a look at the following snippet:

struct Foo: Comparable {
    static func fooify(_ string: String) -> String {
        return "foo"
    static func ==(lhs: Foo, rhs: Foo) -> Bool { return true }
    static func <(lhs: Foo, rhs: Foo) -> Bool { return true }
typealias Fooquatable = (Foo, Foo) -> Bool

This struct defines three static functions: fooify(_), ==, and <. However, you can access only the fooify member by name:

let foofify = Foo.fooify
let lessThan = Foo.< // use of unresolved operator '.<'

So what do you do if you want to pull some Fooquatable static member into an expression? The answer is to both parenthesize and type the operator:

// Parenthesized and typed
let lessThan: Fooquatable = (<) // works 
let fooperations: [Fooquatable] = [(==), (<)] // Works

// Untyped or non-parenthesized
let fooperations = [(==), (<)] // ambiguous use of operator '=='
let fooperations: [Fooquatable] = [==, <] // expected expression after unary operator
let lessThan = (<) // Ambiguous use of operator '<'

This behavior raises several interesting questions: why does Xcode not see or offer to autocomplete static operator implementations, why does it produce the “unary” error message for an infix operator, and why are those parentheses needed?

Xcode does not, at least at this time, include static operator implementations in its completion list. Member operators cannot be referenced using dot syntax (SR-7155). Autocomplete excludes both < and == and you cannot refer to Foo.< or Foo.== in code.

Swift’s “unary” error messages seem to derive from its tokenizer (SR-7131):

if (OperEndLoc == Tok.getLoc())
 diagnose(PreviousLoc, diag::expected_expr_after_unary_operator);
 diagnose(PreviousLoc, diag::expected_prefix_operator)
 .fixItRemoveChars(OperEndLoc, Tok.getLoc());

It’s built in that after the previous operator’s end location, there is a space so it goes to the else clause. A standalone < or == following an assignment wants another token for unary application, which it does not get, rather than treating this as a static method reference. The remove-characters fixit somewhat pointlessly deletes any comment and newline that follows the operator.

The parentheses bypass ambiguity. Adding parentheses, whether in a standalone assignment or as part of a collection of operators, allows the compiler to recognize the operator as a static method. Explicit typing is still needed since you can’t refer to Foo.<.

Jared Sinclair and I tried a lot of workarounds beyond parentheses that all failed:

  • Foo.< did not work, even though operators are not Swift sugar and are full static members.
  • Foo.(<) failed as invalid syntax.
  • Foo[keyPath: \<] and its parenthesized variations were also failures. (“expected expression path in Swift key path”)
  • < as a standalone operator failed as the compiler was unable to resolve adjacent operators. Attempting to manipulate relative precedence had no effect. The compiler never interpreted < as an expression.
  • You cannot try to game the system with “clever” workarounds like: let lessThan: Fooquatable = { return <}() or let lessThan: Fooquatable = { return <; }().
  • (<) worked because the parentheses created an explicit expression value, so there was sufficient disambiguation for the compilation to proceed.

Have any further thoughts? Drop a note and let me know or add to the comments at

Update: Mark Lacey has been working on something for “operators as members“.

One Comment

  • You also can’t get CMD+click help on custom operators in Xcode, which is a shame because it can be unclear exactly which custom operator is being called in a given position ( what a + b does may change depending on the types of a and b )