为什么括号会影响 TypeScript 中的类型缩小?
Why do parentheses affect type narrowing in TypeScript?
在打字稿中:
let str: string = 'abc';
let val: unknown = 'test';
if (typeof val === 'string') {
str = val;
}
// this code does not report any error, everything works fine.
但是,如果我稍微更改一下代码:
if ((typeof val) === 'string') {
str = val;
}
// add the () to hold typeof val;
// error report in typescript in this line: str = val !
这真是让我很困惑,谁能帮忙解释一下这里发生了什么。
TypeScript 的 typeof
type guards 小心翼翼。 typeof val
是一个字符串,你可以对它进行任意的字符串操作,但是typeof val === "string"
是一个特殊的构造,当表达式为true
时,它会缩小val
的类型。因此,TypeScript 被显式编程为匹配 typeof ${reference} ${op} ${literal}
和 ${literal} ${op} typeof ${reference}
(对于 op = ==
、!=
、===
和 !==
),但是typeof ${reference}
没有内置括号容差(这是 SyntaxKind.ParenthesizedExpression
而不是 SyntaxKind.TypeOfExpression
)、字符串操作或其他任何东西。
TypeScript 负责人 Ryan Cavanaugh 在 microsoft/TypeScript#42203, "typeof type narrowing acts differently with equivalent parentheses grouping", with gratitude to jcalz 中为 link 描述了这一点:
Narrowings only occur on predefined syntactic patterns, and this isn't one of them. I could see wanting to add parens here for clarity, though -- we should detect this one too.
这听起来像是未来修复的候选者,但即使添加了模式,您仍然会在某种程度上限制作为类型保护的 typeof
表达式的复杂性。
来自编译器源代码 microsoft/TypeScript main/src/compiler/checker.ts
,评论我的:
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
switch (expr.operatorToken.kind) {
// ...
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
const operator = expr.operatorToken.kind;
const left = getReferenceCandidate(expr.left);
const right = getReferenceCandidate(expr.right);
// Check that the left is typeof and the right is a string literal...
if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) {
return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue);
}
// ...or the opposite...
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
}
// ...or skip it and move on. Don't bother trying to remove parentheses
// or doing anything else clever to try to make arbitrary expressions work.
if (isMatchingReference(reference, left)) {
return narrowTypeByEquality(type, operator, right, assumeTrue);
}
if (isMatchingReference(reference, right)) {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
// ...
}
return type;
}
在打字稿中:
let str: string = 'abc';
let val: unknown = 'test';
if (typeof val === 'string') {
str = val;
}
// this code does not report any error, everything works fine.
但是,如果我稍微更改一下代码:
if ((typeof val) === 'string') {
str = val;
}
// add the () to hold typeof val;
// error report in typescript in this line: str = val !
这真是让我很困惑,谁能帮忙解释一下这里发生了什么。
TypeScript 的 typeof
type guards 小心翼翼。 typeof val
是一个字符串,你可以对它进行任意的字符串操作,但是typeof val === "string"
是一个特殊的构造,当表达式为true
时,它会缩小val
的类型。因此,TypeScript 被显式编程为匹配 typeof ${reference} ${op} ${literal}
和 ${literal} ${op} typeof ${reference}
(对于 op = ==
、!=
、===
和 !==
),但是typeof ${reference}
没有内置括号容差(这是 SyntaxKind.ParenthesizedExpression
而不是 SyntaxKind.TypeOfExpression
)、字符串操作或其他任何东西。
TypeScript 负责人 Ryan Cavanaugh 在 microsoft/TypeScript#42203, "typeof type narrowing acts differently with equivalent parentheses grouping", with gratitude to jcalz 中为 link 描述了这一点:
Narrowings only occur on predefined syntactic patterns, and this isn't one of them. I could see wanting to add parens here for clarity, though -- we should detect this one too.
这听起来像是未来修复的候选者,但即使添加了模式,您仍然会在某种程度上限制作为类型保护的 typeof
表达式的复杂性。
来自编译器源代码 microsoft/TypeScript main/src/compiler/checker.ts
,评论我的:
function narrowTypeByBinaryExpression(type: Type, expr: BinaryExpression, assumeTrue: boolean): Type {
switch (expr.operatorToken.kind) {
// ...
case SyntaxKind.EqualsEqualsToken:
case SyntaxKind.ExclamationEqualsToken:
case SyntaxKind.EqualsEqualsEqualsToken:
case SyntaxKind.ExclamationEqualsEqualsToken:
const operator = expr.operatorToken.kind;
const left = getReferenceCandidate(expr.left);
const right = getReferenceCandidate(expr.right);
// Check that the left is typeof and the right is a string literal...
if (left.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(right)) {
return narrowTypeByTypeof(type, left as TypeOfExpression, operator, right, assumeTrue);
}
// ...or the opposite...
if (right.kind === SyntaxKind.TypeOfExpression && isStringLiteralLike(left)) {
return narrowTypeByTypeof(type, right as TypeOfExpression, operator, left, assumeTrue);
}
// ...or skip it and move on. Don't bother trying to remove parentheses
// or doing anything else clever to try to make arbitrary expressions work.
if (isMatchingReference(reference, left)) {
return narrowTypeByEquality(type, operator, right, assumeTrue);
}
if (isMatchingReference(reference, right)) {
return narrowTypeByEquality(type, operator, left, assumeTrue);
}
// ...
}
return type;
}