Is this a bug of Xcode or is this intended?

This line of code is not diagnosed at compile time in Xcode 11 (don't have Xcode 10 at the moment to test it).

let array = [[1, 1, 1], [], [4, 5, 6, 7], [0], [], [] [42]]

It just compiles but crashes at runtime. The typo was hard to spot, it's just missing a comma before [42].

error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
  * frame #0: 0x0000000102a8e3ef libswiftCore.dylib`function signature specialization <Arg[0] = Exploded, Arg[1] = Exploded, Arg[2] = Dead, Arg[3] = Dead> of Swift._fatalErrorMessage(_: Swift.StaticString, _: Swift.StaticString, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 463
    frame #1: 0x000000010286238f libswiftCore.dylib`Swift._ArrayBuffer._checkInoutAndNativeTypeCheckedBounds(_: Swift.Int, wasNativeTypeChecked: Swift.Bool) -> () + 239
    frame #2: 0x0000000102865221 libswiftCore.dylib`Swift.Array.subscript.getter : (Swift.Int) -> A + 81
    frame #3: 0x0000000104eaa34e $__lldb_expr100`main at Untitled Page 2.xcplaygroundpage:7:55
    frame #4: 0x000000010279b580 DeinitTest`linkResources + 304
    frame #5: 0x00007fff23a5ce4c CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
    frame #6: 0x00007fff23a5c5b8 CoreFoundation`__CFRunLoopDoBlocks + 312
    frame #7: 0x00007fff23a57444 CoreFoundation`__CFRunLoopRun + 1284
    frame #8: 0x00007fff23a56c16 CoreFoundation`CFRunLoopRunSpecific + 438
    frame #9: 0x00007fff37a98bb0 GraphicsServices`GSEventRunModal + 65
    frame #10: 0x00007fff46c97bef UIKitCore`UIApplicationMain + 1621
    frame #11: 0x000000010279b64d DeinitTest`main + 205
    frame #12: 0x00007fff50fb24ad libdyld.dylib`start + 1

Filed bug report just in case: [SR-11134] Missing comma not diagnosed resulting a runtime crash rather than compile time error · Issue #53530 · apple/swift · GitHub

Same on Xcode 10.2.1 :thinking: This is a really weird bug and yeah it should result in a parsing error IMO.

1 Like

Reduced example: let array = [[] [0]] also causes the same crash: Fatal error: Index out of range. If you do [[] []] then it complains cannot subscript a value of type '[Any]' with an index of type '()'

Interesting, so the compiler thinks [][42] is a subscript call. :open_mouth:

Yeah, when you do [[][8]] it's trying to subscript it hence why the fatalError crash.

I think this should at least result in a warning because there is a whitespace in between the square brackets so the user at least is informed that this will be a subscript operation. Then the user can decide if it should be [][42] or [], [42].

1 Like

I am not sure if there's a way to fix it, maybe we can emit a warning diagnostic if we see a subscript expression inside an [...]

I realize this is a special case, but this could also be diagnosed as out-of-range at compile time, given that it's an array literal and an index literal. Getting the diagnostic would indicate that perhaps it's just a typo.

2 Likes

Yeah but [[0][0]] is perfectly valid and shouldn't cause an error, but a warning, sure. Something like subscript expression '[0][0]' inside a collection might be unexpected; did you mean '[0], [0]'?

So I think @Avi is on the right track. The compiler does know that we're subscripting an empty array literal which is also known to be of type Array in our case. If it's a stdlib collection type, it should be possible to correctly diagnose this, no?

For example:

[[0], [] [42]]

Here the type will be inferred as [[Int]] in which case we're subscripting an empty [[Int]], which will crash at runtime.


If not any current diagnostics then I hope this will be matched by compile time evaluation evolution of Swift in case that collection subscript will be marked as evaluable at compile time.

What about Double? or Float? It might involve too much work trying to deal with this. We might be able to do this much easily once we have support for compile-time evaluation. For now, I think a simple warning diagnostic (bonus points for a fix-it but I think this will be tricky) is good enough for most cases.

let array = [[1], 2, [3] [4]] // warning: subscript expression '[3][4]' inside a collection might be unexpected

The error diagnostic would only come in to play if the subscript is an integer literal and it is subscripting an array literal. No other case can be handled at compile time with the current compiler.

I am also pro warning about subscripts of elements inside collection literals in general, but only in the following conditions:

The subscript is an integer literal or compile-time-evaluated constant.

It is common to select a single element from a known list to be interpolated into the array. However, this is usually intended to be done at runtime. If it is intended at compile time, see the next condition.

There is a space between the array literal closing bracket and the subscript's opening bracket.

As noted above, selecting from an array literal is a common idiom, and is less-likely to be an error if written as [...][x], rather than [...] [x].

The OP’s problem could be addressed without any false positives by diagnosing out-of-bounds access at compile time for literal arrays of type Array with literal subscripts. I think that’s the way to go here.

3 Likes

That's also good, but even if it's in-bounds, it's probably a mistake. We can do both.

2 Likes

It seems like it could be possible to construct a type that's ExpressibleByArrayLiteral and has a subscript such that [[1, 2, 3], [4, 5, 6] [7, 8, 9]] is a valid literal. Not that it's a common or likely combination.

Is there a reason that we even allow whitespace before the opening bracket of a subscript at all?

I think it's a general part of the design of the language that you can put various sorts of whitespace between tokens:

@
    objc
    class Formatting: NSObject {
        let opt: Bool? = . none
    
        override
        init     () {
            print ( "hello" )
            #if    swift ( >=5)
            var a = [1.1, 2.2, 3.3]
            #endif
            a    [0] = 3.4  .    bitPattern < 5 ? 1.1 : 2.2
        }
    }

^ That compiles.

My question was of the “why” variety.

@krilnon's answer is as correct as any. In most cases, Swift does not impose any whitespace requirements on its tokens, other than not running two identifier-ish things together. The main exceptions are operators, where whitespace disambiguates prefix/postfix vs. infix operations, and optional trailing elements (trailing closures, the expression in a return statement), which can't be wrapped to the next line. Unless there was a deliberate reason to disallow something, the parser mostly doesn't.

(Does this count as a deliberate reason to disallow something? Maybe. But that'd be source-breaking at this point.)

2 Likes