Why don’t implicitly opened existentials support operator functions?

i have some rendering context protocol RenderContext

protocol RenderContext:AnyObject

i want to support some syntax resembling

let context:any RenderContext
let foo:Foo

html[.ol]
{
    $0[.li] = foo | context 
}

let foos:[Foo]

html[.ol] = foos | context

the first thing i tried was an extension on RenderContext

extension RenderContext
{
    static func | (item:Foo, self:Self) -> FooItem?

    static func | (list:[Foo], self:Self) -> FooList?
}

but that didn’t work, because you cannot implicitly open an existential RenderContext this way.

so i then rewrote the first operator as an extension of Foo, because Foo is a type we own.

extension Foo
{
    static 
    func | (self:Self, context:some RenderContext) -> FooItem?
}

but this doesn’t generalize to [Foo], because that would have to become an extension on Array, a shared namespace.

extension [Foo]
{
    static 
    func | (self:Self, context:some RenderContext) -> FooList?
}

this causes no problems for code completion, because it is still constrained to the type we own. however, in generated documentation this API would not be associated with Foo, or FooList even, instead it appears as an extension of Array.

i decided this was okay because this is an internal API and having disorganized documentation felt like the lesser evil. but for a public API, it would be much better for these operator functions to be associated with a more relevant type than Array. ideally, they would just be associated with RenderContext.

we could promote them all to free functions, and curate them manually. but this approach has major issues, because |(_:_:) will require hash disambiguation 100 percent of the time, and hash disambiguation 1) imposes ABI stability and 2) limits the package to a single platform.

are there good reasons why implicitly-opened existentials can’t Just Work with operator functions?

1 Like