[Pitch] Allow trailing comma in tuples, arguments and if/guard/while conditions

is there any chance @greggwon could be using a result builder or builder-like API? i have also experienced similar issues (triggering a similar reaction) when working with DSLs, and while they are fundamentally issues with typechecker performance, they look indistinguishable from issues with parser performance.

I suggest instrumenting the compiler to determine where it’s spending those thirty seconds before it gives up. You might arrive at the same conclusion as the people who have been working on Swift compiler performance for years, which is that the timeouts come from trying to solve type inference, not getting lost in recursive descent.

1 Like

Sure, that's one of the more likely reasons for it. But whether they might look indistinguishable from parser performance issues is somewhat irrelevant to the discussion here, and was the reason I re-emphasized Joe's point that it's not related to parser performance, because the concern was brought up in this discussion about allowing trailing commas and related to statement termination and so forth.

More to that point, I would expect that adding trailing commas wouldn't affect the result builder transform at all. By the time the transform runs, the source has already been parsed. With this change, the transform would be receiving nearly the same AST as it did before—the structure of the tree would be identical, only with a few additional comma tokens sprinkled into places. The overhead, as I understand it, comes from complex inference across statements/closures and has nothing to do with whether Swift terminates statements with semicolons or not.

It's absolutely fair (and good!) to question whether additional lookahead in the parser to support cases like this would cause performance concerns and confirm that there's not a blocker for implementation. But it's misdirected to point to slowness in other parts of the compiler in the discussion of this pitch.

18 Likes

I've submitted PRs to both swift and swift-syntax with my implementation gated behind -enable-experimental-feature TrailingComma.

9 Likes
A possibility to use trailing commas to represent single-element tuples

BTW, trailing commas could be a neat way to represent one-element tuples:

let a = ()      // ()
let b = 1       // Int
let c = (1)     // Int
let e = (1, 2)  // (Int, Int)
print(type(of: a))
print(type(of: b))
print(type(of: c))
print(type(of: e))

// NOT current Swift
let d = (1,)
print(type(of: d))  // (Int)

I think (_: 1) would be clearer about the intent, but this is probably a topic for another thread.

(1) is already a single-element tuple, which is the same thing as the single element.

5 Likes
func foo(_ a: Int, _ b: Int) { print("\(a), \(b)") }
func foo(_ a: Int, _ b: Int, _ c: Int) { print("\(a), \(b), \(c)") }

foo(1, 2, /*3*/)

Which one is called?

A tangent about single-element tuples

It doesn't look so to me:

print(1 is any Equatable)       // true
print((_: 1) is any Equatable)  // true
print((1) is any Equatable)     // true

print(() is any Equatable)      // false
print((2, 2) is any Equatable)  // false
1 Like

What's surprising there? () and (2, 2) are of type () and (Int, Int) respectively, which don't conform to Equatable. (_: 1) and (1) are of type (Int), which is equivalent to Int, which does conform to Equatable.

2 Likes
Another tangent about single-element tuples

I considered tuples don't conform to Equatable and if (1) conforms to equatable (and is in fact an equivalent of 1 that doesn't make it a proper tuple to me.

Another point of difference is that we don't require parens:

var x: (Int) = 1

whereas for "normal" tuples we do.

Yet another point of difference:

    var xy: (Int, Int) = (0, 0)
    xy.0 // ✅
    var x: (Int) = (0)
    x.0 // 🛑 Value of type 'Int' has no member '0'

or

    let xy = (x: 0, y: 0) // ✅
    let x = (x: 0)        // 🛑 Cannot create a single-element tuple with an element label

or

struct S: Codable { var x = (0, 0) }    // 🛑
struct S: Codable { var x = () }        // 🛑
struct S: Codable { var x = (0) }       // ✅

The Swift book says:

access the individual element values in a tuple using index numbers starting at zero (tuple.0, etc)

without mentioning that "oh, btw, that doesn't work for single-element tuples".

It would be just fairer to say that Swift doesn't have single element tuples instead of claiming that it does and that it is equivalent to the tuple element type and thus we have such differences between single-element tuples and other tuples.

Maybe that's just me.

1 Like

The first obviously.

iirc, it is possible to get a single-element tuple using reflection on an enum with a payload, but it’s impossible to store it as anything other than Any, because it’s impossible to spell the concrete type to cast it to.

I think you're letting your non-Swift expectations get ahead of you.

  • () isn't the Swift syntax for a 0-element tuples. It's the syntax for a type also known (via a typealias) as Void, and for the single value of that type.

  • (1) isn't the Swift syntax for a 1-element tuple. It's the syntax for a simple expression of type Int, enclosed in redundant parentheses.

  • (…, …, …), with 1 or more commas, is the syntax of a tuple with 2 or more elements.

However, the compiler is free to represent () and (1) in its parse tree as 0-tuples and 1-tuples, if it "knows" that these parse-representations are the "same thing" as Void values and simple expressions.

That might sound a bit irrational (a slightly more complex parser could look ahead for the commas), but that doesn't really matter because (1) could never be repurposed as a 1-tuple even if we wanted to.

1 Like

There was a very long discussion on 1-tuples during the discussion of variadic generics. IIRC, either @Slava_Pestov or @John_McCall believed in maintaining a very strong separation between tuples and non-tuples. 1-tuples confuse things far too much, so the compiler distinguishes between tuples and non-tuples very early to prevent the internal representation of a 1-tuple from being accidentally created.

1 Like

Sorry to derail this discussion. I think the main relevance of the independent existence or not of 1-tuples is whether they would ever need a distinct syntax; as it stands, I'm fairly confident they don't, since we were able to successfully implement variadic generics without creating that distinction, and that's about the only place it would really matter. When you substitute a variadic (repeat each _), generically that behaves as a tuple, but in a concrete context where the pack expands to a single element, you end up with the single scalar type.

3 Likes

I’m surprised to hear that, it looks like an error to me since the intent was to pass 3 arguments.

1 Like

Tuples seem different to me here than functions, tuples can’t be overloaded in the same sense. Tuple arguments can I suppose but that seems uncommon from experience. Having overloaded function tuple args might even indicate variadic generics as the better choice anyway.

Overloaded functions are exactly one of the places I'd be looking forward to using this since the convention is to put the optional ones towards the end and I frequently find myself trying them out. Probably not great for production code but SUPER handy when sussing things out.

let myThing = NewThing(with: 12,
                       so: 13,
                       many: 14,
                       parameters: 15,
                       but: 16,
                       some: 17,
                       are: 18,
                       //optional: 19,
             ) 
3 Likes

But I think part of the point tera's making is that S.E.T.s already have a distinct syntax from longer tuples, you can't use mySingletonTuple.0 on them, etc. I understand how Swift got here, but that a "single element tuple" gets granted absolutely no tupleyness, if it's allowed to be created, can be a little bit of a head tilter.

That said, imma gonna stay out of what I think should happen. Tuple's are weird, man. The've got their whole own thing going on. Love 'em but they're no easy drinking like Array or Structs, which are just smooth (good work Swift team).

1 Like