Your browser may have trouble rendering this page. See supported browsers for more information.

This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

A tuple-inference bug in the Swift 3.0.1 compiler

Description

I encountered some curious behavior while writing a service-locator interface (_protocol_) in Swift. I've reproduced the issue in a stripped-down playground<fn> and am almost certain I've found a bug in the Swift 3.0.1 compiler included in XCode 8.2.1. <n>Update: At the suggestion of a reader, I searched and found <a href="https://bugs.swift.org/">Apple's Jira for Swift</a><fn> and reported this issue as <a href="https://bugs.swift.org/browse/SR-3865">A possible tuple-inference/parameter-resolution bug in Swift 3.0.1</a></n> <h>A Simple, Generic Function</h> We'll start off with a very basic example, shown below. <img src="{att_link}simplesinglelabeledargument.png" href="{att_link}simplesinglelabeledargument.png" align="none" caption="Simple argument with label" scale="50%"> The example above shows a very simple function, generic in its single parameter with a required argument label <c>a:</c>. As expected, the compiler determines the generic type <c>T</c> to be <c>Int</c>. I'm not a big fan of argument labels for such simple functions, so I like to use the <c>_</c> to free the caller from writing the label, as shown below. <img src="{att_link}simplesinglenolabelargument.png" href="{att_link}simplesinglenolabelargument.png" align="none" caption="Simple argument without label" scale="50%"> As you can see, the result of calling the function is unchanged. <h>Or Maybe Not So Simple?</h> Let's try calling the function with some other combinations of parameters and see what happens. <img src="{att_link}nolabelargumentmakeseverythingatuple.png" href="{att_link}nolabelargumentmakeseverythingatuple.png" align="none" caption="Label-less argument with tuples and multiple parameters" scale="50%"> If you're coming from another programming language, it might be quite surprising that the Swift compiler happily compiles every single one of these examples. Let's take them one at a time. <ul> <c>int</c>: This works as expected <c>odd</c>: This is the call that I experienced in my original code. At the time, I was utterly mystified how Swift---a supposedly very strictly typed language---allowed me to call a function with a single parameter with <i>two</i> parameters. This example's output makes it more obvious what's going on here: Swift interpreted the two parameters as a <i>Tuple</i>. Is that correct, though? Are the parentheses allowed to serve double-duty both as part of the function-call expression <i>and</i> as part of the tuple expression? <c>tuple</c>: With two sets of parentheses, it's clear that the compiler interprets <c>T</c> as tuple <c>(Int, Int)</c>. <c>labels</c>: The issue with double-duty parentheses isn't limited to anonymous tuples. The compiler treats what looks like two labeled function-call parameters as a tuple with two Ints labeled <c>a:</c> and <c>b:</c>. <c>nestedTuple</c>: The compiler seems to be playing fast and loose with parentheses inside of a function call. The compiler sees the same type for the parameter with one, two and three sets of parentheses.<fn> I would have expected the type to be <c>((Int, Int))</c> instead. <c>complexTuple</c>: As with <c>tuple</c>, the compiler interprets the type for this call correctly. </ul> <h>Narrowing Down the Issue</h> The issue with double-duty parentheses seems to be limited to function calls without argument labels. When I changed the function definition to require a label, the compiler choked on all of the calls, as expected. To fix the problem, I added the argument label for each call and you can see the results below. <img src="{att_link}labeledargumentmakesmoresense.png" href="{att_link}labeledargumentmakesmoresense.png" align="none" caption="Labeled argument with tuples and multiple parameters" scale="50%"> <ul> <c>int</c>: This works as expected <c>odd</c>: With an argument label, instead of inferring the tuple type <c>(Int, Int)</c>, the compiler correctly binds the label to the first parameter <c>1</c>. The second parameter <c>2</c> is marked as an error. <c>tuple</c>: With two sets of parentheses, it's clear that the compiler interprets <c>T</c> as tuple <c>(Int, Int)</c>. <c>labels</c>: This example behaves the same as <c>odd</c>, with the second parameter <c>b: 2</c> flagged as an error. <c>nestedTuple</c>: This example works the same as <c>tuple</c>, with the compiler ignoring the extra set of parentheses, as it did without an argument label. <c>complexTuple</c>: As with <c>tuple</c>, the compiler interprets the type for this call correctly. </ul> <h>Swift Grammar</h> I claimed above that I was pretty sure that we're looking at a compiler bug here. I took a closer look at the productions for tuples and functions defined in <i>The Swift Programming Language (Swift 3.0.1)</i> manual available from Apple. First, let's look at tuples: <img src="{att_link}grammarofatupleexpression.png" href="{att_link}grammarofatupleexpression.png" align="none" caption="Grammar of a Tuple Expression" scale="50%"> As expected, a tuple expression is created by surrounding zero or more comma-separated expressions (with optional identifiers) in parentheses. I don't see anything about <i>folding</i> parentheses in the grammar, so it's unclear why <c>(((1)))</c> produces the same type as <c>(1)</c>. Using parentheses makes it a bit difficult to see what's going on with the types, so I'm going to translate to C# notation. <ul> <c>()</c> => empty tuple<fn> <c>(1)</c> => <c>Tuple<int></c> <c>((1))</c> => <c>Tuple<tuple>></c> ...and so on. </ul> This seems to be a separate issue from the second, but opposite, problem: instead of ignoring parentheses, the compiler allows one set of parentheses to simultaneously denote the argument clause of a single-arity function call <i>and</i> an argument of type <c>Tuple</c> encompassing all parameters. A look at the grammar of a function call shows that the parentheses are required. <img src="{att_link}grammarofafunctioncallexpression.png" href="{att_link}grammarofafunctioncallexpression.png" align="none" caption="Grammar of a Function Call Expression" scale="50%"> Nowhere did I find anything in the grammar that would allow the kind of folding I observed in the compiler, as shown in the examples above. I'm honestly not sure how that would be indicated in grammar notation. <h>Conclusion</h> Given how surprising the result is, I can't imagine this is anything but a bug. Even if it can be shown that the Swift compiler is correctly interpreting these cases, it's confusing that the type-inference is different with and without labels. <hr> <ft>The X-Code playground is a very decent REPL for this kind of example. Here's the code I used, if you want to play around on your own. <code> func test<t>(_ a: T) -> String { return String(describing: type(of: T.self)) } var int = test(1) var odd = test(1, 2) var tuple = test((1, 2)) var labels = test(a: 1, b: 2) var nestedTuple = test(((1, 2))) var complexTuple = test((1, (2, 3)))</code></ft> <ft>I was amazed to find that Apple actually has a normal bug tracker for which I could create an account. Wonders never cease.</ft> <ft>I didn't include the examples, but the type is unchanged with four, five and six sets of parentheses. The compiler treats them as semantically irrelevant, though the Swift grammar doesn't allow for this, as far as I could tell from the BNF in the official manual.</ft> <ft>This is apparently legal in Swift, but I can't divine its purpose in an actual program</ft>