-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Support for a built-in "constructable" type or "class type" #17572
Comments
We can actually get a little bit closer than this, but it's still lacking. type Abstract<T> = Function & {prototype: T};
type Constructor<T> = new (...args: any[]) => T;
type Class<T> = Abstract<T> | Constructor<T>; This can work okay for some APIs, but also accepts functions, as their I've actually just found another trick playing around with this now. I haven't seen this anywhere before, I don't know if this really works in the general case, would be nice to get some feedback. abstract class EmptyAbstractClass {}
type Class<T = {}> = typeof EmptyAbstractClass & {prototype: T}; This seems to work nicely. abstract class Foo {}
class Bar extends Foo {}
function foo<T>(C: Class<T>) {} // Accepts any class
foo(Foo); // Works as it should
foo(Bar); // Works as it should
foo(class{}); // Works as it should
foo(function(){}); // Fails as it should And you can restrict the types of the class just fine. abstract class Base { isBase = true; }
class Child extends Base {}
function bar<T extends Base>(C: Class<T>) {} // Accepts any class that extends Base
bar(Base); // Works as it should
bar(Child); // Works as it should
bar(class{}); // Fails as it should
bar(function(){}); // Fails as it should
function makeDerived(C: Class<{}>) {
return class extends C {};
} However, this doesn't quite seem to work generically. This complains that function makeDerivedGeneric<T>(C: Class<T>) {
return class extends C {}; // Error here
} All this is in 2.4.2. |
There's an issue with @TheOtherSamP 's example: arrays. Exampleclass Foo{
public bar: string;
}
function getData<T>(clazz: Class<T>):T{
//Whatever implementation, the signature is the problem.
}
const instance: Foo = getData<Foo>(Foo);// Works as it should.
const array1: Foo[] = getData<Foo[]>(Foo);// Fails because Foo is not Foo[]'s class.
const array2: Foo[] = getData<Array<Foo>>(Array<Foo>);// Fails because type Foo[] provides no match for the signature 'new (...args: any[]): Foo[]' |
@Supamiu |
@noemi-salaun Yes and that's the issue because there's no valid signature for this type of call. Actually, there's no type to say 'I want an array of this class', the only way to do this is: function getArray<T>(clazz: Class<T>): T[]{
return [].push(new clazz()); //This is a bad implementation bus just giving an example here.
}
// And then we make the call for an explicit 'getArray' function, there's no way to include this in a more generic function.
const array: Foo[] = getArray<Foo>(Foo); Because I don't know if this is clear because I'm struggling to explain it. |
@Supamiu To be honest, I'm not entirely sure what you're saying with this example. That |
Just to clear up what the |
Yes that's it. |
@noemi-salaun That sounds good. I wonder if it would perhaps make sense to express this concept slightly differently. Would allowing the type AbstractConstructable<T> = abstract new(...args: any[]) => T; A concrete type would still be valid here, it wouldn't force abstractness, but allow it. I think this fits fairly nicely with the rest of the language, and allows this to be worked fairly nicely into interfaces. This would also allow you to specify the parameters of the abstract constructor. More involved example: // A variable of this type is valid to extend from.
export interface FooConstructor {
abstract new(dependency: number): Foo;
readonly defaultFoo: Foo;
}
export interface Foo {}
abstract class FooInternal implements Foo {
static readonly defaultFoo: Foo;
constructor(dependency: number) {}
} |
The point of course being that you could extend from a variable of type |
@TheOtherSamP Sorry for the delay of my answer, I think that the best way to show you an example of what I'm trying to explain is to show you the issue in actual code implementation. This test : https://github.com/kaiu-lab/serializer/blob/proper-type-matching/test/serializer.spec.ts#L223 is failing to compile because Foo's constructor doesn't return a Foo[].
Here is the signature of the method involved (
Where What is missing here is the ability to provide a constructor for an array type, because
If we had such functionality, I'd think about something like N.B: I'm starting to think that this is a complete different issue... I'll probably check if an issue about this exists and create one if it doesn't. |
@SamPruden is there still no workaround for the generic case from your example above: function makeDerivedGeneric<T>(C: Class<T>) {
return class extends C {}; // Error here
} |
@mshoho as @mhegazy said in #8853 (comment)
So it seems that |
Thank you for your post @noemi-salaun! Now I know I need to wait few years to come back to typescript. The workaround solutions below are awkward. |
First things first, this code has not even been run, it's only a draft of an API I think could be nice for initialising components, accounting for the nuances found in the different components of the Design System. - Some components run on a specific element, some not. So the API need to allow both. - When initialised on a specific element, the module name could be a static property of the component, however, it's probably useful to let it be set explicitely - Further selection, like finding the for the Copy, should probably be part of the component's responsibility, rather than the initialisation code - Similarly, if a component needs to be initialised only once, this could be part of the component's responsibility, rather thatn the initialisation loop. TypeScript might get in the way of having something so dynamic. Especially, I'm not 100% sure how we can note that a function accepts a 'class' as parameter. Seems it's a bit tricky based on this: microsoft/TypeScript#17572
First things first, this code has not even been run, it's only a draft of an API I think could be nice for initialising components, accounting for the nuances found in the different components of the Design System. - Some components run on a specific element, some not. So the API need to allow both. - When initialised on a specific element, the module name could be a static property of the component, however, it's probably useful to let it be set explicitely - Further selection, like finding the for the Copy, should probably be part of the component's responsibility, rather than the initialisation code - Similarly, if a component needs to be initialised only once, this could be part of the component's responsibility, rather thatn the initialisation loop. TypeScript might get in the way of having something so dynamic. Especially, I'm not 100% sure how we can note that a function accepts a 'class' as parameter. Seems it's a bit tricky based on this: microsoft/TypeScript#17572
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572 Co-authored-by: Owen Jones <owen.jones@digital.cabinet-office.gov.uk>
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572 Co-authored-by: Owen Jones <owen.jones@digital.cabinet-office.gov.uk>
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572 Co-authored-by: Owen Jones <owen.jones@digital.cabinet-office.gov.uk>
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572 Co-authored-by: Owen Jones <owen.jones@digital.cabinet-office.gov.uk>
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572 Co-authored-by: Owen Jones <owen.jones@digital.cabinet-office.gov.uk>
TypeScript doesn’t really have the concept of passing a class to a function, so we have to fake it by defining a type that is constructable and has the properties we need to be able to instantiate it (a `moduleName` and optionally a set of `defaults` from which we can infer the config type). This is based on approaches from: - https://stackoverflow.com/questions/71086547/build-a-function-that-accepts-a-class-in-typescript - microsoft/TypeScript#17572 Co-authored-by: Owen Jones <owen.jones@digital.cabinet-office.gov.uk>
TypeScript Version: 2.4.0
It would be interesting to allow type hinting for class type.
Something like
Currently, the closest we have is
It is of course logical that the abstract class causes an error with the code above, this is why it could be nice to have something to handle this case.
To go further, we can think about adding some genericity on it.
The text was updated successfully, but these errors were encountered: