Помилка компілятора 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

5
Інтерполяція струн, як рекомендує @efischency, як правило, є кращим варіантом, ніж ручне з'єднання з +.
Маттт

5
Звичайно, але це не в цьому. Мені байдуже, чи це "запропонований" спосіб чи ні, я просто хочу знати, чому компілятор задихається. У мене є рішення, яке працює, це не про виправлення помилки, а про розуміння помилки.
Кендрік Тейлор

2
З того, що я чув, компілятор Swift все ще дуже працює. Команда може оцінити звіт про помилку щодо цього.
molbdnilo

У мене не було проблем з компіляцією цього пункту 6.3.1. У мене були подібні смішні повідомлення в минулому. Нам потрібно почекати, поки Свіфт покине свій альфа-стан.
qwerty_so

Відповіді:


183

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

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

Цей висновок може ускладнитися - наприклад, якщо ви додасте a UInt8і Intuse +, результат буде an Int, але є деяка робота, яка полягає в оцінці правил змішування типів з операторами.

І коли ви використовуєте літерали, як, наприклад, Stringлітерали у вашому прикладі, компілятор виконує роботу з перетворення Stringлітералу в a String, а потім виконує роботу виведення аргументів і типів повернення для +оператора тощо.

Якщо вираз є досить складним - тобто, він вимагає від компілятора зробити занадто багато висновків щодо аргументів та операторів - він закриває і каже вам, що він вийшов.

Намиснення компілятора, коли вираз досягає певного рівня складності, є навмисним. Альтернативою є дозволити компілятору спробувати це зробити, і подивитися, чи може, але це ризиковано - компілятор може продовжувати спроби назавжди, збиватися з ладу або просто завершити роботу. Отже, я розумію, що існує статичний поріг складності виразу, який компілятор не вийде за рамки.

Я розумію, що команда Swift працює над оптимізаціями компілятора, які зроблять ці помилки менш поширеними. Ви можете трохи дізнатися про це на форумах Apple Developer, натиснувши це посилання .

На форумах Dev Кріс Леттнер попросив людей надсилати ці помилки як радіолокаційні звіти, оскільки вони активно працюють над їх виправленням.

Ось як я це розумію, прочитавши низку публікацій тут і на форумі Dev про це, але моє розуміння компіляторів наївне, і я сподіваюся, що хтось із глибшими знаннями про те, як вони впораються з цими завданнями, розшириться на те, що я написали тут.


Я щось додумався до цього, але все-таки це була корисна відповідь. Дякую за відповідь. Ви порахували кількість + операторів вручну чи є якийсь стрункий спосіб, про який я не знаю?
Кендрік Тейлор

Я просто зазирнув на SwiftDoc.org і порахував їх вручну. Це сторінка, про яку я говорю: swiftdoc.org/operator/pls
Аарон Расмуссен

28
Це помилка, незалежно від того, чи це вони назватимуть. Компілятори інших мов не мають проблем з кодом, аналогічним тому, що був розміщений. Запропонувати кінцевому користувачеві зробити це нерозумно.
Іван

7
Введіть умовивід? Який сенс у тому, щоб у цій глузливій ситуації мати мову такого типу, як Swift (в якій ви навіть не можете об'єднати String + Int без того, щоб викинути Int)? Вкотре Свіфт намагається вирішити проблеми, які ні в кого не виникали.
Азурлаке

10
@John Не помилка, просто поганий дизайн мови, якщо ви запитаєте мене! Швидкий заходить занадто далеко, просто намагаючись бути іншим.
Т. Рекс

31

Це майже так само, як прийнята відповідь, але з деяким додатковим діалогом (я мав з Роб Неп'єром, його іншими відповідями та Меттом, Олівером, Девідом з Слака) та посиланнями.

Дивіться коментарі в цій дискусії. Суть її полягає в:

+ сильно перевантажений (Apple, здається, виправила це в деяких випадках)

+Оператор сильно перевантажений, як зараз вона має 27 різних функцій , так що якщо ви конкатенація 4 рядки , тобто у вас є 3 +операторів, компілятор повинен перевірити між 27 операторами , кожен раз, так що 27 ^ 3 рази. Але це не все.

Існує також перевірка , щоб побачити , якщо lhsі rhsз +функцій є дійсними , якщо вони викликаються через до сердечника 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

Щоб дізнатися, як записувати час складання, дивіться тут або тут


Інші подібні відповіді Роб Нап'єр на 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, який намагається вивести тип даних з коду.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.