This project demonstrates that TypeScript's parameter arity variance behavior can lead to potential runtime errors and inconsistent type checking.
TypeScript allows functions with fewer parameters to be assigned to function types with more parameters. While this enables common JavaScript patterns like array callbacks, it can also introduce subtle bugs when a function is expected to handle all provided parameters.
This project uses a modified version of TypeScript that reports errors when functions with fewer parameters are assigned to function types with more parameters: https://github.com/topce/typescript-go/tree/parameter-arity-variance-is-not-correct
.\bin\tsgo.exe tsc --noParameterVariance --skipLibCheck .\main.ts
Still you can use default TypeScript go compiler if you do not specify noParameterVariance flag
.\bin\tsgo.exe tsc .\main.ts
// Typical callback scenario - TypeScript allows this
function handler(arg: string) {
console.log(arg)
}
function doSomething(callback: (arg1: string, arg2: number) => void) {
callback('hello', 42); // Second parameter is silently ignored by handler
}
doSomething(handler); // Should warn that handler doesn't use the second parameter
interface I {
hi(a: string, b: string): void;
}
// Error - TypeScript correctly prevents adding MORE parameters
class A implements I {
hi(a: string, b: string, c: string): void { // Error: Too many parameters
throw new Error("Method not implemented." + a);
}
}
// No error - but should be flagged as potentially unsafe
class B implements I {
hi(a: string): void { // Only handles first parameter when interface requires two
throw new Error("Method not implemented." + a);
}
};
// A service interface that processes users
interface UserService {
processUser(name: string, id: number): void;
}
class BrokenUserService implements UserService {
// TypeScript accepts this despite missing the required id parameter
processUser(name: string): void {
// This implementation never uses the id, which could cause logic errors
console.log(`Processing user ${name}`);
// What if business logic depended on the id parameter?
}
}
function requireBothParameters(callback: (a: string, b: number) => void) {
// This function assumes callback will use both parameters
callback("test", 123);
}
// TypeScript allows this despite handler ignoring the second parameter
requireBothParameters(handler);
// Standard array iteration - here we want to allow partial parameter usage
let items = [1, 2, 3];
items.forEach(arg => console.log(arg)); // Only using first parameter is fine
items.forEach(() => console.log("Counting")); // Sometimes we don't need parameters at all
TypeScript's official position (from their FAQ) is that this behavior is "correct" because it supports common JavaScript patterns like array callbacks.
However, this creates a blind spot in the type system:
- TypeScript enforces that you can't add MORE parameters than an interface specifies
- But allows you to implement FEWER parameters, potentially ignoring critical information
The parameter arity variance issue creates an inconsistency in TypeScript's otherwise strong type-checking:
- Silent failures: Implementations can silently ignore parameters without warning
- Inconsistent enforcement: Different rules applied to extra vs. missing parameters
- False sense of safety: Interface conformance doesn't guarantee parameter handling
Add an optional compiler flag for stricter parameter checking --noParameterVariance
- #13043: Type hole with compatibility between optional parameters/extra parameters
- #17868: Reject functions with not enough parameters on strict mode
- #20274: Feature request - Make a parameter required for callback function
- #20541: Function argument comparison doesn't match expectations
- #21868: Typecheck passes for incorrect assignment