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
inferred generic type param of higher order function is too wide #29873
Comments
I think this is the expected behavior. With class Animal { a: any }
class Dog extends Animal { d: any }
type Y = ((a: Animal) => void) extends ((a: Dog) => void) ? "Y" : "N"
type N = ((a: Dog) => void) extends ((a: Animal) => void) ? "Y" : "N" So basically if the parameter is of a derived type the relationship is inversed, the function with the base type argument ( So this would actually work, as interface BaseOptions {
foo: string,
baz: number
}
declare function higherOrderFunction<
F extends (options: BaseOptions) => any,
O extends BaseOptions
>(fn: F): (options: BaseOptions & { bar: string }) => any
declare function fn(options: { foo: string }): any
const test = higherOrderFunction(fn) |
One more point. Your workaround removes the constraint that the parameter be derived from A workaround that preserves both the restriction and the full function type could be: declare function higherOrderFunction<F extends (options: O) => any, O extends BaseOptions>(fn: F & ((o: O) => any)): F
declare function fn(options: { foo: string; baz: number }): any
const test = higherOrderFunction(fn)
declare const fnWithProps: {
(options: { foo: string; baz: number }): any
defaultProps: {
foo: string
}
}
const testWithProps = higherOrderFunction(fnWithProps) // { (options: { foo: string; baz: number; }): any; defaultProps: { foo: string; }; } And you could pick the extra properties from the function, and modify the signature if need. For example: interface BaseOptions {
foo: string
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
declare function higherOrderFunction<F extends (options: O) => any, O extends BaseOptions>(fn: F & ((o: O) => any)): Pick<F, keyof F> & ((o: Omit<O, 'foo'>) => ReturnType<F>)
declare function fn(options: { foo: string; baz: number }): any
const test = higherOrderFunction(fn)
declare const fnWithProps: {
(options: { foo: string; baz: number }): any
defaultProps: {
foo: string
}
}
const testWithProps = higherOrderFunction(fnWithProps) |
Yes sorry I mistyped the workaround, it needs the additional intersection against the I looked at your first response and I don't see the signature of Issue here is that explicitly providing the generic type params still satisfied the constraints, but I wish that the inference engine would have inferred that type right away instead of prioritizing the wider base type: // this works
const test = higherOrderFunction<typeof fn, BaseOptions & { bar: string }>(fn) |
With the upcoming partial inference feature in 3.4 the pain may be lessened by doing this, but still doesn't resolve the issue fully: const test = higherOrderFunction<typeof fn, _>(fn) |
@ferdaber My point is that it is not a priority issue. The way function types work (under The example demonstrated that reverse is true, that if |
Ok, yeah that's fair, this would not work: const test = higherOrderFunction<typeof fn, BaseOptions>(fn) Since we're contravariant for function parameter types. What, then, would be the construct and ordering of generic type params such that we use |
@ferdaber I think the version in my second comment does a pretty good job declare function higherOrderFunction<F extends (options: O) => any, O extends BaseOptions>(fn: F & ((o: O) => any)): F If gives the compiler a site to infer BTW, this: |
Ah! I like that approach, sorry it was difficult to grok at first and understand why this is happening, but I see why that works now. I think this signature below is even simpler and still works; do you see any reason why this would fail? declare function higherOrderFunction<O extends BaseOptions, F>(fn: ((options: O) => any) & F): F This makes it somewhat more explicit that |
@ferdaber Yeah I think you are right, I just left in the original constraint and didn't think about removing it, but your version looks cleaner. I believe it will work just as well. |
Wonderful! This is awesome. Thanks so much for the help! |
TypeScript Version: typescript@next
Search Terms: higher order function component generic inference
Code
Expected behavior:
Generic params of
higherOrderFunction
are inferred to betypeof fn
andBaseOptions & { bar: string }
, respectively.Actual behavior:
Type error is resulted because generic params of
higherOrderFunction
are inferred to be(options: BaseOptions) => any
andBaseOptions
, respectively:Playground Link: link
Related Issues: Not that I saw, but this relates closely to how React HOCs are constructed, where we require the type of the component itself to resolve default props and prop types using
JSX.LibraryManagedAttributes
.Current workaround:
Kinda gross but using a conditional type that references the the generic type parameter inside its own constraint works, which feels very hacky and weird to me??
The text was updated successfully, but these errors were encountered: