-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Default generic return types for class methods #18636
Comments
Default type parameters just allow users of the type to avoid specifying the default on every reference. In side the case, Even assuming that these classes has to be |
It is clear to me, that all types declared by the generic type should equal the generic type and this behaviour would not be changed. However, I forgot to say, that the desired behaviour with default parameters only applies if the generic type was combined with an extend constraint: class C<T extends A = A> {
// this is the default implementation of the `create` method
public create(): T {
// Until now the error `Type 'A' is not assignable to type T` is thrown
// To suppress this error, you must make an unsafe upcast to `T`
// e.g. `return new A() as T;`
return new A();
}
} With this approach only public/protected return types and public/protected class fields will be affected. Other type declarations like parameters would persists. Even multiple type parameters would not change the behaviour, because everywhere where a public/protected method, which returns a generic type with an extend constraint and a default type or writes a value, which has the type of the default type, to a class member, the method would no longer be valid and must be overwritten. So, when exactly a method should be overwritten:
Here is a more real-world scenario to understand it better: /* AbstractAPI for defining a base API */
abstract class AbstractController {
public abstract control(): void;
}
abstract class AbstractPermissions {
public abstract asList(): string[];
}
class EmptyPermissions extends AbstractPermissions {
public asList(): string[] {
return [];
}
}
// The generic type U has an extend constraint and a default type
abstract class AbstractAPI<T extends AbstractController,
U extends AbstractPermissions = EmptyPermissions> {
private _controller: T;
private _permissions: U;
public getController(): T {
return this._controller;
}
public get controller(): T {
return this.getController();
}
public getPermissions(): U {
return this._permissions;
}
public get permissions(): U {
return this.getPermissions();
}
public abstract createController(): T;
// this is the default implementation of the `createPermissions` method
public createPermissions(): U {
// Until now the error `Type 'EmptyPermissions' is not assignable to type U` is thrown
// To suppress this error, you must make an unsafe upcast to `U`
// e.g. `return new EmptyPermissions() as U;`
return new EmptyPermissions();
}
public do(): void {
// Until now the error `Type 'EmptyPermissions' is not assignable to type U` is thrown
// To suppress this error, you must make an unsafe upcast to `U`
// e.g. `this._permissions = new EmptyPermissions() as U;`
this._permissions = new EmptyPermissions();
}
}
/* SearchAPI with custom controller and permissions */
class SearchController extends AbstractController {
public control(): void {
console.log('Hello search world!')
}
}
class SearchPermissions extends AbstractPermissions {
public asList(): string[] {
return ['search'];
}
}
// An error should be thrown, that the `createPermissions` method must be overwritten,
// because it returns `EmptyPermissions` and not `SearchPermissions`
// A second error should be thrown, that the `do` method must be overwritten,
// because it writes an `EmptyPermissions` and not a `SearchPermissions` value
// to `this._permissions`, which is implicitly public accessible
class SearchAPI extends AbstractAPI<SearchController, SearchPermissions> {
public createController(): SearchController {
return new SearchController();
}
}
/* PublicAPI with custom controller, but empty permissions */
class PublicController extends AbstractController {
public control(): void {
console.log('Hello public world!')
}
}
// No error should be thrown, because the default permissions type is used
class PubliAPI extends AbstractAPI<PublicController> {
public createController(): PublicController {
return new PublicController();
}
} I hope now it is clearer what I mean with a default implementation of methods, which interfere with generic types with an extend constraint and default type. Maybe another keyword like default should be introduced: class C<T extends A = A> {
public default create(): T {
return new A();
}
} This would make it a lot clearer I think. |
Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed. |
@mhegazy Have you reviewed my last comment, before closing the issue? |
#13487 added default generic types, but it's still not possible to provide a default implementation for a method, which returns the default generic type:
Here the class
C
, which has the default typeA
for its generic typeT
, provides a default implementation for the methodcreate
. If this method is not overwritten it returns an instance of the default typeA
. Now there are two possible options of extending the classC
:A
was specified: An error should be thrown, that the type of the returned value of thecreate
method is not assignable to the specified type and the method should be overwritten to return a value of the correct type.A
or no type was specified: The default implementation of thecreate
method works fine and no error should be thrown.With this approach unsafe upcast to the generic type
T
of the return value of the defaultcreate
method would be prevented.The text was updated successfully, but these errors were encountered: