Swift编译器错误:"字符串连接过于复杂"

145

对我来说,这更多是有趣的事情。我已经修复了它,但我在思考原因。以下是错误: DataManager.swift:51:90: 表达式过于复杂无法在合理的时间内解决;考虑将表达式分解为不同的子表达式。为什么会出现这个问题呢?它似乎是可能存在最简单的表达式之一。

编译器指向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语句="create table if not exists \(self.tableName()) (\(columns))"


11
你看过这个代码是否有效:var statement = "create table if not exists \(self.tableName()) (\(columns))"?它用于创建一个表,如果该表不存在的话。 - efischency
6
建议采用@efischency推荐的字符串插值(String interpolation)方式,通常比使用+手动进行字符串拼接更好。 - mattt
5
当然,但这不是重点。我不在乎它是否是“建议”的方式,我只想知道为什么编译器会出错。我有一个可行的解决方案,这不是修复错误的问题,而是理解错误的原因。 - Kendrick Taylor
2
据我所知,Swift编译器仍然在不断完善中。团队可能会感激一份关于此问题的错误报告。 - molbdnilo
我在6.3.1下编译这个没有问题。过去我也遇到过类似的荒谬信息。我们需要等到Swift离开alpha状态。 - qwerty_so
显示剩余3条评论
5个回答

185

我不是编译器专家 - 我不知道这个答案是否会 "以有意义的方式改变你的思考方式",但我对问题的理解如下:

这与类型推断有关。每次使用+运算符时,Swift都必须搜索所有可能的+重载,并推断您正在使用哪个版本的+。我数了一下+运算符的不到30种重载。这是很多可能性,当您将4或5个+操作链接在一起并要求编译器推断所有参数时,您所要求的不仅仅是表面上看起来的那么简单。

推断可以变得非常复杂-例如,如果您使用+UInt8Int相加,则输出将为Int,但需要进行一些工作来评估使用运算符混合类型的规则。

而且,当您使用字面量,例如您示例中的String字面量时,编译器会执行将String字面量转换为String的工作,然后执行推断+运算符的参数和返回类型等工作。

如果表达式足够复杂 - 即需要编译器对参数和运算符进行太多的推断 - 则编译器会退出并告诉您它退出了。

当表达式达到一定复杂度时,使编译器退出是有意义的。另一种选择是让编译器尝试做这件事,看看它是否能够完成,但这是有风险的-编译器可能会一直尝试下去,拖垮或崩溃。因此,我了解到表达式的复杂性存在一个静态阈值,编译器不会超越这个阈值。

我了解到Swift团队正在开发编译器优化,以使这些错误更少出现。您可以通过单击此链接在Apple Developer论坛上了解一些信息

在开发论坛上,Chris Lattner要求人们将这些错误作为radar报告提交,因为他们正在积极修复这些问题。
这是我在这里和开发论坛上阅读了一些帖子后的理解,但我的编译器知识很肤浅,希望有更深入了解它们如何处理这些任务的人能够扩展我在这里写的内容。

32
无论是否称之为“bug”,这都是一个问题。其他语言的编译器对类似于发布的代码没有任何问题。建议最终用户去修复它是荒谬的。 - John
7
类型推断?在强类型语言Swift中有何意义(例如您甚至不能将字符串和整数连接而不必转换整数)?在这种荒谬的情况下,Swift试图解决没有人首先遇到的问题。 - Azurlake
12
如果你问我,这不是一个 Bug,只是糟糕的语言设计!Swift 过于追求与众不同。 - SamAko
1
@kevin Java 也是强类型语言,但它很聪明,知道除非将 String 与 int 的字符串表示连接起来,否则没有有用的意义。很抱歉,与 Java 不同,Swift 是一个愚蠢的不向后兼容的烂货。我只发现了一些有用的特性,比如扩展。但是,再一次地,XCode 让它变得不值得。 - Azurlake
1
@Azurlake 使用运算符进行连接并不是 Swift 的方式,你可以抱怨说你的锤子无法将螺丝钉钉入,或者直接使用螺丝刀。如果你想在 Swift 中连接一个字符串和一个变量,你可以这样做:"myString (myVar)"。你抱怨必须强制转换整数,但其实完全没有必要。 - kevin
显示剩余6条评论

31
This is almost the 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, i.e., you have 3 + operators, the compiler has to check between 27 operators each time. That's 27^3 times. But that's not it.

还有一个检查,用于检查+函数的lhsrhs是否都有效,如果是,则调用核心append。在那里,您可以看到可能会发生许多有点密集的检查。如果字符串存储为非连续的,那么似乎是实际上桥接到NSString的情况。然后,Swift必须将所有字节数组缓冲区重新组装成单个连续缓冲区,并需要沿途创建新缓冲区。然后,最终获得包含要尝试连接在一起的字符串的一个缓冲区。

简而言之,编译器检查分为3个集群,会减慢速度,即必须考虑每个子表达式在可能返回的情况下的影响。因此,使用插值串联字符串,例如使用" 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)
}

编译时间为88毫秒

问题的根本原因是编译器不能将""识别为String类型,实际上它是ExpressibleByStringLiteral

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

要了解如何记录编译时间,请参见此处此处


以下是 Rob Napier 在 SO 上提供的其他类似答案:

为什么字符串相加需要很长时间才能构建?

如何合并多个数组而不会减慢编译器速度?

Swift 数组包含函数使构建时间变长


19

无论你说什么,这都很荒谬! :)

输入图片描述

但这很容易通过

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

2

我曾遇到类似的问题:

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编译器试图从周围的代码推断数据类型有关。


0

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

我正在为此提交雷达报告:

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

... 而且苹果刚刚确认这已经被修复。

我已经测试了我使用的所有带有复杂表达式和SwiftUI代码的情况,一切都在Xcode 13中运行得非常好。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接