-
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
Meta-issue: Use Full Unification for Generic Inference? #30134
Comments
Wouldn't that be a solution: function identity<T>(arg: T) { return arg; }
function memoize<F extends <G>(...args: G[]) => G,G>(fn: F): F { return fn; }
// memid<T>(T) => T
const memid = memoize(identity); Which wouldn't work with non generic functions, but this could be fixed like that: function memoize<F extends ( <G>(...args: G[]) => G ) | ( (...args: G[]) => G ),G>(fn: F): F { return fn; }
// memid<T>(T) => T
const memid1 = memoize(identity);
function stupid(arg: string) { return arg; }
// memid (string) => string
const memid2 = memoize(stupid); It's definitely not pretty, but it seems to do the job. |
Correct. |
This approach would work only for really simple cases. If for example there is any constraint on the type parameter of @RyanCavanaugh type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
function HOC<P extends { hocProp: string }>(component: (p: P) => void): (p: Omit<P, 'hocProp'>) => void {
return null!;
}
// Works great for regulart components
// const Component: (props: { prop: string }) => void
const Component = HOC(function (props: { prop: string, hocProp: string}) {});
// Curenly an error, ideally we could write HOC to get
// const GenericComponent: <T extends number | string>(p: { prop: T }) => void
const GenericComponent = HOC(function <T extends number | string>(p: { prop: T, hocProp: string}) {
}) |
@RyanCavanaugh is #29791 another example of something that could be addressed by this? You mention the absence full unification in your comment there. |
I'm currently running into this issue, or at least what I think is this issue. I have a class that extends an interface that uses generics. I have to explicitly type the parameters on methods in my class even though the type is already explicitly stated on the interface. If I don't, it complains that I didn't type it and calls it an An example:
In Would this be fixed by this? It would be a lot less verbose if I didn't have to re-explicitly-type everything. |
I think I also have an example for this issue: class Pair<L, R> {
constructor(readonly left: L, readonly right: R) {}
}
class PairBuilder<L, R> {
constructor(readonly r: R) {}
build(l: L) { return new Pair(l, this.r); }
map<R2>(f: ((r1: R) => R2)): PairBuilder<L, R2> { return new PairBuilder(f(this.r)); }
}
const makePairBuilder = <L> (r: string) => new PairBuilder<L, string>(r);
function f0(): PairBuilder<string, string> {
return makePairBuilder('hello');
// resolves as expected
// const makePairBuilder: <string>(r: string) => PairBuilder<string, string>
}
const badPair = f0().build({});
// type error as expected
// Argument of type '{}' is not assignable to parameter of type 'string'.
const pair = f0().build('world');
// resolves as expected
// const pair: Pair<string, string>
function f1(): PairBuilder<string, string> {
return makePairBuilder('hello').map(s => s + ' world');
}
// unexpected type error:
// Type 'PairBuilder<unknown, string>' is not assignable to type 'PairBuilder<string, string>'. in This case may be a more specific and easier to analyze issue; the left type is unchanged by (edit1: fix some wording) (edit2: remove the unnecessary |
import React from 'react'
function Parent<T>({ arg }: { arg: T }) {
return <div />
}
interface Props<T> {
arg: T;
}
const Enhance: React.FC<Props<number>> = React.memo(Parent)
const foo = () => <Enhance arg={123} /> |
Just tried out above example, with TS 3.7.2 it seems to be working now?
In contrast to 3.6.3 Playground I am bit puzzled (in positive sense), @RyanCavanaugh did I miss out a feature or so? |
@Ford04 good point - the issue in the OP was tactically fixed by allowing some transpositions of type parameters. I need to write up a new example |
Is this SO post relevant for your need for a new example @RyanCavanaugh ? https://stackoverflow.com/questions/58469229/react-with-typescript-generics-while-using-react-forwardref Specifically, I got here because I'm trying to correctly type a call to |
Another example that happens all the time to those of us who use interface Test<S> {
func1: (arg) => S;
func2: (arg:S) => any;
}
function createTest<S>(arg: Test<S>): Test<S> {
return arg;
}
createTest({
func1: () => {
return { name: "eliav" };
},
func2: (arg) => {
console.log(arg.name); //works - name is recognized
},
});
createTest({
func1: (arg) => {
return { name: "eliav" };
},
func2: (arg) => {
arg; // type unknown, why?
console.log(arg.name); //ERROR - name is NOT recognized
},
}); very annoying |
My HashSet wont work anymore
IUserDTO-> hash()
typescript says value.hash() is not a function when I modeled it properly with an interface and a class that implements the interface in fact it had been working great as hashable
but now my generic wont work anymore whats going on? Only diff is Im pulling entities off the back end http and collecting them as typeorm entities are generics toast now? This would take out the entire abstraction layer across the industry. |
I use |
well my issue might be this declaring IHashable interface to be a class Hashable maybe this is why Im not getting anything under the hood
strange how angular and typescript will let one get away with and run with until it finally shows up I cant imagine generics not working |
still fails
Im peeling a TypeORM entity off the back end successfully in its own class that implements hash() can anyone answer if generics are broken? |
Another example: function test<TItem, TValue>(data: {
item: TItem;
getValue: (item: TItem) => TValue;
onChange: (value: TValue) => void;
}) {}
test({
item: { value: 1 },
getValue: (item) => item.value,
onChange: (value) => {}, // value is unknown
});
test({
item: { value: 1 },
getValue: (item) => 1,
onChange: (value) => {}, // value is unknown
});
test({
item: { value: 1 },
getValue: () => 1,
onChange: (value) => {}, // value is number
}); |
Based on the comment here: #44999 (comment) the "full unification" algorithm would solve the issue outlined in that issue. |
Adding to the list of examples: Excerpt: interface Example<A, B> {
a: A
aToB: (a: A) => B
useB: (b: B) => void
}
const fn = <A, B>(arg: Example<A, B>) => arg
const example = fn({
a: 0,
aToB: (a) => `${a}`,
useB: (b) => {}
})
// want: Example<number, string>
// got: Example<number, unknown> Similar (/identical?) in structure to what was reported in #25092. Have additional utility types or syntax—i.e. some way of helping the TS compiler determine where to infer a generic, and where to just enforce it—been considered? In the example above, the TS compiler currently seems to want to infer Some examples, extending the example above: // Explicitly specify from where to infer generic `B`.
interface Example<A, B> {
aToB: (a: A) => infer B
// or
aToB: (a: A) => assign B
// or
aToB: (a: A) => determine B
// or
aToB: (a: A) => Infer<B>
}
// Or explicitly specify from where to _not_ infer (i.e. just enforce) generic `B`.
interface Example<A, B> {
useB: (b: derive B) => void
// or
useB: (b: Derived<B>) => void
// or
useB: (b: Weak<B>) => void
} Understandably this would just be a duct tape solution to the bigger shortcoming; ideally TypeScript would be able to infer these generics correctly. Given that a revamp of the generic inference algorithm (full unification or some multi-pass attempt) may be too complex at this point, maybe something like this could serve as an "intermediary" solution? No idea if this would be trivial to implement, or equally too complex. One minor added benefit of explicitly telling the compiler from where to infer a given generic might be that type collisions could then be reported in places where such errors may be more expected. In the example above, a mismatch would be detected and reported in the Obvious downsides to this approach include additional syntax/utility type, and extra onus on code authors to comply with and work around the compiler. EDIT (2024-02-08): |
Is this issue the most appropriate one for the inference failure in the following? declare function g<T>(x: T): T;
declare function h<U>(f: (x: U) => string): void
h(g) // error, inference of U fails and falls back to unknown
//~ <-- Argument of type '<T>(x: T) => T' is not assignable to parameter of type '(x: unknown) => string'.
h<string>(g) // okay, U manually specified as string, T is inferred as string
h(g<string>) // okay TS4.7+, T is manually specified as string, U is inferred as string Or does there exist another more specific GitHub issue somewhere for this? I can't find one if there is. |
@RyanCavanaugh is there a possibility of taking a look at this issue for the next version? I've been running into this issue a lot lately within the react ecosystem. is there currently a way to overcome this shortcoming without specifying the generic? |
I can't restrict the last parameter to be RequestParams, I don't know if it's related to this problem type BaseFunc<
P extends
| [RequestParams]
| [never, RequestParams]
| [never, never, RequestParams]
| [never, never, never, RequestParams]
| [never, never, never, never, RequestParams],
T = any,
E = any,
> = (...args: P) => Promise<HttpResponse<T, E>>;
export function useFetch<
TP extends
| [RequestParams]
| [never, RequestParams]
| [never, never, RequestParams]
| [never, never, never, RequestParams]
| [never, never, never, never, RequestParams],
TFunc extends BaseFunc<TP>,
>(fetchApi: TFunc, ...params: Parameters<TFunc>) {
let controller: AbortController;
const fetch = (...args: Parameters<TFunc> | []) => {
if (controller) {
controller.abort();
}
controller = new AbortController();
args[fetchApi.length] ??= {};
args[fetchApi.length].signal = controller.signal;
return fetchApi(...args).then(res => {
return res;
});
};
fetch(...params);
onUnmounted(() => {
controller?.abort();
});
return { fetch };
} |
Thanks for this playground–I thought I was losing my mind seeing |
Linking to @ahejlsberg's comment #17520 (comment) which I always look for when coming here:
|
Search Terms
unification generic inference
Suggestion
Today, TypeScript cannot retain or synthesize free type parameters during generic inference. This means code like this doesn't typecheck, when it should:
Use Cases
Many functional programming patterns would greatly benefit from this.
The purpose of this issue is to gather use cases and examine the cost/benefit of using a different inference algorithm.
Examples
Haven't extensively researched these to validate that they require unification, but it's a start:
#9366
#3423
#25092
#26951 (design meeting notes with good examples)
#25826
#10247
The text was updated successfully, but these errors were encountered: