Swift Compiler Error: "Expression too complex" on a string concatenation

IosXcodeSwiftCompiler ErrorsFunctional Programming

Ios Problem Overview


I find this amusing more than anything. I've fixed it, but I'm wondering about the cause. Here is the error: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Why is it complaining? It seems like one of the most simple expressions possible.

The compiler points to the columns + ");"; section

func tableName() -> String { return("users"); } 

func createTableStatement(schema: [String]) -> String {

    var schema = schema;

    schema.append("id string");
    schema.append("created integer");
    schema.append("updated integer");
    schema.append("model blob");

    var columns: String = ",".join(schema);

    var statement = "create table if not exists " + self.tableName() + "(" + columns + ");";

    return(statement);
}

the fix is:

var statement = "create table if not exists " + self.tableName();
statement += "(" + columns + ");";

this also works (via @efischency) but I don't like it as much because I think the ( get lost:

var statement = "create table if not exists \(self.tableName()) (\(columns))"

Ios Solutions


Solution 1 - Ios

I am not an expert on compilers - I don't know if this answer will "change how you think in a meaningful way," but my understanding of the problem is this:

It has to do with type inference. Each time you use the + operator, Swift has to search through all of the possible overloads for + and infer which version of + you are using. I counted just under 30 overloads for the + operator. That's a lot of possibilities, and when you chain 4 or 5 + operations together and ask the compiler to infer all of the arguments, you are asking a lot more than it might appear at first glance.

That inference can get complicated - for example, if you add a UInt8 and an Int using +, the output will be an Int, but there's some work that goes into evaluating the rules for mixing types with operators.

And when you are using literals, like the String literals in your example, the compiler doing the work of converting the String literal to a String, and then doing the work of infering the argument and return types for the + operator, etc.

If an expression is sufficiently complex - i.e., it requires the compiler to make too many inferences about the arguments and the operators - it quits and tells you that it quit.

Having the compiler quit once an expression reaches a certain level of complexity is intentional. The alternative is to let the compiler try and do it, and see if it can, but that is risky - the compiler could go on trying forever, bog down, or just crash. So my understanding is that there is a static threshold for the complexity of an expression that the compiler will not go beyond.

My understanding is that the Swift team is working on compiler optimizations that will make these errors less common. You can learn a little bit about it on the Apple Developer forums by clicking on this link.

On the Dev forums, Chris Lattner has asked people to file these errors as radar reports, because they are actively working on fixing them.

That is how I understand it after reading a number of posts here and on the Dev forum about it, but my understanding of compilers is naive, and I am hoping that someone with a deeper knowledge of how they handle these tasks will expand on what I have written here.

Solution 2 - Ios

This is almost same as the accepted answer but with some added dialogue (I had with Rob Napier, his other answers and Matt, Oliver, David from Slack) and links.

See the comments in this discussion. The gist of it is:

+ is heavily overloaded (Apple seems to have fixed this for some cases)

The + operator is heavily overloaded, as of now it has 27 different functions so if you are concatenating 4 strings ie you have 3 + operators the compiler has to check between 27 operators each time, so that's 27^3 times. But that's not it.

There is also a check to see if the lhs and rhs of the + functions are both valid if they are it calls through to core the append called. There you can see there are a number of somewhat intensive checks that can occur. If the string is stored non-contiguously, which appears to be the case if the string you’re dealing with is actually bridged to NSString. Swift then has to re-assemble all the byte array buffers into a single contiguous buffer and which requires creating new buffers along the way. and then you eventually get one buffer that contains the string you’re attempting to concatenate together.

In a nutshell there is 3 clusters of compiler checks that will slow you down ie each sub-expression has to be reconsidered in light of everything it might return. As a result concatenating strings with interpolation ie using " My fullName is \(firstName) \(LastName)" is much better than "My firstName is" + firstName + LastName since interpolation doesn't have any overloading

Swift 3 has made some improvements. For more information read https://stackoverflow.com/questions/39490808/how-to-merge-multiple-arrays-without-slowing-the-compiler-down/39511195#39511195. Nonetheless the + operator is still overloaded and it's better to use string interpolation for longer strings


Usage of optionals (ongoing problem - solution available)

In this very simple project:

import UIKit

class ViewController: UIViewController {

    let p = Person()
    let p2 = Person2()
    
    func concatenatedOptionals() -> String {
        return (p2.firstName ?? "") + "" + (p2.lastName ?? "") + (p2.status ?? "")
    }
    
    func interpolationOptionals() -> String {
        return "\(p2.firstName ?? "") \(p2.lastName ?? "")\(p2.status ?? "")"
    }
    
    func concatenatedNonOptionals() -> String {
        return (p.firstName) + "" + (p.lastName) + (p.status)
    }
    
    func interpolatedNonOptionals() -> String {
        return "\(p.firstName) \(p.lastName)\(p.status)"
    }
}


struct Person {
    var firstName = "Swift"
    var lastName = "Honey"
    var status = "Married"
}

struct Person2 {
    var firstName: String? = "Swift"
    var lastName: String? = "Honey"
    var status: String? = "Married"
}

The compile time for the functions are as such:

21664.28ms	/Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:16:10	instance method concatenatedOptionals()
2.31ms	/Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:20:10	instance method interpolationOptionals()
0.96ms	/Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:24:10	instance method concatenatedNonOptionals()
0.82ms	/Users/Honey/Documents/Learning/Foundational/CompileTime/CompileTime/ViewController.swift:28:10	instance method interpolatedNonOptionals()

Notice how crazy high the compilation duration for concatenatedOptionals is.

This can be solved by doing:

let emptyString: String = ""
func concatenatedOptionals() -> String {
    return (p2.firstName ?? emptyString) + emptyString + (p2.lastName ?? emptyString) + (p2.status ?? emptyString)
}

which compiles in 88ms

The root cause of the problem is that the compiler doesn't identify the "" as a String. It's actually ExpressibleByStringLiteral

The compiler will see ?? and will have to loop through all types that have conformed to this protocol, till it finds a type that can be a default to String. By Using emptyString which is hardcoded to String, the compiler no longer needs to loop through all conforming types of ExpressibleByStringLiteral

To learn how to log compilation times see here or here


Other similar answers by Rob Napier on SO:

https://stackoverflow.com/questions/53556804/why-string-addition-takes-so-long-to-build/53564569#53564569

https://stackoverflow.com/questions/39490808/how-to-merge-multiple-arrays-without-slowing-the-compiler-down

https://stackoverflow.com/questions/40556751/swift-array-contains-function-makes-build-times-long/40556813#40556813

Solution 3 - Ios

This is quite ridiculous no matter what you say! :)

enter image description here

But this gets passed easily

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"

Solution 4 - Ios

I had similar issue:

expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions

In Xcode 9.3 line goes like this:

let media = entities.filter { (entity) -> Bool in

After changing it into something like this:

let media = entities.filter { (entity: Entity) -> Bool in

everything worked out.

Probably it has something to do with Swift compiler trying to infer data type from code around.

Solution 5 - Ios

Great news - this seems to be fixed in the upcoming Xcode 13.

I was filing radar reports for this:

http://openradar.appspot.com/radar?id=4962454186491904 https://bugreport.apple.com/web/?problemID=39206436

... and Apple has just confirmed that this is fixed.

I have tested all cases that I have with complex expressions and SwiftUI code and everything seems to work great in Xcode 13.

> Hi Alex, > Thanks for your patience, and thanks for your feedback. We believe this issue is resolved. > Please test with the latest Xcode 13 beta 2 release and update your feedback report with your results by logging into https://feedbackassistant.apple.com or by using the Feedback Assistant app.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKendrick TaylorView Question on Stackoverflow
Solution 1 - IosAaron RasmussenView Answer on Stackoverflow
Solution 2 - IosmfaaniView Answer on Stackoverflow
Solution 3 - IoskarimView Answer on Stackoverflow
Solution 4 - IosvedranoView Answer on Stackoverflow
Solution 5 - IosCrulexView Answer on Stackoverflow