arraysswiftswift4.2

Cannot convert value of type '[UITableViewCell.Type]' to '[_.Type]' in Swift 4.2


Well, today after I update Xcode to version 10.0, I faced following error in my code.

// error: Cannot convert value of type '[UITableViewCell.Type]' to expected argument type
// '[_.Type]'  
table.registerCells(cells: [MainMenuTableViewCell.self,
                            RescueServicesTableViewCell.self])

Here is the registerCells function:

func registerCells<T> (cells : [T.Type]) where T: UITableViewCell  {
        for cell in cells  {
            let nib = UINib(nibName: String(describing: cell), bundle: nil)
            register(nib, forCellReuseIdentifier: String(describing: cell))
        }
    }

At the beginning I thought it might be swift re-versioning issue so I convert from swift 3 to swift 4 and after 2 hours spending time to fix syntaxes the error was still there, until I did the magic trick.

let cellItems = [MainMenuTableViewCell.self,
                 RescueServicesTableViewCell.self]

table.registerCells(cells:cellItems)

This solution works and error gone. Now my question is why I'm getting this error is this Xcode problem or I did something wrong?


Solution

  • This is an interesting bug (SR-8825) where the compiler appears to be unable to perform type joining (the process of inferring a common supertype for a collection of types) within a member access on an implicitly unwrapped optional (IUO) declaration (presumably in your case table is an IUO @IBOutlet).

    A minimal example would be:

    class C {}
    class D : C {}
    class E : C {}
    
    struct X {
      func foo<T>(_: [T.Type]) where T : C {}
    }
    
    var x: X!
    
    // error: Cannot convert value of type '[C.Type]' to expected argument type '[_.Type]'
    x.foo([D.self, E.self]) 
    

    Making x either non-optional, or a strong optional (i.e X?) while performing either optional chaining (i.e x?.foo) or force unwrapping (i.e x!.foo) to perform the member access allows the code to compile.


    There are a few workarounds you can use, first of which is to explicitly specify the array type, saving the compiler from having to infer the type join:

    x.foo([D.self, E.self] as [C.Type])
    

    In your case, this translates to:

    table.registerCells(cells: 
      [MainMenuTableViewCell.self, RescueServicesTableViewCell.self] as [UITableViewCell.Type]
    )
    

    Second workaround is to use a non-optional base. In your case, you can force unwrap the IUO into a local variable before performing the member access:

    // this is just explicitly doing what the compiler would have otherwise done implicitly.
    let table = self.table!
    table.registerCells(cells: [MainMenuTableViewCell.self, RescueServicesTableViewCell.self])
    

    Third workaround is, as you've already discovered, to separate out the array into its own expression – which allows the compiler to do the type joining on its own:

    let cellItems = [MainMenuTableViewCell.self, RescueServicesTableViewCell.self]
    table.registerCells(cells: cellItems)
    

    Though the solution I would go with in your case is to make registerCells(cells:) non-generic, as it doesn't appear that you're using the generic placeholder T for anything useful:

    extension UITableView {
      func registerCells(_ cells: [UITableViewCell.Type]) {
        for cell in cells  {
          let nib = UINib(nibName: String(describing: cell), bundle: nil)
          register(nib, forCellReuseIdentifier: String(describing: cell))
        }
      }
    }
    

    Which you can now just call like so:

    table.registerCells([MainMenuTableViewCell.self, RescueServicesTableViewCell.self])