-
Notifications
You must be signed in to change notification settings - Fork 13.1k
more precise type facts for intersection #47282
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
Conversation
|
@typescript-bot test this |
|
Heya @andrewbranch, I've started to run the extended test suite on this PR at 2489916. You can monitor the build here. |
|
Heya @andrewbranch, I've started to run the perf test suite on this PR at 2489916. You can monitor the build here. Update: The results are in! |
|
Heya @andrewbranch, I've started to run the parallelized Definitely Typed test suite on this PR at 2489916. You can monitor the build here. |
|
Heya @andrewbranch, I've started to run the inline community code test suite on this PR at 2489916. You can monitor the build here. Update: The results are in! |
|
@andrewbranch |
|
@andrewbranch Here they are:Comparison Report - main..47282
System
Hosts
Scenarios
Developer Information: |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (flags & TypeFlags.Object && !ignoreObjects) { | ||
| if (flags & TypeFlags.Object) { | ||
| if (ignoreObjects) { | ||
| return TypeFacts.None; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ignoreObjects is only going to be true if we are in the middle of computing type facts for an intersection type. In this case, the neutral value returned by getTypeFacts can't be TypeFacts.All (as it was before), because we are oring the type facts. So it should be TypeFacts.None.
|
I'm definitely in favor of fixing this issue, but I'm not crazy about adding 175 lines of code to do it. Couldn't we just say that certain type facts for intersections are computed as |
Fixes #45801
Brief background
When narrowing a type, we rely on
TypeFactsflags that tell us what narrowing facts could be true for a value of a given type. The flags cover the possible results of narrowing usingtypeof(e.g.TypeFacts.TypeofEQString), comparsions tonullorundefined(e.g.TypeFacts.UndefinedOrNullmeansif (v === null || v === undefined)could be true), or if the value is truthy or falsy (e.g.TypeFacts.Truthymeansif (v) { ... }could be true).To compute the type facts for a type
T, we callgetTypeFacts(T).The issue
Up until v4.2, when computing type facts for an intersection type, we
ored the facts of each of the intersection type's component type. e.g.getTypeFacts(A & B)=getTypeFacts(A) | getTypeFacts(B).Starting at v4.3, this changed to an
andof the facts of each intersection type's component type, e.g.getTypeFacts(A & B)=getTypeFacts(A) & getTypeFacts(B), to fix cases like the following:If we have
T & number, we know the value can only be of type number (or null or undefined, in non-strict), never of type string. However, we don't know anything aboutT, so when computing type facts forT, we think it can be anything at all (including of type string). If weorthe type facts forTandnumber, then, we get thatT & numbercould be string. If weandthe type facts, we knowT & numbercan't be of type string, becausenumbercan't be of type string.However, in some cases we don't want an
andof the type facts. Consider the example from issue #45801:For type
F & Meta, we know values ofFhave to be functions, sinceFhas a call signature declared. But typeMetadoesn't, it is a regular object. If weandthose facts, we concludeF & Metacannot be a function, because we don't think values ofMetacould be functions (no call signature). So in this case, we probably want toorthe type facts.(Note: We could include
TypeFacts.TypeofEQFunctionin the flags returned for object types such asMetaabove, and keep usingandfor computing type facts of intersection types. I think we don't want to do that, since we explicitly make that distinction ingetTypeFacts's code and it generally seems a useful distinction.)The possible fix
Considering both examples above, it seems we sometimes know a fact will always be true (i.e. true for all values of a type), e.g. values of type
T & numbercan't be strings (if (typeof v === 'string')can't be true). This contrasts with the other uses of type facts, which are facts that can be true for values of type T, but are not necessarily always true/true for all values.So that's why sometimes we want to use an
andto compute facts of intersection types, and sometimes we want to use anor.tl;dr
This PR changes the way we compute type facts of intersection types. For each component type, in addition to computing its type facts, we also compute which facts are always true (true for all values of that type) and which are always false.
We then
orthe regular type facts of the component types, and we alsoorthe always false facts. We then exclude the facts that are always false from the regular facts.So, in cases like
T & number, even though when weorthe components' type facts, we include a flag such asTypeFacts.TypeofEQString(because that's one of the flags forT), we will delete it later becauseTypeFacts.TypeofEQStringis always false for typenumber.Questions I had
When isIt seemsTypeFacts.TypeofEQHostObjectalways false?typeof v === "xxx"("xxx" being something other than the usual strings returned bytypeof) can only be true ifvis a host object (and hosts return a customtypeoffor those), or if we don't yet recognize the "xxx" in question (e.g. if there's ever a newFractiontype andtypeof v === "fraction"). So it should always be false for known primitives at least.Should we care about contradictory types (e.g.I'm assuming we shouldn't, because existing code forstring & number) ingetTypeFactsorgetAlwaysFalseTypeFacts?getTypeFactsdoesn't address this, and this looks like somethinggetIntersectionTypetakes care of.