Skip to content

string literal type defined via a mapped type is sometimes erroneously interpreted as simply string #16553

Closed
@seansfkelley

Description

@seansfkelley

A "direct" string literal type defined using type Foo = "A"; behaves properly in more places than an apparently-equivalent "indirect" string literal type defined using mapped types.

TypeScript Version: 2.3.4

Code

I discovered this issue while using https://github.com/dphilipson/typescript-string-enums, and as such, have inlined the relevant part of the library into the top of the example.

// Inlined, stripped-down version of the relevant part of typescript-string-enums.

function Enum<V extends string>(...values: V[]): { [K in V]: K };
function Enum<
    T extends { [_: string]: V },
    V extends string
>(definition: T): T;
function Enum(...values: any[]): object {
   // ... elided for brevity ...
   return {} as any;
}

type Enum<T extends object> = T[keyof T];



// Version 1: Using a "direct" string literal type.

// type MyEnum = "ENUM";



// Version 2: Using an "indirect" string literal type.

const MyEnum = Enum("ENUM",);
type MyEnum = Enum<typeof MyEnum>;



// The offending code sample.

interface MyInterfaceWithEnum {
    foo: MyEnum;
}

function parseToEnum(s: string): MyEnum {
    return null as any;
}

function failingFunctionWithMap(s: string): MyInterfaceWithEnum[] {
    // This block fails to compile when using an "indirect" string literal type, claiming that
    //     Type 'string' is not assignable to type '"ENUM"'.
    // This implicit change in type does not happen with the "direct" string literal type.
    return [""].map((i) => ({
        // Note that VSCode correctly reports the output of `parseToEnum` here for both enums.
        foo: parseToEnum(i),
    }));
}

function successfulFunctionWithoutMap(s: string): MyInterfaceWithEnum {
    // This block always compiles, using either of the "direct" or "indirect" string literal types.
    return {
        foo: parseToEnum(s),
    };
}

Expected behavior:

Both definitions of MyEnum are equal and interchangeable.

Actual behavior:

The "indirect" type erroneously fails to compile in one of the two functions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions