Closed
Description
TypeScript Version: 3.5.1
Search Terms:
Code
interface Foo {
prop1: { value: number }[];
prop2: string[];
}
function getFoo<T extends keyof Foo>(key: T): Foo[T] | undefined {
if (key === "prop1") {
return [{ value: 1 }]; // Error here
}
}
const bar = getFoo("prop1"); // has correct type
Expected behavior:
No error. This used to work in 3.4.5
Actual behavior:
Type '{ value: number; }[]' is not assignable to type 'Foo[T]'.
Type '{ value: number; }[]' is not assignable to type '{ value: number; }[] & string[]'.
Type '{ value: number; }[]' is not assignable to type 'string[]'.
Type '{ value: number; }' is not assignable to type 'string'.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
fatcerberus commentedon May 30, 2019
I'm thinking this is probably a result of #30769.
AnyhowStep commentedon May 30, 2019
I'm not sure if this is a bad thing at all. More inconvenient, for sure. But if you're sure it's correct, I think having an explicit type assertion (
as T[K]
oras unknown as T[K]
) for unsound code is a good thing.fatcerberus commentedon May 30, 2019
Yeah, the new behavior is in general much more sound.
In this particular case the code looks okay since the
key === "prop1"
check narrows things down. The trouble arises because the type guard only affects the parameterkey
itself;Foo[T]
is in a generic context so the compiler treats it likeT
is as wide as possible, i.e. as its constraintkeyof Foo
.Too bad there wasn’t a way to make the compiler narrow type parameters like we can narrow variable and property types...
RyanCavanaugh commentedon May 31, 2019
This wasn't properly checked in 3.4:
fatcerberus commentedon May 31, 2019
Yep, might be nice if there was a way to narrow whole type parameters based on type guards, but that kind of thing is probably really challenging to implement in practice. Particularly in cases where there's more than one inference site for a type.
RyanCavanaugh commentedon May 31, 2019
You can't narrow a type parameter based on a value, because you can't know that the type parameter isn't wider than the value you saw:
jack-williams commentedon May 31, 2019
@fatcerberus
Even limiting it to types with one inference site is insufficient because one site can produce multiple values: Eg.
() => T
orT[]
.Validark commentedon Jun 2, 2019
@RyanCavanaugh Don't you think cases like this should work though? Is this unsound?
fatcerberus commentedon Jun 2, 2019
Counterexample:
The above code will be accepted by TS 3.4, but not by 3.5.
Basically the problem is this:
K
is a type. Inside the function, we don't know exactly what type it is other than it's some subset ofkeyof ConfigMap
.key
is a value of typeK
, but not the type itself. In general you can't prove anything about a type by checking the value of some example of the type. It just so happens that in this specific case:In the case where all conditions above are met, the code is sound. The compiler can prove 1 and 2, but without dependent types it can't prove 3, and because this is unsafe more often than not, you get a type error to warn you of that.
This is an example of contravariance at work, so I can see why it throws people off (contravariance is confusing!) - but it's there for a good reason 😄
Validark commentedon Jun 2, 2019
@fatcerberus I am not sure whether it is "unsafe more often than not", but I don't dispute that a case like the example you gave should make typescript error. I simply think it would be nice if TypeScript allowed cases where the types are sound, and restricted them when they are unsound.
fatcerberus commentedon Jun 2, 2019
That's the point though - the compiler has no way to prove that it's safe (that is, it can't distinguish the safe cases from the unsafe ones). In my checklist above, it's only safe when all 3 of those conditions are met, and the compiler can't prove the third condition without dependent types. So the choice you have is either to let all the unsafe cases through (i.e. any case not meeting the 3 conditions, such as the code above), or restrict it entirely at the cost of a few specific examples.
AnyhowStep commentedon Jun 2, 2019
Proposal: introduce one-of type parameter constraint.
AnyhowStep commentedon Jun 2, 2019
You can sort of already force users to always pass in a single string literal that is not a union of other types,
Playground
17 remaining items