Alright, folks, it's time to take another crack at this.
There are a bunch of situations where one name shadowing another can make it impossible to address the declaration you want. One common example that comes up in module interfaces happens when a type has the same name as the module it belongs to:
// In module XCTest:
open class XCTest: NSObject { ... }
open class XCTestCase: XCTest { ... }
// In module MyTestHelpers:
import XCTest
extension XCTestCase {
public func myAssert(...) { ... }
}
// In generated MyTestHelpers.swiftinterface:
import XCTest
extension XCTest.XCTestCase { // Error--this looks for `XCTestCase` in `XCTest.XCTest`!
public func myAssert(...) { ... }
}
But there are a lot of minor variations on this theme that can come up in both module interfaces and human-written code.
To fix this, I propose adding a syntax called a "module selector" which always, unambiguously finds a top-level declaration in a given module. That way, the module interface can say something like this and get the right behavior:
import XCTest
extension XCTest::XCTestCase { // `XCTest::` is always interpreted as a module
public func myAssert(...) { ... }
}
I also propose supporting a module selector on member lookups:
_ = foo.Bar::baz() // calls the `baz()` declared in `Bar`, not one from an unrelated module
The full draft proposal is available here. This idea has previously been discussed in a pitch from 2019.
I'm not totally convinced of the spelling, but this is a much needed feature, and I'd rather have it under a suboptimal name than not have it at all, so I very much want this to move forward no matter how it's spelled.
Would it also be reasonable to use this spelling for protocol selectors, for a type that conforms to multiple protocols with same-named requirements and/or extension methods?
Love the feature, love every single future direction listed!
I had an idea on how to unambiguously solve the protocol referencing issue mentioned in the future directions:
The super expression could gain the ability to be parametrized by the name of the type whose slice of the instance is being referred to. Specifying a protocol name will provide access to symbols defined in an extension on that protocol (enabling disambiguation between conflicting defaults from different protocols). For a class, or a class-bound protocol, it can also be any superclass in the inheritance chain. The only circumstance where the super parameter would be optional would be in a class, which would default to the direct superclass, retaining the current behavior.
protocol P1 { }
extension P1 { var name: String { "P1" } }
protocol P2 { }
extension P2 { var name: String { "P2" } }
class C1 { var name: String { "C1" } }
class C2: C1 { override var name: String { "C2" } }
class Example: C2, P1, P2 {
func describe() {
print(super<Self::P1>).name) // Can use module selector
print(super<P2>.name)
print(super<C1>.name)
print(super<C2>.name) // Same as `print(super.name)`
}
}
+1 Please, and thank you. We've sorely needed this for years.
I also fully support the future direction of using _:: as shorthand for the current module. If only so one can still do module based look up in situations where one doesn't know what the module name is (Compiler Explorer and playgrounds mostly).
Now if we just had a lighter way of making namespaces than uninhabited enums we'd be all set.
Yes please. Let’s finally tackle this problem. I can’t count how many times I had to advise people not to declare top level types with the same name as the module.
I have a future direction discussing this. The tl;dr is that I don't think we can because allowing a protocol name on the left side of :: means we'd have to do an unqualified lookup to resolve that identifier, which reintroduces the shadowing and ambiguity we're trying to avoid with this feature. Probably best to use something that's syntactically distinguishable to handle protocols.
Additionally, I think the problem inherent with the example given in your link above isn't that you want to disambiguate between two requirements with the same name at the call site. It's earlier that that—any type that conforms to both of those protocols may only implement a single witness that simultaneously satisfies both very semantically different requirements. We need to formalize something like @_implements instead.
(I don't want to rathole on this too much since it's not strictly part of this proposal, but) My gut feeling is that a concrete type that implements protocols with colliding requirements should be forced to rename at least one of the witnesses to make concrete call sites completely unambiguous, with the special syntax only used in the definition to connect the witness to the requirement so that it can be resolved for generic and existential dispatch. A concrete type that always requires you to write something like value.Weapon::draw in order to use it feels broken from an API design point of view.
This is almost what @_implements does today, except that it also allows the requirement's original name to be used, so it can't actually be used to solve collisions:
protocol P { func f() }
protocol Q { func f() }
struct S: P, Q {
func f() {}
@_implements(P, f())
func g() {}
}
S().f() // still ambiguous because S.g is a candidate
This doesn't help with a protocol composition—there's no way to say which draw() you want to call on an any Weapon & Shape. Same for a generic parameter <T> where T: Weapon, T: Shape.
For the any Weapon & Shape case, I would question whether value::Weapon.draw is better than just (value as any Weapon).draw, but there's not a good equivalent to that for the generic constraint case that doesn't require decaying to an existential.