Take a look at the following two expressions:
baz(Foo < Bar, Bar > (0))
// two different outcomes, difference shown with parentheses
baz((Foo<Bar,Bar>(0))) // generics
baz((Foo < Bar), (Bar > 0)) // less-than
println(Dictionary < String, String > (0))
In certain constructs, operators with a leadingor
<may be split into two or more tokens. The remainder is treated the same way and may be split again. As a result, there is no need to use whitespace to disambiguate between the closing
>characters in constructs like
>. In this example, the closing
Dictionary<String, Array<Int>>characters are not treated as a single token that may then be misinterpreted as a bit shift
explicit-member-expression → postfix-expressionidentifiergeneric-argument-clauseopt
Thanks to @Martin R, I found the relevant part of the compiler source code, which contains a comment that explains how it resolves the ambiguity.
/// The generic-args case is ambiguous with an expression involving '<' /// and '>' operators. The operator expression is favored unless a generic /// argument list can be successfully parsed, and the closing bracket is /// followed by one of these tokens: /// lparen_following rparen lsquare_following rsquare lbrace rbrace /// period_following comma semicolon
Basically, the compiler attempts to parse a list of types and then checks the token after the closing angle bracket. If that token is
>[, but not
It parses the expression as a generic call, otherwise it parses it as one or more relational expressions.
As described in the book Annotated C#, the problem is solved in a similar way in C#.