Description
Check for Strict Subtyping and Fall Back to Regular Subtyping in getNarrowedType
-
Type guard affects type of variable in surprising way #50916
- User-defined type-guards kept building up bigger and bigger types.
- One issue - UDTG use the "regular" subtype reduction.
unknown
should narrow to the asserted type.- What about any array where the type guard says "this array is an array of
{}
?- Want to keep the original type.
-
This definitely comes up with index signatures,
any
vs.unknown
. -
Strict subtype relationship aims to create a partial ordering among types.
-
If you end up with mutual subtypes, then at the "join points" of control flow analysis you will possibly lose the original type.
-
We did have to fix a few things in strict-subtyping in 5.0 in strict-subtype (Improvements to
strictSubtypeRelation
andgetNarrowedType
#52282) -
Check for strict subtypes and then regular subtypes in getNarrowedType #52984
- Ran into issue with lodash
isArrayLike(value: any): value is { length: number }
- Using the asserted type only if the asserted type is a strict subtype of the original means that you can get back to the original.
- But was too breaky.
- In cases where neither is a strict subtype of the other, to avoid breaks, fall back to using the regular subtyping relationship.
- Not perfect, but better than today
-
Theoretically can break this:
type FooLike = { readonly a: string, b: string } | { c: number }; declare function isBar(x: any): x is { a: string }; function fn(y: FooLike) { if (isBar(y)) { y.b; } }
-
Idea: once test cases added, once reviewed, merge. Then run on top 200-300 GH repos.
-
Regardless of results, current state is pretty bad.
- 3 individual bug reports in the last few days for 5.0.
-
Any reason why we have to "merge" instead of falling back to original type?
-
Info is lossy as to why something got narrowed.
Circularity Errors Depending on Ordering
function f() {
const b = new Bar();
// Changes depending on if the next line is commented.
console.log(b.value);
}
class Bar<T> {
num!: number;
// Also changes depending on if this next line is commented.
field: number = (this as Bar<any>).num;
value = (this as Bar<any>).num;
// ^?
// ~~~~~ implicit any
}
- Sometimes we hit a circularity, sometimes we don't.
- Even with inlay hints/semantic highlighting off.
- Get an error in the editor, but not the compiler.
- In an editor context, the order of type-checking two files changes.
- This is all due to variance computation.
- Why?
- Need to see if the
this
type can be related toBar<any>
. - Means eventually we need to see if
Bar<T>
is related toBar<any>
. - That means we need to calculate variance of the type parameters of
Bar
. - And so this variance computation means we will re-encounter
Bar<T>
in the resolution stack because in the editor, this might
- Need to see if the
- Why?
- Is it possible to "reset" the resolution stack when we start doing variance computations?
- What are the implications of doing that?
- Could get strange behavior, but worth trying.
- But type variances can relate on other types' type variances!
- If you reset the resolution stack, you need to keep track of when you do that.
- "Resolution resolution stack?" 🥴😵💫
- No, idea is you'd only reset the stack the first time you start computing variance.
- It seems like we really shouldn't need to see if
this
is related toBar<any>
.-
It should be "trivial" to just fetch the type of
num
without checking the type assertion. -
Type resolution vs. type checking.
-
Could defer the rest of the computation?
-
Resolution could be smarter and not trigger checking.
-
Not a sufficient fix - can encounter the same issue with overload resolution issues. May not be enough.
class Bar<T> { num!: number; // Swap to remove error Value = callme(this).num; Field: number = callme(this).num; } declare function callme(x: Bar<any>): Bar<any>; declare function callme(x: object): string;
- Not clear what you would defer checking here - checking and resolution is intertwined.
-
Octal Escape Sequences
- Octal escape sequences are disallowed in strict mode and disallowed in ES5.
- ES3 is deprecated now.
- Also a perf win!
- Need to better understand the nuances - octals, escape sequences, inside/outside of strings.
- But we want it in TS 5.1.