Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why must a type predicate's type be assignable to its parameter's type?

Tags:

typescript

I have a type predicate:

// tslint:disable-next-line:no-any
const isString = (value: any): value is string {
  return typeof value === 'string'
}

This works, but it requires that I disable my linter. I would rather do this:

const isString = <T>(value: T): value is string {
  return typeof value === 'string'
}

That way the type is not any but instead we have 1 typeguard function for each type, that is one function a -> Boolean for every a.

Typescript complains that:

A type predicate's type must be assignable to its parameter's type. Type 'string' is not assignable to type 'T'.

This doesn't make sense to me... Why should it matter what the type predicate's type is?

like image 992
Ziggy Avatar asked May 19 '18 01:05

Ziggy


People also ask

Why must the predicate type be assignable to the value type?

Regarding the error message, the predicate type must be assignable to the value type because the type guard is used to check whether a value with a less-specific type is in fact a value with a more-specific type. For example, consider this guard: function isApe (value: Animal): value is Ape { return /* ...

Why can't I guard the type of a type predicate?

If there is no relationship between the value's type and the type in the type predicate, the guard would make no sense. For example, TypeScript won't allow a user-defined guard like this: function isString (value: Date): value is string { return typeof value === "string"; } [ts] A type predicate's type must be assignable to its parameter's type.

What are type predicates in typescript?

Type predicates are a special return type that signals to the Typescript compiler what type a particular value is. Type predicates are always attached to a function that takes a single argument and returns a boolean. Type predicates are expressed as argumentName is Type.

Can a string type be assignable to a date?

[ts] A type predicate's type must be assignable to its parameter's type. Type 'string' is not assignable to type 'Date'. A Date value will never be a string, so the guard is pointless: its runtime check is unnecessary and should always return false.


2 Answers

Adding to the accepted answer, if you happen to need to use a type guard against a mixin, you'll get this error too, since the is operator doesn't behave as an implements would.

interface Animal { ... }

interface Climber { ... }

interface Ape extends Animal, Climber { ... } 

const isClimberMixin = (animal: Animal): animal is Climber => ...

Such code fails because Climber is not assignable to Animal since it doesn't extend from it.

The solution, if you can't avoid the mixin pattern, is to use an union type:

const isClimberMixin = (animal: Animal): animal is Animal & Climber => ...
like image 120
Taro Avatar answered Sep 23 '22 18:09

Taro


A user-defined type guard performs a runtime check to determine whether or not value of a particular type satisfies a type predicate.

If there is no relationship between the value's type and the type in the type predicate, the guard would make no sense. For example, TypeScript won't allow a user-defined guard like this:

function isString(value: Date): value is string {
    return typeof value === "string";
}

and will effect this error:

[ts] A type predicate's type must be assignable to its parameter's type.
Type 'string' is not assignable to type 'Date'.

A Date value will never be a string, so the guard is pointless: its runtime check is unnecessary and should always return false.

When you specify a generic, user-defined type guard, T could be anything, so - as with Date - for some types, the type guard would make no sense.

If you really don't want to use any, you could use an empty interface - {} - instead:

function isString(value: {}): value is string {
    return typeof value === "string";
}

If you also want to allow for null and undefined values to be passed to the guard, you could use:

function isString(value: {} | null | undefined): value is string {
    return typeof value === "string";
}

Regarding the error message, the predicate type must be assignable to the value type because the type guard is used to check whether a value with a less-specific type is in fact a value with a more-specific type. For example, consider this guard:

function isApe(value: Animal): value is Ape {
    return /* ... */
}

Ape is assignable to Animal, but not vice versa.

like image 24
cartant Avatar answered Sep 21 '22 18:09

cartant