Protocol conformance stopped working in Xcode 13

Hey all, I had this problem show up when using Xcode 13 with the code below. Basically, the code worked fine with Xcode 12.5, but in Xcode 13 the structs and classes no longer conform to the protocols for some reason. I get the following error message (many times): Unavailable subscript 'subscript(_:)' was used to satisfy a requirement of protocol 'RangeReplaceableCollection'.

I'm not aware of any intentional source-breaking changes made to Swift 5.5, is this maybe due to some change in the implementation of the conformance-checking algorithm? In any case, I can't figure out what I need to do to make this compile again, so any help would be appreciated.

Here's a reduced version of the problematic file. I didn't try to reduce it more because I wasn't sure which methods I could remove without screwing up the protocol conformances. The original file can be found here.

public struct _ListSlice<Element>: Collection,
								   BidirectionalCollection,
								   RandomAccessCollection,
								   MutableCollection,
								   RangeReplaceableCollection
{
	public typealias Index = Int
	public typealias SubSequence = _ListSlice<Element>

	let list: List<Element>
	let range: Range<Int>

	public var startIndex: Int {
		return range.startIndex
	}

	public var endIndex: Int {
		return range.endIndex
	}

	public subscript(position: Int) -> Element {
		get {
			return list[position]
		}

		// MutableCollection
		set {
			list._setElement(newValue, atIndex: position)
		}
	}

	public func index(after i: Int) -> Int {
		return list.index(after: i)
	}

	// BidirectionalCollection
	public func index(before i: Int) -> Int {
		return list.index(before: i)
	}

	// RangeReplaceableCollection
	public init() {
		self.list = []
		self.range = 0..<0
	}
}

public class List<Element>: CustomStringConvertible,
							CustomDebugStringConvertible,
							ExpressibleByArrayLiteral,
							Sequence,
							Collection,
							BidirectionalCollection,
							RandomAccessCollection
{
	public typealias Buffer = [Element]
	public typealias ArrayLiteralElement = Element
	public typealias Index = Int
	public typealias SubSequence = _ListSlice<Element>

	public var array: Buffer

	public init(_ array: Buffer) {
		self.array = array
	}

	// Custom (Debug) String Convertible
	public var description: String {
		return array.description
	}

	public var debugDescription: String {
		return array.debugDescription
	}

	// Expressible By Array Literal
	public required init(arrayLiteral elements: Element...) {
		self.array = elements
	}

	// Sequence
	public func makeIterator() -> IndexingIterator<List<Element>> {
		return IndexingIterator(_elements: self)
	}

	// Collection
	public var startIndex: Int {
		return array.startIndex
	}

	public var endIndex: Int {
		return array.endIndex
	}

	public subscript(position: Int) -> Element {
		return array[position]
	}

	public func index(after i: Int) -> Int {
		return array.index(after: i)
	}

	// BidirectionalCollection
	public func index(before i: Int) -> Int {
		return array.index(before: i)
	}

	// Used for _ListSlice to conform to MutableCollection
	fileprivate func _setElement(_ element: Element, atIndex index: Int) {
		array[index] = element
	}

	// Other methods
	public init<S>(_ sequence: S) where Element == S.Element, S: Sequence {
		self.array = Array(sequence)
	}

	public init() {
		self.array = []
	}
}

public class MutableList<Element>: List<Element>,
								   MutableCollection,
								   RangeReplaceableCollection
{
	// MutableCollection
	public override subscript(position: Int) -> Element {
		get {
			return array[position]
		}
		set {
			array[position] = newValue
		}
	}

	// RangeReplaceableCollection
	override public required init() {
		super.init([])
	}

	public required init(arrayLiteral elements: Element...) {
		super.init(elements)
	}

	// Other methods
	public func append(_ newElement: Element) {
		array.append(newElement)
	}

	public func append<S>(contentsOf newElements: S) where S: Sequence, Element == S.Element {
		self.array.append(contentsOf: newElements)
	}

	public func insert(_ newElement: Element, at i: Index) {
		array.insert(newElement, at: i)
	}

	@discardableResult
	public func removeFirst() -> Element {
		return array.removeFirst()
	}

	@discardableResult
	public func removeLast() -> Element {
		return array.removeLast()
	}

	public func removeAll(keepingCapacity keepCapacity: Bool = false) {
		array.removeAll(keepingCapacity: keepCapacity)
	}

	@discardableResult
	public func remove(at index: Int) -> Element {
		return array.remove(at: index)
	}

	public func reverse() {
		self.array = self.array.reversed()
	}
}

1 Like

All right, so there's a few things I can help with here. I could just tell you straight-up what you need to do to fix this error, but I'm going to try to do you one better by teaching you the skills you'll need to understand and address the problem yourself going forward, step by step.


First, how do you understand the error you've been given? Notice that there's a series of error messages that look similar and complain about the same missing requirement, but that not all of them refer to RangeReplaceableCollection.

Yes, Swift could certainly do much better in this respect—you can file a bug if you'd like—but for your part you can try to figure out which protocol is really at issue here by searching for the least refined in the hierarchy.

In this case, you'll notice that there's also an error message saying that you didn't satisfy a requirement of Collection. That's helpful: if you had nothing else to go on, you could then go through the requirements of Collection one-by-one and find your answer that way.


Second, what's the protocol requirement that's missing? When there's a lot of errors, sometimes you don't notice the notes that go with the errors, perhaps because the IDE makes them less noticeable. You can reduce the file further, as you allude to—but how?

Well, a common style that people adopt for Swift conformances is to add them one-by-one in extensions. This isn't just an arbitrary choice. When you adopt conformances in that way, you can ensure that your protocol fully conforms to the requirements of the least refined protocol (here, Collection) before you build up conformance to the next protocol in the hierarchy. If you've missed a step, you'll notice right then and there, instead of having to sort through multiple problematic protocols.

Here, you can go back and do something like that by taking the first type that's causing problems and conforming it to the least refined protocol that's causing problems. That is, _ListSlice conforming to Collection. Of course, if we delete all the other types, we'll need to substitute something for your use of List<Element> in _ListSlice, but fortunately just using Array will do. So here's my reduced version of your reduced version:

public struct _ListSlice<Element>: Collection {
  public typealias Index = Int
  public typealias SubSequence = _ListSlice<Element>

  let list: [Element]
  let range: Range<Int>

  public var startIndex: Int {
    return range.startIndex
  }

  public var endIndex: Int {
    return range.endIndex
  }

  public subscript(position: Int) -> Element {
    get { return list[position] }
  }

  public func index(after i: Int) -> Int {
    return list.index(after: i)
  }

  public init() {
    self.list = []
    self.range = 0..<0
  }
}

You'll see that it still presents you with the same error regarding your protocol conformance. So we can figure out the rest using this reduced version. Inspect the diagnostics you now get:

error: ConformancePlayground.playground:1:15: error: type '_ListSlice<Element>' does not conform to protocol 'Collection'
public struct _ListSlice<Element>: Collection {
              ^

error: ConformancePlayground.playground:1:15: error: unavailable subscript 'subscript(_:)' was used to satisfy a requirement of protocol 'Collection'
public struct _ListSlice<Element>: Collection {
              ^

Swift.Collection:3:12: note: 'subscript(_:)' declared here
    public subscript(bounds: Range<Self.Index>) -> Self.SubSequence { get }
           ^

Swift.Collection:12:5: note: requirement 'subscript(_:)' declared here
    subscript(bounds: Range<Self.Index>) -> Self.SubSequence { get }
    ^

Aha! Now we can clearly see not only the protocol that's problematic, but the specific requirement that's missing. Your type was relying on a default implementation of subscript(bounds: Range<Self.Index) -> Self.SubSequence, but that implementation has been marked as unavailable.

You can now stop here and just implement this requirement. But you seem to be the curious type—so, how do we figure out why this requirement isn't fulfilled already, and why was it fulfilled before? Read on...


There can't be a more authoritative source for the requirement than the source itself! Indeed, the diagnostics even reference the standard library source code. Fortunately, the standard library is part of an open source project, and we can open up the referenced source file itself—in this case, Collection.swift.

Now, what are we looking for here?

Well, it's unlikely we'll find much that we don't already know by examining the requirement declaration itself, but you can certainly find it in that file.

But we already know that there's some default requirement that's declared unavailable which you've unintentionally relied on—so let's look for that to see if there's some explanation of the unavailability. While we're here, let's also see if there's some other default implementation that's available (since after all, many people conform to Collection without implementing their own version of that subscript).

Here's what we find:

First, there is this default implementation for conforming types where SubSequence == Slice<Self>. So that explains why most people don't need to write their own implementation, but why you do: you've declared a different SubSequence type (namely, Self). That solves that mystery.

Next, there is the following default implementation for all other conforming types, and it contains a note explaining exactly why it's there and why it's unavailable:

extension Collection {
  // This unavailable default implementation of `subscript(bounds: Range<_>)`
  // prevents incomplete Collection implementations from satisfying the
  // protocol through the use of the generic convenience implementation
  // `subscript<R: RangeExpression>(r: R)`. If that were the case, at
  // runtime the generic implementation would call itself
  // in an infinite recursion because of the absence of a better option.
  @available(*, unavailable)
  @_alwaysEmitIntoClient
  public subscript(bounds: Range<Index>) -> SubSequence { fatalError() }
}

And if you use the "blame" function (either on GitHub or in your local copy of the repository), you can see that this code was added 4 months ago. Aha. Now we have the complete picture:

  • Your implementation used to compile just fine only because Swift allowed you to use a default implementation of a Collection requirement that calls itself, infinitely recursing—if users of your type actually tried to call that method, bad things would happen at runtime.
  • In the last few months, we've figured out a way to teach the Swift compiler to call out this problem at compile time. And thanks to that, you've now found a latent bug in your code, and you're now all set to fix it!

Hope this helps.

56 Likes

Xiaodi this was perhaps the best way anyone could have replied to a question. Informative and helping others to learn and to troubleshoot better. Thank you!

4 Likes

Thanks!

Thinking aloud about how Swift could make this easier:

  1. The duplicative diagnostics all along the protocol hierarchy could be suppressed—this is one error, not eight, and even the two errors and two notes after reducing this case is rather noisy.

  2. The text “unavailable subscript subscript(_:)…” is both verbose and lacking in information. The unavailability is of a default implementation that the reader is unlikely to know about, which is not explained. The name is unhelpful other than in giving the arity of the parameter list. The later text “…was used to satisfy…” speaks purely from the compiler’s perspective instead of the user’s mental model—the user didn’t volitionally use anything to satisfy anything. This could be improved, therefore, with something like: “default implementation of 'Collection' requirement 'subscript(_:)' of type '(Range<Self.Index>) -> Self.SubSequence' is marked unavailable; did you mean to implement your own?”.

  3. There are usually helpful fix-its when a requirement hasn’t been implemented, but there isn’t here presumably because a default implement exists but is unavailable. In such a circumstance, a fix-it should be provided too.

If all three improvements could be implemented, then I think the user experience would be greatly improved. I’ll leave it as an exercise to the reader to file a bug, another skill that’s pretty important in that it can help to make one’s problems solved definitively and for everyone.

17 Likes

Thanks so much @xwu, that was an excellent explanation that really helped me understand the problem and solve it. I just published the bugfix :smile:

Also, it's good to know the compiler helped me have one less bug in my code, even if the error messages were cryptic. I'll be sure to open the bug reports as soon as I find the time.

2 Likes

For future reference, I opened the following bugs:

@xwu, for the second one I kept your suggestion of a new error message for the bug report, I hope you don't mind:

6 Likes