Repository: TypeScript
Issue: Allow mapped type to declare functions
Labels: Suggestion,In Discussion

# Suggestion
## 🔍 Search Terms

 * mapped types
 * instance member function
 * instance member property
 * instance method
 * subclass constructor
 * overriding

## ✅ Viability Checklist
My suggestion meets these guidelines:

* [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
## 📃 Motivating Example

<!--
  If you were announcing this feature in a blog post, what's a short explanation that shows
  a developer why this feature improves the language?
-->

In our universe, there are two kinds of animals, dogs and cats.
```ts
type Animal = 'dog' | 'cat';
```

Some people can handle dogs. Some people can handle cats. Some people can even handle both. But if you can't handle dogs or cats, you're just not a handler. We can express this generically in TypeScript.

```ts
type Handler<A extends Animal> = {[animal in A as `handle${Capitalize<animal>}`]: (animal: animal) => void};
```

Let's say you have a dog handler that doesn't do anything interesting.

```ts
class BoringDogHandler implements Handler<'dog'> {
	handleDog(dog : `dog`) {}
}
```

You could also have someone who can handle both and keeps a record of how many animals handled.

```ts
class CountingUniversalAnimalHandler implements Handler<Animal> {
	count = 0;

	handleDog() {this.count++;}
	handleCat() {this.count++;}
}
```

Some dog handlers are well known for broadcasting to the world whenever they are handling a dog. Since this is such a common thing, we'd like to create a mixin for it.

```ts
function NoisyDogHandler<H extends {new (...args: any[]): Handler<'dog'>}>(base: H) {
	return class extends base {
		handleDog(dog : `dog`) {
			console.log("Handling dog");
			return super.handleDog(dog);
		}
	};
}
```

Looks innocuous enough. But this doesn't work! Typescript informs us that
> Class `Handler<"dog">` defines instance member property `handleDog`, but extended class `(Anonymous class)` defines it as instance member function. (2425)

If we were allowed to declare that the `handle${Capitalize<A>}` members were not mere properties, but actually methods, TypeScript would not fear letting us override them. The natural syntax would look like this, which is currently not allowed:

```ts
type Handler<A extends Animal> = {[animal in A as `handle${Capitalize<animal>}`](animal: animal): void};
```

A subtle distinction for a subtle distinction.

[Playground Example](https://www.typescriptlang.org/play?noImplicitAny=false&q=1#code/C4TwDgpgBAggdgSwLYEMA2UC8UDkATAewHMcoAfXAYxWBwG4BYAKGdEigAkU480IAnADwwoEAB7AIPAM6xEqNAD4sUAN4BtbsnRQEcWFBSyABgAtuvCABJVAYRRgEwdAgBeEQVoWKAvsYC6AFxQABRe6MHhaACUWMoAbgQIeD6MLEyUaEayAEIE-HpEACLEXDx8-LpIYHxIUsCyZZZC+MQ4yqrMAJDm5RAlRCGERFDBxsPGsao+zDPpmdlQtgQArnDAhQCqiPEC0ujw2mhNFVU1EHXrjRYVwvLoHd2Uq+sqAAxp3b2WAyFTwKYENIAHTPNbAADUELocx6Nwg9mAfzUAKBoJekOhczmzAAZmtKBsCPoAHJJaQgAYnASCDiiCRSPCyVRwCAAd1CwK5KH4RGkkTgIHU-miwWpLWG7R8ihCACMjBAxVNuvwIMAVvx9AtpLJxJIZFB5dJoJ0mF04X1fsNRlBxsRJmpuubnnBpAQ+MC0MQQgAiE6FKDDH3RNLmrqq9WaqDSFaQfjA758K3EENO2GpWbMIA)

## 💻 Use Cases


###  What do you want to use this for?
Mix-ins are powerful, and when they work they're fantastic, but they're currently somewhat brittle to work with. 

This is one of the pieces of the puzzle needed for classes extending constructors of mapped types to override methods (#27689). The other piece needed is to be able to keep the member function status of methods obtained from type indexing (#38496, c.f. #35416, and #46802).

###  What shortcomings exist with current approaches?

For small enough use cases, you can do the mapping "by hand":

```ts
type Animal = 'dog' | 'cat';

type DogHandler = {handleDog(dog: 'dog'): void};
type CatHandler = {handleCat(cat: 'cat'): void};
type BothHandler = DogHandler & CatHandler;

type Handler = DogHandler | CatHandler;
type HandlerFor<A extends Animal> = ('dog' extends A ? DogHandler : {}) & ('cat' extends A ? CatHandler : {})
```

Obviously this doesn't scale well if you create multiple methods with `Animal` instead of just one, or `Animal` has many more cases. Less obviously, there isn't a really satisfactory way to implement a generic method that takes an animal and handler, calls the right handle method, ensures at compile time that the handler can handle the animal, and doesn't involve casting. See [this playground](https://www.typescriptlang.org/play?noImplicitAny=false&q=1#code/C4TwDgpgBAggdgSwLYEMA2UC8UDkATAewHMcoAfXAYxWBwG4BYAKGdEigBFiAJFOPNBABOWKAG8AFnwEQuRABSEiALlxKcASlUA3AgjwBfRkzbQAwjV79BI7JOmCLwedWCqcrzTr2HjpqABCBMASVjK2nDwOwlAAZFBOYTbGrODQSTHYchkiFInRQn5pUDkAYgRCADwwUBAAHsAQ-ADOsIioaAB8ovL4xKT1jS2wUAD8kUQ5UKpiBhpxUL2etQ1NeK014-nWMTNzzAdMlGgoza1BQghwRNkFUMhggkhNwK23OyJizACQUjtyimI0ygAAMlCD5rNmAZDsdTq0zAQAK5wYBXIgAVUQ2mEzXQ8GQ6CmDyeL3OwVCdy+TG+lGRqNEAAYUjS-jIAZCQghmgA6Oko4AAakFdBhrOiTnknIk3L59KFIrFYuYADMUZQ0QQ4FAAHJ6Zogd7hSrcFZDdbiOAQADuix59pQQiIzVUfBAAG0ALpaCY5AydeQAI1OEFU3EhPyEEGASKE2rhZzNa1awea0Gp31+0QBSmBYOIEPEP0zdLgzQIgh5aGI8gAREl0VAlLWNMZM98ozG41BmkjIEIeWzBDniK3i2LvkZoYc1XANQgtVAhxB5HxCWhVASOgAaJcFMMFCM0hAqxZrjpYTDYPokI+Zk+LHDLuSkK57j4aYtZj6D7M18-oGONLfBAaBpl+IRCAQtoAKJCFBQh1hYcA4MA74yAAhC2bYGLUYHpsWD69MuTivtqy5CJ+wHfuEv47JKAFoEBmageB1GQdBUBwQhSF8Kh6GCFhzFKjO6qatqRBNMICCUBkMDAI0SBgMAACM1RJsMW7oAGjGbruFHAmUFTVJ0d5EYxl7Xuod40TYdHsv+7SATheFpkWwEUfZjg0KuTlMSyk7TiwTCzvOi6SValyydE8mKcpABM6mDMmbTrjpfl6QJuwlAU5RVDApnud8RFPn+JD3ORh5FbZwhebIjnrq2UAAPTNVAADqEggPcrQUTgrT+FcKrCFGeBQMABBQIG0BWjiQjjAESJEKMPy4axBEeQUdUMX5QGrUFqpiQuElSVFckKRASnAAAzElqyaX56XrplBkHh8eUmWZp6+eullqP0cTxKV-wA2+FE2Z5z4NR0TUtW1ADKaJoBgs3CBhq2uRtmaQxKPmMXtNIicFoXiVAEXSdFOyxZdylqTUyUPWlP0dC9dxveEH0FV9Z5+X9N6aNVOMgwo+MuetgtbSReO7QFROpOwWloB9poMxaOTdNg8jutwnoaRa7pGjYuvjPzwKo5R5CLNruuq607rbOExtUDQpCqObVGHXOpPk2dMUXVdAAsJp6606vM+gm5+crnT6WzJTc+HGBXlZ-QQ5LZWJ8JmMSz+UvOKLhyBUwyohUd4WnTJ51xcAACswe2zlHxPSzqUdNHscfIZCfAw55Vg3csTxBZV7-beOe0VDIsyxj4sZjVA555nOEHaXXvHWTFeUzI1NXQAbPX91qwUzcR636Dt1lIhhgnw8j6bg+4JPZGX2nucZwXhPZ+ZvPJ87tCA4-XG-9+4fnHnZReH9JxQGhEAA) for several attempts.