-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Pick<T, Exclude<keyof T, K>> & Pick<T, Extract<keyof T, K>> should be assignable to T #28884
Comments
Because it's not valid~ Consider if enum AContext {
Context = "Context",
OtherContext = "OtherContext"
} You can have But beyond that, I'm pretty sure we don't unify an |
@weswigham FWIW, I have a similar issue in my code using higher order components in React, but have the type constraint function decorate<P extends Partial<OwnProps>>(c: React.ComponentType<P>) {
type RequiredProps = Pick<P, Exclude<keyof P, keyof OwnProps>>
// RequiredProps + OwnProps *should* be assignable to P
return (props: RequiredProps) => (
// Compiler error here: this literal is not assignable to P
// We can't even use a type assertion to assert to the compiler that it is RequiredProps + OwnProps, even though as a human we can clearly see that is the case as { bar: 'baz' } satisfies OwnProps and ...rest satisfies what is 'left' in P
React.createElement(c, { bar: 'baz', ...props })
)
} The use case behind this particular pattern is creating a higher-order component that allows a user to specify additional props which are passed through to the child component and is fairly common. :( |
Encountering the same issue as well in my higher order components or functions: type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
interface Foo {
value: number
}
function withFoo <T extends Foo>(bar: T) {
type U = Omit<T, keyof Foo>
return function foo(foo: U): [T, T] {
const t: T = {
...foo,
value: 42,
}
return [t, t]
}
}
Also tried: const t: U & Foo = {
...foo,
value: 42,
} which outputs:
|
Same here. The following minimal example does not compile because it cannot find property import { Component, ComponentType } from 'react'
interface Props {
selected: boolean
}
export default function withSelection<P extends Props>(
Target: ComponentType<P>
) {
return class Wrapper extends Component {
public render() {
return <Target selected />
}
}
} |
I need a correct way to implements providing default options to required options, let them be optional for using-convenience. like #29062: interface HasHeight {
height: number
}
type Omit<P, K extends keyof P> = Pick<P, Exclude<keyof P, K>>
function makeHeightOptional<P extends HasHeight>(heightRequired: (p: P) => number) {
return function heightOptional(params: Omit<P, 'height'> & { height?: number }) {
const height = params.height || 10
const newParams = { ...params, height }
// Argument of type 'Pick<P, Exclude<keyof P, "height">> & { height: number; }' is not assignable to parameter of type 'P'.
return heightRequired(newParams)
}
} It's a common and useful high order function. |
@ahejlsberg, you changed the title, but in the sample I provided above, this is not the case. The previous title describe more precisely what the problem is, don't you think? |
I changed the title of the issue since the core problem is that an intersection of complementary subsets of a higher order type, constructed using Meanwhile, investigating the issue revealed other issues, notably #29067 and #29081. I have fixed both, and with the fixes in place it is now possible to use a type assertion back to the higher order type. For example: function withDefaults<T, K extends keyof T>(f: (obj: T) => void, defaults: Pick<T, K>) {
// In the resulting function, properties for which no defaults were provided are required,
// and properties for which defaults were provided are optional.
return (obj: Pick<T, Exclude<keyof T, K>> & Partial<Pick<T, K>>) =>
f({ ...defaults, ...obj } as T); // Assertion required for now
}
type Item = { name: string, width: number, height: number };
function foo(item: Item) {}
let f1 = withDefaults(foo, { name: 'hi ' });
let f2 = withDefaults(foo, { name: 'hi', height: 42 })
f1({ width: 10 }); // Error, missing 'height'
f1({ width: 10, height: 20 }); // Ok
f2({ width: 10 }); // Ok
f2({ width: 10, height: 20 }); // Ok If and when we implement the improvement suggested by this issue the |
Is this issue supposed to be resolved in version 3.3? |
It seems this still not fixed in v3.4.0-dev.20190310 |
I did it this way: // object with property
interface IFoo {
bar: string;
}
// some React.Component like function
type TComponent<T> = (param: T) => string;
// global value for "bar" property
const globBar: string = 'lalala';
// function takes bar from global variable and passes it into WrappedComponent
// just like we do this while using React.ContextConsumer
//
// @example
// const MyComponent = withGlobBar(BaseComponent);
// MyComponent({});
export function withGlobBar<P = {}>
// all magic goes here ↓↓↓↓↓
(WrappedComponent: TComponent<P & IFoo>): TComponent<P> {
return props => WrappedComponent({ ...props, bar: globBar });
} Hope it helps |
The result of |
I found myself faced with this error in several HOCs, however the workaround of adding a cast wasn't sufficient in all cases. Here's a guide which will hopefully help others facing this issue. Please correct me if I've got anything wrong. HOC applies props to composed componentReduced test case: import * as React from 'react';
import { ComponentType, FC } from 'react';
type UserProp = { user: string };
const getUser = <ComposedProps extends UserProp>(
ComposedComponent: ComponentType<ComposedProps>,
) => {
type Props = Omit<ComposedProps, keyof UserProp>;
const GetUser: FC<Props> = (props) => {
/*
Type '{ user: string; } & Pick<ComposedProps, Exclude<keyof ComposedProps, "user">> & { children?: ReactNode; }' is not assignable to type 'ComposedProps'.
'{ user: string; } & Pick<ComposedProps, Exclude<keyof ComposedProps, "user">> & { children?: ReactNode; }' is assignable to the constraint of type 'ComposedProps', but 'ComposedProps' could be instantiated with a different subtype of constraint 'UserProp'.
*/
const composedProps: ComposedProps = {
user: 'bob',
...props,
};
/*
*/
return <ComposedComponent {...composedProps} />;
};
return GetUser;
}; Workaround this issue by using a cast as suggested in #28884 (comment). If/when this issue is fixed, the cast won't be needed. const composedProps: ComposedProps = {
user: 'bob',
...props,
} as ComposedProps; HOC receives its own propsReduced test case: import * as React from 'react';
import { ComponentType, FC } from 'react';
const requireUser = <ComposedProps,>(
ComposedComponent: ComponentType<ComposedProps>,
) => {
type Props = ComposedProps & { user: string };
const RequireUser: FC<Props> = ({ user, ...composedProps }) => {
// … do something with `user`;
/*
Type 'Pick<PropsWithChildren<Props>, "children" | Exclude<keyof ComposedProps, "user">>' is not assignable to type 'IntrinsicAttributes & ComposedProps & { children?: ReactNode; }'.
Type 'Pick<PropsWithChildren<Props>, "children" | Exclude<keyof ComposedProps, "user">>' is not assignable to type 'ComposedProps'.
'ComposedProps' could be instantiated with an arbitrary type which could be unrelated to 'Pick<PropsWithChildren<Props>, "children" | Exclude<keyof ComposedProps, "user">>'.
*/
return <ComposedComponent {...composedProps} />;
};
return RequireUser;
}; This is unsafe because If you're happy to live with unsafe types, you can cast: return <ComposedComponent {...composedProps as ComposedProps} />; A safer solution would be to separate the HOC props from the composed props: import * as React from 'react';
import { ComponentType, FC } from 'react';
const requireUser = <ComposedProps,>(
ComposedComponent: ComponentType<ComposedProps>,
) => {
type Props = { composedProps: ComposedProps; user: string };
const RequireUser: FC<Props> = ({ user, composedProps }) => {
// … do something with `user`;
return <ComposedComponent {...composedProps} />;
};
return RequireUser;
}; HOC applies props to composed component and receives its own propsA combination of the above two. Reduced test case: import * as React from 'react';
import { ComponentType, FC } from 'react';
type UserProp = { user: string };
const getUserById = <ComposedProps extends UserProp>(
ComposedComponent: ComponentType<ComposedProps>,
) => {
type Props = Omit<ComposedProps, keyof UserProp> & { userId: string };
const GetUserById: FC<Props> = ({ userId, ...restProps }) => {
/*
Type '{ user: string; } & Pick<PropsWithChildren<Props>, "children" | Exclude<Exclude<keyof ComposedProps, "user">, "userId">>' is not assignable to type 'ComposedProps'.
'{ user: string; } & Pick<PropsWithChildren<Props>, "children" | Exclude<Exclude<keyof ComposedProps, "user">, "userId">>' is assignable to the constraint of type 'ComposedProps', but 'ComposedProps' could be instantiated with a different subtype of constraint 'UserProp'.
*/
const composedProps: ComposedProps = {
user: 'bob',
...restProps,
};
return <ComposedComponent {...composedProps} />;
};
return GetUserById;
}; This is unsafe because If you're happy to live with unsafe types, you can cast: const composedProps: ComposedProps = ({
user: 'bob',
...restProps,
} as unknown) as ComposedProps; A safer solution would be to separate the HOC props from the composed props: import * as React from 'react';
import { ComponentType, FC } from 'react';
type UserProp = { user: string };
const getUserById = <ComposedProps extends UserProp>(
ComposedComponent: ComponentType<ComposedProps>,
) => {
type Props = { composedProps: Omit<ComposedProps, keyof UserProp> } & {
userId: string;
};
const GetUserById: FC<Props> = ({ userId, composedProps }) => {
const composedPropsComplete = {
user: 'bob',
...composedProps,
} as ComposedProps;
return <ComposedComponent {...composedPropsComplete} />;
};
return GetUserById;
}; |
Still no resolution? I think I'm running into this now on 3.7.3 :( |
Please do something for this... |
@OliverJAsh To answer your comment on SO yes I think my workaround there could be used in your use case as well: type UserProp = { user: string };
type ComposeHocProps<TProps, TExtra> = TProps | (Omit<TProps, keyof TExtra> & TExtra)
export const getUserById = <ComposedProps extends UserProp>(
ComposedComponent: ComponentType<ComposeHocProps<ComposedProps, UserProp>>
) => {
type Props = { composedProps: Omit<ComposedProps, "user"> } & {
userId: string;
};
const GetUserById: FC<Props> = ({ userId, composedProps }) => {
return <ComposedComponent {...composedProps} user={userId} />;
};
return GetUserById;
}; Not sure if it works in any case, but for this specific case it appears works |
Related (I think): microsoft/TypeScript#28884
Also another example, it worked in my case:
import React, { ComponentType, useContext } from 'react';
import { SwitchContext } from './Switch';
type CaseProps<Props extends {}> = {
id: string;
component: ComponentType<Props>;
} & Props;
type Remaining<P> = Pick<CaseProps<P>, keyof P>;
export const Case = <Props extends {}>(props: CaseProps<Props>) => {
const { id, component: Component, ...rest } = props;
const selected = useContext(SwitchContext);
return id === selected ? <Component {...rest as Remaining<Props>} /> : null;
}; When using So saying type ComponentAProps = {
foo: string;
} I'll have to use it this way: <Case id="id" component={ComponentA} foo="bar" /> playground |
Similar syntax, without the
Should also work and seems simpler without the |
TypeScript Version: 3.2.1
Search Terms:
3.2.1
extends
intersection generic
Code
Expected behavior:
Code compiles without errors
Actual behavior:
Playground Link: http://www.typescriptlang.org/play/#src=type%20Omit%3CT%2C%20K%20extends%20keyof%20T%3E%20%3D%20Pick%3CT%2C%20Exclude%3Ckeyof%20T%2C%20K%3E%3E%3B%0A%0Atype%20Func%3CT%3E%20%3D%20(arg%3A%20T)%20%3D%3E%20null%0A%0Atype%20Context%20%3D%20'Context'%3B%0A%0Aexport%20function%20withRouteContextPropConsumer%3C%0A%20%20%20%20T%20extends%20%7B%20routeContext%3A%20Context%20%7D%0A%3E(%0A%20%20%20%20funcToWrap%3A%20Func%3CT%3E%2C%0A)%3A%20Func%3COmit%3CT%2C%20%22routeContext%22%3E%3E%20%7B%0A%20%20%20%20return%20(args%3A%20Omit%3CT%2C%20%22routeContext%22%3E)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20routeContext%3A%20Context%20%3D%20'Context'%3B%0A%20%20%20%20%20%20%20%20return%20funcToWrap(%7B%20...args%2C%20routeContext%20%7D)%3B%0A%20%20%20%20%7D%3B%0A%7D
Related Issues: Nothing obvious
After upgrading from 3.0.3 to 3.2.1, it seems that tsc has (at least partially) lost the ability to reason about constrained generics.
In the example above (one of our React context helper functions, modified to remove the React dependency), the function is parameterised over a constrained generic:
But a few lines later, the compiler complains that the generic
T
may not have arouteContext
attribute. T must have arouteContext
attribute however, because of the constraint. Perhaps theOmit
helper is confusing things?The text was updated successfully, but these errors were encountered: