Skip to content

[SR-3668] "Expression was too complex" errors for short dictionary literals of simple closure expressions #46253

@swift-ci

Description

@swift-ci
Previous ID SR-3668
Radar rdar://problem/30213053
Original Reporter chowell (JIRA User)
Type Bug
Environment

macOS 10.12.2, MacBook Pro (Retina, 15-inch, Mid 2014)
Swift version 3.1-dev (LLVM 7e421db87c, Clang 80a3776e67, Swift a43bebb)

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, Performance, TypeChecker
Assignee None
Priority Medium

md5: e03eaff993e24b14bc7af8e8623ef449

Issue Description:

A short dictionary literal with values that are simple closure expressions can cause the Swift compiler to report an "expression was too complex to be solved in reasonable time" error, following a large increase in CPU and memory usage. This happens most readily when the closure expression contains an operator that is heavily overloaded, which is true for many common expression operators. This problem is not new; the latest code in current master is affected, but so are the Swift 3.0.2 and Swift 2.3 toolchains in Xcode 8.2.1. (I tested using the "swift" command REPL in all cases: for current master this is the integrated development REPL, while for the Xcode toolchains it's the LLDB-based REPLs.)

Only dictionary literals suffer from this problem; array literals seem to be completely unaffected.

A trivial example using ==, which seems to be the most heavily overloaded operator:

let d: [Int: (Int) -> Bool] =
    [ 0: { $0 == $0 }, 1: { $0 == $0 }, 2: { $0 == $0 }, 3: { $0 == $0 } ]

For current master and Xcode's Swift 3.0.2, this is just over the threshhold of failure; if one element is removed from the literal, the expression is evaluated successfully, although still with high CPU and memory usage. (For Xcode's Swift 2.3, the failure threshhold is three elements rather than four.)

Another example with unary minus, which is much less heavily overloaded:

let d: [Int: (Int) -> Int] =
    [ 0: { -$0 }, 1: { -$0 }, 2: { -$0 }, 3: { -$0 }, 4: { -$0 },
      5: { -$0 }, 6: { -$0 }, 7: { -$0 }, 8: { -$0 } ]

Again, this is just over the failure threshhold for current master, and it will pass if one element is removed. (For Xcode's Swift 3.0.2, the failure threshhold is seven elements; for Swift 2.3, it is six elements.)

Other operators, like infix +, *, |, <, and prefix ~, lie between these extremes in terms of how many closure expression values are needed in the literal to trigger an error.

I don't think it makes a difference whether the closures that use infix operators take two distinct operands or a single one; I chose the latter to simplify the testcases. Also, the types of the closure operands or results doesn't seem to matter for this, and neither does the type of the dictionary keys (again, I chose Int for simplicity).

While these testcases are obviously contrived, I first encountered this problem while working on Stanford's online course "Developing iOS 9 Apps with Swift", which is linked to from Apple's own Swift developer resources web page. That course's first lectures and its initial programming project implement a calculator app. This app uses a dictionary, initialized by a literal, with values that are enums which themselves have associated values, and some of these associated values are simple closure expressions. The dictionary allows each operation key's mathematical operation to be looked up and invoked in a simple and elegant manner, and it can be easily extended to add more operations just by adding more elements to the literal--but this is where you get bitten by the "expression was too complex" problem. (In fact, SR-2102, which is marked as Resolved, seems to be an instance of this very problem and is obviously derived from the Stanford course.)

While Stanford's course used Xcode 7.3 and Swift 2.2, I was working on it in Xcode 8.2.1 with Swift 3.0.2; I found the problem affected not just the compiler but also SourceKitService, so syntax highlighting was also affected. I'm running macOS 10.12.2 and found it difficult to test Swift 2.2 on this platform, but since it does affect Swift 2.3, it seems likely to have been in Swift 2.2 as well.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions