Thanks. I will think on these further, but here is my first stab. (Anyone) Feel free to point out any mistakes or things I am missing:
My thought is to have a special protocol inheritance (similar to
CaseIterable). When a protocol or class inherits from this special protocol, the compiler generates a static function on the type returning all conformers/subclasses as an array. If a static function is impossible (I know we can't add them manually to protocols right now... but the compiler might have a special way to do this), then we could use a free function as suggested above.
Looking at how Sourcery handles things like this as inspiration, we should probably return some sort of reflection object/struct rather than the types directly. This object should have the type as one of it's properties, but also a way to pick apart any nested generics, etc... that it has. But if that is too difficult to implement, we could just return an array of Types, and then have the function take some options about what it returns (e.g. Do we want all the specializations of generic types returned or just the base type?).
- What's the name of the attribute?
I've been using ConformerIterable, which I kind of hate. Open to suggestions here...
- How do you enumerate the conformances of a protocol in code?
See above. A static func
allConforming(options:) on the actual protocol if possible, or a free function
typesConforming(to: options:) if not.
- Are there ordering guarantees in that enumeration?
It should be stable, but other than that no specific ordering. If you want an ordering, you can sort the resulting list in some way (most likely using a static function defined in the protocol).
- What happens to generic types that conform to the protocol?
If we are returning a reflection object, we could potentially group the specializations into a single object, and they can then be iterated over if desired, or just seen as the single base type as desired. If we are returning types directly, then we should have an option to include all conforming specializations or just the base generic type. By default, I think all conforming specializations should be in the list.
(As an optimization, the list of specializations for a generic object could be a separate function call on the reflection object. That way, the information is only given if requested.)
• How are types that conditionally conform handled?
Only specializations that conform should be included
- Does the enumeration include subclasses of classes that conform to the protocol?
See above about generics, but in general, I would say yes, since they all conform to the protocol. Again they could be grouped together either by option or as a returned reflection object. I think the default should be not to group them though.
- Does the enumeration include non-public types?
This is an interesting question. I think it should include all visible types from where the function is called, at least by default. If this is part of the information in the reflection object, then it would be easy to filter by access level.
- Is there a way to opt out of the enumeration?
I don't think so. Let's say we don't supply a built-in way to opt out, but someone really needs it. They could just add a static var/func to the protocol which lets them filter the list based on the result.
- On systems that allow code to be dynamically loaded, should there be some way to get notified of the existence of future types that conform to the protocol?
Ideally, I think the function should give you all the types that are currently loaded. If new code has been dynamically loaded, you can call the function again, and it would include the new types as well. If we choose to notify, that could be part of an add-on proposal later.
Thoughts? Suggestions? Problems?