Why is the following monkey patch not allowed in Typescript?
const oldXHROpen = window.XMLHttpRequest.prototype.open
window.XMLHttpRequest.prototype.open = function (
method: string,
url: string
): void {
return oldXHROpen.apply(this, [method, url])
}
It gives the following error:
Argument of type '[string, string]' is not assignable to parameter of type '[method: string, url: string | URL, async: boolean, username?: string | null | undefined, password?: string | null | undefined]'.
Source has 2 element(s) but target requires 3.
However when looking at the definitions of open there is a method which only requires two arguments.
open(method: string, url: string | URL): void;
CodePudding user response:
This is a design limitation of TypeScript; see microsoft/TypeScript#38353 for more information.
The TypeScript standard library's typings for the XHMLHttpRequest.open() method looks like this:
interface XMLHttpRequest {
open(method: string, url: string | URL): void;
open(method: string, url: string | URL, async:
boolean, username?: string | null, password?: string | null): void;
}
There are two call signatures, which makes open() an overloaded method in TypeScript. If you call an overloaded method or function directly, the compiler will select one of the overloads based on the parameters you pass in.
But it is unable to do this purely at the type level. If you start to programmatically manipulate the type of an overloaded function type, the compiler will just immediately pick one call signature without trying to figure out which one is the most appropriate for the situation. Usually this is the last call signature.
In this case, presuming you have the --strictBindCallApply compiler option enabled, the apply() method for CallableFunction only sees the second call signature for XHMLHttpRequest.open(). That call signature requires at least three parameters, so the compiler gets angry when you apply() two of them. Oops.
The workaround in this situation is for you to manually widen the type of oldXHROpen from a pair of call signatures to the single call signature you're trying to support. Since widening is a safe operation, you can do this with a type annotation and not give up any type safety:
const oldXHROpen: (method: string, url: string | URL) => void =
window.XMLHttpRequest.prototype.open // okay
Once you do that, the apply() method only sees the open() call signature you care about, and everything works:
window.XMLHttpRequest.prototype.open = function (
method: string,
url: string
): void {
return oldXHROpen.apply(this, [method, url]) // okay
}
