microsoft / microsoft/TypeScript

Keyword for inferring a new type from destructured parameters

Open
#55,908 9 comments 8 reactions 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
destructuring, destruct, infer destruct, infer destructuring, function destruct, function destructuring

Issue https://github.com/microsoft/TypeScript/issues/42419 (+ https://github.com/microsoft/TypeScript/issues/48220) discusses changing the current behavior of TypeScript to address the same underlying issue.

My proposal is different as it introduces a new explicit language feature and does not change any current behavior.

I also found https://github.com/microsoft/TypeScript/issues/7576. The essence here is that they want some sort of shorthand for destructured parameter types.

I think this is vaguely related to my proposal since it addresses a way to do that—it touches on a similar pain point.

✅ Viability Checklist
  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ Suggestion

Let's introduce a feature that allows for TypeScript to infer a new type from destructured function parameters.

To conceptualize this idea, I'm introducing a new keyword called from which can be used after : when destructuring a function parameter:

type Person = { first: string, last: string, age: number };

const isAdult = ({ age }: from Person) => age > 18;

This declares that typeof isAdult is (person: Pick<Person, "age">) => ... instead of (person: Person) => ... meaning that any object matching { age: number } can be passed in as a parameter.

You could also potentially do this when destructuring a local variable, though, I struggle to see its usefulness:

const { age, first }: from Person = person;

I'm not even sure whether this should be legal.

📃 Motivating Example

We often use destructuring in functions, because it helps us stay DRY:

type Person = { first: string, last: string, age: number };

const formatName = ({ first, last }: Person) => `${first} ${last}`;
const isAdult = ({ age }: Person) => age > 18;

I think this is good practice and a sensible thing to include in your company style guide.

But if we look critically at the above snippet, we can see that the signatures for both formatName and isAdult are lying about their implementation. They (quite excessively) declare that they need all properties from Person when they actually only need a few. And rightly so: I have explicitly declared that by writing : Person, but my intention here was actually to be able to pass any object that matches { age: number } into isAdult (for example).

To solve this, I have to jump through some hoops, either like this …

type Person = { first: string, last: string, age: number };

const isAdult = ({ age }: Pick<Person, "age">) => age > 18;
const formatName = ({ first, last }: Pick<Person, "first" | "last">) => `${first} ${last}`;

... or the inverse:

type Named = { first: string, last: string };
type Aged = { age: number; }
type Person = Named & Aged;

const formatName = ({ first, last }: Named) => `${first} ${last}`;
const isAdult = ({ age }: Aged) => age > 18;

However…

… the first solution is noisy and un-DRY since have to write the "pick part" twice: first for the destructuring itself, and then for the Pick type. Note: this effect is greatly exacerbated as the number of properties of a type increases, so this example is quite tame in that regard.

… the second solution (workaround?) feels too verbose for my use case (there's too much detail), I have no intent of re-use for the Aged or Named type. If the Person type comes from a third-party library (or another source that I don't control, e.g. Prisma), then this option is out of the question entirely as I can't "build up" my own type from smaller fragments (i.e. Person = Aged & Named).

This means that the first solution is often the only viable option—and it just isn't a great experience.

So—with the addition of the from keyword (or something similar), this headache is entirely relieved! It would make it much more convenient to declare the explicit requirements of a function to effortlessly make your code more expressive.

💻 Use Cases
  1. What do you want to use this for?

This would have great utility in React components where you just want to pick a few props out of e.g. ButtonProps from @mui/material/Button:

function FooterButton({ color, href, children }: from ButtonProps)

However, I see many use cases for this: Anywhere where you need more generically defined function type signatures without having to create a new explicit type or use Pick/Omit.

  1. What shortcomings exist with current approaches?

You need to manually use Pick/Omit or break types into smaller (often hard-to-name) types and compose them later on, or just live with the "excessive" type definitions.

  1. What workarounds are you using in the meantime?

The above 👆

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 by reading the linked issues 42419, 48220, and 7576, then compare their discussions with the proposed from syntax and the existing destructured-parameter behavior. Clarify whether only function parameters are in scope, how the inferred Pick type should behave, and what syntax and type-checking tests would establish completion.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.