Repository: TypeScript
Issue: Suggestion: one-sided or fine-grained type guards
Labels: Suggestion,Awaiting More Feedback

## 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.

```ts
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.

```ts
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][1] – 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).

```ts
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`.

```ts
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][2] 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:

```ts
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:

```ts
function isCool(value: any): boolean { /* ... */ }
function isCool(value: any): true else false { /* ... */ }
```

And the following would narrow either side of the conditional independently:

```ts
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:

```ts
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.

[1]: https://github.com/Microsoft/TypeScript/blob/dce7fca83d6bbf0bf72fd0a4237ad152599bd2f7/lib/lib.es2015.core.d.ts#L234
[2]: https://github.com/Microsoft/TypeScript/issues/15048#issuecomment-292771713