Ошибка компилятора Swift: "Слишком сложное выражение" при конкатенации строк

Я нахожу это забавным больше всего. Я исправил это, но мне интересно о причине. Вот ошибка: 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))"

Ответ 1

Я не эксперт по компиляторам - я не знаю, будет ли этот ответ "меняться, как вы думаете значимым образом", но мое понимание проблемы таково:

Это связано с типом вывода. Каждый раз, когда вы используете оператор +, Swift должен выполнить поиск всех возможных перегрузок для + и указать, какую версию + вы используете. Я подсчитал менее 30 перегрузок для оператора +. Это много возможностей, и когда вы объединяете 4 или 5 + операций и просите компилятор вывести все аргументы, вы спрашиваете намного больше, чем может показаться на первый взгляд.

Этот вывод может усложниться - например, если вы добавили UInt8 и Int с помощью +, вывод будет Int, но есть некоторая работа, которая идет на оценку правил смешивания типы с операторами.

И когда вы используете литералы, например литералы String в вашем примере, компилятор выполняет работу по преобразованию литерала String в String, а затем выполняет работу по выводу аргументов и возвращаемых типов для оператора + и т.д.

Если выражение достаточно сложное, т.е. требуется, чтобы компилятор делал слишком много выводов о аргументах и ​​операторах - он завершает работу и говорит, что он уходит.

Когда компилятор завершает работу после того, как выражение достигает определенного уровня сложности, оно преднамеренно. Альтернатива заключается в том, чтобы позволить компилятору попробовать и сделать это, и посмотреть, можно ли это сделать, но это рискованно - компилятор может продолжать настойчиво, болото или просто сбой. Поэтому я понимаю, что существует статический порог для сложности выражения, которое компилятор не выйдет за пределы.

Я понимаю, что команда Swift работает над оптимизацией компилятора, которая сделает эти ошибки менее распространенными. Вы можете немного узнать об этом на форумах разработчиков Apple, нажав на эту ссылку.

На форумах Dev Крис Лэттнер попросил людей записать эти ошибки в виде радарных отчетов, потому что они активно работают над их исправлением.

Вот как я это понимаю, прочитав несколько сообщений здесь и на форуме Dev об этом, но мое понимание компиляторов наивно, и я надеюсь, что кто-то с более глубоким знанием того, как они справятся с этими задачами, будет расширяться на том, что я здесь написал.

Ответ 2

Это почти то же самое, что принятый ответ, но с некоторым дополнительным диалогом (я имел с Робом Нейпиром, его другими ответами и другим другом с встречи с Cocoa-головой) и ссылками.

Смотрите комментарии в этом обсуждении. Суть этого такова:

Оператор + сильно перегружен, на данный момент он имеет 27 различных функций, поэтому, если вы объединяете 4 строки, т.е. у вас есть 3 оператора +, компилятор должен проверять между 27 операторов каждый раз, так что 27 ^ 3 раза. Но это не так.

Существует также проверка, чтобы увидеть, действительны ли функции lhs и rhs +, если они вызваны для вызова ядра append. Там вы можете увидеть несколько интенсивных проверок, которые могут произойти. Если строка хранится несмежно, что, как представляется, имеет место, если строка, с которой вы имеете дело, фактически соединена с NSString. Затем Swift должен собрать все буферы байтового массива в единый непрерывный буфер, который требует создания новых буферов. а затем вы в конечном итоге получите один буфер, содержащий строку, которую вы пытаетесь объединить вместе.

В двух словах, есть 3 кластера проверок компилятора, которые замедляют работу, т.е. каждое подвыражение должно быть пересмотрено в свете всего, что оно может вернуть. В результате конкатенация строк с интерполяцией, т.е. с использованием " My fullName is \(firstName) \(LastName)", намного лучше, чем "My firstName is" + firstName + LastName, поскольку интерполяция не имеет перегрузки

.Swift 3 сделал некоторые улучшения. Для получения дополнительной информации прочитайте Как объединить несколько массивов без замедления компилятора?. Тем не менее, оператор + все еще перегружен, и для более длинных строк лучше использовать интерполяцию строк


Другие подобные ответы Роба Нейпира на SO:

Почему сложение строк занимает так много времени?

Как объединить несколько массивов, не замедляя компилятор?

Swift Array содержит функцию, увеличивающую время сборки

Ответ 3

Это довольно нелепо, независимо от того, что вы говорите! :)

enter image description here

Но это легко проходит

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

Ответ 4

У меня была похожая проблема:

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, который пытается вывести тип данных из кода.