You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
functiontest1(){typestringType1="foo"|"bar";typestringType2="baz"|"bar";interfaceTemp1{getValue(name: stringType1): number;}interfaceTemp2{getValue(name: stringType2): string;}functiontest(t: Temp1|Temp2){constz=t.getValue("bar");// Should be fine}}
The above now has no errors, and z is of type number | string.
The caveats are that only one type in the list of union members is allowed to have overloads, and only one type within the list of union members is allowed to have type parameters. The first is because creating the power set of overloads results in a (long) list of overloads whose ordering is unclear. This is maybe surmountable in the future, but for now this seems like a sane place to stop. The second is because introducing multiple sets of type parameters requires unifying them, as multiple type parameters in the same position likely do not infer well (eg, (arg: A & B & {x: string}) => A | B | {x: string}). Again, with more infrastructure, this is potentially fixable, but for now, this seems like a good stopping point. In the case of a union where either of those things are true, we simply do not create a union signature and fall back to the old behavior of not making a signature at all (and so you'll get the "type has no call or construct signatures" error).
There's one open question here which is contextual typing: In the new test I added, I included one of the motivating examples: using forEach on a union of array types:
With this change, that is now callable, but the argument to the callback you pass will be implicitly any if defined inline. This is because the argument types are intersected, and the intersection of two signatures currently creates an overload list of signatures. As-is, we have logic that explicitly removes contextual typing from overload lists, so no contextual type is provided. This seems incorrect - I believe fixing this could go in one of two ways:
Generate a contextual type from an overload list with similar requirements/caveats that union signature creation has.
Generate a combined intersection signature just like we now do for union signatures, rather than an overload set.
Even as-is, this seems like a huge improvement over what we do today; we could also choose to do nothing and require an annotation and still be better off than now. I'd rather we make the usecase work fully without user code changes, but it's still better than what we do in master.
I also have one small TODO I'm still working on (and then writing tests for) which is deriving the names for the amalgamated signature's parameters from the input signatures' parameter names. It's more complex than it was the last time I tried this since we added tuple types - I don't really know how to look up symbol names through the effective argument types which may have been fulfilled by a tuple.
Post-design meeting update. In the meeting, we said we wanted to look at changing contextual typing based on overloading. Looked at it, and recognized that when applied, things like
which was not an error outside noImplicitAny mode (as x had no contextual type and was implicitly any) becomes an error (as x is assigned number | string which then flows to the return type and is incompatible with both overload return types).
In lieu of fixing this now, we think we'll wait on being able to properly run a lambda in multiple contexts to fix that and leave this PR as is (so the .forEach case is still an implicit any error).
Also: I've finished the TODO in the OP - parameter names are now copied into the combined signature (and there are tests verifying this).
@ahejlsberg this is done and could use a review now.
This PR allows unions of signatures to (almost always) be callable, however take the union var c: (x: number) => string | (x: string) => number. I can call c, but there is no value I can use for the first parameter that actually satisfies both signatures (except something of type any). Our error message in this case will be that your argument type is not assignable to string & number, which is pretty easy to understand. However if that vacuous intersection is simplified to never by the presence of a union (eg, one parameter type is boolean | number and the other is string), it can be harder to understand. So a signature always exists and is always callable, however individual parameters may be unsatisfiable, and that may be represented with the never type for that parameter. Keeping these signatures around is important for correctly checking any other satisfiable arguments (this way you can continue in the presence of errors), allowing just the parameter to be cast to allow the unsatisfiable call (rather than casting the variable you're calling and losing checking on the other arguments), and for allowing the type to be satisfied in contravariant positions (eg, the parameter of a parameter) where the parameter can simply be ignored or unused.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7294 with caveats.
The above now has no errors, and
z
is of typenumber | string
.The caveats are that only one type in the list of union members is allowed to have overloads, and only one type within the list of union members is allowed to have type parameters. The first is because creating the power set of overloads results in a (long) list of overloads whose ordering is unclear. This is maybe surmountable in the future, but for now this seems like a sane place to stop. The second is because introducing multiple sets of type parameters requires unifying them, as multiple type parameters in the same position likely do not infer well (eg,
(arg: A & B & {x: string}) => A | B | {x: string}
). Again, with more infrastructure, this is potentially fixable, but for now, this seems like a good stopping point. In the case of a union where either of those things are true, we simply do not create a union signature and fall back to the old behavior of not making a signature at all (and so you'll get the "type has no call or construct signatures" error).There's one open question here which is contextual typing: In the new test I added, I included one of the motivating examples: using
forEach
on a union of array types:With this change, that is now callable, but the argument to the callback you pass will be implicitly
any
if defined inline. This is because the argument types are intersected, and the intersection of two signatures currently creates an overload list of signatures. As-is, we have logic that explicitly removes contextual typing from overload lists, so no contextual type is provided. This seems incorrect - I believe fixing this could go in one of two ways:Even as-is, this seems like a huge improvement over what we do today; we could also choose to do nothing and require an annotation and still be better off than now. I'd rather we make the usecase work fully without user code changes, but it's still better than what we do in
master
.I also have one small TODO I'm still working on (and then writing tests for) which is deriving the names for the amalgamated signature's parameters from the input signatures' parameter names. It's more complex than it was the last time I tried this since we added tuple types - I don't really know how to look up symbol names through the effective argument types which may have been fulfilled by a tuple.