microsoft / microsoft/TypeScript
Suggestion: one-sided or fine-grained type guards
まだ誰も着手していません。
- 主要言語
- Go
- スター
- 111k
- フォーク
- 14.4k
- 平均マージ
- 1日 19時間
- マージ済み PR(30日)
- 117
説明
The Problem
User-defined type guards assume that all values that pass a test are assignable to a given type, and that no values that fail the test are assignable to that type. This works well for functions that strictly check the type of a value.
function isNumber(value: any): value is number { /* ... */ }
let x: number | string = getNumberOrString();
if (isNumber(x)) {
// x: number
} else {
// x: string
}
But some functions, like Number.isInteger() in ES2015+, are more restrictive in that only some values of a given type pass the test. So the following does't work.
function isInteger(value: any): value is number { /* ... */ }
let x: number | string = getNumberOrString();
if (isInteger(x)) {
// x: number (Good: we know x is a number)
} else {
// x: string (Bad: x might still be a number)
}
The current solution – the one followed by the built-in declaration libraries – is to forgo the type guard altogether and restrict the type accepted as an argument, even though the function will accept any value (it will just return false if the input is not a number).
interface NumberConstructor {
isInteger(n: number): boolean;
}
A Solution: an "as" type guard
There is a need for a type guard that constrains the type when the test passes but not when the test fails. Call it a weak type guard, or a one-sided type guard since it only narrows one side of the conditional. I would suggest overloading the as keyword and using it like is.
function isInteger(value: any): value as number { /* ... */ }
let x: number | string = getNumberOrString();
if (isInteger(x)) {
// x: number
} else {
// x: number | string
}
This is only a small issue with some not-too-cumbersome workarounds, but given that a number of functions in ES2015+ are of this kind, I think a solution along these lines is warranted.
A more powerful solution: an "else" type guard
In light of what @aluanhaddad has suggested, I feel the above solution is a bit limited in that it only deals with the true side of the conditional. In rare cases a programmer might want to narrow only the false side:
let x: number | string = getNumberOrString();
if (isNotInteger(x)) {
// x: number | string
} else {
// x: number
}
To account for this scenario, a fine-grained type guard could be introduced: a type guard that deals with both sides independently. I would suggest introducing an else guard.
The following would be equivalent:
function isCool(value: any): boolean { /* ... */ }
function isCool(value: any): true else false { /* ... */ }
And the following would narrow either side of the conditional independently:
let x: number | string = getNumberOrString();
// Narrows only the true side of the conditional
function isInteger(value: any): value is number else false { /* ... */ }
if (isInteger(x)) {
// x: number
} else {
// x: number | string
}
// Narrows only the false side of the conditional
function isNotInteger(value: any): true else value is number { /* ... */ }
if (isNotInteger(x)) {
// x: number | string
} else {
// x: number
}
For clarity, parentheses could optionally be used around one or both sides:
function isInteger(value: any): (value is number) else (false) { /* ... */ }
At this point I'm not too certain about the syntax. But since it would allow a number of built-in functions in ES2015+ to be more accurately described, I would like to see something along these lines.
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
lib/lib.es2015.core.d.ts にあるリンク先の宣言と、一方向および細粒度の type guards について説明している例を読んでください。実装前に合意された構文とセマンティクスが必要です。この issue の完了条件は、現在議論されている代替案ではなく、決定された提案があることです。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- typescript
- 領域
- compilers
- issue の種類
- 機能追加
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 活発さ
- 停滞
- 明瞭さ
- おおむね明確
- 初心者へのやさしさ
- 25/100