-
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
Allow overloads to be specified in interface #7230
Comments
This is intentional. The signature function convert(arg: number | string): number | string {
return Math.random() > 0.5 ? 42 : 'foo';
} which doesn't maintain the |
@RyanCavanaugh Yes, but I fail to see how this is any different from regular overloads. You can just as easily write: function convert(arg: string): number;
function convert(arg: number): string;
function convert(arg: number | string): number | string {
return Math.random() > 0.5 ? 42 : 'foo';
} And when you request parameter completion at the following position: convert(| You will get |
The difference is that the implementation is free to delegate how users should call the function. Flexibility in authoring overloads, on the other hand, is strictly necessary. Linking to #1805. |
Shouldn't the interface be authoritative as to how users should call the function? That is the point of an interface after all. Thanks for the link, I'm reading through that now. The discussion in #1805 suggests this is difficult to implement (or that the implementation would be computationally expensive). Couldn't the existing logic for ensuring that the bottom overload in an overload list satisfies all prior overloads be reused, in order to ensure that the function can be called with all signatures specified in the interface? |
It certainly could, but that logic would reject the assignment anyway. It's legal to invoke your function with a |
@DanielRosenwasser I should mention that what I'm suggesting is for a function that already accepts union types to be assignable to an interface with multiple overloads, not vice versa. The example given by @danquirk in that issue: function foo(x: string, y: string) {}
function foo(x: number, y: number) {}
var arg: string|number;
foo(arg, arg); // should be an error is not relevant, since that's trying to pass a union argument to an overloaded function. Instead, I'm suggesting that the following (which works): interface Convert {
(arg: string): number;
(arg: number): string;
}
function convert(arg: string): number;
function convert(arg: number): string;
function convert(arg: number | string): number | string {
return typeof arg === 'number' ? arg.toString() : parseFloat(arg);
}
let foo: Convert = convert; behave identically to the following (which doesn't currently work): interface Convert {
(arg: string): number;
(arg: number): string;
}
function convert(arg: number | string): number | string {
return typeof arg === 'number' ? arg.toString() : parseFloat(arg);
}
let foo: Convert = convert; since it is exactly as type safe. |
@masaeedu the assignability rules do work that way. For example: declare function fn(x: string): HTMLElement;
declare function fn(x: number): Element;
function myFn(x: string|number): HTMLDivElement { return undefined };
// OK
var x: typeof fn = myFn; |
@RyanCavanaugh I hope I haven't been blathering on about something that already works 😱 Are you saying the last snippet in my previous comment should work? |
It shouldn't work because it's not "exactly as type safe.". Just to break it down:
Each of these should be uncontroversial, I hope? Which of these do you consider to not be correct? |
Okay, thanks. That helps. Let's start with 1. An instance of |
Typo fixed |
2 is fine. 3 is also true, but is not relevant, since we ignore this problem when creating overloads as well. When I do: function convert(arg: string): number;
function convert(arg: number): string;
function convert(arg: number | string): number | string {
return typeof arg === 'number' ? arg.toString() : parseFloat(arg);
} there's no flow analysis being done to ensure that the function actually returns In other words, we don't enforce the covariance of the function in its return type. We assume a function returning This is why I was saying it is exactly as type safe. |
I understand what you're suggesting, but don't see why it's at all desirable. What makes function return types special here compared to any other type system position? This line of reasoning works equally well anywhere -- we could just as well make all assignability relations completely bidirectional. It seems like working backward from an example to produce a rule that doesn't generalize well. |
@RyanCavanaugh You are right that this doesn't work as a general principle. This would be a special case with respect to overload assignability only. I would imagine there's some special-cased, not broadly applicable code in the type checker to deal with function overloads anyway, no? The reason this is desirable is because duplicating all the overloads you specify in an interface as vestigial function declarations atop the implementation is annoying, and bad for refactorability. |
Please revisit this. My function implementations are starting to look like this, which is a total nightmare from a readability and DRY perspective: presignedGetObject(bucketName: string, objectName: string, callback: Callback<string>): void
presignedGetObject(bucketName: string, objectName: string, expires: number, callback: Callback<string>): void
presignedGetObject(bucketName: string, objectName: string, expires: number, respHeaders: ResponseHeaders, callback: Callback<string>): void
presignedGetObject(bucketName: string, objectName: string, arg3: number | Callback<string>, arg4?: ResponseHeaders | Callback<string>, arg5?: Callback<string>) { I can construct type signatures much more conveniently in I am able to do |
Just to comment, from a readability and DRY perspective, it is much better to author APIs that take an options bag, or even a tagged union option bag instead of creating overloads with complex signatures. |
I'm not authoring these APIs; I'm converting a project to TypeScript. We can't get rid of the problem of difficult to use overloads by saying "don't use overloads", since the reason we have overloads is because nothing else will suffice to describe certain JS code.
Neither unions nor destructured object params are capable of describing functions with heterogeneous return types. Additionally, for functions where the return type doesn't matter, you still can't get around using overloads using unions: TypeScript does not support spread parameters consisting of a union of tuples: `(...params: [A] | [A, B] | [C, B, A]): void`.
|
TypeScript Version:
nightly (1.9.0-dev.20160217)
Code
A contrived example:
Expected behavior:
Code above compiles, and the following compiles and runs without errors:
Actual behavior:
Compilation error:
Current approach:
Specify all overloads again above function:
The text was updated successfully, but these errors were encountered: