Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript - Why can't this string literal type be inferred?

The following snippet does not pass the type check:

type TaskType = 'SIMPLE' | 'COMPLEX'

interface TaskDefinition {
    name: string,
    task: string,
    taskType: TaskType
};

const test: TaskDefinition = { 
    name: '',
    task: '',
    taskType: 'SIMPLE' // This is fine
};

const tasks : TaskDefinition[] = ["apples", "pears"].map(i => {
    return {
        name: i,
        task: i,
        taskType: 'SIMPLE' // This one is not
    };
})

{ name: string; task: string; taskType: string; }[] is not assignable to type TaskDefinition[].

Try it

It seems that taskType gets inferred as string instead of TaskType despite the target type being TaskDefinition

What's causing this and how can I fix it?

like image 883
ᴘᴀɴᴀʏɪᴏᴛɪs Avatar asked Jun 08 '18 14:06

ᴘᴀɴᴀʏɪᴏᴛɪs


People also ask

What are string literal types in TypeScript?

The string literal type allows you to specify a set of possible string values for a variable, only those string values can be assigned to a variable. TypeScript throws a compile-time error if one tries to assign a value to the variable that isn't defined by the string literal type.

Is a string literal a data type?

String literals are specified by one or more characters enclosed in single quotes. The default data type for string literals is varchar, but a string literal can be assigned to any character data type or to money or date data type without using a data type conversion function.

What is a literal type?

Literal types let you indicate that an expression is equal to some specific primitive value. For example, if we annotate a variable with type Literal["foo"] , mypy will understand that variable is not only of type str , but is also equal to specifically the string "foo" .

Is string a literal?

A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string.


1 Answers

Typescript will infer string literal types only when you assign it to a const. When you are creating object literals, the compiler will infer string for string constants not string literal types. If you assign the object literal directly to something that requires a string literal type, that is ok, as in this case the compiler just checks that the string constant is assignable to the string literal type.

The simple solution here is to specify the type argument to map, this will still preserve compiler checks on the return value from map :

const tasks = ["apples", "pears"].map<TaskDefinition>(i => {
    return {
        name: i,
        task: i,
        taskType: 'SIMPLE'
    };
})

Or to use a type assertion on the string to the expected string literal type:

const tasks:TaskDefinition[] = ["apples", "pears"].map(i => {
    return {
        name: i,
        task: i,
        taskType: 'SIMPLE' as 'SIMPLE'
    };
}) 

Edit Since typescript 3.4 (PR) you can also use an as const assertion to get the string literal type:

const tasks:TaskDefinition[] = ["apples", "pears"].map(i => {
    return {
        name: i,
        task: i,
        taskType: 'SIMPLE' as const
    };
}) 

End Edit

You could also type assert directly on the return value, but this will disable some checks on the return value:

const tasks:TaskDefinition[] = ["apples", "pears"].map(i => {
    return <TaskDefinition>{
        wrongValue: "", // no error since we are asserting
        name: i,
        task: i,
        taskType: 'SIMPLE'
    };
}) 
like image 85
Titian Cernicova-Dragomir Avatar answered Oct 04 '22 17:10

Titian Cernicova-Dragomir