Closed
Description
declare function isArrayLike(value: any): value is { length: number };
declare const value: { [index: number]: boolean, length: number } | undefined;
if (isArrayLike(value)) {
const result: { length: number } = value;
} else {
const result: undefined = value;
}
Before #52282: value: undefined
in the false branch.
After #52282: value: { [index: number]: boolean, length: number } | undefined
in the false branch.
This is more consistent, although the full repro at DefinitelyTyped/DefinitelyTyped#64406 seems less consistent than the 4.9 semantics.
Activity
ahejlsberg commentedon Feb 24, 2023
There are a lot of subtleties at play here. Some facts:
strictSubtypeRelation
andgetNarrowedType
#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,
value
ends up with its original type after theif
statement 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.