Erreur du compilateur Swift: "Expression trop complexe" sur une concaténation de chaînes

143

Je trouve cela plus amusant que tout. Je l'ai réparé, mais je m'interroge sur la cause. Voici l'erreur: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Pourquoi se plaint-il? Cela semble être l'une des expressions les plus simples possibles.

Le compilateur pointe vers la columns + ");";section

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

le correctif est:

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

cela fonctionne aussi (via @efischency) mais je ne l'aime pas autant car je pense que ça (se perd:

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

Kendrick Taylor
la source
10
Avez-vous vu si cela fonctionne var statement = "create table if not exists \(self.tableName()) (\(columns))":?
efischency
5
L'interpolation de chaîne, comme recommandé par @efischency, est généralement une meilleure option que la concaténation manuelle avec +.
mattt
5
Bien sûr, mais ce n'est pas le point. Je me fiche que ce soit la manière "suggérée" ou non, je veux juste savoir pourquoi le compilateur s'étouffe dessus. J'ai une solution qui fonctionne, il ne s'agit pas de corriger l'erreur, mais de comprendre l'erreur.
Kendrick Taylor
2
D'après ce que j'ai entendu, le compilateur Swift est toujours un travail en cours. L'équipe pourrait apprécier un rapport de bogue à ce sujet.
molbdnilo
Je n'ai eu aucun problème à compiler cela avec 6.3.1. J'ai eu des messages ridicules similaires dans le passé. Nous devons attendre que Swift quitte son état alpha.
qwerty_so

Réponses:

183

Je ne suis pas un expert des compilateurs - je ne sais pas si cette réponse "changera votre façon de penser de manière significative", mais ma compréhension du problème est la suivante:

Cela a à voir avec l'inférence de type. Chaque fois que vous utilisez l' +opérateur, Swift doit rechercher toutes les surcharges possibles +et en déduire quelle version de +vous utilisez. J'ai compté un peu moins de 30 surcharges pour l' +opérateur. C'est beaucoup de possibilités, et quand vous enchaînez 4 ou 5 +opérations ensemble et demandez au compilateur de déduire tous les arguments, vous demandez beaucoup plus qu'il n'y paraît à première vue.

Cette inférence peut devenir compliquée - par exemple, si vous ajoutez un UInt8et un Intusing +, la sortie sera un Int, mais il y a du travail à faire pour évaluer les règles de mélange de types avec des opérateurs.

Et lorsque vous utilisez des littéraux, comme les Stringlittéraux dans votre exemple, le compilateur effectue le travail de conversion du Stringlittéral en a String, puis effectue le travail d'inférence de l'argument et des types de retour pour l' +opérateur, etc.

Si une expression est suffisamment complexe - c'est-à-dire qu'elle oblige le compilateur à faire trop de déductions sur les arguments et les opérateurs - elle se ferme et vous dit qu'elle se ferme.

Faire quitter le compilateur une fois qu'une expression atteint un certain niveau de complexité est intentionnel. L'alternative est de laisser le compilateur essayer de le faire, et voir si c'est possible, mais c'est risqué - le compilateur pourrait continuer à essayer pour toujours, s'enliser ou simplement planter. Donc, je crois comprendre qu'il existe un seuil statique pour la complexité d'une expression que le compilateur n'ira pas au-delà.

Je crois comprendre que l'équipe Swift travaille sur des optimisations du compilateur qui rendront ces erreurs moins courantes. Vous pouvez en apprendre un peu plus à ce sujet sur les forums des développeurs Apple en cliquant sur ce lien .

Sur les forums de développement, Chris Lattner a demandé aux gens de déposer ces erreurs sous forme de rapports radar, car ils travaillent activement à les corriger.

C'est ainsi que je le comprends après avoir lu un certain nombre de messages ici et sur le forum Dev à ce sujet, mais ma compréhension des compilateurs est naïve, et j'espère que quelqu'un avec une connaissance plus approfondie de la façon dont ils gèrent ces tâches développera ce que je ont écrit ici.

Aaron Rasmussen
la source
J'ai pensé à quelque chose à cet effet, mais ce n'était pas moins une réponse utile. Merci de répondre. Avez-vous compté le nombre d'opérateurs + à la main ou y a-t-il un moyen astucieux dont je ne suis pas au courant?
Kendrick Taylor
Je viens de jeter un coup d'œil sur SwiftDoc.org et je les ai comptés à la main. C'est la page dont je parle: swiftdoc.org/operator/pls
Aaron Rasmussen
28
C'est un bogue, qu'ils l'appellent ou non ainsi. Les compilateurs d'autres langages n'ont aucun problème avec un code similaire à ce qui a été publié. Suggérer à l'utilisateur final de le réparer est ridicule.
John
7
Taper l'inférence? Quel est l'intérêt d'avoir un langage fortement typé comme Swift (dans lequel vous ne pouvez même pas concaténer String + Int sans avoir à lancer l'Int) dans cette situation délicate? Une fois de plus, Swift essaie de résoudre des problèmes que personne n'avait eu en premier lieu.
Azurlake
10
@John Pas un bug, juste une mauvaise conception du langage si vous me demandez! Swift va trop loin en essayant simplement d'être différent.
T. Rex
31

C'est presque la même chose que la réponse acceptée mais avec quelques dialogues supplémentaires (j'ai eu avec Rob Napier, ses autres réponses et Matt, Oliver, David de Slack) et des liens.

Voir les commentaires dans cette discussion. L'essentiel est:

+ est fortement surchargé (Apple semble avoir corrigé ce problème dans certains cas)

L' +opérateur est fortement surchargé, à partir de maintenant il a 27 fonctions différentes, donc si vous concaténez 4 chaînes, c'est-à-dire que vous avez 3 +opérateurs, le compilateur doit vérifier entre 27 opérateurs à chaque fois, soit 27 ^ 3 fois. Mais ce n'est pas ça.

Il y a aussi une vérification pour voir si les fonctions lhset rhsdes +fonctions sont toutes les deux valides si elles sont appendappelées à cœur de l' appelé. Là, vous pouvez voir qu'il y a un certain nombre de vérifications quelque peu intensives qui peuvent avoir lieu. Si la chaîne est stockée de manière non contiguë, ce qui semble être le cas si la chaîne que vous traitez est en fait pontée vers NSString. Swift doit ensuite réassembler tous les tampons du tableau d'octets dans un seul tampon contigu et ce qui nécessite la création de nouveaux tampons en cours de route. et vous obtenez finalement un tampon contenant la chaîne que vous essayez de concaténer.

En un mot, il y a 3 groupes de vérifications du compilateur qui vous ralentiront, c'est -à- dire que chaque sous-expression doit être reconsidérée à la lumière de tout ce qu'elle pourrait renvoyer . En conséquence, concaténer des chaînes avec interpolation, c'est-à-dire utiliser " My fullName is \(firstName) \(LastName)"est bien meilleur que "My firstName is" + firstName + LastNamepuisque l'interpolation n'a pas de surcharge

Swift 3 a apporté quelques améliorations. Pour plus d'informations, lisez Comment fusionner plusieurs tableaux sans ralentir le compilateur? . Néanmoins, l' +opérateur est toujours surchargé et il est préférable d'utiliser l'interpolation de chaîne pour les chaînes plus longues


Utilisation d'options (problème en cours - solution disponible)

Dans ce projet très simple:

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

Le temps de compilation des fonctions est comme tel:

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

Remarquez à quel point la durée de compilation concatenatedOptionalsest folle .

Cela peut être résolu en faisant:

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

qui compile en 88ms

La cause première du problème est que le compilateur n'identifie pas le ""comme un String. C'est en faitExpressibleByStringLiteral

Le compilateur verra ??et devra parcourir tous les types qui se sont conformés à ce protocole , jusqu'à ce qu'il trouve un type qui peut être un type par défaut String. En utilisant emptyStringqui est codé en dur String, le compilateur n'a plus besoin de parcourir tous les types conformes deExpressibleByStringLiteral

Pour savoir comment enregistrer les temps de compilation, voir ici ou ici


Autres réponses similaires de Rob Napier sur SO:

Pourquoi l'ajout de chaînes prend si longtemps à construire?

Comment fusionner plusieurs tableaux sans ralentir le compilateur?

Swift Array contient une fonction qui prolonge les temps de construction

Mon chéri
la source
19

C'est assez ridicule quoi que vous disiez! :)

entrez la description de l'image ici

Mais cela se passe facilement

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
Karim
la source
2

J'ai eu un problème similaire:

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

Dans Xcode 9.3, la ligne ressemble à ceci:

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

Après l'avoir changé en quelque chose comme ça:

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

tout a fonctionné.

Cela a probablement quelque chose à voir avec le compilateur Swift qui tente d'inférer le type de données à partir du code.

vedrano
la source