-
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
Use different relation when checking overload vs implementation signature compatibility #943
Comments
Was looking at wrong issue |
Come up with some reasonable behavior specification |
+1, this is unfortunate because, as you said, we trade type safety in our implementation in favor of better type safety for consumers of our library. related: palantir/plottable#3010 (comment) |
I have an implementation of "assignable to or from" on return types working, but @JsonFreeman suggested an approach where we take the union of overload return types and check if the implementation is compatible with the produced union. So with function f(x: number): Dog;
function f(y: string): Cat;
function f(x: number | string): /*????*/ { return undefined; } That way, Of course, this might get a little unwieldy when you might return 20 different types depending on the overload, and your implementation has to name them all again. |
If the user has lots of overloads returning different specific animals, they could add one overload at the end, which returns Animal. That way they don't have to write out the union of 20 different types. This is pretty similar to the string literal overloads in lib.d.ts, which all have a catch-all overload that returns |
I'll also add that if the user does not want to write the long union type, nor do they want to provide a catch-all overload, return type inference can infer the return type for the implementation signature. |
@JsonFreeman not quite, you'll get something like "No common supertype exists between returned expressions" or we'd infer |
That's true, but it might make sense to lift the common supertype restriction in the presence of overloads. If you are going to check the implementation return type against all the overload return types, I'm not sure what would be the point of enforcing that there exists a common supertype. It's a bit like how you infer the union type of the return expressions (instead of the common supertype) when the function expression is contextually typed. |
We discussed this a bit offline. There are two scenarios that the union of all returns doesn't answer:
The second one has certain non-trivialities associated with it. Consider the following: function hasKind(x: Node, kind: "StringLiteral"): x is LiteralNode;
function hasKind(x: Node, kind: "PropertyAccess"): x is PropertyAccess;
function hasKind(x: Node, kind: "StringLiteral" | "PropertyAccess"): /*????*/ {
x.kind === kind;
} Specific issues in figuring out what to write for
For this reason, I think we are leaning towards checking for assignability to/from. |
function func(name: "Cat"): Cat;
function func(name: string): Animal {
return new Dog();
} So you are effectively assigning a Dog-returning function to a Cat-returning function, which ideally should not be allowed. The key is that this bivariance rule would not require users to be exhaustive in their list of overloads. They would not have to account for everything that the function could return. If you indeed want the Animal case in the original bug description to succeed, and you don't mind letting through examples like the one above, then bivariance will get you that. But that also means you don't want to require the overload set to be exhaustive. Am I correct then that you don't care about checking whether the overload return types are exhaustive with respect to the implementation? |
@JsonFreeman |
@JsonFreeman that's correct, that's the general feeling we had offline. If @ahejlsberg or @RyanCavanaugh would like to weigh in, it'd be welcome. All of you are encouraged to leave feedback on #6075. 😃 |
This should be fixed for TypeScript 1.8. |
Consider some code:
Perhaps surprisingly, this yields errors on the overload signatures because the function implementation signature is not assignable to the overload signatures. This is unfortunate for implementors as they must use
:any
when implementing a function with disjoint parameter types in its overloads. Now that union types are possible, the implementor off
could even writeGiraffe|Elephant
to get more specific type checking of their return expressions.One option is to change the relation in 6.1 from "assignable to" to "assignable to or from". This solves the issue above, but has a negative side effect of allowing an implementation signature to have more required parameters than any of the overloads. This might be a desirable trade-off compared to not allowing implementations to have good return expression checking. Alternatively, we could create some new kind of relation that is bivariant for return types but maintains the existing requirements of parameter arity.
Note that we do want to use the opposite assignability relation here as the implementation signature may be of an inconsequentially more-specific type.
The text was updated successfully, but these errors were encountered: