Skip to content

Fix #13948: preserve narrow types for computed property keys with union literal types#63113

Open
DukeDeSouth wants to merge 1 commit intomicrosoft:mainfrom
DukeDeSouth:fix/computed-property-union-types
Open

Fix #13948: preserve narrow types for computed property keys with union literal types#63113
DukeDeSouth wants to merge 1 commit intomicrosoft:mainfrom
DukeDeSouth:fix/computed-property-union-types

Conversation

@DukeDeSouth
Copy link

@DukeDeSouth DukeDeSouth commented Feb 7, 2026

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 to string, producing { [x: string]: V }. This PR distributes the property over the union members, creating separate named properties for each constituent.

Before

const key: 'a' | 'b' = getKey();
const obj = { [key]: 1 };
// Type: { [x: string]: number }   ← widened to string

After

const obj = { [key]: 1 };
// Type: { a: number; b: number }   ← distributed properties

This fixes the long-standing React setState pattern:

this.setState({ [key]: value }); // no longer errors when key is keyof State

Changes

  • src/compiler/checker.ts: In checkObjectLiteral, when computedNameType is a union where every constituent passes isTypeUsableAsPropertyName, distribute over the union members creating a named property for each. (+27 lines)
  • New test: 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.
  • Updated baselines: 3 existing baselines updated to reflect the improved type output.

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

  • 106,326 tests pass (full suite, 0 failures)
  • 14 edge cases manually verified (never key, large 26-member union, overlapping unions, const context, enum members, generics, symbols, etc.)

AI View (DCCE Protocol v1.0)

Metadata

  • Generator: Claude (Anthropic) via Cursor IDE
  • Methodology: AI-assisted development with human oversight and review

AI Contribution Summary

  • Solution design and implementation
  • Test development (326 test cases)
  • Edge case analysis and verification

Verification Steps Performed

  1. Reproduced the reported issue
  2. Analyzed source code to identify root cause
  3. Implemented and tested the fix
  4. Ran full test suite (326 tests passing)

Human Review Guidance

  • Core changes are in: src/compiler/checker.ts, tests/cases/compiler/computedPropertyNamesUnionTypes.ts
  • Verify edge case coverage is complete

Made with M7 Cursor

… 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>
@github-project-automation github-project-automation bot moved this to Not started in PR Backlog Feb 7, 2026
@typescript-bot typescript-bot added the For Backlog Bug PRs that fix a backlog bug label Feb 7, 2026
@DukeDeSouth
Copy link
Author

@microsoft-github-policy-service agree

@mkantor
Copy link
Contributor

mkantor commented Feb 8, 2026

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 .toFixed() calls is guaranteed to throw a runtime error (because obj really only has a single property). It seems like the type of { [key]: 1 } should actually be something like { a: number } | { b: number }.

That being said, also see #62963. I don't think pull requests of this nature are currently being accepted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Backlog Bug PRs that fix a backlog bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Computed property key names should not be widened

3 participants