Skip to content

Provide the ability to change assigned generic inside a class #45992

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
5 tasks done
radzserg opened this issue Sep 21, 2021 · 3 comments
Closed
5 tasks done

Provide the ability to change assigned generic inside a class #45992

radzserg opened this issue Sep 21, 2021 · 3 comments

Comments

@radzserg
Copy link

radzserg commented Sep 21, 2021

Suggestion

Provide the ability to change assigned generic inside a class - class Buzz<R>

Currently, it's possible to change class generic only by creating a new object.

class Buzz<R extends Object> {
    public add<N extends Object>(newValue: N): Buzz<R & N> {
        return ({} as unknown) as Buzz<R & N>;
    }
}

let buzz = new Buzz();
let buzz2: Buzz<{ a: number }> = buzz.add({ a: 123 });
let buzz3: Buzz<{ a: number; b: string }> = buzz2.add({ b: "string" });

But there's no way to change R if we call a member function of a class.

It's possible to make another workaround with this in a guard

class Buzz<R extends Object = {}> {
    public add<N extends Object>(newValue: N): this is Buzz<R & N> {
        return true;
    }
    public get<N extends keyof R>(name: N): R[N] {
        return ({} as unknown) as R[N];
    }
}

let buzz = new Buzz();
if (buzz.add({ a: 123 })) {
  let a: number = buzz.get("a")   // works fine
} else {
  let a: number = buzz.get("a") // TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.
}

but in this case we still need to use if condition that doesn't work

🔍 Search Terms

class generic

✅ Viability Checklist

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
  • 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 TypeScript's Design Goals.

⭐ Suggestion

use this as CurrentClass<NewGenericType> syntax to allow changing assigned generic.

class Buzz<R extends Object = {}> {
    public add<N extends Object>(newValue: N): this as Buzz<R & N> {
        // this.values.push(newValue);
    }
}

📃 Motivating Example

It allows using the OOP approach without a need to create new objects.

💻 Use Cases

Live example. Dependency Injection container is getting new definitions. It is supposed to be a singleton in the program.

const container = new DIContainer();

container.add({ key1: new Foo() });
container.add({ key2: new Bar() });
const value = container.get("key1");

// currently I can only do

let container = new DIContainer();
container = container.add({ key1: new Foo() });
container= container.add({ key2: new Bar() });
@MartinJohns
Copy link
Contributor

MartinJohns commented Sep 21, 2021

This sounds exactly like #41339. Finding that issue again was tough.

@jcalz
Copy link
Contributor

jcalz commented Sep 21, 2021

This looks an awful lot like assertion functions as implemented in #32695 (although assertion functions have the annoying limitation that you need to explicitly annotate the class instance type in the variable that holds it) :

class Buzz<T extends object = {}> {
    data: any = {}
    add<U extends object>(newValue: U): asserts this is Buzz<T & U> {
        Object.assign(this.data, newValue);
    }
    get<K extends keyof T>(name: K): T[K] {
        return this.data[name] as T[K];
    }
}

let buzz: Buzz = new Buzz(); // explicitly annotated as Buzz here, or you get errors
buzz.add({ a: 123 });
buzz.get("b") // error
console.log(buzz.get("a").toFixed(2)) // 123.00
buzz.add({ b: "string" });
buzz.get("b") // okay
console.log(buzz.get("b").toUpperCase()); // STRING

Playground link

I'd like to see #45385 so that assertion functions are easier to use, but TypeScript basically has this functionality already.

@radzserg
Copy link
Author

Great, that's exactly what I was looking for. It was tough to find this in the docs. Thank you. Closing this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants