Why can't a parameter of function type take an argument whose parameters implement the original parameter's interface?

Jon Cohen

Okay so the title is weird. But it seems strange that you can't pass a function which takes a type which implements the interface the passed-in function is supposed to take:

interface Empty { }

class Implements implements Empty {
    constructor(public s: string) {}
}

type TakesEmpty = (e: Empty) => void;

function TakesImplementor(e: Implements): void {}

function f(t: TakesEmpty) { }

/*
  Argument of type '(e: Implements) => void' is not assignable to parameter of type 'TakesEmpty'.
  Types of parameters 'e' and 'e' are incompatible. Property 's' is missing in type 'Empty' but required in type 'Implements'.
*/
f(TakesImplementor);

// But this works
function te (e: Empty): void {}
te(new Implements(""));

Playground link

Why is this the case?

jcalz

Well, in your example code nothing bad will actually happen at runtime because the functions don't do anything at all. But given the signature of TakesImplementor(), it could be implemented like this:

function TakesImplementor(e: Implements): void {
    console.log(e.s.toUpperCase());
}

That's fine, right? TakesImplementor() accepts a parameter named e of type Implements, which has an s property of type string. Inside TakesImplementor() you can access e.s.toUpperCase().

And then, given the signature of f(), it could be implemented like this:

function f(t: TakesEmpty) {
    t({});
}

That's also fine, right? The t parameter passed into f() is itself a function of the type TakesEmpty, meaning you should be able to call t({}), since the object {} is a valid Empty.

Yep, everything great until you go ahead and run this:

f(TakesImplementor);

And it explodes at runtime. f(TakesImplementor) calls TakesImplementor({}), which calls {}.s.toUpperCase(), which doesn't exist because {}.s is undefined. Boom. That is why the line f(TakesImplementor) gives an error message:

// Property 's' is missing in type 'Empty' but required in type 'Implements'.

TakesImplementor is not a TakesEmpty, because TakesImplementor only accepts Implements types, while TakesEmpty is required to accept all Empty types, not just Implements types.


It might be counterintuitive, but when dealing with functions, you narrow its type when you widen the type of its parameters, and you widen its type when you narrow the type of its parameters. This "going-the-other-way" of function parameters is known as contravariance. They vary in the opposite way, or "contra-vary". (This is as opposed to function return types, which widen the function type when widened and narrow the function type when narrowed... they vary together, or "co-vary", and thus are covariant.)

By narrowing the parameter type of TakesImplementor from Empty to Implements, you have widened the type of TakesImplementor itself compared to TakesEmpty. Every TakesEmpty can be used in place of TakesImplementor, but not vice versa. Contravariant.

Contravariance of function parameter types is enforced in TypeScript 2.6 and above when you use the --strictFunctionTypes compiler flag (also enabled with just --strict). It's generally a good thing to enforce.

Hope that helps; good luck!

Link to code

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

Why can't a generic member function in a class implementing an interface take an argument of the type of the class (instead of the interface)?

Why can't I take a contravariant interface as a parameter to a method on the interface?

The argument type 'Function?' can't be assigned to the parameter type 'void Function()?'

The argument type 'Function' can't be assigned to the parameter type 'void Function()?

The argument type 'Function' can't be assigned to the parameter type 'void Function()?'

Why this argument type can't be assigned to the parameter type?

Flutter - The argument type 'Null' can't be assigned to the parameter type 'Function'

Why can't I use 'as' with generic type parameter that is constrained to be an interface?

The argument type 'T Function<T>(ProviderListenable<T>)' can't be assigned to the parameter type 'Reader<dynamic, dynamic>

Why can't I call a generic function with a union type parameter?

Why Dll can't export a function with parameter type FILE*

F#: Can't omit generic type parameter in interface function parameter

How can a method take an output parameter of an interface type?

Why can't interface be used as type argument?

The argument type 'Function' can't be assigned to the parameter type 'void Function(bool)?'

Flutter - Argument of type function can't be assigned to the parameter of type `void function()`

The argument type 'void Function()' can't be assigned to the parameter type 'void Function(BuildContext)?

The argument type 'Void Function(BuildContext)' can't be assigned to the parameter type 'Void Function(BuildContext, Meal)'

Flutter / Dart Error: The argument type 'Null Function(Key)' can't be assigned to the parameter type 'void Function(Object)'

The argument type 'Function?' can't be assigned to the parameter type 'void Function(String)?'. Is there a fix for this code?

The argument type 'void Function()' can't be assigned to the parameter type 'void Function(LongPressEndDetails)?'

DropdownButton error: The argument type 'void Function(String)' can't be assigned to the parameter type 'void Function(String?)?'

The argument type 'Container Function(BuildContext, int)' can't be assigned to the parameter type 'Widget Function(BuildContext, int, int)'

Flutter error: The argument type 'Future<Object?> Function()' can't be assigned to the parameter type 'FutureOr<dynamic> Function(dynamic)'

The argument type 'String Function(String)' can't be assigned to the parameter type 'FutureOr<String> Function(PhoneNumber)'

Error: The argument type 'Function?' can't be assigned to the parameter type 'void Function(bool?)?'

The argument type 'Widget Function()' can't be assigned to the parameter type 'String? Function(String?)?' In flutter

error: The argument type 'void Function(bool)' can't be assigned to the parameter type 'void Function(bool?)'

error: The argument type 'void Function()?' can't be assigned to the parameter type 'void Function(String?)?'

TOP Ranking

HotTag

Archive