问题归结为checker 中checkSwitchStatement 中的这段代码,该代码自2016 年以来一直存在:
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);
}
(有类似的代码会影响直接比较x === "x"。)
a === b 的规则和类似的 switch 语句基于双向类型“可比性”的概念,它(省略了很多与此处无关的细节)表示一侧的联合组成部分必须是可分配的到另一方的工会成员。这应该是对两侧的类型是否重叠的启发式方法。启发式方法适用于通常使用对象的方式,但不适用于原语,其中(例如)如果a 的类型是受string 约束的某个类型参数T,我们希望能够比较发给"x";已知T 和"x" 都不能分配给另一个,但T 可能包括"x"。因此,当比较的一侧是文字并集而另一侧不是时,代码会将作为文字并集的一侧替换为底层原始类型。这种情况会在您的代码中触发,其中"x" 是一个文字,而T["type"] 本身不是一个文字,尽管它受到文字联合的约束。
我认为我们应该提出一个问题,建议您的代码应该给出编译错误。而且我写完那句话后,看到artem提出了一个issue,所以我会在那里添加我的分析。
您认为您之前遇到过错误,也许您正在考虑以下代码。类型参数上的属性访问的类型,如果可能的话,会根据类型参数的约束急切地解析,所以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
}
}