This was a bit of a doozy and took me the better half of a day to find a semi decent fix for it.
Here's the unsuspecting use case and why it caused problems (try it out on Typescript playground)
- SingleConfig and MultiConfig are different types. One is a single object value, the other is an array of that.
- conditionalReturn() returns either one of those depending on the input.
- onlyWorksWithSingleConfig() as the name states will only work with an instance of SingleConfig
- Typescript doesn't seem to detect an issue when using onlyWorksWithSingleConfig() with the result from conditionalReturn()
Why? Because Javascript is a hot mess when Arrays are considered to be objects. It is possible that Typescript allows that behaviour because Javascript arrays are keyed by strings (not numbers) so it will fit the type interface specified by SingleConfig.
For instance, look at this output.
So now that we have a possible reason why this is allowed in Typescript, how can we prevent it?
Attempt 1 - use type guards
As documented here, I tried using them to enforce some sort of sanity. It did not work.
Attempt 2 - disallow [index: number] in SingleConfig
I found this on StackOverflow and thought it was an elegant solution to the problem, however quickly realised it broke a valid use case where this syntax would be marked as incorrect.
Attempt 3 - Disallow "random array prototype member" ✔
A smart fellow at work suggested being a bit more specific with the "never" clause.
Instead of trying to capture all the cases, why not filter out a specific case, like "length"? (Just to be safe, I used something a bit more weird like "lastIndexOf" to avoid potential issues)
And it worked! Just like that, Typescript is now correctly detecting the two separate types.
It even fixed up the type guard function isSingleType() !