-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Cannot use matching indexes across objects with similar types #31445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
These errors are correct from the type system's perspective because there's no mechanism that correlates that both utterings of The "expanded" form checks as expected: (['a', 'b'] as const).forEach(key => {
if (key === 'a') {
b[key].push(a[key]);
b[key] = [...b[key], a[key]];
d[key].push(c[key]);
d[key] = [...d[key], c[key]];
} else if (key === 'b') {
b[key].push(a[key]);
b[key] = [...b[key], a[key]];
d[key].push(c[key]);
d[key] = [...d[key], c[key]];
}
}); If you imagine the compiler expanding out all code bodies to be an individually-rechecked block that iterates over all possible combinations of union values it closes over, then it'd be possible to check this function as expected, but you can also see why that kind of implementation would be thousands of times slower for any nontrivial block. |
I'm somewhat skeptical that this needs to be "thousands of times slower". The important part here is that there is no k2 as argued in your example above:
This is only operation on k1 and it is not possible that k1 can be both a and b simultaneously - it must be one or the other. Maybe your point is that
But i would argue that's a solvable problem - it is inferrable from the source that key has not changed between invocations, which is precisely why i filed this ticket. It's hard for me to accept that writing the same block of code for every possible value here is the typescript supported way of doing this... especially when you would have to actually write out every line many times given that this cannot be written as a function or the same issue arises. |
It's not, it was just a demonstration of the fact that the checker can reason about the code in the case where the type is known to be exactly one value.
Certainly possible, yes, just a massively complex undertaking we're not inclined to tackle at this point. |
In other words, the compiler sees at each line “object indexed with key of type |
Thinking about this some more, it’s kind of interesting. I’m not convinced it would be enough just for TS to know As human beings, we know if the key is the same, so will the type retrieved, regardless of what types are involved, but this doesn’t automatically follow from the compiler’s perspective unless the compiler is explicitly programmed to track it. |
Ran into this today, but with a simpler reproduction that uses only one type (playground link): interface Foo {
one: number;
two: string;
};
function copy(source: Foo): void {
const newFoo: Foo = { one: 0, two: '' };
for (const key of ['one', 'two'] as const) {
newFoo[key] = source[key]; // Error: Type 'string | number' is not assignable to type 'never'.
}
} This case might be simpler to solve than the OP because it's indexing into the same type each time. It's also probably much more common than the OP for the same reason. |
I really hated that mistake today lol |
I'm having the same issue I think. Here's what I'm working with: // Imagine these are records in a database.
type X = { id: string, x: number }
type Y = { id: string, y: number }
// A mapping of which records live in which tables.
type TableToValue = { x: X, y: Y }
type Table = keyof TableToValue
type Value = TableToValue[Table]
// A datastructure representing a bunch of records indexed by table and id.
type ValueMap = {[T in Table]: {[id: string]: TableToValue[T]}}
function getValue<T extends keyof ValueMap>(valueMap: ValueMap, table: T, id: string): ValueMap[T][string] {
const tableMap = valueMap[table] // Inferred as ValueMap[T] ✅
const value = tableMap[id] // Inferred as X | Y 🤔
return value // Broken in v3.9.2, works in v3.3.3 ❌
}
const value = getValue({} as ValueMap, "x", "1") |
For others who are running into this issue, I think I've finally figured it out. I'm going leave an explanation here in the interest of others 😄 Let's break it down into a really simple example. type Key = "x" | "y"
type IdentityMap = {[T in Key]: T}
function getValue<T extends Key>(map: IdentityMap, key: T): T {
return map[key] // ❌ IdentityMap[T] is not assignable to type T
} It's frustrating that this does not work because we can observe that it should be correct. And in fact, if we represent the type this way, it does work: function getValue<T extends Key>(map: IdentityMap, key: T): IdentityMap[T] {
return map[key]
}
const x = getValue({x: "x", y: "y"}, "x") // Type is "x" Now let's consider what happens with union types. const k = "y" as Key
const y = getValue({x: "x", y: "y"}, k) // Type is Key This makes sense, but now let's try a more complex example: type Obj<T extends Key> = {key: T}
type ObjMap = {[T in Key]: Obj<T>}
function getObjValue<T extends Key>(map: ObjMap, key: T): Obj<T> {
return map[key] // ❌ 'Obj<"x"> | Obj<"y">' is not assignable to type 'Obj<T>'
} This code used to work for me in TypeScript 3.4. Now the code must be written as: function getObjValue<T extends Key>(map: ObjMap, key: T): ObjMap[T] {
return map[key]
} This appears to work the exact same: const obj = getObjValue2({x: {key: "x"}, y: {key: "y"}}, "y") // Obj<"y"> But the main difference is how TypeScript treats unions. If we pass a key which is a union, we get a union return type. const k = "y" as Key
const obj = getObjValue2({x: {key: "x"}, y: {key: "y"}}, k) // Obj<"x"> | Obj<"y"> instead of Obj<"x" | "y"> In the old implementation, we would get back |
I guess this was also closed due to the "Design Limitation" label? Eg. due to it being a limitation in the design of TypeScript - and this behavior will not be changed? |
I think #32693 is the still-open issue about this. |
TypeScript Version: Reproduced in the playground - i assume this is running latest. before running in the playground was running on 3.4.5
Search Terms:
keys across objects cannot be compared
type values at same indexes do not honor that keys can only assume one value at a time
(I'm having a hard time describing this issue, so I may have failed to search for it effectively; forgive me if that's the case.)
Code
Expected behavior:
All of the options for adding the keys to the array in the second half of the example above should compile and work correctly.
Actual behavior:
The LHS of the equation is considered an
&
type while the RHS is an|
type, which makes them incompatible. These types are clearly correct and should work, but e.g.b[key]
is consider anever
for the purposes of push ('a' | 'b' & 'c' |'d'
), whiled[key]
is considered astring & number
for those purposes (not sure why this isn't also anever
)The important part of this issue is that none of the possible ways of using these types together compile. All possible variants create too strict of a typing for what needs to go into the array variant of the original type, even though the types of the objects at identical keys are compatible
Playground Link:
playground link
Related Issues:
The text was updated successfully, but these errors were encountered: