Repository navigation
Cannot assign property to same type with generic key #32693
Description
Activity
See #30769 and #31445. The compiler only sees the types when checking the assignment and doesn't realize that
[key]accesses the same property on both sides, and therefore can't prove the assignment is safe. As far as TS is concerned you might be doingtarget.b = source.a(or vice versa).This used to work prior to #30769 (TS 3.5), but only as a consequence of being unsound in general:
type A = { a: number } type B = { b: number } interface Foo { a: A b: B } declare let target: Foo declare let source: Foo declare let k1: keyof Foo declare let k2: keyof Foo // this was incorrectly allowed before TS 3.5 target[k1] = source[k2]
jack-williams commented
on Aug 5, 2019 CollaboratorMore actionsI think the title is slightly misleading here: the key in your example is not generic and #30769 specifically does not affect assignments where the key is generic.
function assignProp<K extends keyof Foo>(target: Foo, source: Foo, key: K) { target[key] = source[key]; // no error }
Reacted by deathdayss and Joe CalzarettaIn a sense
keyofitself could be seen as generic, insofar as it’s a type which is parameterized on another type. But you’re right that it’s not “a generic key” in the sense that we would normally use the term. 😄RyanCavanaugh commented
on Aug 5, 2019 MemberMore actions#31445 is the canonical one for this problem.
That said, I think we should revisit since it seems to be coming up reasonably often. Special-casing
a[k] = b[k]for identical identifierskwould fix 95% of these and in fact it's hard to imagine a sound assignment where theks differedReacted by Linus Unnebäck, bre1470, Jingkun Hua, Sindre Sorhus, Joe Calzaretta, TeamworkGuy2, Yannis Kommana, Zeno Wu and Antoine PoliakovReacted by Linus Unnebäck- addedIn DiscussionNot yet reached consensusNot yet reached consensusSuggestionAn idea for TypeScriptAn idea for TypeScript
on Aug 5, 2019 Yes, please consider special case handling. In our project we have to stick on
3.4.5, because TypeScript does not get the following pattern anymore:interface O { a?: (object|string); b?: string; } function B (p?: ('a'|'b')): void { var o: O = {}; if (p) { o[p] = o[p] || {}; } }
- addedExperience EnhancementNoncontroversial enhancementsNoncontroversial enhancementsand removedIn DiscussionNot yet reached consensusNot yet reached consensus
on Mar 13, 2020 RyanCavanaugh commented
on Mar 13, 2020 MemberMore actionsApproved for the case where
t[x] = s[x]where
xis an "identical reference" (the same function used for CFA). When this happens, relate the assignment using the old "union target" rules about the left-hand side instead of the newer stricter "intersection target" put in place. This would be a special case incheckBinaryExpression. Ping me for clarification if needed.Reacted by Joe Calzaretta, Sean Vieira, bre1470, Zeno Wu, Omer Gronich, Linus Unnebäck, Jo and Dzmitry TolpekinReacted by Jorelated to explanation from Ryan Cavanaugh (@RyanCavanaugh)
Your code makes sense, but it is a different case from the one I submitted.
You proposed general case and x[g] is not assignable to x[f]. However its a bit tricky and special case should be considered. So, if the indexer is the same type than operation should be allowed.
The type of indexer is not just its declared type, perhaps it also depends on exact instance. This is not currently checked with compiler, and this raise error for correct code , please check the code.type SimpleType = { a: string, b: number } let x,y: SimpleType = { a: "", b: 9 } let fields: (keyof SimpleType)[] = ["a", "b"] for (const g of fields) { for (const f of fields) { x[f] = x[g] //OK (Error -> and should not be allowed; type are not same: each one is string|number ) y[f] = x[f] //Error -> but is OK (types are the same: each one is string or each one is number) x[f] = x[f] //Error -> but is OK (types are the same) let h = (Math.random() > 0.5) ? fields[0] : fields[1] y[h] = x[x] //Should be an error let j = f y[j]=x[f] //Would be nice if the complier let this compile to } }
Proposal:
-
add additional check while compiling that checks both type and variable in indexer
-
add special keyword (to simplify and limit compiling effort)
for (const f of /*keyword_of_mapped_field*/ fields)
and implement additional indexer instance check
- add special keyword (to simplify and limit compiling effort)
y[/*keyword_of_mapped_indexer(*/f/*)*/]= y[/*keyword_of_mapped_indexer(*/g/*)*/]
and implement additional indexer instance check
-
Very much looking forward to the special case getting implemented at some point. We just ran across this in a slightly different scenario [Playground]:
interface test { a: number; b: boolean; } const x: test = { a: 0, b: false }; const y: test = { a: 1, b: true }; // Is Okay: const simple: keyof test = "b"; x[simple] = y[simple]; // <-- All good! // Is Bad: const props: Array<keyof test> = ["a", "b"]; for (const prop of props) { // Type 'number | boolean' is not assignable to type 'never'. // Type 'number' is not assignable to type 'never'.(2322) x[prop] = y[prop]; // <-- Error! }
Reacted by bre1470 and Yehor KolesnykovEric Robinson (@ericdrobinson) Just to clear up any potential confusion, your "simple case" works because
simplegets narrowed to"b"on assignment, not because there's any special case for that in the compiler.Reacted by Eric RobinsonBruce Pascoe (@fatcerberus) Ahh, yes, good point. Definitely better to be clear on that for this issue.
Your pointer also made me realize that there's an even simpler example that shows the issue [Playground]:
interface test { a: number; b: boolean; } const x: test = { a: 0, b: false }; const y: test = { a: 1, b: true }; declare const prop: keyof test; // Type 'number | boolean' is not assignable to type 'never'. // Type 'number' is not assignable to type 'never'.(2322) x[prop] = y[prop]; // <-- Error!
Reacted by YaojianReacted by Yaojian- marked Unexpected "property is not assignable to type 'undefined'" on dynamic assignment #61409 as a duplicate of this issue
on Mar 12, 2025
TypeScript Version: 3.6.0-dev.20190803
Search Terms: assigning keyof generic key
Code
Expected behavior:
I expect it to allow the assignment. Since
typeof targetandtypeof sourceare the same, I expecttypeof target[key]to always be the same astypeof source[key].Actual behavior:
Playground Link: https://www.typescriptlang.org/play/#code/C4TwDgpgBAglC8UDeUCGAuKA7ArgWwCMIAnKAXwChRIoAhBZKAzXQk8iigSy2BIDNUAY2gAxAPbjkFKLLSYYMuczoVKFACYQhAG1TFoOiMCjB9Ac2OYJ4zdr0GoRkwGdxOYiOuS7u-YeMoAGsIEEwQkHF+KBtOM2JLYABtCIBdBjcPERTQ1IogA
Related Issues: #31665 (but it was closed and the comments seem to indicate that this should work 🤔) ping Ryan Cavanaugh (@RyanCavanaugh)