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 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
- Introduce syntax to mark parameters that must be handled by implementations
- Provide linting rules to detect potentially problematic parameter arity variance
This project aims to highlight the need for TypeScript to revisit this design decision.