Type reverts to declaration type inside loop
#49,529 opened on 2022幎6æ13æ¥
Repository metrics
- Stars
- Â (48,455 stars)
- PR merge metrics
-  (å¹³åããŒãž 6d 17h) (30d ã§ 9 merged PRs)
説æ
Bug Report
ð Search Terms
- type revert
- type for loop wrong
- type narrowing loop
ð Version & Regression Information
Happens in versions 3.3.3333 to 4.8.0-dev.20220613 (latest nightly). I couldn't test prior versions as that's the oldest the playground will go.
⯠Playground Link
ð» Code
interface XMLNode {
}
// Other functions have the same signature, but it is not needed to replicate this issue
function accessArray(node:XMLNode, createIfNotExists:true):XMLNode;
function accessArray(node:XMLNode, createIfNotExists:boolean):XMLNode|undefined;
function accessArray(node:XMLNode, createIfNotExists:boolean = false):XMLNode|undefined {
return undefined;
}
function accessCatalogEntry(createIfNotExists:boolean):XMLNode|undefined {
return undefined;
}
function accessFieldValue(createIfNotExists:boolean):XMLNode|undefined {
let cur = accessCatalogEntry(createIfNotExists);
if(!cur){
return undefined;
}
// At this point, cur is XMLNode
for(let i = 0; i < 5; ++i){
// But here, cur is XMLNode|undefined
let tmp = accessArray(cur, createIfNotExists);
// Declaring the type of tmp to be XMLNode|undefined, fixes this issue
if(!tmp){
return undefined;
}
cur = tmp; // This line is what's confusing the compiler
}
return cur;
}
ð Actual behavior
The compiler forgets that cur can only be XMLNode inside the loop. It prints an error on the line with the call to accessArray since it thinks cur might be undefined
ð Expected behavior
It is provably that its value will always be XMLNode, since once the loop is entered, cur is XMLNode, and the only assignment to it comes from a variable that is narrowed to also be XMLNode.
To be frank, the logic seems a bit circular and hard to analyze from the compiler perspective. Removing the cur = tmp; is enough to remove the type error, for example.
Changing the code to this is also enough to let the compiler type check properly:
function accessFieldValue_workaround1(createIfNotExists:boolean):XMLNode|undefined {
let cur_ = accessCatalogEntry(createIfNotExists);
if(!cur_){
return undefined;
}
// At this point, cur_ is XMLNode
// This line will enforce the typecheck to know the type is XMLNode inside the loop too
let cur = cur_;
for(let i = 0; i < 5; ++i){
let tmp = accessArray(cur, createIfNotExists);
if(!tmp){
return undefined;
}
cur = tmp;
}
return cur;
}