2016-01-18 10 views
7

Stiamo cercando di capire se si tratta di un bug in Swift o di abuso di generici, optionals, inferenza di tipo e/o operatore a coalescenza nil.L'inferenza di tipo fallisce quando si utilizza l'operatore a coalescenza nulla con due optionals

La nostra struttura contiene del codice per l'analisi dei dizionari in modelli e abbiamo riscontrato un problema con proprietà facoltative con valori predefiniti.

Abbiamo un protocollo SomeProtocol e due funzioni generiche definite in un protocollo di estensione:

mapped<T>(...) -> T? 
mapped<T : SomeProtocol>(...) -> T? 

nostre strutture e classi aderire a questo protocollo e quindi analizzare le loro proprietà all'interno di una funzione init richiesto dal protocollo.

All'interno della funzione init(...) cerchiamo di impostare un valore della proprietà someNumber in questo modo:

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber 

Il dizionario, naturalmente, contiene il valore effettivo per la chiave someNumber. Tuttavia, questo fallirà sempre e il valore effettivo non verrà mai restituito dalla funzione mapped().

O commentare la seconda funzione generica o forzare il downcasting del valore sul rhs del compito risolverà questo problema, ma pensiamo che questo dovrebbe funzionare nel modo in cui è attualmente scritto.


Qui di seguito è un frammento di codice completo che dimostra la questione, insieme a due opzioni che (temporaneamente) risolvere il problema con l'etichetta OPTION 1 e OPTION 2 nel codice:

import Foundation 

// Some protocol 

protocol SomeProtocol { 
    init(dictionary: NSDictionary?) 
} 

extension SomeProtocol { 
    func mapped<T>(dictionary: NSDictionary?, key: String) -> T? { 
     guard let dictionary = dictionary else { 
      return nil 
     } 

     let source = dictionary[key] 
     switch source { 

     case is T: 
      return source as? T 

     default: 
      break 
     } 

     return nil 
    } 

    // --- 
    // OPTION 1: Commenting out this makes it work 
    // --- 

    func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? { 
     return nil 
    } 
} 

// Some struct 

struct SomeStruct { 
    var someNumber: Double? = 0.0 
} 

extension SomeStruct: SomeProtocol { 
    init(dictionary: NSDictionary?) { 
     someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber 

     // OPTION 2: Writing this makes it work 
     // someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber! 
    } 
} 

// Test code 

let test = SomeStruct(dictionary: NSDictionary(object: 1234.4567, forKey: "someNumber")) 
if test.someNumber == 1234.4567 { 
    print("success \(test.someNumber!)") 
} else { 
    print("failure \(test.someNumber)") 
} 

Si prega di notare, che questo è un esempio che manca le effettive implementazioni delle funzioni mapped, ma il risultato è identico e per motivi di questa domanda il codice dovrebbe essere sufficiente.


EDIT: avevo segnalato questo problema un po 'indietro e ora è stato contrassegnato come fisso, quindi speriamo che questo non deve accadere più a Swift 3.
https://bugs.swift.org/browse/SR-574

risposta

7

Hai dato il compilatore ha troppe opzioni e sta scegliendo quello sbagliato (almeno non quello che volevi). Il problema è che ogni T può essere banalmente elevato a T?, incluso T? (elevato a T??).

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber 

Wow. Tali tipi. Quindi opzionale. : D

Quindi come fa Swift a capire questa cosa. Beh, è ​​someNumberDouble?, quindi cerca di trasformare questo in:

Double? = Double?? ?? Double? 

funziona? Cerchiamo un generico mapped, a partire dal più specifico.

func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? { 

Per fare questo lavoro, T deve essere Double?. È Double?:SomeProtocol? No. Andare avanti.

func mapped<T>(dictionary: NSDictionary?, key: String) -> T? { 

Funziona? Sicuro! T può essere Double? Restituiamo Double?? e tutto si risolve.

Quindi, perché funziona?

someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber! 

Questo risolve a:

Double? = Optional(Double? ?? Double) 

e poi le cose funzionano il modo in cui si pensa che si suppone che.

Prestare attenzione a tanti Optionals. someNumber deve essere facoltativo? Dovrebbe una di queste cose throw? (Non sto suggerendo throw è un aggiramento generale per i problemi facoltativi, ma almeno questo problema ti dà un momento per considerare se questa è davvero una condizione di errore.)

È quasi sempre una cattiva idea digitare -parameterizza esclusivamente sul valore restituito in Swift come fa mapped. Questo tende a essere un vero caos in Swift (o in qualsiasi linguaggio generico che abbia molta inferenza di tipo, ma esplode davvero in Swift quando sono coinvolti Optionals). I parametri di tipo dovrebbero generalmente apparire negli argomenti. Vedrete il problema se si cerca qualcosa di simile:

let x = test.mapped(...) 

Non sarà in grado di dedurre il tipo di x. Questo non è un anti-modello, e talvolta la seccatura ne vale la pena (e in tutta onestà, il problema che stai risolvendo potrebbe essere uno di quei casi), ma evitalo se puoi.

Ma sono gli Optionals che ti stanno uccidendo.


EDIT: Dominik chiede una domanda molto buona sul perché questo comporta in modo diverso quando viene rimossa la versione vincolata di mapped. Non lo so. Ovviamente il motore di corrispondenza dei tipi verifica i tipi validi in un ordine leggermente diverso a seconda di quanti modi mapped è generico. Puoi vederlo aggiungendo print(T.self) a mapped<T>. Questo potrebbe essere considerato un bug nel compilatore.

+1

Questa è una spiegazione molto buona. Riesco a vedere il tuo punto e posso capire perché funziona 'someNumber!', Anche se non sono sicuro se vedo perché il codice funziona come previsto quando commenta la funzione 'mapped ...' . Potresti commentare un po 'di più su questo? –

+1

Tali optionals, molto mal di testa. : D –

Problemi correlati