Pitch: Deprecate strange interpolations in Swift 4.2

So, everyone knows what a string interpolation looks like:

print("Hello, \(firstName)!")

But let's talk about some other things you might write, perhaps because you hate compiler engineers. What do you think these do?

print("Hello, \()!")                                    // #1
print("Hello, \(first: firstName)!")                    // #2
print("Hello, \(firstName, lastName)!")                 // #3
print("Hello, \(first: firstName, last: lastName)!")    // #4

Most people I've spoken to say "produce an error message", and they're right about #1—it produces the error "expected expression in list of expressions". Maybe not the clearest message, but it's not technically wrong.

But the other three? They actually compile without complaint and form a tuple. So #2, #3, and #4 above are equivalent to writing:

print("Hello, \((first: firstName))!")
print("Hello, \((firstName, lastName))!")
print("Hello, \((first: firstName, last: lastName))!")

(Why isn't #1 equivalent to print("Hello, \(())!")? Because of an odd inconsistency in string interpolation parsing. Long story.)

#2 forms a single-element tuple which somehow doesn't get diagnosed, but whatever, those sneak through sometimes. (Currently, #2 sometimes causes mischief; I'm working on that.) But #3 and #4 are vaguely reasonable things to do. On the other hand, they're not documented, and at least #3 can confuse people—the only person I've talked to who didn't assume it was an error guessed "maybe it concatenates them with a space between, like print()."

What should we do?

Well, our default should be to preserve source compatibility:

  • Probably keep #1 the same, perhaps changing the error message.
  • Either keep the current behavior of #2, silently remove the label during parsing, or perhaps emit a warning and fix-it. (I assume source compatibility keeps us from making this a hard error.)
  • Keep the current behavior of #3 and #4.

If we keep the current behavior, we should probably document it. The syntax currently happens to work because of a quirk of the parser's implementation; if it's not documented, people will keep being surprised.

But I would prefer to deprecate #2, #3, and #4 with warnings and fix-its in Swift 4.2. #2 is an outright bug; #3 and #4 have counter-intuitive semantics, have never been documented or tested, and examples of them are rare at best. The fix—add an extra set of parentheses—is trivial to apply. And I'd really like to use this syntax in Swift 5 for some flexible new string interpolation features which might, for instance, allow us to implement the print()-like behavior that the only person who didn't think it was an error guessed it might have.

Thoughts?

17 Likes

I agree that none of those examples should parse, and I think deprecating them is the right thing to do. Even though the interpolated segments may have a tuple-like representation in the compiler, the user model should be that string interpolation is completely different to passing a tuple. Adding a set of brackets to form a tuple isn't burdensome and helps to keep things consistent.

2 Likes

Aligning this with all the other cases where parentheses are required around tuples makes sense to me, so deprecation would be good if possible. The only one I'm not sure about is #1, which I would have guessed would just (somewhat uselessly but benignly) print "Hello, !". I would guess that future formatting features might be approximately equivalent to calling initialisers so \() being equivalent to String() would make sense to me. This is not an important point though, and I think the fact that the current behaviour is the exact opposite to what I would expect is some sort of sign.

2 Likes

Ugh, yes, we should deprecate this.

The reason we do this is the old behavior where parameter lists used to be tuples. This meant that you could do things like:

print("foo \(a: 1, b: 2)")

and have it invoke the String(a: Int, b: Int) initializer. This is apparently broken in the work to fix the argument lists and no one noticed. Given where things are, I strongly +1 deprecating these forms, since they are almost certainly broken.

-Chris

14 Likes

i didn’t even know you could do that

1 Like

Funny—I nearly proposed bringing that back last year, without ever knowing it was part of the language in prerelease.

1 Like

If I had known about that behaviour, I would have raised it as a bug. To me, those examples should not compile, so +1 for deprecation.

You probably are aware of this, but #2 doesn't compile (contrary to what you said). I've tested the following program in Xcode 9.4, Xcode 10 beta and dev snapshot 2018-06-14), and for all three compiler versions:

let firstName = "Ada"
print("Hello, \(first: firstName)!")                    // #2

Results in:

error: type of expression is ambiguous without more context

But there are other ways of forming a single-element tuple:

let x = (label: 123).label

This compiles in all of the above three versions of the compiler.


And the following one (which succeeds in instantiating a single-element tuple constant and variable and using the label on them rather than just on the literal tuple expression as above) will only compile with Xcode 10 beta and later:

let c = (label: 123)
let twice = c.label + c.label
print(twice) // 246
var v = c
v.label = 3
print(v.label) // 3

Which makes me wonder if work has begun to remove the weird exception of banning single-element tuples.

1 Like

Vague answer time: I am told that some code like #2 does compile, and we need evolution involved if we’re going to break it. (I’m surprised too; I learned this the hard way.)

Yeah. Circa 2012 or so, we had the idea of incorporating printf style (but type safe) string formatting into string interpolation with something like this:

"decimal: \(x)  hexadecimal: \(x, format: .hex)"

That got shelved because it wasn't the most important thing to worry about at the time :slight_smile:

-Chris

9 Likes