question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Type logical operators "and", "or" and "not" in extends clauses for mapped types

See original GitHub issue

Search Terms

Pretty much the ones in the title…

Suggestion

I would like the ability to use logical operators for types.

I am aware that not is already planned as a feature, but I would like to make sure that this gets further extended to other logical operators, and I couldn’t find any hints that this is in your minds already.

Use Cases

Type composition, better readability…

Examples

Current tricks to get to the desired behavior:

type Not<T extends boolean> = T extends true ? false : true

type Or<A extends boolean, B extends boolean> = A extends true
    ? true
    : B extends true
        ? true
        : false

type Or3<A extends boolean, B extends boolean, C extends boolean> = Or<A, Or<B, C>>

type And<A extends boolean, B extends boolean> = A extends true
    ? B extends true
        ? true
        : false
    : false

A few arbitrary use cases:

type Primitive = boolean | number | string | symbol | null | undefined | void
type IsA<T, E> = T extends E ? true : false

type IsIndexSignature<P> = Or<IsA<string, P>, IsA<number, P>>

type IsCallback<F extends Function> = F extends (...args: any[]) => any
    ? And<Not<IsA<Parameters<F>[0], Primitive>>, IsA<Parameters<F>[0], Event>> extends true
        ? true
        : false
    : false

All together in the Playground: here

Desired syntactic sugar to write the same:

type IsIndexSignature<P> = IsA<string, P> or IsA<number, P>

type IsCallback<F extends Function> = F extends (...args: any[]) => any
    ? not IsA<Parameters<F>[0], Primitive> and IsA<Parameters<F>[0], Event> extends true
        ? true
        : false
    : false

It would make the most sense to accompany this with better support for boolean checks in type definitions. That is, to allow to use the ternary operator directly without the constant need for extends true everywhere. Possibly, a new sort of “boolean type declaration” could be introduced, as to avoid having to propagate the boolean value all the way. For example, it should be possible to define KnownKeys (not full implementation here) like this:

type KnownKeys<T> = {
    [P in keyof T]: IsIndexSignature<P> ? never : T[P]
}

Without the need to do:

type KnownKeys<T> = {
    [P in keyof T]: IsIndexSignature<P> extends true ? never : T[P]
}

Checklist

My suggestion meets these guidelines:

  • This wouldn’t be a breaking change in existing TypeScript/JavaScript code
  • This wouldn’t change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn’t a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:1
  • Comments:8 (4 by maintainers)

github_iconTop GitHub Comments

5reactions
jcalzcommented, May 25, 2019

I suppose “hack” is in the eye of the beholder, here. While the boolean literal types true and false have evocative names, they just represent the types of boolean expressions that exist at runtime, they don’t behave like booleans at the type level.

On the other hand, unknown and never are TypeScript’s names for the top and bottom types, respectively, which behave much more like booleans at the type level, which is what you’re asking for.

The correspondence comes from equating a type to the statement that some value can be assigned to the type:

Propositional Logic Type System Correspondence
T/true/⊤ unknown (top) value can be assigned to unknown? true.
F/false/⊥ never (bottom) value can be assigned to never? false.
and/∧ & (intersection) value can be assigned to A & B iff it can to A and B.
or/∨ | (union) value can be assigned to A | B iff it can to either A or B.
not/¬ not* (complement) value can be assigned to not A iff it is not to A.

* well, if we get negated types

Hopefully that seems less arbitrary. Of course, there is no not yet, and propositional logic doesn’t usually have a primitive symbol for a ternary operator, so we’ll need our own Not<T> and Cond<T,Y,N>, as I said before:


So, let’s look at your example:

type Primitive = boolean | number | string | symbol | null | undefined | void
type IsA<T, E> = T extends E ? unknown : never;

type Cond<C, Y=unknown, N=never> = [C] extends [never] ? N : Y
type IsIndexSignature<P> = IsA<string, P> | IsA<number, P>

type Yes = IsIndexSignature<number> // (never | unknown) = unknown
type No = IsIndexSignature<boolean> // (never | never) = never

Does that make more sense?

1reaction
yume-chancommented, Nov 1, 2019

It’s sly that TypeScript Handbook describes Conditional Types as

A conditional type selects one of two possible types based on a condition expressed as a type relationship test:

T extends U ? X : Y

The type above means when T is assignable to U the type is X, otherwise the type is Y.

(from https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types)

It never says Conditional Types will resolve to true or false, so if we want to make specific the result of the condition, any binary values can work, unknown and never are definitely ok.


But not true/false

Everyone knows that at runtime the opposite of true is false, and vice versa (except NaN, of course).

But in negated types, not true means “anything not assignable to true” and not false means “anything not assignable to false”.

So for example string is not true and not false at the same time, what will string ? true: false resolve to?


However I have to admit that using unknown and never is way more less intuitive.


Originally I came here because I found a super cool blog post (in Chinese) that uses only the type system to build a script parser.

image (Playground link)

To chain multiple expressions, the author has to heavily use Conditional Types. So I wonder is there any proposal to add logical operators to the Conditional Types, as in JavaScript we can use &&.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Documentation - Mapped Types - TypeScript
Generating types by re-using an existing type. ... In TypeScript 4.1 and onwards, you can re-map keys in mapped types with an as...
Read more >
TypeScript recursive conditional mapped types - Stack Overflow
It says Type 'Conditions[K]' is not assignable to type 'TestFunctionObject' . Type conditioning doesn't seem to narrow Conditions to just ...
Read more >
Mastering TypeScript mapped types - LogRocket Blog
Indexed access types in TypeScript; Index signatures in TypeScript; Using union types in TypeScript; Using the keyof type operator ...
Read more >
Object orientation - The Apache Groovy programming language
But this is not the case, as Groovy will compile your operators into their ... The extends keyword is used immediately prior to...
Read more >
Logical OR (||) - JavaScript - MDN Web Docs - Mozilla
However, the || operator actually returns the value of one of the specified operands, so if this operator is used with non-Boolean values,...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found