The optional chaining operator (?.) offers protection against null and undefined values, but it does not narrow down a union-typed expression to only those members that are known to contain a specific key. For more information, refer to this comment in Microsoft/TypeScript#33736.
One issue arises from the fact that object types in TypeScript are open and can have properties beyond what the compiler recognizes. Even if the property block.data is truthy, it doesn't guarantee the existence of block.data.text:
const oof = {
id: "x",
author: "weirdo",
data: 123
}
mapper(oof); // approved
In this case, oof qualifies as a valid CommentBlock, allowing mapper(oof) without error. Despite oof.data being truthy, oof.data.text resolves to undefined. This situation could lead to issues if you try to treat it as a string.
Therefore, enabling optional chaining to filter unions as desired would be unsound and lacking type safety.
A workaround would involve using the in operator as a type guard, especially when scenarios like oof are rare:
function mapper(block: BlockTypes) {
if ("data" in block) {
block.data.text.toUpperCase()
}
}
This approach eliminates compiler errors but remains unsound since runtime errors may still occur with mapper(oof). Proceed cautiously.
It's interesting that TypeScript permits 'in' to function this way while limiting similar constructs. To understand why this decision was made, check out the discussion on microsoft/TypeScript#10485. The TypeScript team's rationale may shed light on this choice despite potential misuse of other constructs like bar.foo && bar.foo.baz. The team believes such constructs are less prone to mistakes based on their observations. 🤷♂️
Explore the code on Playground