Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

negating type constraints #7993

Open
zpdDG4gta8XKpMCd opened this issue Apr 10, 2016 · 14 comments
Open

negating type constraints #7993

zpdDG4gta8XKpMCd opened this issue Apr 10, 2016 · 14 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@zpdDG4gta8XKpMCd
Copy link

zpdDG4gta8XKpMCd commented Apr 10, 2016

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
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Apr 11, 2016
@RyanCavanaugh
Copy link
Member

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

@zpdDG4gta8XKpMCd
Copy link
Author

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;
}

@RyanCavanaugh
Copy link
Member

Seems like you're looking for #1809?

@zpdDG4gta8XKpMCd
Copy link
Author

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

@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature and removed In Discussion Not yet reached consensus labels May 9, 2016
@RyanCavanaugh
Copy link
Member

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 👍 reaction on the original post. Comments outlining different scenarios that would be benefited from the feature are also welcomed.

@zpdDG4gta8XKpMCd
Copy link
Author

one more scenario: #8545 (comment)

@zpdDG4gta8XKpMCd
Copy link
Author

zpdDG4gta8XKpMCd commented May 21, 2016

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;
}

@zpdDG4gta8XKpMCd
Copy link
Author

zpdDG4gta8XKpMCd commented Jun 1, 2016

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];
}

@Artazor
Copy link
Contributor

Artazor commented Jul 17, 2016

I'd propose less radical approach that already has all the machinery to be implemented: #9776 (comment)

@zpdDG4gta8XKpMCd
Copy link
Author

zpdDG4gta8XKpMCd commented Aug 3, 2016

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

@siegebell
Copy link

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
Copy link

vvscode commented Nov 27, 2019

type Not<T> = {
  [key in keyof T]?: never
}

https://www.typescriptlang.org/play/#code/C4TwDgpgBAcg9sAPAFQHxQLxQN4CgpQDaA1hCFAJYB2UpIcAZlMgLoD8AXFFRAG4QAnXAF9cuasEEMAhgGNoABUEBnODTwEq0gLYQuy4AOoBzANwixEqXOgBBY9IBGAG2gao043u4BXbY8FzUVxZNQMPLngkJQFVKlQAMnsnV0wcfG4dbwByaWUAEwZsgBoMz28AJgBmCxCw4ChHSIREGLjE5JdoLHdyrmqRIA

@AprilArcus
Copy link

AprilArcus commented Apr 3, 2020

@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>
  )
}

@Lonli-Lokli
Copy link

Unfortunately, it does not help too much with limiting choice.

type DogHome = {
  citizens: 'dog',
  canDo: 'wow'
}

type CatHome = {
  citizens: 'cat',
  canDo: 'meuw'
}

type Not<T> = { [key in keyof T]?: never }

type UnknownHome = Not<DogHome> & Not<CatHome> & {
  citizens: string;
  canDo?: string;
  phone?: string;
}

type AllHomes = DogHome | CatHome | UnknownHome;

const home: AllHomes = {
  citizens: 'lonely' // Type '"lonely"' is not assignable to type '"dog" | "cat"'.
}

https://tsplay.dev/NnLbVW

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

7 participants