microsoft / microsoft/TypeScript

Deprecate homomorphic mapped type over primitive

Open
#56,940 10 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Awaiting More Feedback Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
1d 19h
Merged PRs (30d)
117

Description

🔍 Search Terms

homomorphic mapped primitive

✅ Viability Checklist
⭐ Suggestion

I would like homomorphic mapped types over primitives to emit a warning if the base type is not an object type. An explicit conditional type Foo<T> = T extends object ? { [K in keyof T]: ...} : T can be used to recover the pre-existing behavior. This should be mentioned in the warning message.

Homomorphic mapped type over primitives has surprising behavior that bears mention in the FAQ:

  • This mapped type returns a primitive type, not an object type.
    • Mapped types declared as { [ K in keyof T ]: U } where T is a type parameter are known as homomorphic mapped types, which means that the mapped type is a structure preserving function of T. When type parameter T` is instantiated with a primitive type the mapped type evaluates to the same primitive.

This behavior is surprising since:

  1. It is not referentially transparent; that is, replacing a type parameter with its value does not result in the same type
  2. Although a mapped type may be assignable to the original type, it may specifically not be, e.g. {[k in keyof T]: never} whereas it always is in the case of primitives.
  3. The expression keyof T has surprising behavior on primitive types, differing substantially from object types.
  4. The syntax of a mapped type visually suggests an object-like type, which is incorrect when mapping over null.
📃 Motivating Example
type NullValued<X> = {[k in keyof X]: null};
type Okay = NullValued<{x:1, y:2}>; // {x:null, y:null}
type Oops = NullValued<number>; // oops! This is `number`, which is likely not what you expected
💻 Use Cases
  1. What do you want to use this for?
  2. What shortcomings exist with current approaches?
  • You have to know about homeomorphic mapped types and anticipate this corner case.
  1. What workarounds are you using in the meantime?
  • A type guard type Foo<T extends object> = { [K in keyof T]: ...} guards against the current behavior.
  • A conditional type Foo<T> = T extends object ? { [K in keyof T]: ...} : T makes the current behavior explicit.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the motivating NullValued example and the FAQ passage in the issue; no implementation file or test is named. Done means homomorphic mapped types over primitive bases produce the requested warning, and the warning explains the explicit conditional workaround while the surprising behavior is documented in the FAQ.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.