-
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
inherit
keyword as shorthand to refer to super class fields and methods.
#36165
Comments
I don't think https://github.com/falsandtru/spica/blob/master/src/promise.ts |
There may be better approaches but I don't think this is too complicated or involves upstream types. The way I see it typescript throws errors that clearly demonstrate inconsistencies in type when appropriate, for example: class A {
foo(a: { x: "Some Data", y: "That is known" }) { };
}
class B extends A {
foo(a: number) { }
/* Gives this error:
Property 'foo' in type 'B' is not assignable to the same property in base type 'A'.
Type '(a: number) => void' is not assignable to type '(a: { x: "Some Data"; y: "That is known"; }) => void'.
Types of parameters 'a' and 'a' are incompatible.
Type '{ x: "Some Data"; y: "That is known"; }' is not assignable to type 'number'.(2416)
*/
} So clearly in this case typescript knows that I can for example define type Func = (...args: any[]) => any;
// given number of normal parameters before the rest parameter, this gives the appropriate type for the rest parameter.
// there might be a way to define this recursively but for now it is only well defined for 0 to 4 arguments.
// otherwise this returns a string constant explaining limitation in implementation.
type GetRestParam<F extends Func, P extends number> = P extends 0 ? F extends ((...args: infer A)=>any) ? A : never
: P extends 1 ? F extends ((x0: any, ...args: infer A) => any) ? A : never
: P extends 2 ? F extends ((x0: any, x1: any, ...args: infer A) => any) ? A : never
: P extends 3 ? F extends ((x0: any, x1: any, x2:any, ...args: infer A) => any) ? A : never
: P extends 4 ? F extends ((x0: any, x1: any, x2:any, x3:any, ...args: infer A) => any) ? A : never
: "REST PARAMETERS ONLY IMPLEMENTED FOR UP TO 4 PRIOR ARGUMENTS"
type KeysOfMethods<BaseType> = { [K in keyof BaseType]: BaseType[K] extends Func ? K : never }[keyof BaseType];
/**
* demonstration of how the `inherit` keyword would work as working with explicit type arguments.
* @param BaseType - intersection of extended base class and all implemented interfaces
* @param Field - the field name or method name being used. if Pos is not "Field" this must point to a method.
* @param Pos - one of:
* "Field" for the type of the defined field (default if not given)
* "Return" for the return type of the method
* "Param" for a parameter of the method (Idx specifies position)
* "Rest" for a rest parameter (...args).
* @param Idx - when Pos is "Param" this is the position of the parameter,
* when Pos is "Rest" this is the number of parameters before the rest parameter.
*/
type inherit<BaseType, Field extends (Pos extends ("Param" | "Return" | "Rest") ? KeysOfMethods<BaseType> : keyof BaseType),
Pos extends "Field" | "Param" | "Return" | "Rest" = "Field",
Idx extends (Pos extends "Param" | "Rest" ? number : never) = never
> = Pos extends "Field" ? BaseType[Field]
: BaseType[Field] extends Func
? Pos extends "Return" ? ReturnType<BaseType[Field]>
: Pos extends "Param" ? Parameters<BaseType[Field]>[Idx]
: Pos extends "Rest" ? GetRestParam<BaseType[Field], Idx>
: never // case where Pos is not one of the 4 choices
: never; // case where BaseType[Field] is not a function for one of the choices that require a function Here is an example usage with explicit type arguments: Also as a Playground interface Base {
field: number;
foo(a: number, b: string): boolean;
bar(a: number, b: string, c?: any): boolean;
}
class Sub implements Base {
field: inherit<Base, "field"> = 1;
foo(a: inherit<Base, "foo", "Param", 0>, b: inherit<Base, "foo", "Param", 1>): inherit<Base, "foo", "Return"> {
return true;
}
bar(a: inherit<Base, "bar", "Param", 0>, ...rest: inherit<Base, "bar", "Rest", 1>): inherit<Base, "bar", "Return"> {
return true;
}
} |
I would personally prefer class Foo extends Base {
inherit method(arg1, arg2) {}
method(inherit arg1, inherit arg2) {}
method(arg1: number, arg2: string) {} // Explicitly type
} |
See the example of |
Search Terms
Inherit, inheritance, type inference, subclass, superclass, override,
Suggestion
This issue addresses the lack of easy way to refer to types of fields or methods of a base class or implemented interfaces without a high amount of repetition. Proposed is a keyword
inherit
which will act as a type alias which is contextually resolved for the corresponding type of the base class.A simpler way to understand this proposal is to think of it as a solution to #10570 by offering an explicit but convenient way to refer to the base class, let us look at a typical example:
In this case we want to preserve the same type for field
model
from the classGeometry
. the most obvious solution is to rewrite"square" | "circle"
but we can do a little better by instead usingGeometry["model"]
to allow for changes in the base class to propagate automatically.This is better but still requires us to refer to the base class and rewrite the field name for each field initialized in this way. The proposed solution is to add a keyword
inherit
which will use the base class and the field name to resolve to the appropriate type.Here is an example playground where I define
inherit
as a type alias where the arguments could be gained automatically from the context of where it is used.This would also support being used in method parameters and return types which would be equivalent to using
Parameters
andReturnType
where appropriate. For example each of these 3 declarations would be equivelent to the following commentThis would also address #2000 with additional capability, if all parameters and return type use
inherit
it will ensure the call signature exactly matches the super class. except without forcing redefining it and allowing extensions likeReadonly<inherit>
shown above or"fly" | inherit
shown below.Use Cases and Examples
Composition
The proposed keyword would not be required to use alone but could be used like any other type alias. This means that further additions to the original type can be done such as
Partial<inherit>
or similar. Consider this example where we want to override a methoddoAction
to accept additional kinds of parameters:This allows us to support an obvious variation of the type defined by the base class and can be easily understood and written without even looking at the original declaration of
Robot[“doAction”]
.Interfaces and Multiple Implementations
In the event that a class implements several interfaces,
inherit
will look up fields on an intersection of the base class (if present) and all implemented interfaces:For cases where multiple interfaces would define the same method or field
inherit
would resolve to the intersection of all necessary values, or in the case of method overrides resolve only to the last method to remain consistent withParameters
and similar constructs.Constraints, Valid and Invalid Uses.
The
inherit
keyword only makes sense inside a class that either extends another class and/or implements at least one interface. If there is no base class then an error should be raised. As well theinherit
keyword would only be applicable in one of the following positions:For parameters and return types, if the corresponding field on the base class is not a function then a compile error should be thrown. Either with a very similar message to the one thrown by
Parameters<0>
or one more specific like “cannot inherit parameter when super field “foo” is not a function”.For rest parameters,
inherit
should be equivalent to aextends / infer
statement with the same number of other parameters as specified, with all other types set toany
for the inference. So if the function is written likefoo(a: number, b: string, …rest: inherit)
the inherit would resolve like this:Base[“foo”] extends (a:any, b:any, ...rest: infer T)=>any ? T : never
. And thenever
there would never be used since ifBase[“foo”]
is not a function then a compile error is thrown. Any other case even whereBase[“foo”]
takes only 1 parameter will still give valid results for the rest parameter.Note that for fields initialized to arrow functions, it is not valid to use
inherit
in the parameters or return type of the arrow function. Instead the full field can useinherit
then the parameters will be inferred using normal means: Playground LinkCases that don’t work
Methods with several overloads and methods with generics are 2 cases where the currently existing
Parameters
and similar construct cannot fully capture the information. This proposal would suggest that no additional work be done to letinherit
do any extra work in these cases but does recognize that that would be possible in the future. This means that if a method has a generic theinherit
keyword will only use the constraint without preserving the generic. Similarly when a method has several overloadsinherit
will only use the type specified in the last overload which without specifying the other necessary call signatures is not valid.Checklist
My suggestion meets these guidelines:
*
*
in order to not break any existing code that may use a type alias calledinherit
, this would need to allow the keyword to be shadowed similar to other built in names.The text was updated successfully, but these errors were encountered: