Skip to content

topce/parameter-arity-variance-is-not-correct

Repository files navigation

Parameter Arity Variance in TypeScript: A Critical Analysis

TypeScript GitHub Custom

A robust analysis of a critical type-safety issue in TypeScript's parameter checking mechanism.

📑 Table of Contents

📌 The Problem

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.

🚀 Running the Examples

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

⚙️ Differences Between Versions

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.

🔍 Examples Demonstrating the Issue

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 Justification vs. Reality

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

❗ Why This Matters

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

💡 Solution

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

🛠️ Modifying d.ts Files

To modify d.ts files in the bin folder, run the following commands:

node .\transform-dts-files.js
npx dprint fmt "bin/*.d.ts"

🔄 Previously Reported Issues Now Resolved

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published