Fix #13948: preserve narrow types for computed property keys with union literal types#63113
Fix #13948: preserve narrow types for computed property keys with union literal types#63113DukeDeSouth wants to merge 1 commit intomicrosoft:mainfrom
Conversation
… with union literal types
When the computed property name type in an object literal is a union of
literal property name types (e.g., `'a' | 'b'`), distribute the property
over the union members, creating a separate named property for each
constituent type.
Before this fix, `{ [key]: value }` where `key: 'a' | 'b'` would produce
`{ [x: string]: V }` because `isTypeUsableAsPropertyName` rejects union
types. Now it produces `{ a: V; b: V }`, consistent with how mapped types
handle the same scenario.
This fixes the long-standing React setState pattern:
```ts
this.setState({ [key]: value }); // no longer errors
```
Co-authored-by: Cursor <cursoragent@cursor.com>
|
@microsoft-github-policy-service agree |
|
It sounds like after this change, this program would typecheck: const key: 'a' | 'b' = Math.random() > 0.5 ? 'a' : 'b';
const obj: { a: number; b: number } = { [key]: 1 };
obj.a.toFixed();
obj.b.toFixed();Why is that desirable? One of those That being said, also see #62963. I don't think pull requests of this nature are currently being accepted. |
|
@mkantor You're absolutely right — this is an unsoundness issue I should have caught. At runtime I made the mistake of drawing an analogy with mapped types ( After your comment I found that @sandersn's PR #21070 (2018) already implemented the correct approach with union-type lifting:
That PR was shelved due to complexity (baseline changes, destructuring interactions, error reporting), not because the approach was wrong. Given this soundness issue and the maintenance mode announcement (#62963), I'll close this PR. The correct union-type approach would need to be reimplemented in the Go codebase for TypeScript 7.0. Thank you for catching this. |
Human View
Summary
Fixes #13948 (9 years old, 126 reactions).
When a computed property name in an object literal has a type that is a union of literal property name types (e.g.,
'name' | 'age'), TypeScript previously widened the key tostring, producing{ [x: string]: V }. This PR distributes the property over the union members, creating separate named properties for each constituent.Before
After
This fixes the long-standing React
setStatepattern:Changes
src/compiler/checker.ts: IncheckObjectLiteral, whencomputedNameTypeis a union where every constituent passesisTypeUsableAsPropertyName, distribute over the union members creating a named property for each. (+27 lines)tests/cases/compiler/computedPropertyNamesUnionTypes.ts— 10 test cases covering union literals,keyof,Partial<T>assignability, number literal unions, mixed properties, mapped type equivalence, and more.Design
The approach is consistent with how mapped types handle union keys:
{ [P in 'a' | 'b']: V }already produces{ a: V; b: V }. This PR makes object literal computed properties behave the same way.The fix only triggers when ALL members of the union are
isTypeUsableAsPropertyName(string/number literal or unique symbol). Dynamic keys (string), template literal patterns, and mixed unions still produce index signatures as before.Testing
AI View (DCCE Protocol v1.0)
Metadata
AI Contribution Summary
Verification Steps Performed
Human Review Guidance
src/compiler/checker.ts,tests/cases/compiler/computedPropertyNamesUnionTypes.tsMade with M7 Cursor