镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content
This repository was archived by the owner on Sep 1, 2026. It is now read-only.

Negated Types - #4200

Closed
Wesley Wigham (weswigham) wants to merge 10 commits into
microsoft:mainfrom
weswigham:port-negated-types
Closed

Wesley Wigham (weswigham) wants to merge 10 commits into
microsoft:mainfrom
weswigham:port-negated-types

Conversation

@weswigham

@weswigham Wesley Wigham (weswigham) commented Jun 4, 2026 •

Copy link
Copy Markdown
Member

This PR is a port of microsoft/TypeScript#29317 for this codebase, with further work to enable control flow creation of negations added on.

To repeat from that PR:

Long have we spoken of them in hushed tones and referenced them in related issues, here they are:

Negated Types

Negated types, as the name may imply, are the negation of another type. Conceptually, this means that if string covers all values which are strings at runtime, a "not string" covers all values which are... not. We had hoped that conditional types would by and large subsume any use negated types would have... and they mostly do, except in many cases we need to apply the constraint implied by the conditional's check to it's result. In the true branch, we can just intersect the extends clause type, however in the false branch we've thus far been discarding the information. This means unions may not be filtered as they should (especially when conditionals nest) and information can be lost. So that ended up being the primary driver for this primitive - it's taking what a conditional type false branch implies, and allowing it to stand alone as a type.

Syntax

not T

where T is another type. I'm open to bikeshedding this, or even shipping without syntax available, but among alternatives (!, ~) not reads pretty well.

Identities

These are little tricks we do on negated type construction to help speed things along (and give negations on algebraic types canonical forms).

  • not not T is T
  • not (A | B | C | ...) is not A & not B & not C & not ...
  • not (A & B & C & ...) is not A | not B | not C | not ...
  • not unknown is never
  • not never is unknown
  • not any is any (since any is the NaN of types and behaves as both the bottom and top)
  • T | not T is unknown, T & not T is never

Assignability Rules

Negated types, for perhaps obvious reasons, cannot be related structurally - the only sane way to relate them is in a higher-order fashion. Thus, the rules governing these relations are very important.

  • A negated type not S is related to a negated type not T if T is related to S.
    This follows from the set membership inversion that a negation implies - if normally a type S and a type T would be related if S is a subset of T, when we take the complements of those sets, not S and not T, those sets share an inverse relationship to the originals.
  • A type S is related to a negated type not T if the intersection of S and T is empty
    We want to check if for all values in S, none of those values are also in T (since if they are, S is not in the negation of T). The intersection of S and T, when simplified and evaluated, is exactly the description of the common domain of the two. If this domain is empty (never), then we can conclude that there is no overlap between the two and that S must lie within not T.
  • A negated type not S is not related to a type T.
    A negated type describes a set of values that reaches from unknown to its bound, while a normal type describes values from its bound to never - it's impossible for a negated type to satisfy a normal type

Assignability Addendum for Fresh Object Types

Frequently we want to consider a fresh object type as a singleton type (indeed, some examples in the refs assume this) - it corresponds to one runtime value, not the bounds on a value (meaning, as a type, both its upper and lower bounds are itself). Using this, we can add one more rule that allows fresh literal types to easily satisfy negated object types.

  • A fresh object type S is related to a negated type not T if S is not related to T.
    Since S is a singleton type, we can assume that so long as it's type is not in T, then it is in not T.

Examples

Examples of negated type usage can be found in the tests of this PR (there's a few hundred lines of them, and probably some more to come for good measure), but here's some of the common ones, pulled from the referenced issues:

declare function ignore<T extends not (object & Promise<any>)>(value: T): void;
declare function readFileAsync(): Promise<string>;
declare function readFileSync(): string;
ignore(readFileSync());     // OK
ignore(readFileAsync());    // Should error

declare function map<T, U extends not void>(values: T[], map: (value: T) => U) : U[]; // validate map callback doesn't return void

function foo() {}

map([1, 2, 3], n => n + 1); // OK
map([1, 2, 3], foo);        // Should error

function asValid<T extends not null>(value: T, isValid: (value: T) => boolean) : T | null {
    return isValid(value) ? value : null;
}

declare const x: number;
declare const y: number | null;
asValid(x, n => n >= 0);    // OK
asValid(y, n => n >= 0);    // Should error

function tryAt<T extends not undefined>(values: T[], index: number): T | undefined {
    return values[index];
}

declare const a: number[];
declare const b: (number | undefined)[];
tryAt(a, 0);    // OK
tryAt(b, 0);    // Should error

@weswigham
Wesley Wigham (weswigham) marked this pull request as ready for review August 11, 2026 17:48
Copilot AI balanced review requested due to automatic review settings August 11, 2026 17:48
@weswigham Wesley Wigham (weswigham) changed the title WIP: Negated Types Negated Types Aug 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@RyanCavanaugh Ryan Cavanaugh (RyanCavanaugh) added the Unmigrated PR This PR was open at the time of the repo move back to TypeScript label Aug 19, 2026
@RyanCavanaugh

Copy link
Copy Markdown
Member

Thank you for contributing to the TypeScript native port!

Development has moved from this repository back to the main microsoft/TypeScript repository. GitHub does not have PR transfer functionality, so we're closing this PR here.

If this change is still relevant, please reopen it as a new pull request in microsoft/TypeScript. We'd appreciate your help carrying the contribution over, and we apologize for the extra work.

See microsoft/typescript-go#4918 for more information about the move.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Unmigrated PR This PR was open at the time of the repo move back to TypeScript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants