The problem with this is that then I can't use the AnyList as a type eraser because of PAT restrictions, which is the reason why I wanted the generic method rather than an associated type
That's kind of what type erasure means, though: throwing away information about the type. Either you care about the list only being subscripted by the correct identifier type, or you don't.
It's not my preferred solution. If you don't want to write a manual type eraser, I'd rather do something like this:
public protocol AnyList
{
subscript(itemName: String) -> AnyItem? { get }
}
extension AnyList
{
// if you still want this
subscript<identifierT : SomeStringBasedType>(assumingAppropriate Identifier: identifierT) -> AnyItem? {
return self[Identifier.rawValue]
}
}
That way you're acknowledging that you're not using the entry point specific to the conforming type, which still allowing an unlabeled subscript on the conforming type that does do the static check. I admit that the naive implementation here is probably slower, though, since it's going to have to go from raw value back to identifier and that's going to do a string comparison to see if it's valid.
Slight problem there - the dictionary holding the items is keyed by the enum behind the generic parameter type. All your solution does in my case is to call the string subscript, which then builds the enum value from its raw string value before accessing it in the dictionary by the enum type
That's true, which is why I said it was potentially less efficient. (I should amend that to "or less safe" if the init(rawValue:) that gets used doesn't do any validation.) The important thing is the labeled subscript, though: you've called out that this entry point is going to do stringy things, as opposed to the "good" entry point that has a concrete type.