Bug Report
π Search Terms
control flow analysis type guard narrowing widening no-op
π Version & Regression Information
- This changed between versions 4.9.5 and 5.0.2
β― Playground Link
Playground Link
π» Code
type FooNode = {
kind: 'foo';
children: Node[];
};
type BarNode = {
kind: 'bar';
}
type Node = FooNode | BarNode;
type Document = {
kind: 'document';
children: Node[];
};
declare function isNode(node: unknown): node is Node;
declare function isBar(node: Node): node is BarNode;
export function visitNodes<T extends Node>(
node: Document | Node,
predicate: (testNode: Node) => testNode is T,
): void {
// Spooky action at a distance: this no-op statement causes the compiler to reject the assignment
// guarded by the conditional, below. Comment it out to fix the failure.
isNode(node) && predicate(node);
if (!isNode(node) || !isBar(node)) {
// This line should compile fine!
const nodes: Node[] = node.children;
}
}
The original code that turned this issue up was a bit more indirect and looked like:
const shouldYield = isNode(node) && predicate(node);
if (shouldYield) {
yield node;
}
const hasChildren = !isNode(node) || !isBar(node);
if (hasChildren) {
yield* yieldAll(node.children);
}
I was able to work around the issue by inlining the guards directly into the if rather than using an intermediate value.
π Actual behavior
The assignment const nodes: Node[] = node.children; fails, complaining that
Property 'children' does not exist on type 'FooNode | Document | T'.
Property 'children' does not exist on type 'T'.(2339)
but only if the unused type guard at the beginning of the function is present. Commenting the dead code out fixes the issue, and the compiler correctly narrows the type of node to just FooNode | Document.
π Expected behavior
The unused type guard/dead code has no effect on the inferred type of the value.
Bug Report
π Search Terms
control flow analysis type guard narrowing widening no-op
π Version & Regression Information
β― Playground Link
Playground Link
π» Code
The original code that turned this issue up was a bit more indirect and looked like:
I was able to work around the issue by inlining the guards directly into the
ifrather than using an intermediate value.π Actual behavior
The assignment
const nodes: Node[] = node.children;fails, complaining thatbut only if the unused type guard at the beginning of the function is present. Commenting the dead code out fixes the issue, and the compiler correctly narrows the type of
nodeto justFooNode | Document.π Expected behavior
The unused type guard/dead code has no effect on the inferred type of the value.