Skip to content

Request: Easy way to remove Properties from Distributive Conditional Types #30295

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

Closed
jasonswearingen opened this issue Mar 9, 2019 · 5 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

@jasonswearingen
Copy link

jasonswearingen commented Mar 9, 2019

When manipulating types it is currently very difficult to actually remove properties from a type.

Consider an example where you want to remove properties of a certain type:

/** tries to remove props, but they still exist with type 'never' */
type PropsRemoveSimple<TTarget, TPropToRemove> = { [ K in keyof TTarget ]: TTarget[ K ] extends TPropToRemove ? never : TTarget[ K ] };

/** demo type */
type TestType = {
	a: number;
	b: boolean;
}

//remove props of type number
let testRemovalSimple: PropsRemoveSimple<TestType, number>;
testRemovalSimple.a; //type "never"
testRemovalSimple.b; //type "boolean";

This works, however the property still exists with type never

The only way to actually remove the property is to have an intermediate keys type:

/** helper that returns prop names except those to filter.  This helper type is needed to actually remove the prop, as otherwise the prop still exists in the type just as "never".  */
type PropsRemove_Name<TTarget, TPropToRemove> = { [ K in keyof TTarget ]: TTarget[ K ] extends TPropToRemove ? never : K }[ keyof TTarget ];
/** remove props of the given type.   always removes ```never``` type props.  if no ```TPropToRemove``` is provided, removes just ```never``` type props. */
type PropsRemove<TTarget,TPropToRemove=never> = Pick<TTarget, PropsRemove_Name<TTarget,TPropToRemove>>;

/** demo type */
type TestType = {
	a: number;
	b: boolean;
}

//actually remove props of type number
let testRemoval: PropsRemove<TestType, number>;
testRemoval.b; //boolean

SUGGESTION: use the ! symbol to avoid emitting

as in this mockup, the use of ! instead of never would cause the output to be skipped.

type MockPropsRemove<TTarget, TPropToRemove> = { [ K in keyof TTarget ]: TTarget[ K ] extends TPropToRemove ? ! : TTarget[ K ] };

USE CASES:

being able to remove properties on a first pass (without using an intermediate [keyof] type greatly simplifies the work and understanding needed to create advanced types.

I came across this issue when creating a mixin/union/default values type that prioritizes the first input object's properties over the second.

additionally, when doing type unions, removal of never properties is important

Checklist

My suggestion meets these guidelines:

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

jack-williams commented Mar 11, 2019

EDIT: My mistake didn't see you had already covered the example using intermediate keys.

If I understand your proposal correctly, would the type:

type Foo<T> = { [ K in keyof T]: ! }

resolve to {} for all T? Or do you only intend for it to be used within a conditional type?

To play devil's advocate: I don't usually associate filtering with mapping, so I think it would somewhat overload the term "mapped type".

@jasonswearingen
Copy link
Author

jasonswearingen commented Mar 12, 2019

yes, I think your hypothetical type Foo<T> = { [ K in keyof T]: ! } would resolve to {}.

I feel the real benefit is it's simplicity in use with conditional types. Without an easier way to actually remove a property, the developer needs to learn how to use an intermediate keys type. As I just spent the time learning how to do this fancy mixin stuff, I can tell you that figuring out the intermediate keys is aprox 2x the work to get the mixin type working correctly. Also, even though I got it working, I honestly can't say I really know how the intermediate keys really logically "works".

So to summarize my justification: Adding explicit property removal would simplify the developer experience when making complex types.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Mar 12, 2019
@YowaiCoder
Copy link

type PropsRemoveSimple<O, T> = Pick<O, { [ K in keyof O ]: O[K] extends T ? never : K }[keyof O]>;

@jasonswearingen
Copy link
Author

@fightingcat yes, my initial post shows an expanded form of that as the current solution

/** helper that returns prop names except those to filter.  This helper type is needed to actually remove the prop, as otherwise the prop still exists in the type just as "never".  */
type PropsRemove_Name<TTarget, TPropToRemove> = { [ K in keyof TTarget ]: TTarget[ K ] extends TPropToRemove ? never : K }[ keyof TTarget ];
/** remove props of the given type.   always removes ```never``` type props.  if no ```TPropToRemove``` is provided, removes just ```never``` type props. */
type PropsRemove<TTarget,TPropToRemove=never> = Pick<TTarget, PropsRemove_Name<TTarget,TPropToRemove>>;

The problem is that it requires understanding of intermediate key types. My own anecdote is it took me twice as long to figure intermediate keys out, and I still don't really get how the final [ keyof TTarget ]in the PropsRemove_Name is supposed to do.

So my suggestion hopefully offers a way for noob devs to make complex types while reducing the cognitive burden of doing so.

@jasonswearingen
Copy link
Author

You guys added the Omit helper in 3.5 so good nuff.

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

4 participants