-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Generic inference to unknown after using function arguments #43371
Comments
type Connector = <
AccessToken
>(optsA: {
getAccessToken(input: {code: string}): Promise<AccessToken>;
}, optsB: {
validateAuth(input: {fields: AccessToken}): Promise<{name: string}>;
}) => any;
const connector: Connector = (inp) => undefined
connector({
getAccessToken: async () => ({token: 'token'}),
}, {
validateAuth: async ({fields}) => { //fields: {token: string}
// ^?
return {name: 'qwe'}
}
})
connector({
getAccessToken: async (inp) => ({token: 'token'}), // mention input argument breaks inference
}, {
validateAuth: async ({fields}) => { // fields: {token: string}
// ^?
return {name: 'qwe'}
}
})
|
This issue might be related: 38264. |
Our inference algorithm is based on doing a fixed number of passes to collect inference candidates (see also #30134 which proposes that we use an unbounded number of passes). In this case, the parameterless function is non-context-sensitive, meaning that its return type can be known ahead-of-time to not depend on the ultimate result of inference, so we're able to use its return type to collect an inference candidate. The parameterful version is context-sensitive, because it has an unannotated parameter. From the outside we can see that this ultimately won't matter, but the inference process is not far enough along at this point to use that information, and thus delays collecting the return type as an inference candidate until later on at which point The workaround in this case is to write |
wow, thanks for the detailed answer and the workaround |
Thanks for the great explanation @RyanCavanaugh, very helpful! Ran into this exact issue today (playground link), and it was especially insidious because it silently introduced type InputData = { someNum: number; }
type Options<T> = {
selector: (data: InputData) => T;
equalityFn?: (a: T, b: T) => boolean;
}
function useSelector<T>(options: Options<T>): T {
return options.selector({someNum: 20});
}
// GOOD: works when there's an implict type for `data` and no `equalityFn`,
// `foo` is correctly of type `number`
const { foo } = useSelector({
selector: (data) => ({ foo: data.someNum }),
});
// GOOD: works when there's an expicit type for `data` and an `equalityFn`
// `foo2` is correctly of type `number`
const { foo2 } = useSelector({
selector: (data: InputData) => ({ foo2: data.someNum }),
equalityFn: (a,b) => a === b,
});
// BAD: fails silently when there's an implicit type for `data` and an `equalityFn`
// foo3 is `any`
const { foo3 } = useSelector({
selector: (data) => ({ foo3: data.someNum }),
equalityFn: (a,b) => a === b,
}); |
@jkillian that's a bug caused by the binding pattern (aka destructuring). I think it's reported already but can't find it at the moment - can you log a new issue? |
@RyanCavanaugh, thanks, agreed after looking at it again that the |
Closed #46977 (duplicate) for this one. We're trying to do get around this in our React Query library and it's definitely been weirding us out 😂 . I think it's worth dropping in a quick snippet of our use-case for posterity: function useQuery<TQueryKey, TData>(_options: {
queryKey: TQueryKey;
queryFn: (context?: { queryKey: TQueryKey }) => TData;
onSuccess: (data: TData) => void;
}) {}
const queryKey = ["test", 1, 2, { 3: true }]
// no context no cry
useQuery({
queryKey,
queryFn: (): number => 1,
onSuccess: (data) => data.toFixed(),
});
// as soon as I use ctx, data is no longer of type number for onSuccess
useQuery({
queryKey,
queryFn: (ctx): number => 1,
// Why is `data` of type `any`?
onSuccess: (data) => data.toFixed(),
}); For now, we've dropped the proposal to adopt this syntax as the primary syntax in our API, but we would really love to make it happen in the future. Where should we go from here to help that happen? cc @TkDodo |
@RyanCavanaugh it seems that this got fixed by intra-inference improvements in 4.7, all reported playgrounds work: The issue can be closed. |
So the order of properties is important when inferring For example this works function createModule<RequestData, FormValues>(o: {
values: FormValues;
mapValues: (values: FormValues) => RequestData;
onCreate: (data: RequestData) => void;
}) {}
createModule({
values: {
name: 2
},
mapValues: (values) => ({
title: values.name
}),
onCreate: (data) => data.title.toFixed(),
}); But this doesn't function createModule<RequestData, FormValues>(o: {
values: FormValues;
mapValues: (values: FormValues) => RequestData;
onCreate: (data: RequestData) => void;
}) {}
createModule({
onCreate: (data) => data.title.toFixed(),
values: {
name: 2
},
mapValues: (values) => ({
title: values.name
}),
}); |
Yes, this is an existing limitation. Note that it isn't about properties but certainly, it's easier to run into this with object properties. Let's take a look at this example with positional arguments (TS playground): declare function createModule1<RequestData, FormValues>(
values: FormValues,
mapValues: (values: FormValues) => RequestData,
onCreate: (data: RequestData) => void,
): void;
// this works
createModule1(
{
name: 2,
},
(values) => ({
title: values.name,
}),
(data) => data.title.toFixed(),
);
declare function createModule2<RequestData, FormValues>(
values: FormValues,
onCreate: (data: RequestData) => void,
mapValues: (values: FormValues) => RequestData,
): void;
// this doesn't
createModule2(
{
name: 2,
},
(data) => data.title.toFixed(), // 'data' is of type 'unknown'.(18046)
(values) => ({
title: values.name,
}),
); As we can see, when context-sensitive functions are involved it always has been important to have the "producer" before the "consumer". |
Bug Report
Hello 👋🏻
I'm facing some strange issue with Generics. Until I specify input arguments in function all generics are inferred correctly. But as soon as I mention argument in function the inference is broken.
Thanks in advance for any help 🙏🏻
🔎 Search Terms
🕗 Version & Regression Information
typescript: 4.2.3
Also tried nightly build but the issue remains
⏯ Playground Link
Workbench Repro
💻 Code
🙁 Actual behavior
fields
insidevalidateAuth
infer to unknown after using function argument ingetAccessToken
. Until I use arguments everything works OK🙂 Expected behavior
Usage of function arguments inside
getAccessToken
should not affect Generic inference since they are statically typeThe text was updated successfully, but these errors were encountered: