Why does the code below silently assumes fooValues is any. And is it possible to avoid this implicit any. I tried the "noImplicitAny" tsconfig option, but that doesn't seem to help.
type Record1 = Record<string, { a: 1 }>
type Record2 = Record<string, { b: 1 }>
const foo: Record1 | Record2 = {}
const bar: Record1 = {}
// Why is `fooValues` any[]???
const fooValues = Object.values(foo)
// This one is OK.
const barValues = Object.values(bar)
This isn't an issue about implicit
any. Theanyyou're seeing here is actually quite explicit, and has to do with the standard library's call signatures forObject.values():That's an overloaded function with two call signatures. The first is a generic function that requires a parameter with an index-signature and returns a strongly-typed array; the second is a non-generic function that takes any non-nullish parameter and returns
any[]. When you callObject.values(), the compiler has to choose which call signature to apply by trying each one in turn. Apparently when you callObject.values(bar)it chooses the first signature, but when you callObject.values(foo)it chooses the second.Let's investigate what's happening by breaking each call signature into its own separate function:
Here,
objectValues1only has the generic signature andobjectValues2has only the non-generic one. Let's look atbar:Great,
objectValues1()works;Tis inferred as{a: 1}and you get a strongly-typed output. Now let's tryfoo:Oops, that didn't work and we had to try
objectValues2()which producesany. But why didn't it work?If you look, the compiler just infers
Tas{a: 1}instead of as the union{a: 1} | {b: 1}. Generic type parameter inference is an art, not a science. Well, for humans, anyway. For the compiler, type parameter inference operates by some heuristics that the language designers came up with. These heuristics will often reject a call that requires synthesizing a union type not present in any of the inference sites. Why? Because it turns out that this ends up accepting things which weren't intended. See this question for more info. The type offoopresents both{a: 1}and{b: 1}as possible candidates forT, but since neither choice works, the inference fails, and the compiler proceeds to the second call signature.So, what can you do? Well, the easiest thing is to just manually specify the generic type parameter as the union. The compiler won't infer it for you, but it will accept it if you specify it:
Playground link to code