This is again one of those "what section do I put it in?" questions.
I have a modified Array
in my standard library that subscripts (and conforms to Collection
) with a different Index
type called ArrayIndex
that's a simple wrapper for an Int
.
The purpose of this oddness is really to hide the normal array subscripting by Int
behaviour from casual use, so that I can add my own, different subscripting by Int
to return an optional Element
instead.
I modelled this somewhat on how Dictionary
works.
// we use a special type ArrayIndex for indexing into arrays
// it's basically just a wrapped int
// this allows our array to satisfy the requirements of Collection
// without exposing the subscript(i: Int) function
public struct ArrayIndex: Comparable, Strideable {
public let _value: Int
public init(_ value: Int) {
self._value = value
}
public func distance(to other: ArrayIndex) -> Int {
other._value - _value
}
public func advanced(by n: Int) -> ArrayIndex {
ArrayIndex(_value + n)
}
}
extension Array: RandomAccessCollection, MutableCollection {
public typealias Index = ArrayIndex
public typealias Indices = Range<ArrayIndex>
public typealias Iterator = IndexingIterator<Array>
@inlinable
public var startIndex: ArrayIndex {
return ArrayIndex(0)
}
@inlinable
public var endIndex: ArrayIndex {
@inlinable
get {
return ArrayIndex(_getCount())
}
}
...etc...
@inlinable
public subscript(index: ArrayIndex) -> Element {
get {
var index = index._value
... the usual get stuff ...
}
_modify {
var index = index._value
... the usual stuff ...
}
}
@inlinable
public subscript(bounds: Range<ArrayIndex>) -> ArraySlice<Element> ...
All of this complexity is so I can write the extension...
extension Array {
subscript(i: Int) -> Element? {
get {
guard i >= 0, i < count else {
return nil
}
return self[ArrayIndex(i)]
}
set(newValue) {
guard i >= 0, i < count, let newValue = newValue else {
return
}
self[ArrayIndex(i)] = newValue
}
}
}
The intention is now when users write code...
let i: Int = getSomeIndex()
let v = myArray[i]
...the value v is optional and is nil if the index i
is out of range of the array. Likewise attempting to set a value outside the range is a no-op.
That all works.
What doesn't work, rather oddly, is indexing using integer literals.
So...
let p = myArray[8]
...fails to compile with the error...
main.swift:64:24: error: cannot convert value of type 'Int' to expected argument type 'ArrayIndex'
let p = myArray[8]
Even more strangely, if I specify the type of the integer literal, it works as I'd expect...
let p = myArray[8 as Int] // compiles fine, p is an optional type
So it's like the type resolver is getting confused by something. And somehow we're picking the wrong overload of subscript
.
I've tested similar things in a playground and everything behaves normally, you can subscript with integer literals just fine. So it seems to be either some special compiler treatment of Array
? Or some feature in the standard library that is making this not work?
Can anyone advise a way to work through this. I'm a bit stumped about debugging the compiler's constraint checking system etc.
Ideally someone who knows this part the standard library can say "yeah, with Arrays, you have this extra bit of compiler logic or you have to put this function in a special extension, etc. etc."?
Thanks for any help anyone!