Errore del compilatore Swift: "Espressione troppo complessa" su una concatenazione di stringhe


143

Lo trovo più divertente di ogni altra cosa. L'ho risolto, ma mi chiedo della causa. Ecco l'errore: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Perché si lamenta? Sembra una delle espressioni più semplici possibili.

Il compilatore punta alla columns + ");";sezione

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

la correzione è:

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

anche questo funziona (tramite @efischency) ma non mi piace tanto perché penso che mi perdo (:

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


10
Hai visto se funziona var statement = "create table if not exists \(self.tableName()) (\(columns))":?
efischency,

5
L'interpolazione di stringhe, come raccomandato da @efischency, è generalmente un'opzione migliore rispetto alla concatenazione manuale con +.
mattt,

5
Certo, ma non è questo il punto. Non mi importa se è il modo "suggerito" o no, voglio solo sapere perché il compilatore ci soffoca. Ho una soluzione che funziona, non si tratta di correggere l'errore, si tratta di capire l'errore.
Kendrick Taylor,

2
Da quello che ho sentito, il compilatore Swift è ancora in fase di sviluppo. Il team potrebbe apprezzare una segnalazione di bug al riguardo.
molbdnilo,

Non ho avuto problemi a compilare questo con 6.3.1. Ho avuto simili messaggi ridicoli in passato. Dobbiamo aspettare che Swift lasci il suo stato alfa.
qwerty_so,

Risposte:


183

Non sono un esperto di compilatori - non so se questa risposta "cambierà il modo in cui pensi in modo significativo", ma la mia comprensione del problema è questa:

Ha a che fare con l'inferenza del tipo. Ogni volta che usi l' +operatore, Swift deve cercare tutti i possibili sovraccarichi +e dedurre quale versione di +te stai usando. Ho contato poco meno di 30 sovraccarichi per l' +operatore. Sono molte le possibilità, e quando concatenate 4 o 5 +operazioni insieme e chiedete al compilatore di dedurre tutti gli argomenti, chiedete molto di più di quanto possa sembrare a prima vista.

Tale inferenza può diventare complicata - ad esempio, se aggiungi un UInt8e un Intutilizzo +, l'output sarà un Int, ma c'è del lavoro che va nella valutazione delle regole per mescolare i tipi con gli operatori.

E quando usi letterali, come i Stringletterali nel tuo esempio, il compilatore fa il lavoro di convertire il Stringletterale in a String, e quindi fa il lavoro di inferire l'argomento e restituire i tipi per l' +operatore, ecc.

Se un'espressione è sufficientemente complessa, ovvero richiede al compilatore di fare troppe inferenze sugli argomenti e sugli operatori, si chiude e ti dice che è uscita.

Far intenzionalmente uscire dal compilatore quando un'espressione raggiunge un certo livello di complessità. L'alternativa è lasciare che il compilatore provi e lo faccia, e vedere se è possibile, ma è rischioso: il compilatore potrebbe continuare a provare per sempre, impantanarsi o semplicemente arrestarsi. Quindi la mia comprensione è che esiste una soglia statica per la complessità di un'espressione che il compilatore non andrà oltre.

La mia comprensione è che il team Swift sta lavorando su ottimizzazioni del compilatore che renderanno questi errori meno comuni. Puoi saperne di più sui forum degli sviluppatori Apple facendo clic su questo link .

Nei forum Dev, Chris Lattner ha chiesto alle persone di archiviare questi errori come rapporti radar, perché stanno attivamente lavorando per risolverli.

È così che lo capisco dopo aver letto un numero di post qui e sul forum Dev su di esso, ma la mia comprensione dei compilatori è ingenua e spero che qualcuno con una conoscenza più profonda di come gestiscono questi compiti si espanderà su ciò che ho scritto qui.


Ho pensato a qualcosa in tal senso, ma è stata comunque una risposta utile. Grazie per aver risposto. Hai contato il numero di + operatori a mano o esiste un modo semplice di cui non sono a conoscenza?
Kendrick Taylor,

L'ho appena dato un'occhiata su SwiftDoc.org e li ho contati a mano. Questa è la pagina di cui sto parlando: swiftdoc.org/operator/pls
Aaron Rasmussen

28
Questo è un bug, indipendentemente dal fatto che lo chiamino così. I compilatori di altre lingue non hanno problemi con codice simile a quello che è stato pubblicato. Suggerire all'utente finale di risolverlo è sciocco.
Giovanni,

7
Tipo di inferenza? Qual è il punto di avere un linguaggio tipicamente forte come Swift (in cui non puoi nemmeno concatenare String + Int senza dover lanciare Int) in questa situazione ridicola? Ancora una volta, Swift cerca di risolvere i problemi che nessuno ha avuto in primo luogo.
Azurlake,

10
@John Non un bug, solo un linguaggio volgare se me lo chiedi! Swift si spinge troppo oltre cercando di essere diverso.
T. Rex,

31

È quasi uguale alla risposta accettata, ma con alcuni dialoghi aggiunti (ho avuto con Rob Napier, le sue altre risposte e Matt, Oliver, David di Slack) e i collegamenti.

Vedi i commenti in questa discussione. L'essenza è:

+ è fortemente sovraccarico (Apple sembra aver risolto questo problema in alcuni casi)

L' +operatore è pesantemente sovraccarico, al momento ha 27 funzioni diverse, quindi se si stanno concatenando 4 stringhe, ovvero se si hanno 3 +operatori, il compilatore deve controllare tra 27 operatori ogni volta, quindi 27 ^ 3 volte. Ma non è così.

V'è anche un controllo per vedere se la lhse rhsdelle +funzioni sono entrambe valide se sono chiama attraverso al nucleo appendchiamato. Lì puoi vedere che possono verificarsi numerosi controlli piuttosto intensi . Se la stringa viene archiviata in modo non contiguo, il che sembra essere il caso se la stringa con cui hai a che fare è effettivamente collegata a NSString. Swift deve quindi riassemblare tutti i buffer di array di byte in un singolo buffer contiguo e ciò richiede la creazione di nuovi buffer lungo il percorso. e alla fine ottieni un buffer che contiene la stringa che stai tentando di concatenare insieme.

In breve ci sono 3 gruppi di controlli del compilatore che ti rallenteranno, cioè ogni sottoespressione deve essere riconsiderata alla luce di tutto ciò che potrebbe restituire . Di conseguenza, concatenare le stringhe con l'interpolazione, ovvero l'uso " My fullName is \(firstName) \(LastName)"è molto meglio che "My firstName is" + firstName + LastNamepoiché l'interpolazione non ha alcun sovraccarico

Swift 3 ha apportato alcuni miglioramenti. Per ulteriori informazioni leggi Come unire più array senza rallentare il compilatore? . Tuttavia l' +operatore è ancora sovraccarico ed è meglio usare l'interpolazione di stringhe per stringhe più lunghe


Utilizzo di optionals (problema in corso - soluzione disponibile)

In questo semplicissimo progetto:

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

I tempi di compilazione per le funzioni sono tali:

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

Nota quanto è alta la durata della compilation concatenatedOptionals.

Questo può essere risolto facendo:

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

che compila in 88ms

La causa principale del problema è che il compilatore non identifica ""come a String. In realtà lo èExpressibleByStringLiteral

Il compilatore vedrà ??e dovrà scorrere tutti i tipi conformi a questo protocollo , fino a quando non trova un tipo che può essere predefinito String. Usando il emptyStringquale è hardcoded String, il compilatore non ha più bisogno di scorrere tutti i tipi diExpressibleByStringLiteral

Per informazioni su come registrare i tempi di compilazione, vedere qui o qui


Altre risposte simili di Rob Napier su SO:

Perché l'aggiunta di stringhe richiede così tanto tempo per essere costruita?

Come unire più array senza rallentare il compilatore?

Swift Array contiene funzioni che richiedono tempi di costruzione lunghi


19

Questo è abbastanza ridicolo, non importa quello che dici! :)

inserisci qui la descrizione dell'immagine

Ma questo viene superato facilmente

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

2

Ho avuto un problema simile:

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

In Xcode 9.3 la riga va così:

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

Dopo averlo cambiato in qualcosa del genere:

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

tutto ha funzionato.

Probabilmente ha qualcosa a che fare con il compilatore Swift che cerca di dedurre il tipo di dati dal codice in giro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.