Typescript: Can't implement interface by throwing error

Created on 30 Jun 2017  路  4Comments  路  Source: microsoft/TypeScript

TypeScript Version: nightly (2.5.0-dev.20170629)

Code

interface I { m(): number; }
const o: I = { m() { throw new Error("not implemented"); } }

Expected behavior:

No error.

Actual behavior:

Type '() => void' is not assignable to type '() => number'.

The same problem occurs inside a class. Manually annotating m(): number fixes the problem, but shouldn't we get that from the contextual type anyway?

Fixed Suggestion

Most helpful comment

These should be identical:

const o1: I = { m: function () { throw new Error('nyi'); } }
const o2: I = { m() { throw new Error("not implemented"); } }

All 4 comments

Check out #16608 and #8767 for changes/discussion related to this. We could fix this with a voidWideningNever type, or by just checking for a contextual type.

I'm only half-joking.

These should be identical:

const o1: I = { m: function () { throw new Error('nyi'); } }
const o2: I = { m() { throw new Error("not implemented"); } }

@DanielRosenwasser Probably need more input from other people but I think it'd be better to have a flag that undoes #8767 rather than a mashup type of void and never.

I'm actually fine with getting an error in this case:

class Base {
    overrideMe() {
        throw new Error("You forgot to override me!");
    }
}

class Derived extends Base {
    overrideMe() {
        // Code that actually returns here
    }
}

From my perspective, it's better to have to explicitly annotate the return type in the base class, because it might be the case that my to-be-overridden function is actually supposed to return string, or an array, or any number of other things, and an inferred return type of void will just mislead someone who has a reference of type Base.

@masaeedu we talked about this quite a bit last week. The problem with erroring in the above code is that it would be a breaking change. The only reason we even did #8767 was because the number of breaks it induced was too significant.

Breaking change concerns aside, the good thing with giving a type void here is that no one can meaningfully misuse a void value obtained via a Base instance reference. Conversely, you can trivially misuse a never value because it's e.g. a valid function argument to any parameter position. It's worth considering the case where the author of Base does not have any Deriveds in their codebase (because they're shipping a base class library) -- they won't realize they have a "missing" type annotation until one of their consumers shows up complaining.

Re: another flag... this is not something that would be worth doubling the configuration space of TypeScript over.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DanielRosenwasser picture DanielRosenwasser  路  3Comments

dlaberge picture dlaberge  路  3Comments

bgrieder picture bgrieder  路  3Comments

Antony-Jones picture Antony-Jones  路  3Comments

remojansen picture remojansen  路  3Comments