Literals
Swift has a couple of literals that can be used to initialize different types without explicitly calling their initializer. For example,
let x = 5 // x is now an Int
automatically infers x to be of type Int. But if we specify the type of the variable to be something else
let x: UInt = 5 // x is now a UInt
the compiler automatically understands that 5 is now an unsigned integer and so the respective UInt initializer has to be called instead of the Int initializer.
The ExpressibleByIntegerLiteral protocol
This is possible for all types that implement the protocol ExpressibleByIntegerLiteral. Now I add this protocol conformance to my own type:
/// Represents a range of characters in a `String`.
//
/// The only purpose of this type is to provide a wrapper around `CountableClosedRange`
/// that can be initialized both with a range and an **integer literal**.
struct TextRange: ExpressibleByIntegerLiteral {
let range: CountableClosedRange<Int>
var lowerBound: Int {
return range.lowerBound
}
var upperBound: Int {
return range.upperBound
}
init(_ range: CountableClosedRange<Int>) {
self.range = range
}
// This initializer adds the protocol conformance:
init(integerLiteral value: Int) {
self.range = value...value
}
}
With this definition I can write:
let x: TextRange = 5 // x is now a `TextRange`
However,
let x = 5 // x is now an `Int`
still infers x to be an Int. So far, so good.
Overloading the ... operator
Now I also want to be able to create a TextRange with the closed range operator ... like this:
let y: TextRange = 5...10
so I overload the ... operator in an extension for TextRanges:
extension TextRange {
/// Given two text ranges, this operator creates a new text range that incorporates
/// the integers within the operand ranges as well as all integers between the operand ranges.
///
/// - Parameters:
/// - lowerRange: A text range whose lower bound is smaller than the lower bound of `upperRange`.
/// - upperRange: A text range whose lower bound is greater than the lower bound of `lowerRange`.
/// - Returns: A `TextRange` from the first operand's lower bound to the second operand's upper bound.
static func ...(lowerRange: TextRange, upperRange: TextRange) -> TextRange {
let lowerBound = lowerRange.lowerBound
let upperBound = max(lowerRange.upperBound, upperRange.upperBound)
assert(lowerBound <= upperBound, "Cannot create a TextRange because lowerBound > upperBound.")
return TextRange(lowerBound...upperBound)
}
}
And this is where I get into trouble. (What a surprise: The trouble begins with operator overloading! )
Trouble
For a variable of TextRange type this operator works as expected but when I omit the type annotation the variable is still instantiated as a TextRange:
let y = 5...10 // y is now a `TextRange`
That shouldn't be because:
If no type is specified for a variable,
the default inferred type of an integer literal is the Swift standard library type Int.
(see Integer Literals in The Swift Programming Language)
Thus, I would expect
5and10to be inferred toInt, notTextRange.The overload of the
...operator is only implemented for two operands ofTextRangetype. Thus,x...yshould still use the original Swift implementation of...ifxandyare bothInts.
So why does the compiler "decide" that it should now use the overloaded operator by default for ranges between two integer literals and interpret x and y in the pattern x...y as TextRanges?