Typescript Tagged union not type-checked in switch statements

Typescript Tagged union not type-checked in switch statements



I'm using Typescript 3.0.1. In the code below, why is there no compiler error on line 7? I used to have this behavior before; has it been taken out of Typescript or is there some weird regression?


type A = type :"a"
type B = type :"b"
type Any = A | B

function get<T extends Any>(x: T["type"]): T|undefined
switch (x)
case "x": return undefined
default: return undefined






looks like a bug, I created this issue
– artem
Aug 30 at 0:47





Thanks @artem for submitting this issue, I will follow it
– prmph
Aug 30 at 0:53




1 Answer
1



The problem comes down to this code in checkSwitchStatement in the checker, which has been there since 2016:


checkSwitchStatement


let caseType = checkExpression(clause.expression);
const caseIsLiteral = isLiteralType(caseType);
let comparedExpressionType = expressionType;
if (!caseIsLiteral || !expressionIsLiteral)
caseType = caseIsLiteral ? getBaseTypeOfLiteralType(caseType) : caseType;
comparedExpressionType = getBaseTypeOfLiteralType(expressionType);

if (!isTypeEqualityComparableTo(comparedExpressionType, caseType))
// expressionType is not comparable to caseType, try the reversed check and report errors if it fails
checkTypeComparableTo(caseType, comparedExpressionType, clause.expression, /*headMessage*/ undefined);



(There is similar code that affects a direct comparison x === "x".)


x === "x"



The rules for a === b and the analogous switch statement are based on the concept of bidirectional type "comparability", which (omitting a lot of detail that is irrelevant here) says that a union constituent of one side has to be assignable to a union constituent of the other side. This is supposed to be a heuristic for whether the types of the two sides overlap. The heuristic works well for the ways objects are typically used but not so well for primitives, where (for example) if the type of a is some type parameter T constrained by string, we want to be able to compare it to "x"; neither T nor "x" is known to be assignable to the other, but T may include "x". So when one side of the comparison is a union of literals but the other is not, the code replaces the side that is a union of literals by the underlying primitive type. That case is triggering in your code, where "x" is a literal and T["type"] is not itself a literal, though it is constrained by a union of literals.


a === b


a


T


string


"x"


T


"x"


T


"x"


"x"


T["type"]



I think we should file an issue proposing that your code should give a compile error. And after I wrote that sentence, I saw artem filed an issue, so I will add my analysis there.



Re your belief that you got an error before, maybe you were thinking of the following code. The type of a property access on a type parameter is eagerly resolved if possible based on the constraint of the type parameter, so y.type is considered as having type "a" | "b", and both sides of the comparison are a union of literal types, so the special case does not apply.


y.type


"a" | "b"


type A = type :"a"
type B = type :"b"
type Any = A | B

function get<T extends Any>(y: T): T | undefined
switch (y.type)
case "x": return undefined
default: return undefined






Yes, @McCutchen, I believe your example is what I saw before. I'm regularly surprised at how little it takes for Typescript inference to fail. We'll see what comes out of this issue.
– prmph
Aug 30 at 1:01



Required, but never shown



Required, but never shown






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)