Search code examples
typescriptasync-awaittype-definition

In a function, what's the difference between an inlined return type and the return type defined in a separate type definition?


I'm getting an inconsistent behaviour when I inline the return value type of a function and if I define it in a separate type definition.

For example:

interface Foo {
    bar: string;
}

type Fooer = () => Foo;

const foo1: Fooer = () => { // It knows `.bar` should be a string
    return {
        bar: 123,
    };
}

// Return type set in a separate type definition
const foo2: Fooer = () => {
    return {
        foo: 123, // It doesn't complain about additional properties
        bar: "zzz",
    };
}

// Inline (repeated) return type
const foo3: Fooer = (): Foo => {
    return {
        foo: 123, // And now it does...
        bar: "zzz",
    };
}

Try it on the TypeScript Playground

I'd expect foo2 and foo3 to have the same behaviour (personally I expect both to show the same error, or at least to be consistent).

What am I missing here? What's the difference between both approaches?


Solution

  • What's happening here is a result of TypeScript's 'duck typing'. Basically, the fact that the return type in foo2 has a bar field that is a string means that the function has a signature () => Promise<{foo: number, bar: string}>, which can be assigned to the foo2 variable of type Fooer due to duck typing.

    However, in foo3, you can think of the type checking as being applied directly on the return type instead of on the entire function. As a result you see the error Object literal may only specify known properties, and 'foo' does not exist in type 'Foo'. since the type checking is performed on the object literal, which has the additional requirement that it can't specify unknown properties when you give it an explicit type.