Swift编译器错误:字符串连接中的“表达式过于复杂”


143

我发现这比任何事情都有趣。我已经解决了,但是我想知道原因。错误如下: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))"


10
您是否看到了这样的效果var statement = "create table if not exists \(self.tableName()) (\(columns))"
efischency,2015年

5
@efischency建议使用字符串插值,通常比使用手动串联更好+
mattt

5
当然,但这不是重点。我不在乎这是否是“建议”方式,我只想知道为什么编译器会对此感到窒息。我有一个有效的解决方案,不是解决错误,而是了解错误。
肯德里克·泰勒

2
据我所知,Swift编译器仍在进行中。团队可能对此表示感谢。
molbdnilo 2015年

我没有问题用6.3.1进行编译。过去我也有类似的荒谬信息。我们需要等到Swift离开其alpha状态。
qwerty_so 2015年

Answers:


183

我不是编译器专家-我不知道这个答案是否会“以有意义的方式改变您的想法”,但是我对问题的理解是:

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

这种推论可能会变得很复杂-例如,如果添加a UInt8Intusing +,则输出将为an Int,但是还有一些工作需要评估将类型与运算符混合的规则。

当您使用文字时,如String示例中的文字,编译器将把String文字转换为String,然后为+运算符推断参数和返回类型,等等。

如果表达式足够复杂(即,它要求编译器对参数和运算符进行过多推断),它将退出并告诉您退出。

一旦表达式达到一定程度的复杂性,让编译器退出是有意的。另一种选择是让编译器尝试做,看看是否可以,但这是有风险的-编译器可能会一直尝试下去,陷入困境,甚至崩溃。因此,我的理解是,对于表达式的复杂度有一个静态阈值,编译器将无法超越。

我的理解是,Swift团队正在致力于编译器优化,这将使这些错误的发生率降低。您可以通过单击此链接在Apple Developer论坛上了解一点

在开发人员论坛上,克里斯·拉特纳(Chris Lattner)要求人们将这些错误作为雷达报告提出,因为他们正在积极致力于修复这些错误。

在阅读了这里和开发论坛上的许多帖子后,我就是这样理解的,但是我对编译器的理解很幼稚,我希望对他们如何处理这些任务有更深入了解的人能够在我的基础上得到扩展已经写在这里。


我想出了某种效果,但这仍然是一个有用的答案。谢谢回答。您是手工计算+运算符的数量还是有我不知道的一些巧妙方法?
肯德里克·泰勒

我只是在SwiftDoc.org上偷看了一下,然后手工计算了一下。这是我正在谈论的页面:swiftdoc.org/operator/pls
亚伦·拉斯穆森

28
这是一个错误,无论他们是否称呼它。其他语言的编译器对与发布的代码相似的代码没有问题。建议最终用户修复它是愚蠢的。
约翰

7
类型推断?在这种荒唐的情况下,拥有像Swift这样的强类型语言(在这种情况下,您甚至不必连接String + Int而不必强制转换Int),有什么意义呢?Swift再次尝试解决最初没人遇到的问题。
Azurlake

10
@John不是一个错误,如果您问我,只是语言设计不好!Swift试图做到与众不同就太过分了。
T. Rex

31

这几乎与接受的答案相同,但是增加了一些对话(我与Rob Napier,他的其他答案以及Slack的Matt,Oliver和David进行了对话)和链接。

请参阅讨论中的评论。其要点是:

+ 严重超载(Apple似乎在某些情况下已解决此问题)

+运营商的负荷过重,截至目前有27个不同的功能,所以如果你是串联4串,即你有3个+运营商的编译器来检查,每次27个运营商之间,所以这是27 ^ 3倍。但这不是。

还有一个检查,看看是否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)
}

编译在 88ms

问题的根本原因是编译器无法将识别""String。其实是ExpressibleByStringLiteral

编译器将看到??并且将不得不遍历所有符合此协议的类型,直到找到可以默认为的类型为止String。通过使用emptyString硬编码为String,编译器不再需要遍历所有符合类型的ExpressibleByStringLiteral

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


Rob Napier关于SO的其他类似答案:

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

如何合并多个数组而不降低编译器的速度?

Swift Array包含使构建时间变长的功能


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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.