Search code examples
typescript

Type for falsy and truthy values


Is there a better type than any for a boolean like function argument. The function itself will only check for falsy/truthy value. The type should indicate that it is only a switch and the developer does not have to convert any value to boolean.

If you try to compare the value of this type to another value an error will be shown. The only use is to compare if value is something or not.


Solution

  • There is no better type than the any type or possibly the unknown type or its near-equivalent {} | undefined | null for this purpose. Every value in JavaScript is either truthy of falsy, so that means you want a type that accepts every possible value. That's any, or unknown.


    Right now it's not possible to accurately write a type for just the falsy values. It's similar to type Falsy = false | 0 | "" | null | undefined | 0n | "", but there is no literal type for NaN, so there's a hole in Falsy that NaN can slip through. A feature request at microsoft/TypeScript#28682 asks for a NaN type, but it's unlikely to be implemented anytime soon.

    Even worse, it is completely impossible to accurately write a type for just the truthy values. It's similar to type Truthy = true | number | string | bigint | object | symbol, but there is no way to carve out the falsy numbers and strings and bigints. That is, there's no negated types like not X to let you do type subtraction. So there's a hole in Truthy that 0, and 0n, and "" can slip through. Oh, and NaN slips through this one also. With negated types we could write (number & not 0) and (string & not ""). Or we might as well just write type Truthy = not Falsy. But we can't. A feature request at microsoft/TypeScript#4196 asks for negated types, but it's not part of the language and again, unlikely to be implemented anytime soon.

    If both of the above features were implemented, then you could write type Booleany = Truthy | Falsy as the union of Truthy and Falsy.


    But that still wouldn't give you the behavior you're apparently looking for. After all, the above Booleany is still equivalent to any or unknown. You say you want to prevent someone from doing anything with a value b of type Booleany except for passing it directly to a function that accepts such a type like f(b), or possibly doing a truthiness check on it like if (b) or if (!b). If someone does a test like typeof b === "string" or b === b2, you want to see a compiler error. That's not something TypeScript can ever do. TypeScript allows typeof checks and equality checks on all values, whether there types are Booleany or not.

    It's possible that you might be able to use a linter like typescript-eslint, and write a custom rule that detects when values are of the Booleany type and tries to enforce your restrictions. But that's out of scope of TypeScript and has nothing to do with the type system directly. And since Booleany is equivalent to unknown, it might be hard to write a linter rule that doesn't accidentally apply in unexpected places, or fail to apply in expected places.

    The closest you could do in just TypeScript would be to come up with a "placeholder" type for Truthy and Falsy like type Truthy = true & {__placeholder: true} and type Falsy = false & {__placeholder: true}, using nominal-ish aliases, and then you've got type Booleany = Truthy | Falsy. But now Booleany doesn't accept any value at all, really. You'd need to map any value to Booleany, like function toBooleany(x: any): Booleany { return !!x as any }. But if you're going to convert values to a special type that has only two possible values, then... well, that type already exists. It's called boolean: function toBoolean(x: any) { return !!x }.


    So I'd suggest that really what you want to do is convert your values to true booleans and then use them and get all the benefits of strong typing. That's going to be a lot easier and less prone to bugs than trying to fight the type system by promoting JavaScript's loose "truthy/falsy" concept to a first class type in TypeScript.