Repository: TypeScript
Issue: Feature Request: Class Generic Parameter Type Predicates
Labels: Suggestion,Awaiting More Feedback

# Suggestion

## 🔍 Search Terms

List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.

type-predicate, type-guards, narrowing

## ✅ 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:

* [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

When using a class that has a generic parameter it's often useful to communicate that the class's generic parameter is of a specific type. This is especially true when it comes to classes that have conditional types based on its own generic parameter. 

While using `is MyClass<MyType>` is an adequate solution to this issue, it doesn't account for inheritance. For example if class `A` specifies a method with the type predicate as `is A<string>`, the subclass will also have the predicate `is A<string>`. In some situations this is entirely intentional, while in others it may be a limitation. 

<!-- A summary of what you'd like to see added or changed -->

## 📃 Motivating Example

In this code sample I have a classes that have two variants based on their parameters: Cached and Uncached. In this situation the cache state will be represented by a generic parameter on the class that extends a `boolean`. Another class extends this class and wants to take advantage of the generic parameter, but as seen it doesn't work out too well.

```ts
type CacheType<Cached extends boolean, A, B> = [Cached] extends [true] ? A : B;

declare class UserData<Cached extends boolean> {
    username: CacheType<Cached, string, string | null>;
    isCached(): this is UserData<true>;
}

declare class MemberData<Cached extends boolean> extends UserData<Cached> {
    memberID: CacheType<Cached, number, number | null>;
}

const member = new MemberData();

if (member.isCached()) {
    member.memberID 
    // `memberID` Is of type `number | null`, ideally there could be a way to "inherit" the generic cache functionality so it's just `number`
}
```

One way to work around this is to simply override the method in the inheriting class:
```ts
declare class MemberData<Cached extends boolean> extends UserData<Cached> {
    ...
   isCached(): this is MemberData<true>;
}
```

However when it comes to implementation, it's exactly the same as its base class. Overriding a method that simply calls `super()` and does nothing else is quite redundant, its preferred the functionality is inherited instead.

An awesome member of the TS discord was able to provide a workaround that addresses the problem quite well:

```ts
declare const CachableWitness: unique symbol;
type CachableBrand = typeof CachableWitness;

type BrandCachable<T> = T & { [CachableWitness]?: T };
type Cached<C, T> = [C] extends [true] ? BrandCachable<T> : BrandCachable<T> | null;

type MakeCached<T> = Required<T> extends { [CachableWitness]: infer S } | null ? Cached<true, S> : T;
type MakeAllCached<T> = { [P in keyof T]: MakeCached<T[P]>; };

class UserData<C> {
    username: Cached<C, string> = ""; // assignable to plain string
    fullName: string | null = null; // not cachable
    isCached(): this is MakeAllCached<this> { return true; }
}

class MemberData<C = false> extends UserData<C> {
    memberID: Cached<C, number> = 42;
}

const member = new MemberData();

if (member.isCached()) {
    // no error 
    member.memberID.toExponential;
    // @ts-expect-error
    member.fullName.charAt;
} else {
    // @ts-expect-error
    member.memberID.toExponential;
    // @ts-expect-error
    member.fullName.charAt;
}
```

While this simulates inheriting of a method with a type predicate that use a generic parameter, it's quite a lot of setup and not as portable since it's dependent on a lot of different utility types.

If you look back on the intent of the original example, what I really needed was *not* a way to assert the type of the class. But rather, I needed a way to assert the type of the generic parameter on the class.

 As a result I'm proposing a syntax to allow type predicates that can assert the type of a generic parameter.

## Possible Syntax

1.)
```ts
class UserData<Cached extends boolean> {
  isCached(): this.Cached is true;
}
```

2.)
```ts
class UserData<Cached extends boolean> {
  isCached(): Cached is true;
}
```

3.)
```ts
class UserData<Cached extends boolean> {
  isCached(): this is this where Cached is true;
}
```

Note with this implementation the bounds of the generic type *should* be preserved, ie this snippet should not compile:
```ts
class UserData<Cached extends boolean> {
  isCached(): this is this where Cached is number;
  // Error: `number` is not assignable to type `boolean`
}
```

<!--
  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?
-->

## 💻 Use Cases

- Inherited classes that use generic parameters for their field types.
- Allows for easier maintenance for dealing with objects that represent different structures based on their generic parameter.