Comments (14)
@MartinJohns By distribution over unions I think he means that given the overloads...
declare function foo(v: 1): void;
declare function foo(v: 2): void;
...it should be legal to call foo(v)
with a v
typed as 1 | 2
. Which comes up semi-regularly and I'm almost positive there's at least one open issue about it. My understanding was that the reason that isn't done is for performance reasons because it becomes combinatorially explosive in the number of parameters and union members.
from typescript.
it's one case per overload signature
It's not though - if all the overloads aren't there, it might be able to short-circuit once it finds a failing case, but in principle it still has to enumerate all the possible combinations of union members. The "40 questions" bit I described would still happen, even if the matching overloads aren't there.
Okay I think I understand. You're assuming the implementation will be "normalize the types of both the arguments and the parameters to unions of tuple types and check that every argument case is covered by a parameter case". This is a really natural approach to take - canonicalizing to a sum (union) of products (Cartesian product).
If so, then yes, it is an expensive check! But that assumes the worst-case scenario. You don't need to eagerly expand out the argument nor the parameter types into unions. Keep the argument type as a tuple and only split into cases when an overload partially matches the argument type.
e.g. for arguments [A, B]
and an overload with parameters [D, E]
, the case [A&D, B&E]
is handled by the overload. You then need to check [Exclude<A,D>, B] | [A, Exclude<B,E>]
against the other overloads. If A
is disjoint from D
, it simplifies to [A,B]
. And if A
is a subset of D
, it simplifies to [A, Exclude<B,E>]
.
You could also try to gather the argument type into a product of sums (e.g. foo
) but I expect that's not very useful in practice - if the function can be declared that way, it probably is.
TypeScript already allows assigning a tuple of union types to a union of tuple types and vice versa.
Indeed, but IIRC there's already a (fairly low) limit to the size of the unions that can be expanded out during checking that way before it just fails. Something like 25 if my memory is right.
Seems like you're right! And it doesn't even give a meaningful error message!
This does strongly suggest that a casewise analysis like the above is being used in the code. And that it's an opportunity for optimization.
EDIT: the code - your memory is good!
from typescript.
You forgot to fill out the issue template.
type X = ReturnType<Not> // boolean; false if signature 3 is omitted
This is working as intended and documented: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case).
It is unclear to this author whether the implementation signature belongs in a pure declaration.
Implementation signatures can't be part of interfaces because interfaces don't have implementations. Whether you should have a permissive "catch all" signature as part of your interface or not really depends on your use case.
The only weird thing here is that true as any
resolves to the true
overload, but I feel I've seen this in another issue already.
from typescript.
Implementation signatures can't be part of interfaces because interfaces don't have implementations. Whether you should have a permissive "catch all" signature as part of your interface or not really depends on your use case.
Yes, I'm confusing the terms "implementation signature" and "catch all signature". It seems like catch-all signatures are a workaround inspired by implementation signatures. And they would largely not be needed if function overload distributed over union.
The only weird thing here is that
true as any
resolves to thetrue
overload, but I feel I've seen this in another issue already.
That's my understanding too. Maybe a duplicate of #39833?
from typescript.
Yes, I'm confusing the terms "implementation signature" and "catch all signature".
function foo(v: 1): void;
function foo(v: 2): void;
function foo(v: 1 | 2): void; // catch all overload, just another overload signature that's visible to the caller
function foo(v: 1 | 2): void { // implementation signature, not visible for caller
// implementation be here
}
And they would largely not be needed if function overload distributed over union.
No idea what you mean. This behavior is intentional.
That's my understanding too. Maybe a duplicate of #39833?
Yes, that's the one.
from typescript.
@MartinJohns By distribution over unions I think he means that given the overloads...
declare function foo(v: 1): void; declare function foo(v: 2): void;...it should be legal to call
foo(v)
with av
typed as1 | 2
. Which comes up semi-regularly and I'm almost positive there's at least one open issue about it. My understanding was that the reason that isn't done is for performance reasons because it becomes combinatorially explosive in the number of parameters and union members.
Exactly. In this your example, it simplifies out to void
. In general, the return types of chained calls will tend to unknown
or any
. Only if the output types are disjoint will you get the combinatorial growth (and it’s a sum not the product behavior of generic or template string types!).
from typescript.
The combinatorial behavior I’m talking isn’t about the return types, but how much work has to be done to typecheck the call sites. Basically if there are multiple arguments, each with a union type, you’d have to take each union apart and, for each member, check if it matches one of the overloads. That’s on average exponential in the number of arguments provided.
from typescript.
The combinatorial behavior I’m talking isn’t about the return types, but how much work has to be done to typecheck the call sites. Basically if there are multiple arguments, each with a union type, you’d have to take each union apart and, for each member, check if it matches one of the overloads. That’s on average exponential in the number of arguments provided.
I’m still not seeing it. Could you give a concrete example?
from typescript.
Suppose you have
declare function foo(x: A, y: A): void;
declare function foo(x: A, y: B): void;
declare function foo(x: B, y: A): void;
declare function foo(x: B, y: B): void;
declare let x: A | B;
declare let y: A | B;
foo(x, y);
This call should succeed according to the types. But to know that, you have to answer all of these in the affirmative:
- Is
foo(A, A)
a valid call? - Is
foo(A, B)
a valid call? - Is
foo(B, A)
a valid call? - Is
foo(B, B)
a valid call?
and each of those individual checks has to check all the overloads for a match. Now increase the size of the unions and/or the number of parameters and this very quickly gets out of hand.
from typescript.
- That's not a combinatorial explosion in checking difficulty - it's one case per overload signature. Yes, it would require you to declare combinatorially many cases, but if the function can be factored into a single signature, it makes sense to do so (
foo(x:A|B, y:A|B): void
). If not, then the most general signature is awkward anyway. - TypeScript already allows assigning a tuple of union types to a union of tuple types and vice versa. That's morally the same process:
declare let x: [A,A] | [A,B] | [B,A] | [B,B] declare let y: [A|B, A|B] x = y
from typescript.
it's one case per overload signature
It's not though - if all the overloads aren't there, it might be able to short-circuit once it finds a failing case, but in principle it still has to enumerate all the possible combinations of union members. The "40 questions" bit I described would still happen, even if the matching overloads aren't there.
TypeScript already allows assigning a tuple of union types to a union of tuple types and vice versa.
Indeed, but IIRC there's already a (fairly low) limit to the size of the unions that can be expanded out during checking that way before it just fails. Something like 25 if my memory is right.
from typescript.
Overload resolution selects the first matching signature (everyone loves it when language use simple algorithms, right? right??). There have been requests to do something more involved when any
is involved, but none of them have really succeeded in staking out a definition doesn't break huge swathes of user code; many times overloads are arranged with a "common" case first and an "uncommon" case second and people want the "common" one to be selected in an any
invocation.
The one caveat to "first" is that there's an initial pass that tries to discard assignments to any
, so if you really need your interface to do a particular thing on any
, you can declare that without breaking more-specific inferences:
interface Not{
(x: any): boolean;
(x:false):true // signature 1
(x:true):false // signature 2
(x:boolean):boolean // signature 3. Unclear whether this should be declared or not
}
declare var not:Not
const p = not(true);
// ^? still 'false'
from typescript.
The one caveat to "first" is that there's an initial pass that tries to discard assignments to any, so if you really need your interface to do a particular thing on any, you can declare that without breaking more-specific inferences
Clever workaround, but that defeats the type annotation on the argument.
Somehow the only workaround that seems to work is to make it a generic function:
interface Not2 {
<const T extends boolean> (x:T) : T extends true ? false : T extends false ? true : never
}
Overload resolution selects the first matching signature.
Somehow though, adding (or moving) the catch-all signature first results in matching the second overload, with weird results:
interface Not{
(x: boolean): boolean;
(x: false): true;
(x: true): false;
(x: boolean): boolean;
}
declare var not:Not
const notAny = not(true as any) // true !?!?
// ^?
It seems in intellisense that the boolean
-accepting overloads are being shuffled to number 3 and 4 in the list!
from typescript.
This issue has been marked as "Working as Intended" and has seen no recent activity. It has been automatically closed for house-keeping purposes.
from typescript.
Related Issues (20)
- spread operator infer to wrong type with method override and generic HOT 6
- Intellisense is slow and some things don't work HOT 7
- Constructor cannot access own protected instance when typed as union HOT 3
- This comparison appears to be unintentional because the types 'U[K]' and 'T[K]' have no overlap.ts(2367) HOT 3
- [isolatedDeclarations] Optional parameter in arrow function typed as imported generic type gets emitted as `any` HOT 2
- Easier use of indexed access types on nullable/optional types HOT 2
- Narrow conditional type HOT 2
- Error for path not found when ended with asterisk
- `tryGetPerformanceHooks` throws on Stackblitz with ts 5.5.3 HOT 3
- Avoid providing promise methods as completions at return positions in non-async functions
- Consider disallowing `in` operator use with arbitrary key operands on closed types for which the keys are known HOT 2
- Convert to ESM uses template strings instead of string literals HOT 1
- Running type checker produces js out of memory error HOT 2
- Callback argument type not inferred for union of interfaces HOT 1
- Como Hackear Instagram 2024 Hackear Instagram Metodos Atualizados #221857
- [transpileDeclaration API] ts.transpileDeclaration always returns empty sourceMapText
- Incorrect Error Message: "Cannot assign to 'name' because it is a constant" Instead of "Identifier 'name' is not defined" for Undefined Identifier Assignment HOT 3
- Generic with extends yields to different type than directly specifying it HOT 5
- Instantiation expressions don't reject incompatible signatures in situations with mixed call and construct signatures HOT 1
- Parameters of overridden methods are made implicit any when using JSDoc `@override` HOT 16
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typescript.