The other day I was debugging a crash in a UI test for an open pull request at work. The bug turned out to be extremely subtle and difficult to notice. I spent way too much time staring at the changes, trying to understand what was wrong. Let’s see if you can spot the error.
Here’s the problematic line:
func toDictionary() -> [String: Any] {
var dict: [String: Any] = [:]
// code setting other keys and values...
dict[JSONKeys.dateClosed] = self.dateClosed?.toMongoDate
return dict
}
The details here don’t matter. This is some legacy JSON serialization code, predating the introduction of Codable
(SE-0166 and SE-0167). This function serializes the object to a JSON dictionary, self.dateClosed
is a Date
type, and JSONKeys.dateClosed
is a String
constant.
But what’s the bug? Let’s look at the definition of toMongoDate
(also some legacy code).
extension Date {
func toMongoDate() -> [String: Any] {
// return date in expected mongo date format
}
}
Seems fine, right? Everything compiles. There’s no issue with putting a [String: Any]
dictionary as the value in another [String: Any]
dictionary. Any
can be any type. But that’s what the problem turned out to be.
Let’s look at that line again: self.dateClosed?.toMongoDate
. This returns the function toMongoDate
. That is, the reference type () -> [String: Any]
— not the result of calling the function. I forgot the parentheses ()
. That line should read self.dateClosed?.toMongoDate()
. However, this worked and the compiler did not complain because functions are first class types, and setting a function as the value of a [String: Any]
dictionary is valid. This is clearly an argument for adopting Codable
, which would have prevented this mistake.
What’s worse: this exact error has happened in our code base in at least one other scenario. It’s so easy to overlook.
Much faceplam.