Typescript: negating type constraints

Created on 10 Apr 2016  路  13Comments  路  Source: microsoft/TypeScript

Sometimes it's useful to put a limit on what a type parameter can be. In a way it is a counterpart of the extends constraint.

Problem

Consider an example, a classic function that takes whatever and returns void:

function ignore<a>(value: a) : void {};

However we must not apply this function to Promises, because it might get us a temporal leak if we do.

function readFileAsync(): Promise<string>;
ignore(readFileAsync()); // <-- untracked promise, temporal leak

Unfortunately it is way too easy to get into a situation when a promise is passed to that function unintentionally as a result of refactoring:

// before
function readFileSync(): string;
ignore(readFileSync()); // typechecks, works as intended, no problem

// after refactoring
function readFileAsync(): Promise<string>; // <-- went async here
ignore(readFileAsync()); // typechecks, unintended temporal leak, big problem

Solution

The situation above could have been avoided if TypeScript allowed negating constraints:

function ignore<a unlike Promise<any>>(value: a): void {} // <-- hypothetical syntax
Awaiting More Feedback Suggestion

Most helpful comment

Negating types could allow unions with a catch-all member, without overshadowing the types of the known members.

interface A { type: "a", data: number }
interface B { type: "b", data: string }
interface Unknown { type: string unlike "a"|"b", data: any }
type ABU = A | B | Unknown

var x : ABU = {type: "a", data: 5}
if(x.type === "a") {
  let y = x.data; // y should be inferred to be a number instead of any
} 

All 13 comments

You can already get the behavior you want today

interface MyPromise {
    resolve(): void;
}

function ignore(x: { resolve?: {'no promises allowed!': string}}) {
}

var x: MyPromise;
var y: string;

ignore(x); // Error
ignore(y); // OK
function shallowCopy<a unlike number | string | null | undefined | Regex | Date>(value: a): a {
   const result = {}; 
   for (var key in value) {
       if (value.hasOwnProperty(key)) {
          result[key] = value[key];
       }
    }
    return result;
}

Seems like you're looking for #1809?

function map<a, b unlike void>(values: a, map: (value: a) => b) : b[] { // <-- it doesn't make sense to map to void[]
}

This issue is now awaiting more feedback. This means we'd like to hear from more people who would be helped by this feature, understand their use cases, and possibly contrast with other proposals that might solve the problem in a simpler way (or solve many other problems at once).

If this feature looks like a good fit for you, please use the :+1: reaction on the original post. Comments outlining different scenarios that would be benefited from the feature are also welcomed.

one more case for scenarios where, say, null is booked for internal purposes and the calling code cannot use it

function asValid<a unlike null>(value: a, isValid: (value: a) => boolean) : a | null {
    return isValid(value) ? value : null;
}

similar but distinct

// here we want to eliminate a chance of getting a default undefined from the JS runtime
export function tryAt<a unlike undefined>(values: a[], index: number): a | undefined {
    return values[index];
}

I'd propose less radical approach that already has all the machinery to be implemented: https://github.com/Microsoft/TypeScript/issues/9776#issuecomment-233191082

function String<a unlike string>(value: a): string {}
function Number<a unlike number>(value: a): number {}

Negating types could allow unions with a catch-all member, without overshadowing the types of the known members.

interface A { type: "a", data: number }
interface B { type: "b", data: string }
interface Unknown { type: string unlike "a"|"b", data: any }
type ABU = A | B | Unknown

var x : ABU = {type: "a", data: 5}
if(x.type === "a") {
  let y = x.data; // y should be inferred to be a number instead of any
} 

@vvscode phenomenal. I used this to build a static destructuring exhaustiveness check:

interface ExtraProps {
  store: StoreType
  otherProp: OtherType
}
type Not<T> = { [key in keyof T]?: never }

function someHOC<OwnProps>(Component: React.FC<OwnProps>) {
  return ({ store, /* otherProp, */ ...ownProps }: ExtraProps & OwnProps) => (
    <Provider store={store}>
      <Component {...ownProps as Not<ExtraProps> /* error */ as OwnProps} />
    </Provider>
  )
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

jbondc picture jbondc  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

uber5001 picture uber5001  路  3Comments

weswigham picture weswigham  路  3Comments

CyrusNajmabadi picture CyrusNajmabadi  路  3Comments