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

Provide "remove from union" type operator #12959

Closed
PyroVortex opened this issue Dec 15, 2016 · 7 comments
Closed

Provide "remove from union" type operator #12959

PyroVortex opened this issue Dec 15, 2016 · 7 comments
Labels
Duplicate An existing issue was already created

Comments

@PyroVortex
Copy link

PyroVortex commented Dec 15, 2016

With the introduction of the keyof operator and mapped types, the ability to remove some subset of the types in a union becomes increasingly useful. The type system already supports this operation via type guards in control flow, but we have no way to describe the resulting type to the compiler.

Available existing behavior

type Direction = 'north' | 'east' | 'south' | 'west';
type EastWest = 'east' | 'west';
declare function is<T>(x: any): x is T;
let dir: Direction; // dir has type 'north' | 'east' | 'south' | 'west';
let nsDir = !is<EastWest>(dir) && dir; // nsDir has type 'north' | 'south'
// because the compiler removed 'east' | 'west' from the type of dir via the type guard

However, we have no way of specifying the type of nsDir in terms of Direction and EastWest

Proposal

We introduce a new "remove from union" operator A -| B (symbol choice subject to change) that has the following behavior:

// Simple types
type example1 = (number | string) -| number; // string
// number was removed from the union

type example2 = (number | string) -| (string | boolean); // number
// string was removed, boolean was ignored as no overlap with number | string

type example3 = number -| string; // number
// string was ignored as no overlap with number.
// This may need to be revisited with regards to indexers
// since [key: string] is considered a superset of [key: number]

type example4 = number -| number; // never

// Union types
type A = 'a' | 'b' | 1 | 2;
type B = string | number; // union of all string literal types and all numeric literal types
type C = 'a' | 2;

type example5 = A -| B; // never

type example6 = B -| A; // This may need to be defined as "any string except 'a' or 'b',
// and any number except '1' or '2'", which we currently can't represent.
// In the near term, B will suffice as the result

type example7 = A -| C; // 'b' | '1'

// Interfaces
interface IFoo {
    a: number;
}
interface IBar {
    a: number;
    b: string;
}
interface IBaz {
    a: number;
}
type example8 = IFoo -| IBar; // IFoo

type example9 = IFoo -| IBaz; // never, based on the existing behavior of type guards

type example10 = (IFoo | IBar) -| IFoo; // IBar

This will also allow us to solve some current proposals:

// Object rest types
type Rest<T, K extends keyof T> = {[P in (keyof T) -| K]: T[P]};

// Object spread types
type Assign<A, B> = B & Rest<A, (keyof A) & (keyof B)>;
// Note that (keyof A) & (keyof B) is identical to keyof (A | B)

// Wrapping a function and providing some inputs (really just object spread and rest types)
function provideA<PropsType, K extends keyof PropsType>(
    f: (props: PropsType) => void,
    provided: {[P in K]: PropsType[P]}): (remainingProps: Rest<PropsType, K>) => void {
    return (remainingProps: Rest<PropsType, K>): void => {
        return f({...remainingProps, ...provided});
    };
}

Edit: fixed variables in provideA, spacing
@sandersn, @mhegazy

@zpdDG4gta8XKpMCd
Copy link

might be a dup of #4183

@ThomasMichon
Copy link
Member

@Aleksey-Bykov This is actually a distinct proposal. #4183 address the case where you wish for a type not to have specific members. This proposal instead addresses the case where you can know definitively that a value of a union type is definitely not one of a set of types.

@zpdDG4gta8XKpMCd
Copy link

zpdDG4gta8XKpMCd commented Dec 15, 2016 via email

@ThomasMichon
Copy link
Member

I think this is more a duplicate of #12215, actually. #4183 is a superset of both concepts.

@PyroVortex
Copy link
Author

I hadn't seen #12215, mostly due to using the wrong search terms to look for duplicates. However, this proposal is slightly wider in scope, as it applies to union types in general. The main advantage this has over #4183 is that it is much narrower in scope and should therefore be much less work to implement. The type system already has logic to remove types from unions, since type guards do so, this proposal just allows the developer to request that operation when describing a type.

@mhegazy
Copy link
Contributor

mhegazy commented Dec 15, 2016

We would be more in favor of the proposal in #12215. the operator is only applicable to literal types (which includes keyof T). Can you provide scenarios that would require the approach in the OP?

@mhegazy mhegazy added the Duplicate An existing issue was already created label Dec 15, 2016
@PyroVortex
Copy link
Author

Nothing that I actually need this for, no. All of my use cases can be addressed by #12215. Feel free to close as duplicate of #12215.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants