Skip to content

Branded literal type widened in unexpected way #61093

Open
@chkn

Description

@chkn

🔎 Search Terms

literal intersection branded string

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about branded strings/literal intersections.

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=5.7.3#code/CYUwxgNghgTiAEYD2A7AzgF3hgngBxAGkQcAueAVxQEsBHChNHAWwCMkIBuAKG9wPgAVfCACSAEQA8wgQF5KKANYokAdxQAaeBPggAHhhApgaeJhjUUAc3jzzlqwD5b28fABk8AN7wA2vyISAF0AfnIZBABfHm4AMyowDGpUbBEJaREtHX1DY1N7a0cACmpgcgkASnC0qQis8Wcvbnh4OAwKGBR4Up5I3gD4YhwAeVjpbIMjEyEa53lBCdzpiPSoFBwtS1iQGFdnENd4chQQADcdmIGI0fG3HKnTFYaXBbvJvJmCdK2dz5B9v5HeAnc4wS4ieAABRgSFOpRAaEkkN072WsxcPjgUGAqAgOD8gm6XSGN0hjiC1QIpOc0V4AHo6fAALQssAUDAspm8SyGGCxKBgBAAMSQSG8zRa8FioqKVXgpyQPW4fW4yHQWBFYvkAQkRQARJq9RUzFAkmhYtQEX90prHDwGZL4AA9EK8NWYeAAIVg5GhsPhiICSFi8FtGIlLV8mop4sdjulSFl3j6jr60SAA

💻 Code

declare const typeKey: unique symbol;

type TypeID<Type = unknown, ID extends string = string> = ID & { [typeKey]?: Type };

function typeID<Type, ID extends string>(id: ID): TypeID<Type, ID> {
  return id;
}

type KeyOf<TID extends TypeID> = TID extends TypeID<any, infer ID> ? ID : never;

type TypeOf<TID extends TypeID> = TID extends TypeID<infer Type> ? Type : never;

type Provides<P extends TypeID> = { readonly [T in KeyOf<P>]: TypeOf<P> };

// ---cut---

interface Foo {
    foo(): void;
}

const Foo = typeID("Foo") satisfies TypeID<Foo>;
//    ^? const Foo: TypeID<Foo, "Foo">

const Bar: Provides<typeof Foo> = {
    [Foo]: {
        foo() {}
    }
};

🙁 Actual behavior

Fails to compile with error:

Property 'Foo' is missing in type '{ [x: string]: { foo(): void; }; }' but required in type 'Provides<TypeID<Foo, "Foo">>'.

🙂 Expected behavior

I'd expect it to compile. Rather than widening the literal type to string, I'd expect it to discard the { [typeKey]?: Type } part, since that's pretty much bogus at runtime anyway.

However, it should also not lose the type information for the generic types. For instance, if I change the declaration to...

type TypeID<Type = unknown, ID extends string = string> = ID;

...then it does compile, but so does this...

const Bar: Provides<typeof Foo> = {
    [Foo]: {
        // No error about missing member `foo` here!
    }
};

Additional information about the issue

Possibly related to #43852 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions