Arťom Pozdnyakov
Arťom Pozdnyakov

Reputation: 617

Typescript - exact return type of function

I have a problem with return type of the function. In the example, if I explicitly define return type Foo, it shows error as expected. If I add type to function, an error is not shown.

It seems that in foo2 should be at least object extended from Foo, if I remove "a" from foo2, it says this:

Type '() => { b: string; }' is not assignable to type 'FooFn'. Type '{ b: string; }' has no properties in common with type 'Foo'.

interface Foo {
    a?: string
}

type FooFn = () => Foo

const foo = (): Foo => {
    return {
        a: 'a',
        b: 'b', // OK
    }
}

const foo2: FooFn = () => {
    return {
        a: 'a',
        b: 'b', // Why is not error also here???
    }
}

The only option, that I think of is making it something like that:

interface Foo {
    a?: string
}

// I cant figure it out, how to write it ...
type FooFn = () => Record<string, key is from keyof Foo ? string : never>

Is it possible to make type, which only accepts keyof Foo and the rest is never?

Playground:

https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGIHt3IN4FgBQyRycA-AFzIDOYUoA5gQL4EFgCeADihuqiMgC8yABQBKIQD40mVvgToQNZDExDRYyryk4CxZFAhgArlAF5C+4nEoByOLYA0eq0QBGdt0+QB6H8gB5AGkXIhZ8cIIFJTAVTAAmLUx+dXEdC31DEzNdS1cbZHsnUKsPQq9HX38AdQALdmRgKmQQdFjoKHQoEgAbKixa6AhSEZLwpiA

Upvotes: 0

Views: 711

Answers (1)

Ovidijus Parsiunas
Ovidijus Parsiunas

Reputation: 2732

The behaviour you are observing for const foo2: FooFn = () => { is a result of TypeScript's Structural typing system which implies that as long as the returned value of foo2 has all the properties of Foo type - the function will pass no matter what other properties the resulting object may contain.

The reason why you are seeing an error for const foo = (): Foo => { is because you are defining an explicit return type which acts like type annotation, whereas the use of FooFn kicks in a layer of abstraction that acts like type assertion which does not care about extra properties that are returned and only makes sure that the types have matching values.

This can be illustrated by:

let obj1: { 'a': string } = { a: 'a', b: 'b' } // will not pass for annotation
let obj2 = { a: 'a', b: 'b' } as { 'a': string } // will pass for assertion

Referring back to your example, using type assertion is safe as the returned object from foo2 will still have the type of Foo, meaning that only property a can be accessed no matter what else it has during application runtime.

So unfortunately your problem is attempting to break TypeScript's type assertion convention and it may be worth reassessing your approach.

Thankyou to @jcalz for helping to improve this answer.

Upvotes: 1

Related Questions