// Type-Erasure
// - seealso: [AnyIterator](https://github.com/apple/swift/blob/2fe4254cb712fa101a220f95b6ade8f99f43dc74/stdlib/public/core/ExistentialCollection.swift.gyb#L45)

// MARK: remove `Equatable` if not needed
public protocol Protocol: Equatable {
    // MARK: `Protocol` requirements
    associatedtype AssociatedType
    func methodOfProtocol() -> Self.AssociatedType
}

public struct AnyProtocol<AssociatedType> {
    private let _box: _AnyProtocolBoxBase<AssociatedType>
    public init<T: Protocol>(_ base: T) where T.AssociatedType == AssociatedType {
        let box = _ProtocolBox(base)
        self._box = box
        // MARK: remove if `Protocol` does not adopt `Equatable`
        self._isEqual = { box._base == ($0._box as? _ProtocolBox<T>)?._base }
    }
    // MARK: remove if `Protocol` does not adopt `Equatable`
    private let _isEqual: (AnyProtocol) -> Bool
    public static func == (lhs: AnyProtocol<AssociatedType>, rhs: AnyProtocol<AssociatedType>) -> Bool {
        return lhs._isEqual(rhs) || rhs._isEqual(lhs)
    }
}
extension AnyProtocol: Protocol {
    // MARK: `Protocol` conformance
    public func methodOfProtocol() -> AssociatedType {
        return _box.methodOfProtocol()
    }
}

fileprivate class _AnyProtocolBoxBase<AssociatedType>: Protocol {
    @inline(never) private static func _abstract(file: StaticString = #file, line: UInt = #line) -> Never { fatalError("Method must be overridden!", file: file, line: line) }
    // MARK: remove if `Protocol` does not adopt `Equatable`
    @inlinable static func == (lhs: _AnyProtocolBoxBase<AssociatedType>, rhs: _AnyProtocolBoxBase<AssociatedType>) -> Bool { _abstract() }
    // MARK: `Protocol` conformance
    @inlinable internal func methodOfProtocol() -> AssociatedType { Self._abstract() }
}
fileprivate final class _ProtocolBox<Base: Protocol>: _AnyProtocolBoxBase<Base.AssociatedType> {
    internal var _base: Base
    internal init(_ base: Base) { self._base = base }
    // MARK: `Protocol` conformance
    override func methodOfProtocol() -> Base.AssociatedType { return _base.methodOfProtocol() }
}