Skip to content

Type reverts to declaration type inside loop #49529

@Matheus28

Description

@Matheus28

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

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;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions