Erro do compilador Swift: “Expressão muito complexa” em uma concatenação de string

143

Acho isso divertido mais do que tudo. Eu consertei, mas estou me perguntando sobre a causa. Aqui está o erro: DataManager.swift:51:90: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. Por que está reclamando? Parece uma das expressões mais simples possíveis.

O compilador aponta para a columns + ");";seção

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

a correção é:

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

isso também funciona (via @efischency), mas não gosto muito porque acho que eles (se perdem:

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

Kendrick Taylor
fonte
10
Você viu se isso funciona var statement = "create table if not exists \(self.tableName()) (\(columns))":?
efischency
5
A interpolação de strings, conforme recomendado por @efischency, geralmente é uma opção melhor do que a concatenação manual +.
mattt
5
Claro, mas esse não é o ponto. Eu não me importo se é a maneira "sugerida" ou não, eu só quero saber por que o compilador se afoga. Eu tenho uma solução que funciona, não se trata de corrigir o erro, é de entender o erro.
Kendrick Taylor
2
Pelo que ouvi, o compilador Swift ainda é um trabalho em andamento. A equipe pode apreciar um relatório de bug sobre isso.
precisa saber é o seguinte
Não tive nenhum problema ao compilar isso com o 6.3.1. Eu tinha mensagens ridículas semelhantes no passado. Precisamos esperar até Swift deixar seu estado alfa.
precisa saber é o seguinte

Respostas:

183

Não sou especialista em compiladores - não sei se essa resposta "mudará a maneira como você pensa de maneira significativa", mas meu entendimento do problema é o seguinte:

Tem a ver com inferência de tipo. Cada vez que você usa o +operador, o Swift precisa pesquisar todas as sobrecargas possíveis +e inferir qual versão +você está usando. Contei pouco menos de 30 sobrecargas para o +operador. São muitas possibilidades e, quando você +une 4 ou 5 operações e pede ao compilador para inferir todos os argumentos, você está pedindo muito mais do que parece à primeira vista.

Essa inferência pode ser complicada - por exemplo, se você adicionar um UInt8e um Intuso +, a saída será um Int, mas há algum trabalho para avaliar as regras para misturar tipos com operadores.

E quando você está usando literais, como os Stringliterais do seu exemplo, o compilador realiza o trabalho de converter o Stringliteral em um Stringe, em seguida, realiza o trabalho de inferir os argumentos e os tipos de retorno para o +operador, etc.

Se uma expressão é suficientemente complexa - ou seja, requer que o compilador faça muitas inferências sobre os argumentos e os operadores - ela sai e informa que foi encerrada.

Ter o compilador encerrado quando uma expressão atingir um certo nível de complexidade é intencional. A alternativa é deixar o compilador tentar fazê-lo e ver se pode, mas isso é arriscado - o compilador pode continuar tentando para sempre, atolar ou simplesmente travar. Portanto, meu entendimento é que existe um limite estático para a complexidade de uma expressão que o compilador não irá além.

Meu entendimento é que a equipe Swift está trabalhando em otimizações de compilador que tornarão esses erros menos comuns. Você pode aprender um pouco sobre isso nos fóruns de desenvolvedores da Apple, clicando neste link .

Nos fóruns de desenvolvimento, Chris Lattner pediu às pessoas para arquivar esses erros como relatórios de radar, porque eles estão trabalhando ativamente para corrigi-los.

É assim que eu o entendo depois de ler várias postagens aqui e no fórum do Dev sobre isso, mas meu entendimento dos compiladores é ingênuo, e espero que alguém com um conhecimento mais profundo de como eles lidam com essas tarefas expanda o que eu escrevi aqui.

Aaron Rasmussen
fonte
Imaginei algo nesse sentido, mas foi uma resposta útil. Obrigado por responder. Você contou o número de + operadores manualmente ou há alguma maneira inteligente de que eu não conheço?
Kendrick Taylor
Eu apenas dei uma olhada no SwiftDoc.org e contei à mão. Esta é a página que eu estou falando: swiftdoc.org/operator/pls
Aaron Rasmussen
28
Isso é um bug, independentemente de eles chamarem isso. Os compiladores de outros idiomas não têm problemas com código semelhante ao postado. Sugerir que o usuário final conserte é bobagem.
João
7
Inferência de tipo? Qual é o sentido de ter uma linguagem de tipo forte como Swift (na qual você não pode nem concatenar String + Int sem precisar converter o Int) nessa situação árdua? Mais uma vez, Swift tenta resolver problemas que ninguém teve em primeiro lugar.
Azurlake
10
@ John Não é um bug, apenas um design de linguagem ruim, se você me perguntar! Swift vai longe demais, tentando ser diferente.
T. Rex
31

Isso é quase o mesmo que a resposta aceita, mas com alguns diálogos adicionais (tive com Rob Napier, suas outras respostas e Matt, Oliver, David do Slack) e links.

Veja os comentários nesta discussão. A essência disso é:

+ está sobrecarregado (a Apple parece ter corrigido isso em alguns casos)

O +operador está sobrecarregado, a partir de agora possui 27 funções diferentes; portanto, se você estiver concatenando 4 strings, ou seja, você tem 3 +operadores, o compilador deve verificar entre 27 operadores a cada vez, ou seja, 27 ^ 3 vezes. Mas não é isso.

Há também uma verificação para ver se o lhse rhsdas +funções são válidos se forem chama até o núcleo da appendchamada. Lá você pode ver que existem várias verificações intensivas que podem ocorrer. Se a sequência for armazenada de forma não contígua, o que parece ser o caso se a sequência com a qual você está lidando for realmente conectada ao NSString. O Swift precisa montar novamente todos os buffers da matriz de bytes em um único buffer contíguo, o que requer a criação de novos buffers ao longo do caminho. e, eventualmente, você obtém um buffer que contém a string que você está tentando concatenar juntos.

Em poucas palavras, existem três grupos de verificações do compilador que o atrasarão, ou seja, cada subexpressão deve ser reconsiderada à luz de tudo o que possa retornar . Como resultado, concatenar seqüências de caracteres com interpolação, ou seja, usar " My fullName is \(firstName) \(LastName)"é muito melhor do que "My firstName is" + firstName + LastNameuma vez que a interpolação não possui sobrecarga

O Swift 3 fez algumas melhorias. Para obter mais informações, leia Como mesclar várias matrizes sem diminuir a velocidade do compilador? . No entanto, o +operador ainda está sobrecarregado e é melhor usar a interpolação de strings para strings mais longos


Uso de opcionais (problema contínuo - solução disponível)

Neste projeto muito simples:

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

O tempo de compilação para as funções é o seguinte:

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

Observe o quão louca é a duração da compilação concatenatedOptionals.

Isso pode ser resolvido fazendo:

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

que compila em 88ms

A causa raiz do problema é que o compilador não identifica o ""como a String. É realmenteExpressibleByStringLiteral

O compilador verá ??e precisará percorrer todos os tipos que estão em conformidade com este protocolo , até encontrar um tipo que possa ser o padrão String. Ao usar o emptyStringcódigo codificado String, o compilador não precisa mais percorrer todos os tipos conformes deExpressibleByStringLiteral

Para saber como registrar os tempos de compilação, veja aqui ou aqui


Outras respostas semelhantes de Rob Napier no SO:

Por que a adição de strings leva tanto tempo para construir?

Como mesclar várias matrizes sem diminuir a velocidade do compilador?

Swift Array contém a função que aumenta o tempo de construção

Mel
fonte
19

Isso é bastante ridículo, não importa o que você diga! :)

insira a descrição da imagem aqui

Mas isso é passado facilmente

return "\(year) \(month) \(dayString) \(hour) \(min) \(weekDay)"
karim
fonte
2

Eu tive um problema semelhante:

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

No Xcode 9.3, a linha é assim:

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

Depois de mudar para algo assim:

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

tudo deu certo.

Provavelmente tem algo a ver com o compilador Swift tentando inferir o tipo de dados do código ao redor.

vedrano
fonte