microsoft / microsoft/TypeScript
Suggestion: one-sided or fine-grained type guards
Personne n'a encore pris cette issue.
- Langage dominant
- Go
- Étoiles
- 111k
- Forks
- 14.4k
- Merge moyen
- 1 j 19 h
- PR mergées (30 j)
- 117
Description
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.
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Lisez la déclaration liée dans lib/lib.es2015.core.d.ts ainsi que les exemples décrivant les type guards unidirectionnels et à granularité fine. L’issue nécessite qu’une syntaxe et une sémantique soient convenues avant l’implémentation ; elle serait terminée lorsqu’une proposition aurait été arrêtée, plutôt que de conserver les alternatives actuellement discutées.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- typescript
- Domaine
- compilers
- Type d'issue
- Fonctionnalité
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- À l'abandon
- Clarté
- Plutôt claire
- Accessibilité débutants
- 25/100