Pitch: Fully qualified name syntax

I think it should be possible to reduce the use of some form of scoping operator to only every need to occur once in such a chain.
In your example # is used as a kind of prefix. If we instead used it as a suffix, it would be clear that everything preceding the # are module names, and everything following it are member names.
E.g. ModuleName.SubModule.#.Member.Something would unambiguously state that ModuleName and SubModule are modules, and Member and Something are members.
In a way # would then act as a member-qualifer rather than a module-qualifier.

Is there a reason we would need a specific scoping operator, rather than just disambiguating by name, perhaps with a reserved top level indicator (Modules)? For import statements I would think that would work fine for both top level and theoretical submodules. e.g. import Modules.XCTest, import Modules.XCTest.Submodule. And if we disallow inline usage except in cases of required disambiguation, I don't think we need much in the way of special behavior.

1 Like

In that case Modules. becomes the scoping operator. I believe it would also reserve the name "Modules" from being used as...pretty much anything. Type name, Module name, even Variable name.

IMO if the desire is to add any new operator, #module(ModuleName) would be the way to go

1 Like

I am more inclined towards:

I prefer $modules as the top level (or root) indicator. The rule will be: If there is an ambiguity, provide the full path: $modules.XCTest.... and I think it should be possible to provide fix-it for it.

I don't think we should let ad-hoc definition of submodules. This eliminates the possibility of module level declaration clashing with submodule name. They should be both under the control of the same module developer and the compiler should simply reject such name clashes.

Moreover, one day we may need some other top-level indicator. For example to support dynamically loaded or injected modules (ensuring they won't clash with statically present modules)

What about treating XCTest.foo as an overload, consisting of the top-level foo and the class member foo?

That's potentially a source-breaking change, and it doesn't solve the underlying need for an unambiguous syntax even if it makes it less likely.

For the specific case of .swiftinterface files, where every name is fully qualified anyway, one option may be to change import to not add any of the imported module's members to the default namespace at all. (Although I guess this would only work if local definitions get fully qualified, too.)

For the general problem, my exotic syntax idea is to use regular parens and juxtaposition:

(XCTest)Something
myString.(Foundation)range(of: "hi")

(Module.Submodule)member
thing.(Module.Submodule)ambiguousOverload

I find having the delimiters makes these easier to parse*, especially in Harlan's overloading example -- they are clearly little annotations on the names that follow them, and they are visually modest enough not to overwhelm the source text.

* That is, easy to parse for me as a human being, not necessarily the compiler.

1 Like

Unfortunately that's only true of declarations. For expressions in an interface file, like inlinable functions and default arguments, we still have plain source code as written by the user.

2 Likes

I had exactly the same idea with parens, and even started implementing it in the compiler: [Parse] Implement qualified member lookup · broadwaylamb/swift@3bad769 · GitHub, but didn't have time to finish it yet.

It's quite easy to parse, and it also has an advantage of a better code completion: as soon as we meet a lparen following a period, we can suggest only module names just like when completing an import declaration.

While I also like the :: syntax, it doesn't have this nice property.

1 Like

wouldn't one way to gain this property be to place :: to the left of the module name as a prefix?

2 Likes
  1. Parens for qualifying are too much IMHO, that would be an overuse. Especially with functions where parens are also used for the parameters

  2. For me the name of the var/func/prop is more important than than the module and should be written first

Therefore I would like to suggest an email-like syntax:

Something@XCTest
myString.range@Foundation(of: "hi")

member@Submodule.Module
thing.ambiguousOverload@Submodule.Module

This example in particular is a bit difficult to read as a human because I need to think hard about where to mentally apply the parenthesized argument.

2 Likes

Another place where name qualification is an issue is with protocol members, since a type conforming to multiple protocols can end up with ambiguous members coming from the different protocols. In addition to module qualification, it'd be nice if the mechanism we come up with can also deal with protocol qualification (as well as both module and protocol qualification, in the terrible case where you need both). Protocol qualification could potentially also fill in a couple other longstanding gaps:

  • If it's possible to qualify an extension member distinctly from the requirement, then it could give you a way to call into default protocol implementations.
  • If the qualification syntax also works as a func or var declaration name, that could give us a stable way to provide the functionality of @_implements, to specify a method declaration that's intended to implement a specific protocol requirement.
14 Likes

Take this with a grain of salt—I am not one of the trained linguists in this group—but my understanding is that things at the end of a sentence are actually given more emphasis, not less, so if you have a choice, you want to front-load unimportant details and put the most important words at the end.

English put emphases at the beginning of sentences (and so emphasize object with passive voice). At phrasal level, it uses prepositive adjective (put adj in front of the corresponding noun/pronoun).

Other languages do things differently though, and I feel that Swift has its own rules (whether it’s actually a language is another story).

Unsure about this as well and it will differ between (human) languages.

As far as the way Swift does this, the standard is clearly: Broad->MoreSpecific->EvenMoreSpecific. Flipping that order is, IMO, not an option.

2 Likes

I agree with @GetSwifty that putting broad category on the left is a Swifty choice. At least it’s inline with Type.function or Type.NestedType.

I think this is a somewhat orthogonal problem, because the protocol names need to be distinguished from module names in some way, or we'd end up with the same ambiguity that we started with.

Allowing names to be qualified with the usual dot syntax would eliminate most uses of @_implements.

protocol A {
  func doSomething()
}
protocol B {
  func doSomething()
}
extension B {
  func doSomething() { ... }
}

extension Foo: A, B {
  func A.doSomething() {...}  // Implements the requirement in A
  func B.doSomething() {...}  // Implements the requirement in B
  func doSomething() { ... } // Unrelated member with the same name
}

This would compose nicely with whatever construct we choose for fully qualified names.

Unfortunately distinguishing between these at call sites isn't as easy as that. I guess we can stretch the parens+juxtaposition idea a bit, but it's difficult to come up with intuitive syntax that also embraces fully qualified names. All I can think of is a typename-style copout:

let foo: Foo = ...
foo.doSomething() // calls the unrelated member
foo.(protocol A)doSomething() // calls A's implementation
foo.(protocol B)doSomething() // calls B's implementation
foo.(extension B)doSomething() // calls B's default implementation

// with fully qualified names:
foo.(ModuleA.protocol A)doSomething()
foo.(ModuleB.protocol B)doSomething()
foo.(ModuleB.extension B)doSomething()

// nesting is another option:
foo.(protocol (ModuleA)A)doSomething()
foo.(protocol (ModuleB)B)doSomething()
foo.(extension (ModuleB)B)doSomething()

The dramatic difference between the meaning of (protocol B)doSomething() and (extension B)doSometing() could deserve a bit deeper syntax-level distinction.

3 Likes

What if we use keyword to distinguish between protocol and extension?

((ModuleA)A) // ModuleA.protocol A
((ModuleB)B) // ModuleB.protocol B
((ModuleB)B.default) // ModuleB.extension B

Also, limit the parentheses to one for modules, and one for protocols/types.

((Module.SubModule.SubSubModule)Type.NestedType.default)
1 Like

It would also allow for distinction between module qualification and protocol qualification

((ModuleA)) // module ModuleA
(A) // protocol A 
(default) // extension of some inferred protocol
1 Like