strictFunctionTypes

Checks function parameters contravariantly so a narrower handler cannot stand in for a wider one.

Signature
"strictFunctionTypes": boolean  // Default: true if strict; false otherwise
  • Added in TypeScript 2.6

Parameters

ParameterTypeDescription
$strictFunctionTypesoptionalboolean= true if strict; false otherwiseWhen true, parameters of function-typed values are checked contravariantly rather than bivariantly. Members written in method syntax are exempt.

Errors

Nothing at runtime; it reports error 2322 with the follow-on message 'Types of parameters are incompatible'.

Changelog

  • PHP 2.6Introduced. From the outset the check applies only to function-syntax properties, not to method-syntax members, because enforcing it on methods broke widely used hierarchies including the DOM.

A function that accepts string is not a safe substitute for one that must accept string | number — the caller may hand it a number. Without this flag TypeScript compares parameters bivariantly and allows the substitution anyway. strictFunctionTypes compares them contravariantly, which is the sound rule: the replacement must accept at least everything the target accepts.

Return types were always covariant and are unaffected.

The method-syntax exemption

The check applies only to members written in function syntax:

type A = { func: (x: string | number) => void };  // checked
type B = { func(x: string | number): void };      // NOT checked

During development a large number of inherently unsafe class hierarchies were found, some of them in the DOM, so methods were left bivariant deliberately. Array<Dog> being assignable to Array<Animal> depends on it.

What trips people up

People enable strict mode, expect their callback bug to be caught, and find it is not — because the callback is declared as a method on an interface. Rewriting onEvent(e: Event): void as onEvent: (e: Event) => void is what turns the check on for that member.

Example

Example · typescript
function fn(x: string) {
  return "Hello, " + x.toLowerCase();
}

type StringOrNumberFunc = (ns: string | number) => void;

// With strictFunctionTypes on, `let f: StringOrNumberFunc = fn` is
// error 2322: Types of parameters 'x' and 'ns' are incompatible.
// Without the flag it is accepted, and f(10) crashes.

type Methodish = { func(x: string | number): void };
const m: Methodish = { func: fn }; // method syntax: still accepted

console.log(fn("WORLD"));   // Hello, world
console.log(typeof m.func); // function

When to use it

  • Stopping a narrow event handler from being registered where a wider event type is dispatched
  • Auditing a callback-heavy API for parameter substitutions that will crash at runtime
  • Deciding whether an interface member should be declared as a method or a function property

Frequently asked questions

Why is 'Types of parameters are incompatible' only sometimes reported?

Because strictFunctionTypes checks members written in function-property syntax but exempts members written in method syntax. Change onEvent(e: T): void to onEvent: (e: T) => void and the same assignment starts erroring.

What is the difference between bivariant and contravariant parameter checking?

Bivariant accepts a substitute whose parameter is either wider or narrower; contravariant accepts only a substitute whose parameter is at least as wide. Contravariance is the sound rule, and it is what strictFunctionTypes switches on.

Does strictFunctionTypes affect method parameters in classes?

No. Methods declared with method syntax, including class methods, remain bivariant on purpose — the check broke inherently unsafe but widely relied-on hierarchies such as the DOM's, and array covariance depends on the exemption.

More examples

The runtime crash it prevents

Prints "Hello, ok" then TypeError. The cast stands in for the unsound assignment the flag rejects — with strictFunctionTypes on, the assignment never compiles and the TypeError never happens.

Example · typescript
function fn(x: string) {
  console.log("Hello, " + x.toLowerCase());
}

type StringOrNumberFunc = (ns: string | number) => void;

// The cast reproduces what the compiler allows with the flag OFF.
const func = fn as unknown as StringOrNumberFunc;

fn("ok");        // Hello, ok
try {
  func(10);      // x.toLowerCase is not a function
} catch (e) {
  console.log((e as TypeError).constructor.name); // TypeError
}

Method syntax versus property syntax

Prints "ok 7" for the well-formed call. Calling h.onMethod({ kind: "bad" }) would throw on code.toFixed — the exemption means only the onProp line is flagged by tsc.

Example · typescript
interface Handlers {
  onProp: (e: { kind: string; code?: number }) => void; // checked
  onMethod(e: { kind: string; code?: number }): void;   // exempt
}

const narrow = (e: { kind: string; code: number }) => {
  console.log(e.kind, e.code.toFixed(0));
};

// onProp: error 2322 under the flag. onMethod: accepted.
const h = { onProp: narrow, onMethod: narrow } as unknown as Handlers;

h.onMethod({ kind: "ok", code: 7 }); // ok 7

Discussion

  • Be the first to comment on this lesson.