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
{{ message }}
Repository navigation
Improvement to getNarrowedType changes lodash's isArray #52827
declarefunctionisArrayLike(value: any): value is {length: number};declareconstvalue: {[index: number]: boolean,length: number}|undefined;if(isArrayLike(value)){constresult: {length: number}=value;}else{constresult: undefined=value;}
Before #52282: value: undefined in the false branch.
After #52282: value: { [index: number]: boolean, length: number } | undefined in the false branch.
There are a lot of subtleties at play here. Some facts:
Following Improvements to strictSubtypeRelation and getNarrowedType #52282, when the original and asserted types are non-identical mutual subtypes (in the regular subtype relationship), we favor the asserted type in the true branch, but preserve the original type in the false branch.
The types { length: number } and { [index: number]: boolean, length: number } are mutual subtypes in the regular subtype relationship.
Type { length: number } is the supertype of { [index: number]: boolean, length: number } in the strict subtype relationship.
When control flows join we perform subtype reduction on the union of the branch types according to the strict subtype relationship.
When the type in one of the branches of joining control flows is declared type of the variable, we forego subtype reduction and just continue with the declared type.
In the following example, value ends up with its original type after the if statement because of (5):
declarefunctionisArrayLike(value: any): value is {length: number};declareconstvalue: {[index: number]: boolean,length: number}|undefined;if(isArrayLike(value)){value;// { length: number }}else{value;// { [index: number]: boolean, length: number } | undefined}value;// { [index: number]: boolean, length: number } | undefined
However, below the exact declared type doesn't occur in either of the branches, so we subtype reduce according to (4):
declarefunctionisArrayLike(value: any): value is {length: number};declareconstvalue: {[index: number]: boolean,length: number}|string;if(isArrayLike(value)){value;// { length: number } | string}else{value;// { [index: number]: boolean, length: number }}value;// { length: number } | string
So, we generally do a better job of getting back to the original type in control flow joins, but it still isn't perfect. The real culprit here is our desire to go with the asserted type in the true branch when we have mutual subtypes. A more consistent strategy would be to only go with the asserted type when it is a pure subtype of the original type (because it'll then always be removed by subtype reduction). Basically the state we were in before #50044. But that brings about other issues...
I think we're okay for 5.0 but we should continue to think about this.
Activity
ahejlsberg commentedon Feb 24, 2023
There are a lot of subtleties at play here. Some facts:
strictSubtypeRelationandgetNarrowedType#52282, when the original and asserted types are non-identical mutual subtypes (in the regular subtype relationship), we favor the asserted type in the true branch, but preserve the original type in the false branch.{ length: number }and{ [index: number]: boolean, length: number }are mutual subtypes in the regular subtype relationship.{ length: number }is the supertype of{ [index: number]: boolean, length: number }in the strict subtype relationship.In the following example,
valueends up with its original type after theifstatement because of (5):However, below the exact declared type doesn't occur in either of the branches, so we subtype reduce according to (4):
So, we generally do a better job of getting back to the original type in control flow joins, but it still isn't perfect. The real culprit here is our desire to go with the asserted type in the true branch when we have mutual subtypes. A more consistent strategy would be to only go with the asserted type when it is a pure subtype of the original type (because it'll then always be removed by subtype reduction). Basically the state we were in before #50044. But that brings about other issues...
I think we're okay for 5.0 but we should continue to think about this.