Repository: TypeScript
Issue: Can't dynamically map type from class private field
Labels: Suggestion,Awaiting More Feedback

# Suggestion

Support private fields in mapped types.

<!--
  Please fill in each section completely. Thank you!
-->

## 🔍 Search Terms

<!--
  💡 Did you know? TypeScript has over 2,000 open suggestions!
  🔎 Please search thoroughly before logging new feature requests as most common ideas already have a proposal in progress.
  The "Common Feature Requests" section of the FAQ lists many popular requests: https://github.com/Microsoft/TypeScript/wiki/FAQ#common-feature-requests

  Replace the text below:
-->

typescript, private field, mapped types

## ✅ Viability Checklist

<!--
   Suggestions that don't meet all these criteria are very, very unlikely to be accepted.
   We always recommend reviewing the TypeScript design goals before investing time writing
   a proposal for ideas outside the scope of the project.
-->
My suggestion meets these guidelines:

* [ ] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [ ] 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
* [ ] 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

<!-- A summary of what you'd like to see added or changed -->

Include information about private fields types inside class interface.

## 📃 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?
-->

TypeScript have dynamic mapped types which is very powerful feature. But currently can't used with private fields. So its hard to migrate to those.

## 💻 Use Cases

<!--
  What do you want to use this for?
  What shortcomings exist with current approaches?
  What workarounds are you using in the meantime?
-->

**Working example without private field:**
```typescript
interface Runnable {
    (): void;
}

interface Consumer<T> {
    (data: T): void;
}

class EventEmitter<T> {
    emit(data: T): void {
        return void 0;
    }

    subscribe(consumer: Consumer<T>): Runnable {
        const unsubscribe = () => void 0;

        return unsubscribe;
    }
}

class Host {
    private readonly events = {
        init: new EventEmitter<Record<string, number>>(),
        contextUpdate: new EventEmitter<Record<string, string>>(),
        inactivity: new EventEmitter<boolean>(),
    } as const;

    on<T extends HostEventType>(
        type: T,
        consumer: HostEventConsumer<T>,
    ): Runnable {
        return this.events[type].subscribe(consumer);
    }
}

type HostEventType = keyof Host['events'];

type HostEventConsumer<T extends HostEventType> =
    Host['events'][T] extends EventEmitter<infer U> ? Consumer<U> : never;

const host = new Host();

// Here data is mapped to Record<string, number>
host.on('init', (data) => console.log({ data }));
```

**Not working example with private field (expecting to work in future):**
```typescript
interface Runnable {
    (): void;
}

interface Consumer<T> {
    (data: T): void;
}

class EventEmitter<T> {
    emit(data: T): void {
        return void 0;
    }

    subscribe(consumer: Consumer<T>): Runnable {
        const unsubscribe = () => void 0;

        return unsubscribe;
    }
}

class Host {
    readonly #events = {
        init: new EventEmitter<Record<string, number>>(),
        contextUpdate: new EventEmitter<Record<string, string>>(),
        inactivity: new EventEmitter<boolean>(),
    } as const;

    on<T extends HostEventType>(
        type: T,
        consumer: HostEventConsumer<T>,
    ): Runnable {
        return this.#events[type].subscribe(consumer);
    }
}

type HostEventType = keyof Host['#events'];

type HostEventConsumer<T extends HostEventType> =
    Host['#events'][T] extends EventEmitter<infer U> ? Consumer<U> : never;

const host = new Host();

// Here data should be mapped to Record<string, number>
host.on('init', (data) => console.log({ data }));
```