Repository: TypeScript
Issue: Narrow discriminated union based on generic function arguments
Labels: Suggestion,Awaiting More Feedback

# Suggestion



## 🔍 Search Terms

narrowing, generic, arguments

## ✅ Viability Checklist

* [X] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [X] This wouldn't change the runtime behavior of existing JavaScript code
* [X] This could be implemented without emitting different JS based on the types of the expressions
* [X] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [X] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

In a few different projects, I've wanted to write a wrapper function that takes as arguments

* A discriminating union
* A literal discriminator
* A callback function which accepts the _narrowed_ type from the union

To be more specific, I wanted to be able to do something like this:

```TypeScript
interface Thing {
  key: "bar";
  property1: number;
}

interface OtherThing {
  key: "baz";
  property2: string;
}

type Things = Thing | OtherThing;

type NarrowedThing<A, K> = A extends { key: K } ? A : never;

const narrowingFunction = <T extends Things, K extends T["key"]>(
  thing: T,
  key: K,
  callback: (thing: NarrowedThing<T, K>) => void
) => {
  if (thing.key === key) {
    callback(thing); // Type 'Thing' is not assignable to type 'NarrowedThing<T, K>'.(2345)
  }
};

const maybeThing: Things = {
  key: "bar",
  property1: 2,
};

narrowingFunction(maybeThing, "bar", (narrowedThing: Thing) => {
  console.log(narrowedThing.property1);
});
```

Now it may be that my understanding of TypeScript is too limited to understand what is possible here, but it seems to me that it would be correct for `thing` to be automatically narrowed to something like my `NarrowedThings` type

## 📃 Motivating Example

I recently tried to write a small library to simplify the handling of AWS EventBridge events in a way that allowed you to provide handler functions and cleanly narrowed the types. The API looked a little like this:

```TypeScript
const { step, execute } = initPipeline(event);

// Both 'processor' variables below are handler functions that
// take a single event object narrowed to the correct type using the 
// string that is passed in argument one as a discriminator
execute(
  step('EventOne', eventOneProcessor),
  step('EventTwo', eventTwoProcessor),
)
```

Under the hood, this API is just an abstraction over the simple 3 argument function I describe above. In order to narrow the type I had to write a custom type guard as there appeared to be no way for TypeScript to narrow arguments that were typed with generics.


## 💻 Use Cases

I've also wanted something like this when handling redux actions within a reducer - I can see libraries such as `ts-pattern` making use of this  under the hood. As I note above, it is perfectly possible to implement this with a custom type guard, but it seems to me that everything that is needed to safely narrow the type is known at build time so a custom guard shouldn't be necessary.