A robust analysis of a critical type-safety issue in TypeScript's parameter checking mechanism.
📑 Table of Contents
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 includes two versions of TypeScript compiler:
Version | Description | Command |
---|---|---|
🔧 Modified Version (in /bin folder) |
Reports errors when functions with fewer parameters are assigned to function types with more parameters. Source: typescript-go/best-type-script |
.\bin\tsgo.exe tsc .\main.ts |
🔧 Original Microsoft Version (in /_bin folder) |
Uses TypeScript's default parameter arity variance behavior. |
.\_bin\tsgo.exe tsc .\main.ts |
The key difference is that the modified compiler in the /bin
folder:
- ✅ Enforces stricter parameter checking by default
- ✅ Flags functions with fewer parameters when assigned to function types with more parameters
- ✅ Reports errors in cases where the original compiler would silently allow potentially unsafe assignments
This helps identify potential runtime errors that could occur when required parameters are silently ignored.
Example 1: Callback Parameters Being Silently Ignored
// 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
Example 2: Interface Implementation Inconsistency
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);
}
};
Example 3: Real-world Problem Scenario
// 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);
Common Case Where Variance Is Useful
// 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.
❌ | 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:
Problem | Description | Impact |
---|---|---|
Silent failures | Implementations can silently ignore parameters without warning | 🔴 High |
Inconsistent enforcement | Different rules applied to extra vs. missing parameters | 🟠 Medium |
False sense of safety | Interface conformance doesn't guarantee parameter handling | 🔴 High |
The solution to this issue is to use the modified TypeScript compiler (in the /bin
folder) that enforces stricter parameter checking. This compiler:
🛡️ | Reports errors when functions with fewer parameters are assigned to function types that expect more parameters |
🔒 | Maintains TypeScript's type safety without compromising on parameter handling |
🚫 | Helps prevent potential runtime errors from silently ignored parameters |
✅ | Works properly with callbacks due to modified TypeScript definition files in the bin folder |
To modify d.ts files in the bin folder, run the following commands:
node .\transform-dts-files.js
npx dprint fmt "bin/*.d.ts"
The following issues were previously marked as "working as intended" by the TypeScript team, but are now caught and reported as errors by the modified compiler:
Issue | Description | Issue Date | Status |
---|---|---|---|
#13043 | Type hole with compatibility between optional parameters/extra parameters | 2016-12-20 | ✅ Fixed |
#17868 | Reject functions with not enough parameters on strict mode | 2017-08-17 | ✅ Fixed |
#20274 | Feature request - Make a parameter required for callback function | 2018-11-27 | ✅ Fixed |
#20541 | Function argument comparison doesn't match expectations | 2017-12-07 | ✅ Fixed |
#21868 | Typecheck passes for incorrect assignment | 2018-02-11 | ✅ Fixed |
#46881 | Strict check arity of method that implements interface, type or class | 2021-11-20 | ✅ Fixed |