iosswiftcastingxcode16swift6

get cast error for Generic type in xcode 16 for swift 6


I got a build error for below code after using xcode 16

Cast from 'ObservableCollection<E>.SectionsChange' (aka 'CollectionChange<Int, ObservableArray<E>>') to unrelated type 'ObservableCollection<Int>.SectionsChange' (aka 'CollectionChange<Int, ObservableArray<Int>>') always fails

for the section protocol method I implemented, first cast is still working

class ObservableCollectionDelegateMock: ObservableCollectionDelegate {

    var collectionDidRowBatchUpdateAtExpect = LIExpect()
    var collectionDidSectionBatchUpdateAtExpect = LIExpect()
    var rowBatchUpdates: ObservableCollection<Int>.RowsChange?
    var sectionBatchUpdates: ObservableCollection<Int>.SectionsChange?

    func collection<E>(_ collection: ObservableCollection<E>, rowsDidChange change: ObservableCollection<E>.RowsChange) {
        collectionDidRowBatchUpdateAtExpect.called(actualParams: nil)
        rowBatchUpdates = change as? ObservableCollection<Int>.RowsChange
    }

    func collection<E>(_ collection: ObservableCollection<E>, sectionsDidChange change: ObservableCollection<E>.SectionsChange) {
        collectionDidSectionBatchUpdateAtExpect.called(actualParams: nil)
        sectionBatchUpdates = change as? ObservableCollection<Int>.SectionsChange
    }
}

Here is the definition of the ObservableCollectionDelegate which is the protocol delcare these 2 methods, that sublass to override

public protocol ObservableCollectionDelegate: AnyObject {

    func collection<E>(_ collection: ObservableCollection<E>, rowsDidChange change: ObservableCollection<E>.RowsChange)
    func collection<E>(_ collection: ObservableCollection<E>, sectionsDidChange change: ObservableCollection<E>.SectionsChange)
}

here is the definition of the these 2 alias.


 class ObservableCollection<Element> {

    // MARK: - Nested Types

    public typealias RowsChange = CollectionChange<IndexPath, Element>
    public typealias SectionsChange = CollectionChange<Int, ObservableArray<Element>>

     .......
 }

and this is the define of ObservableArray

public class ObservableArray<T> {

    // MARK: - Public Properties

    public var count: Int {
        return elements.count
    }

    public var isEmpty: Bool {
        return elements.isEmpty
    }
    public var hasElements: Bool {
        return !elements.isEmpty
    }

    /// Returns the snapshot of current state as a normal array. (Essentially returning the copy of itself).
    public var snapshot: [T] {
        return elements
    }

    // MARK: - Internal Properties

    internal(set) public var elements: [T]

    // MARK: - Lifecycle

    public init<S>(elements: S) where S: Sequence, S.Element == T {
        self.elements = [T](elements)
    }
    
    ......

From what I am understanding, seems for xcode 16, then can't case the generic type by compiler if there are nested generic types.

which is our case is

ObservableCollection.SectionsChange' (aka 'CollectionChange<Int, ObservableArray>')

wonder if there is anyway to go around this? or this is what swift 6 forbid to do?

thanks

====================================================

To make the code more easy to reproduce, I create a demo to have the same issue

import UIKit
import Foundation

public struct CollectionChange<Index: Hashable, Value> {}
public class ObservableArray<T> {


    // MARK: - Internal Properties

    internal(set) public var elements: [T]

    // MARK: - Lifecycle

    public init<S>(elements: S) where S: Sequence, S.Element == T {
        self.elements = [T](elements)
    }

    public convenience init() {
        self.init(elements: [])
    }
}

open class ObservableCollection<Element> {

    public typealias RowsChange = CollectionChange<IndexPath, Element>
    public typealias SectionsChange = CollectionChange<Int, ObservableArray<Element>>
}

public protocol ObservableCollectionDelegate: AnyObject {

    func collection<E>(_ collection: ObservableCollection<E>, rowsDidChange change: ObservableCollection<E>.RowsChange)
    func collection<E>(_ collection: ObservableCollection<E>, sectionsDidChange change: ObservableCollection<E>.SectionsChange)
}


class ObservableCollectionDelegateMock: ObservableCollectionDelegate {

    var rowBatchUpdates: ObservableCollection<Int>.RowsChange?
    var sectionBatchUpdates: ObservableCollection<Int>.SectionsChange?

    func collection<E>(_ collection: ObservableCollection<E>, rowsDidChange change: ObservableCollection<E>.RowsChange) {
        rowBatchUpdates = change as? ObservableCollection<Int>.RowsChange
    }

    func collection<E>(_ collection: ObservableCollection<E>, sectionsDidChange change: ObservableCollection<E>.SectionsChange) {
        sectionBatchUpdates = change as? ObservableCollection<Int>.SectionsChange
    }
}

you can try in your local, maybe just put in playground. I tried for xcode 15, it works fine with Swift 5 and for xcode 16 with swift 6, it has the error

Cast from 'ObservableCollection<E>.SectionsChange' (aka 'CollectionChange<Int, ObservableArray<E>>') to unrelated type 'ObservableCollection<Int>.SectionsChange' (aka 'CollectionChange<Int, ObservableArray<Int>>') always fails

Solution

  • A different approach, since you are expecting the generic type to be an Int while casting, all the generic type E should be an Int or same type. So you can change the ObservableCollectionDelegateMock to have a generic element

    class ObservableCollectionDelegateMock<Element>: ObservableCollectionDelegate {
        var rowBatchUpdates: ObservableCollection<Element>.RowsChange?
        var sectionBatchUpdates: ObservableCollection<Element>.SectionsChange?
    
        func collection(_ collection: ObservableCollection<Element>, rowsDidChange change: ObservableCollection<Element>.RowsChange) {
            rowBatchUpdates = change
        }
    
        func collection(_ collection: ObservableCollection<Element>, sectionsDidChange change: ObservableCollection<Element>.SectionsChange) {
            sectionBatchUpdates = change
        }
    }
    

    Associated type for protocol

    public protocol ObservableCollectionDelegate: AnyObject {
        associatedtype Element
        func collection(_ collection: ObservableCollection<Element>, rowsDidChange change: ObservableCollection<Element>.RowsChange)
        func collection(_ collection: ObservableCollection<Element>, sectionsDidChange change: ObservableCollection<Element>.SectionsChange)
    }
    

    if you use like below, then the collection function element might not be same as associatedType

    associatedtype Element
    func collection<Element>(_ collection: ObservableCollection<Element>, rowsDidChange change: ObservableCollection<Element>.RowsChange)