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
feat(bindNodeCallback): Works on overloaded functions #6072
Conversation
@@ -276,3 +276,99 @@ export type ValueFromNotification<T> = T extends { kind: 'N' | 'E' | 'C' } | |||
export type Falsy = null | undefined | false | 0 | -0 | 0n | ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benlesh Completely off topic, but shouldn't this include NaN
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, probably.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NaN
is not a type; it's a value, AFAICT; number
is the type.
Note, the only instance where this fails is if the overload callback has a different shape. For some reason changing |
TBH, my fantasy is that once v7 is released I'll no longer need deal with TypeScript and can ride off into the sunset and lead a simpler life. |
* overloaded function. See https://github.com/ReactiveX/rxjs/issues/5942 | ||
* for more details | ||
*/ | ||
type OverloadedArgumentsAndReturnType<T> = T extends { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait... this is a type that is a "better Parameters<T>
"? I still don't quite understand how it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is the magical overloaded function inference technique described at https://stackoverflow.com/questions/52760509/typescript-returntype-of-overloaded-function/52761156#52761156
I have a few questions about this:
|
It falls through to the never case
Anyone working with overloading functions should understand they're pretty busted from a TS inference POV
There really isn't any way to specify which overload they're interested in
I imagine this is pretty uncommon so I'm not too worried about it. The only place I see it being used is for encoding on fs.readFile which returns a string instead of a buffer when encoding is used Note that the current PR is loads better then the default TS behavior of "let's pretend we only know about the last overload" approach I can spend a bit more time on this but this problem is pretty in the weeds already |
I think that TypeScript just can't solve this issue, TBH. |
In that case this PR should be good to go. |
IDK why declare function f(a: "a"): "a";
declare function f(a: "a", b: "b"): "ab";
declare function f(a: "a", b: "b", c: "c"): "abc";
type FP = OverloadedParameters<typeof f>; // = [a: "a"] | [a: "a", b: "b"] | [a: "a", b: "b", c: "c"]
type FR = OverloadedReturnType<typeof f>; // = "a" | "ab" | "abc"
type FABL = AllButLast<FP>; // = [] | [a: "a"] | [a: "a", b: "b"]
type FL = Last<FP>; // = "a" | "b" | "c" | undefined I'm not sure this problem is worth the effort, TBH. |
Interesting, this definition of type Last<T> = T extends [...any, infer Last] ? Last : never; |
It's possible we're union-ing the arg types too soon, we may need them to flow as distinct tuples as long as possible. Honestly this is so technical that it's completely beyond what RxJS should be providing. If anyone wants to try their hand at it purely for the academic challenge. This is pushing the envelope a bit too far IMO. Anyway I believe keeping the arg like this may be a solution |
I think one of the tests that we should apply to this library's types could be framed as a (rhetorical) question to the TypeScript maintainers: "did you expect these TypeScript features to be used like this?" And if the answer is (likely to be) "no", we probably shouldn't do it and library users will just have to live with it and implement their own workaround or take the |
I've had another peek at this. I think the fundamental problem isn't extracting the parameters and the return types of the overloads, it's the disconnect between the all-but-first and last parameters in what's returned the call to rxjs/src/internal/observable/bindCallback.ts Lines 17 to 20 in 2a501b2
I don't see how you can describe the relationship between them such that all-but-last and last correspond the the same overload signature. I don't think this is possible. And I think if we cannot make it work reliably with |
I kinda hate myself for this, but a solution popped into my head whilst I was out for a drive. It involves using specific types for the If you look at the snippet, you'll see that the default and encoding overloads return the expected types. It would need more work done, as, IIRC, the I still don't know how I feel about this solution, but I do know how I feel about TypeScript and I want it out of my head. |
What we really want is a mapped type that can be applied to function types, but, as far as I'm aware, mapped types are only applied to object types (with keys) and to tuples. The (overload) signatures in function types have no keys so ... 🤷♂️ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we're going to refine the types for bindCallback
and bindNodeCallback
, I think we'll need to use the approach I mentioned in this comment. That is, I think we'll have to dispense with the more general OverloadedArgumentsAndReturnType
and use types that are specific to bindCallback
and bindNodeCallback
.
What we really want is a TypeScript mechanism for mapping function signatures, but there is no such thing.
Blocking this 'cause it's not going to make the cut for v7, IMO. |
Core team meeting: Most people don't realize the function returned from Maybe we need to deprecate this API and come up with something better? |
So this has the downside that there's still no way to glue the args with the return type. I think I'll need to change the Is this something we want to do? Also am I missing some magical way to keep it as a |
Looking at the types for interface CustomPromisifyLegacy<TCustom extends Function> extends Function {
__promisify__: TCustom;
}
interface CustomPromisifySymbol<TCustom extends Function> extends Function {
[promisify.custom]: TCustom;
}
type CustomPromisify<TCustom extends Function> = CustomPromisifySymbol<TCustom> | CustomPromisifyLegacy<TCustom>;
function promisify<TCustom extends Function>(fn: CustomPromisify<TCustom>): TCustom; And // NOTE: This namespace provides design-time support for util.promisify. Exported members do not exist at runtime.
export namespace readFile {
/**
* Asynchronously reads the entire contents of a file.
* @param path A path to a file. If a URL is provided, it must use the `file:` protocol.
* If a file descriptor is provided, the underlying file will _not_ be closed automatically.
* @param options An object that may contain an optional flag.
* If a flag is not provided, it defaults to `'r'`.
*/
function __promisify__(path: PathLike | number, options?: { encoding?: null; flag?: string; } | null): Promise<Buffer>;
// ... and many more In light of this, I don't think a general solution is worth the effort. Well, not mine anyway. And I don't think we should attempt to leverage this trickery either. Let's face it. There is no TypeScript feature for mapping signatures. And, also, I vote to close this. Emphatically. |
I thought this PR would be a lot simpler 😅
Fixes #5942