Extensions pollution

Let's say we have multiple modules(A and B) declaring the same extension on a common type (say Array) with the same function signature(say sum()). This can lead to the "Ambiguous use'" error. Even worse, if the client module also declares an extension on Array with the same function signature but a different implementation, the program will compile without any warning or error regarding the other possible candidates for the function.

Furthermore, it's not always easy to control which extensions are visible and accessible when compiling a module. Let's say Module B doesn't directly declare the extension, but it imports module B_1 that declares it. This, in turn, makes the 'sum()' function from B_1 not only visible in B but also in all modules importing B.

Use of @_implementationOnly import B_1 doesn't seem to solve the issue in this case.

One possible solution I found to prevent such namespace pollution through extensions is using the @_spi attribute. By marking the extensions with @_spi, we can hide them from clients unless they explicitly import the module with the required @_spi names.

However, I'd love to find a more systematic solution to this problem.

1 Like

could you constrain sum to a particular Element type, or an Element type that conforms to a protocol you define?

This is irrelevant. Type on which an extension is defined and any constraints can be anything. The extension itself can be useful for clients, but inability to limit their visibility leads to potential clashes.

can you give an example of a completely unconstrained Array extension that could be susceptible to collisions?

extension Array {
  func element(at: Index) -> Element? { ... }
}

But again, this isn't about some particular extension. This is a general problem.

to me, this looks like dialectization, over the years i’ve moved away from doing this sort of thing because it makes refactoring and sharing code between projects more difficult.

3 Likes

Maybe we could use a C cast–like syntax to specify which module we want to look up a declaration from?

let elementA = array.(ModuleA)element(at: 0)
let elementB = array.(ModuleB)element(at: 0)

This would also solve the problem where there is no way to fully qualify a declaration if there is a type in scope with the same name as its module, since declarations would be fully qualifiable like this: (WidgetKit)Widget.

The Github issue Keith linked summarizes this long standing issue well. It's really just a compiler implementation bug, but changing the behavior would be too source breaking to consider without a language version update. Swift 6 presents an opportunity to do so, though.

3 Likes

I think there is an opportunity to make an additive change to the import statement now, in Swift 5. It would allow more granular filtering. Like @_spi imports, but for extensions.