Typescript: No compile time error when passing wrong function object type

Created on 1 Apr 2017  路  6Comments  路  Source: microsoft/TypeScript



TypeScript Version: 2.2.1 / nightly (2.2.0-dev.201xxxxx)

Brief Description
As long as argument types are within the inheritance hierarchy, TSC allows passing around incompatible function object types at compile time, which leads to undefined objects at runtime. The transpiler seems to assume an implicit + forceful downcast which is not safe. Equivalent code in other typed languages (e.g. C++) does not pass compilation. See code below.

Code

// A *self-contained* demonstration of the problem follows...
class BaseMessage {
    content: string;
}

class DerivedMessage extends BaseMessage {
    sender: string;
    recipient: string; 
}

function processMessage(msg: DerivedMessage) : void {
    console.log({message: 'To: ' + msg.recipient + ' Content: ' + msg.content});
}

function iterateMessages(
    messages: BaseMessage[],
    processor: (msg: BaseMessage) => void) {

    for (const message of messages) {
        processor(message);
    }
}

const msg1: BaseMessage = { content: 'hi' };
const msg2: BaseMessage = { content: 'hello' };
iterateMessages([msg1, msg2], processMessage); // second arg should generate ERROR !

Expected behavior:

The TS compiler/transpiler should reject processMessage as the second input (last line), since its signature does not match the outlined functor type in iterateMessage. It only matches partially, i.e. if we do a forceful implicit downcast. But this is not safe and the transpiler should not implicitly do this. As an example, C++ compilers (a typed language) generate compile time error for equivalent code.

Actual behavior:
In practice, this passes the transpilation but at runtime, this leads to undefined objects.

Duplicate

Most helpful comment

Sorry, declaration (where you "use" the type), not calls (the other "use") 馃槄

All 6 comments

Unfortunately this is known and expected behaviour: FAQ. See #1394

Thanks @gcnew for the links and resources. Following your links, I also found an open proposal #10717 by @Igorbek to ensure argument types are 'contravariant' when assigning function objects.

Some common responses I am seeing around ranges from, this is 'by design' to 'Array

I tested equivalent code in c++ and the compiler does complain, so the type system there must be ensuring it somehow without breaking other use cases (like array assignment). I would request maintainers to revisit this if at all possible.

A correct solution involves co- and contravariance annotations at all parameter use sites. It's something we're thinking about but is a very, very large hammer for a fairly small nail.

@RyanCavanaugh Thanks for the update on what is under consideration. I'm curious, why is use site variance required? Declaration site variance seems to be the trend nowadays. Is it a second order effect of a formalized structural type system?
Annotating every use site sounds uncountenanceable!

Sorry, declaration (where you "use" the type), not calls (the other "use") 馃槄

馃槍

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Antony-Jones picture Antony-Jones  路  3Comments

bgrieder picture bgrieder  路  3Comments

seanzer picture seanzer  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments