Rob Simmons
Rob Simmons

Reputation: 51

Preventing typescript from allowing undefined return

I've got all the strict flags that I would expect turned on, so I was confused when Typescript accepted the following code:

export class Test {
    private records: {
        [id: string]: number
    };
    constructor() {
        this.records = {
            "hello": 1,
            "world": 3
        }
    }

    public get(id: string): number {
        return this.records[id];
    } 
}

The get property obviously does not always return a number: it will return undefined when the function is passed anything besides "hello" and "world". Is there a flag that would catch this function and require its return type to be number | undefined, which is the correct return type?

I'm using noImplicitAny and strictNullChecks already. Surely there's some way to do this, as I have a function that claims to return a number returning undefined!

Upvotes: 4

Views: 5489

Answers (2)

Rob Simmons
Rob Simmons

Reputation: 51

Based on jonrsharpe's comments, I eventually found this thread. The answer as I understand it is that this type:

interface Wha { 
    foo: number;
    bar: number;
    [id: string]: number;
};

is always incorrect, because the type system treats it like it is an object that maps every single id to a defined number, and no such object can ever exist. (Every object that you can possibly create will always have strings that don't map to numbers, as lilezek pointed out in comments.)

Typescript is perfectly happy giving this object the Wha type:

let r: Wha = { foo: 4, bar: 7, baz: 33 };

Because it incorrectly thinks that every string index in a Wha object maps to a defined number, Typescript will treat r.notafield, r.fnord, and r["ha"] as having type number. Therefore, it will happily proceed as if all of these things cannot possibly be undefined. But they all are undefined! Avoiding that situation is why I wanted to use Typescript with --strictNullChecks in the first place 😐

If you want to avoid having a bunch of undefined things zipping around the typechecker pretending to be a totally-defined number, the only option is this:

interface Wha { 
    foo: number;
    bar: number;
    [id: string]: number | undefined;
};

This leads to all sorts of problems, because it means that you can have valid keys in the array that mapped to undefined, where what you originally meant was that, if k is a key in Object.keys(r), then r[k] is a number. So there's no good option here.

Maybe the lesson here is that I should use ES6 Maps instead of indexable types, because indexable types lie to you without warning.

Upvotes: 1

lilezek
lilezek

Reputation: 7344

Try with this:

public get(id: string): number | undefined {
    return this.records[id];
} 

Or this:

public get(id: string) {
    return this.records[id] as number | undefined;
} 

One of these should explicitly return undefined as a possible type.

Upvotes: 0

Related Questions