Surprise error: type 'T' constrained to non-protocol, non-class type 'Base'

My attempts to make a generic "type registry" struct The error "type
'T' constrained to non-protocol, non-class type 'Base'" has so far
foiled my attempts to make a generic "type registry" struct and I do not
understand why.

In my project I use an ExampleTypeRegistry to map subtypes of class
Example to UUID and back. In a serialized data structure that
contains Examples, the UUIDs stand for the subtypes.

The code looks like this,

import Foundation

public class Example {
}

public struct ExampleTypeRegistry {
        public typealias Base = Example
        var uuidToType: [UUID : Base.Type] = [:]
        var oidToUUID: [ObjectIdentifier : UUID] = [:]
        public init() {
        }
        public mutating func insert<T : Base>(_ type: T.Type) -> UUID {
                let oid = ObjectIdentifier(type)
                if let found = oidToUUID[oid] {
                        return found
                }
                let uuid = UUID()
                oidToUUID[oid] = uuid
                uuidToType[uuid] = type
                return uuid
        }
        public func contains<T : Base>(_ type: T.Type) -> Bool {
                return oidToUUID[ObjectIdentifier(type)] != nil
        }
        public func contains(_ uuid: UUID) -> Bool {
                return uuidToType[uuid] != nil
        }
        public func lookup<T : Base>(_ type: T.Type) -> UUID? {
                return oidToUUID[ObjectIdentifier(type)]
        }
        public func lookup(_ uuid: UUID) -> Base.Type? {
                return uuidToType[uuid]
        }
        public mutating func remove<T : Base>(_ type: T.Type) -> UUID? {
                let oid = ObjectIdentifier(type)
                guard let uuid = oidToUUID[oid] else {
                        return nil
                }
                defer {
                        oidToUUID[oid] = nil
                        uuidToType[uuid] = nil
                }
                return uuid
        }
        public mutating func remove(_ uuid: UUID) -> Base.Type? {
                guard let type = uuidToType[uuid] else {
                        return nil
                }
                let oid = ObjectIdentifier(type)
                defer {
                        oidToUUID[oid] = nil
                        uuidToType[uuid] = nil
                }
                return type
        }
}

I would like to reuse the same type registry code with other base
classes. I wrote TypeRegistry to work with any base class:

public struct TypeRegistry<Base : AnyObject> {
        var uuidToType: [UUID : Base.Type] = [:]
        var oidToUUID: [ObjectIdentifier : UUID] = [:]
        public init() {
        }
        public mutating func insert<T : Base>(_ type: T.Type) -> UUID {
                let oid = ObjectIdentifier(type)
                if let found = oidToUUID[oid] {
                        return found
                }
                let uuid = UUID()
                oidToUUID[oid] = uuid
                uuidToType[uuid] = type
                return uuid
        }
        public func contains<T : Base>(_ type: T.Type) -> Bool {
                return oidToUUID[ObjectIdentifier(type)] != nil
        }
        public func contains(_ uuid: UUID) -> Bool {
                return uuidToType[uuid] != nil
        }
        public func lookup<T : Base>(_ type: T.Type) -> UUID? {
                return oidToUUID[ObjectIdentifier(type)]
        }
        public func lookup(_ uuid: UUID) -> Base.Type? {
                return uuidToType[uuid]
        }
        public mutating func remove<T : Base>(_ type: T.Type) -> UUID? {
                let oid = ObjectIdentifier(type)
                guard let uuid = oidToUUID[oid] else {
                        return nil
                }
                defer {
                        oidToUUID[oid] = nil
                        uuidToType[uuid] = nil
                }
                return uuid
        }
        public mutating func remove(_ uuid: UUID) -> Base.Type? {
                guard let type = uuidToType[uuid] else {
                        return nil
                }
                let oid = ObjectIdentifier(type)
                defer {
                        oidToUUID[oid] = nil
                        uuidToType[uuid] = nil
                }
                return type
        }
}

The compiler prints an error for several functions in TypeRegistry,

error: type 'T' constrained to non-protocol, non-class type 'Base'
        public mutating func insert<T : Base>(_ type: T.Type) -> UUID {

The error is a surprise to me. Doesn't the declaration struct TypeRegistry<Base : AnyObject> constrain Base to a class type?

Is there some other way to make TypeRegistry work with arbitrary base
classes?

David

1 Like

AnyObject includes final classes and actors, which can't be inherited from. There is, to my knowledge, no way to constrain a type to be only an inheritable class or protocol.

That isn’t really the reason. Even a final class X can be a bound in a generic, though it’s silly to do so, because X itself satisfies the constraint.

Today’s Swift just doesn’t support using generic parameters as bounds for other generic parameters. I don’t remember if there’s a specific problematic pattern that prevents this from being added to the language, or if it’s just added complexity that hasn’t been implemented. Certainly it seems reasonable for classes, but it’s a bit trickier if you want it to support protocols as well, because protocols aren’t types (any MyProtocol is the type).

1 Like