快速编译器错误: 字符串串联上的“表达式太复杂”

我觉得这比什么都有趣。我已经修好了,但我想知道原因。这里有一个错误: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions。它为什么要抱怨?这似乎是最简单的表达方式之一。

编译器指向 columns + ");";

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);
}

解决办法是:

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

这也可以(通过@efischency) ,但我不太喜欢它,因为我认为 (会迷路:

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

39510 次浏览

我不是编译器方面的专家——我不知道这个答案是否会“以一种有意义的方式改变你的思维方式”,但我对这个问题的理解是:

它与类型推断有关。每次使用 +操作符时,Swift 都必须搜索 +的所有可能重载,并推断您使用的是哪个版本的 +。我数了一下,+操作员的超载不到30次。这有很多可能性,当您将4个或5个 +操作链接在一起并要求编译器推断所有参数时,您所要求的要比乍看上去多得多。

这种推断可能会变得复杂——例如,如果您使用 +添加一个 UInt8和一个 Int,那么输出将是一个 Int,但是还有一些工作要做,以评估混合类型和操作符的规则。

当你使用字面值时,比如你的例子中的 String字面值,编译器会把 String字面值转换成 String字面值,然后为 +操作符推断参数和返回类型,等等。

如果一个表达式足够复杂——也就是说,它需要编译器对参数和运算符做出太多的推断——它就会退出,并告诉你它退出了。

让编译器在表达式达到一定复杂度时退出是有意为之的。另一种方法是让编译器尝试这样做,看看它是否可以做到,但这是有风险的——编译器可能会一直尝试下去,陷入僵局,或者直接崩溃。所以我的理解是,对于表达式的复杂性,存在一个静态阈值,编译器不会超过这个阈值。

我的理解是,Swift 团队正在进行编译器优化,以减少这些错误的发生。

在 Dev 论坛上,Chris Lattner 要求人们将这些错误作为雷达报告归档,因为他们正在积极地修复这些错误。

这就是我在这里和 Dev 论坛上阅读了一些关于它的文章后的理解,但是我对编译器的理解是天真的,我希望有人对他们如何处理这些任务有更深的了解,能够扩展我在这里写的东西。

这几乎是相同的接受答案,但有一些附加的对话(我与罗布纳皮尔,他的其他答案和马特,奥利弗,大卫从 Slack)和链接。

请看 这个讨论中的评论,其要点是:

+严重超载(苹果似乎已经在某些情况下修复了这一问题)

+接线员被严重重载,现在它有27个不同的函数,所以如果你连接4个字符串,即你有3个 +操作符,编译器必须在每次27个操作符之间的 检查完毕,所以是27 ^ 3次。但事实并非如此。

还有一个 < strong > check ,用于查看 +函数的 lhsrhs是否都是有效的,如果它们是通过 < strong > check 调用的,以核心调用的 append。在那里你可以看到有一些稍微密集的 < strong > check 可以发生。如果字符串是非连续存储的,那么如果您正在处理的字符串实际上是桥接到 NSString 的,那么就会出现这种情况。然后,Swift 必须将所有字节数组缓冲区重新组装到一个连续的缓冲区中,这需要在此过程中创建新的缓冲区。然后最终得到一个缓冲区,其中包含要连接在一起的字符串。

简而言之,有3个编译器检查集群将减慢你的速度即 每个子表达式都必须根据它 < em > 可能 返回的内容重新考虑。因此,串联字符串与插值即使用 " My fullName is \(firstName) \(LastName)""My firstName is" + firstName + LastName好得多,因为插值 没有有任何重载

Swift 3 改进了 一些。更多信息请阅读 如何在不减慢编译器速度的情况下合并多个数组?。尽管如此,+运算符仍然是重载的,对于较长的字符串,最好使用字符串插值


可选项的使用(正在进行的问题-可用的解决方案)

在这个非常简单的项目中:

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"
}

函数的编译时间如下:

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()

注意,concatenatedOptionals的编译持续时间有多长。

这个问题可以通过以下方法解决:

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

88ms中编译

问题的根本原因是编译器没有将 ""标识为 String,实际上它是 ExpressibleByStringLiteral

编译器将看到 ??,并且必须看到 循环遍历所有符合此协议的类型,直到它找到一个可以是 String的默认类型。 通过使用硬编码为 StringemptyString,编译器不再需要遍历所有符合 ExpressibleByStringLiteral的类型

要了解如何记录编译次数,请参阅 给你给你


罗伯 · 纳皮尔(Rob Napier)关于 SO 的其他类似回答:

为什么字符串加法需要这么长时间来构建?

如何在不减慢编译器速度的情况下合并多个数组?

Swift Array 包含的函数使得构建时间变长

我也有类似的问题:

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

在 Xcode 9.3中,行是这样的:

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

在把它变成这样的东西之后:

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

一切都解决了。

可能与 Swift 编译器试图从周围的代码中推断出数据类型有关。

不管你说什么,这都太荒谬了! :)

enter image description here

但这很容易通过

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

好消息——这似乎在即将发布的 Xcode 13中得到了修复。

我正在整理雷达报告:

Http://openradar.appspot.com/radar?id=4962454186491904 Https://bugreport.apple.com/web/?problemid=39206436

苹果公司刚刚确认这个问题已经解决了。

我用复杂的表达式和 SwiftUI 代码测试了所有的案例,在 Xcode 13似乎一切都很顺利。

嗨,亚历克斯, 感谢您的耐心,感谢您的反馈。我们相信这个问题已经得到解决。 请使用最新的 Xcode 13 beta 2版本进行测试,并通过登录到 https://feedbackassistant.apple.com或使用反馈助手应用程序,用您的结果更新您的反馈报告。