Lack of turbofish-like syntax for generics in expressions

Hey, I'm very new to Swift, learning it for use in Ladybird and so far I like it :)

One thing I've noticed is that generics in expressions uses the Type<T> syntax and not something like turbofish Type::<T> (or Type.<T> which seems more appropriate for Swift).

This makes this code snippet not highlight in Zed (which is using tree-sitter based on this grammar):
image
not highlight correctly (Optional should be green).

I wanted to ask if this means that that parsing cannot be done without more first doing type checking or other analysis (which is a problem e.g. c++ has with generics, and it makes it hard to parse for tools such as tree-sitter that does not perform deeper analysis of the language (like the above screenshot), and potentially confusing error messages from the compiler for subtle syntax and type errors).

At a glance Type<T>.variant seems like it could be parsed as Type < T > .variant, but I couldn't get something like this to compile, does Swift have strict and coherent rules for how this is handled, and if so are they specified somewhere?

4 Likes

Parsing is completely separate from name lookup and semantic analysis in Swift. Here is the Swift parser, written in Swift: swift-syntax/Sources/SwiftParser at main · swiftlang/swift-syntax · GitHub

I don’t remember the details but the ambiguity you refer to is resolved by some combination of rules, one of which is that binary operators must have consistent spacing on both sides, so T> U is not a valid operator expression for example.

Edit: I found the implementation: swift-syntax/Sources/SwiftParser/Types.swift at 8a9445f736171c706bb1c4c7aec0f39dd8d7da54 · swiftlang/swift-syntax · GitHub

7 Likes

Another thing that helps here is that < and > are in the same precedence group, which means that you must use parentheses to specify which operation should be performed first in a < b > c. So generic argument lists could never be valid expression-level syntax.

3 Likes

Now that I think about it, the fixity and precedence of operators doesn’t enter the picture because those are not looked up and resolved until Sema time either. From reading the code I believe it simply tries to parse a generic parameter list first in any position where one may appear, and if this fails, it parses the < as an operator.

8 Likes

Right. Swift borrows a trick from C# where it parses ahead after a < to see if there’s something that looks like a type list followed by a > and then a member access (an opening bracket or .); if that condition holds, then the text is parsed as a generic parameter list. This isn’t foolproof since (a < b, c > (d)) or (a < b, c > .d) are potentially valid tuple expressions in Swift too, but so far nobody seems to have noticed (to my knowledge).

17 Likes

It’s worth noting that Type.<T> would have the same ambiguity problem as Type<T>, since .< is the pointwise less than comparison operator.

1 Like