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
When determining if a union-typed argument is assignable to the parameters of an overloaded function: we should allow the assignment if — for each member in that union — there exists a compatible overload. the returned type should be the union of all applicable overloads.
// current state of TS compiler, as of TS v4.3.2functiondouble(val: number): number;functiondouble(val: string): string;functiondouble(val: string|number): string|number{if(typeofval==='string'){return`${val}${val}`;}returnval*2;}constnum: number=double(2);// works fineconststr: string=double('hey');// works fineconstprocess=(val: string|number): void=>{double(val);// fails with "No overload matches this call"constdoubled: string|number=double(val);// fails for same reason}
The externally-visible overloads of double are:
(val: number): number;(val: string): string;
Neither overload (taken individually) can accept a val: string | number, but we can see that for each member in the union string | number, there is a compatible overload.
There's enough information here to deduce that "assigning either string or number is supported", and that "if we did so: the result would be either string or number".
In the current example, the union of applicable overloads (val: string | number): string | numberhappens to be equivalent to making the implementation signature visible.
but this will not be true in the general case — for example if an overload (val: Date): Date; existed: it would not be considered applicable (the implementation signature would be required to accommodate it, whereas our union of applicable overloads could exclude it).
🔍 Search Terms
No overload matches this call.
The call would have succeeded against this implementation, but implementation signatures of overloads are not externally visible.
✅ Viability Checklist
This wouldn't be a breaking change in existing TypeScript/JavaScript code
This wouldn't change the runtime behavior of existing JavaScript code
This could be implemented without emitting different JS based on the types of the expressions
This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
I hope the minimal repro above is sufficiently clear.
A brief summary could be:
Permit assignment of union-type arguments to overloaded functions, if that assignment can be satisfied by a union of overloads. This eliminates a case where the compiler might reject an assignment, stating "No overload matches this call".
💻 Use Cases
A less contrived example is what I'm trying to contribute to the Snowpack devserver code — I receive a request req: http.IncomingMessage | http2.Http2ServerRequest, and wish to pluck serializable properties off that union argument, creating a new union Http1RequestData | Http2RequestData.
/** * {@link http.IncomingMessage} as a value type -- exposes data properties * without exposing access to sockets, streams, methods or iterators. */exporttypeHttp1RequestData=Pick<http.IncomingMessage,'aborted'|'httpVersion'|'httpVersionMajor'|'httpVersionMinor'|'complete'|'headers'|'rawHeaders'|'trailers'|'rawTrailers'|'method'|'url'>/** * {@link http2.Http2ServerRequest} as a value type -- exposes data properties * without exposing access to sockets, streams, methods or iterators. */exporttypeHttp2RequestData=Pick<http2.Http2ServerRequest,'aborted'|'httpVersion'|'httpVersionMajor'|'httpVersionMinor'|'complete'|'headers'|'rawHeaders'|'trailers'|'rawTrailers'|'method'|'url'|'authority'|'scheme'>/** * {@link http.IncomingMessage} or {@link http2.Http2ServerRequest} as a value type -- exposes data properties * without exposing access to sockets, streams, methods or iterators. * This is provided so that users of dev-server APIs may make decisions about * how to route a request (or transform its response), without being able to perform * side-effects upon the request handle. */exporttypeHttpRequestData=Http1RequestData|Http2RequestData;functionisHttp2Request(req: http.IncomingMessage|http2.Http2ServerRequest): req is http2.Http2ServerRequest{returnreq.httpVersionMajor===2;}functiondataPropertiesOfRequest(req: http.IncomingMessage): Http1RequestData;functiondataPropertiesOfRequest(req: http2.Http2ServerRequest): Http2RequestData;functiondataPropertiesOfRequest(req: http.IncomingMessage|http2.Http2ServerRequest): HttpRequestData{// there's a large number of props common to both request types, but since they have different optionality and readonly-ness:// I've opted to resist doing a "destructure common props".if(isHttp2Request(req)){const{aborted, httpVersion, httpVersionMajor, httpVersionMinor, complete, headers, rawHeaders, trailers, rawTrailers, method, url, authority, scheme}=req;constrequestData: Http2RequestData={aborted, httpVersion, httpVersionMajor, httpVersionMinor, complete, headers, rawHeaders, trailers, rawTrailers, method, url, authority, scheme};returnrequestData;}const{aborted, httpVersion, httpVersionMajor, httpVersionMinor, complete, headers, rawHeaders, trailers, rawTrailers, method, url}=req;constrequestData: Http1RequestData={aborted, httpVersion, httpVersionMajor, httpVersionMinor, complete, headers, rawHeaders, trailers, rawTrailers, method, url};returnrequestData;}constserver: http.Server|http2.Http2Server=createServer(async(req: http.IncomingMessage|http2.Http2ServerRequest,res: http.ServerResponse|http2.Http2ServerResponse)=>{// this assignment fails!constrequestData: HttpRequestData=dataPropertiesOfRequest(req);// this assignment fails too! for brevity I won't show the `convertToSnowpackResponse` function, // or `SnowpackHttpServerResponse` union type — it's the same idea as above.// mainly showing this to demonstrate that the problem hit me twice.constsnowpackResponse: SnowpackHttpServerResponse=convertToSnowpackResponse(requestData,res);});
Other options could be to make the union of overloads externally-visible (which means repeating yourself, and serves counter to the purpose of my providing overloads with correlated parameter/return types):
Another option is type-narrowing & duplication at the call-site:
// recall that `type HttpRequestData = Http1RequestData | Http2RequestData;`letrequestData: HttpRequestData|undefined;if(isHttp2Request(req)){// req is narrowed to http2.Http2ServerRequest; expression returns Http2RequestDatarequestData=dataPropertiesOfRequest(req);}else{// req is narrowed to http.IncomingMessage; expression returns Http1RequestDatarequestData=dataPropertiesOfRequest(req);}assert(requestData);// make the variable truthy from now on
this wasn't super nice because we had to add a condition, a type-guard, implementation duplication and a truthiness assertion at runtime, purely to satisfy the type-system.
for this particular feature, I have the option of hand-waving a bit — the existing code starts with req: any, downstream code assumes req: http.IncomingMessage, so I could just tiptoe around that rather than trying to retrofit typesafety.
The text was updated successfully, but these errors were encountered:
Suggestion
When determining if a union-typed argument is assignable to the parameters of an overloaded function: we should allow the assignment if — for each member in that union — there exists a compatible overload. the returned type should be the union of all applicable overloads.
The externally-visible overloads of
double
are:Neither overload (taken individually) can accept a
val: string | number
, but we can see that for each member in the unionstring | number
, there is a compatible overload.There's enough information here to deduce that "assigning either
string
ornumber
is supported", and that "if we did so: the result would be eitherstring
ornumber
".In the current example, the union of applicable overloads
(val: string | number): string | number
happens to be equivalent to making the implementation signature visible.but this will not be true in the general case — for example if an overload
(val: Date): Date;
existed: it would not be considered applicable (the implementation signature would be required to accommodate it, whereas our union of applicable overloads could exclude it).🔍 Search Terms
✅ Viability Checklist
⭐ Suggestion
Elaborated in full above.
📃 Motivating Example
I hope the minimal repro above is sufficiently clear.
A brief summary could be:
Permit assignment of union-type arguments to overloaded functions, if that assignment can be satisfied by a union of overloads. This eliminates a case where the compiler might reject an assignment, stating "No overload matches this call".
💻 Use Cases
A less contrived example is what I'm trying to contribute to the Snowpack devserver code — I receive a request
req: http.IncomingMessage | http2.Http2ServerRequest
, and wish to pluck serializable properties off that union argument, creating a new unionHttp1RequestData | Http2RequestData
.Other options could be to make the union of overloads externally-visible (which means repeating yourself, and serves counter to the purpose of my providing overloads with correlated parameter/return types):
Another option is type-narrowing & duplication at the call-site:
this wasn't super nice because we had to add a condition, a type-guard, implementation duplication and a truthiness assertion at runtime, purely to satisfy the type-system.
for this particular feature, I have the option of hand-waving a bit — the existing code starts with
req: any
, downstream code assumesreq: http.IncomingMessage
, so I could just tiptoe around that rather than trying to retrofit typesafety.The text was updated successfully, but these errors were encountered: