As I understand it, --strictFunctionTypes
compiler option in Typescript prevents a very common use case of polymorphism from working:
type Handler = (request: Request) => Response
const myHandler: Handler = (request: Request & { extraArg: boolean }) => {
return !!request.extraArg
}
Generally, I assume that all compiler options in the strict
family have some great benefits, but in this case, all I see is that it prevents a very logical behavior from working.
So what are the cases where this option actually gives some benefits? Which harmful scenarios does it prevent?
It's actually very easy to cause a runtime error without strictFunctionTypes
.
Let's consider the following example:
type Handler = (request: Request) => Response
const myHandler: Handler = (request: Request & { extraArg: string }) => {
// extraArg is required so need to check for null
request.extraArg.toUpperCase();
return null as any;
}
declare let r: Request; // comes from sowhere
myHandler(r); // no need to pass in the extraArg not required by the signature
So in the above example, the function signature requires a Request
so that is all we have to pass in a Request
. But the implementation expects to receive Request & { extraArg: string }
in which extraArg
is required, and access it without having to do a check (after all if it's required the called should have passed it in).
This is the kind of errors strictFunctionTypes
prevents. If an argument in the signature is of a base type, while the implementation expects a derived type, there is no guarantee that the implementation will receive the derived type, as the signature only requires the base type to be passed in
This option fixes what is, in my opinion, a bug in the TypeScript compiler. If it’s not a bug, then it was just a bad design decision and the appearance of a new compiler option proves my point. Let’s start with an example, by default, the next code will be compiled without any problem:
// Focus all your attention on callback signature
// It has date parameter which is a union type
function getCurrentYear(callback: (date: string | number) => void) {
callback((Math.random() > 0.5) ? '2020' : 2020);
}
// note that we ignored the fact that in 50% cases our callback returns string type instead of number.
getCurrentYear((date: string) => {
console.log(date.charAt(0)); // in 50% it is RUNTIME ERROR
});
So that arrow function passed to getCurrentYear narrows down the type of “date” parameter and TypeScript doesn’t care about it. However, the same trick in a different context with variables even without any strict rule would produce an error:
let x: string | number = (Math.random() > 0.5) ? '2020' : 2020;
const y: number = x; // COMPILE TIME ERROR
This makes much more sense and enabling --strictFunctionTypes will ask a compiler to follow the same behavior in callback functions. That definitely will help you to prevent some bugs in a big project.
Source:
https://medium.com/javascript-in-plain-english/what-are-these-strict-compiler-options-in-typescript-part-2-a7e974b13e54