Skip to content

Generic types should be compatibleΒ #54892

Open
@ccorcos

Description

@ccorcos

Bug Report

πŸ”Ž Search Terms

  • generic types not equal

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about generics.

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

// These are the types of records in the database.
type TableToRecord = {
	a: { a: number }
	b: { b: string }
	c: { c: string[] }
}

type Table = keyof TableToRecord

// Create a union of {table, id} objects.
type Pointer<T extends Table = Table> = {
    [K in T]: {table: K, id: string}
}[T]

// Hover X to see this is a proper union type
type X = Pointer

// βœ… this works as expected.
declare function getRecord<T extends Table>(pointer: Pointer<T>): TableToRecord[T] 
const x = getRecord({table: "a", id: ""})

// ❌ this surprisingly doesn't work
declare function something(pointer: Pointer): void
const p: Pointer<"a"> = {table: "a", id: ""}
something(p)
function run<T extends Table>(pointer: Pointer<T>) {
    const x = something(pointer)
}

// βœ… However, this does work if run is generic on Pointer instead of Table.
declare function something2(pointer: Pointer): void
function run2<P extends Pointer>(pointer: P) {
    const x = something(pointer)
}

// βœ… Which then makes me wonder if its better to write getRecord this way
declare function getRecord2<P extends Pointer>(pointer: P): TableToRecord[P["table"]] 
const x2 = getRecord2({table: "a", id: ""})

πŸ™ Actual behavior

I'm surprised by this error where Pointer<T> where T extends Table doesn't satisfy the argument Pointer<Table>.

If we were talking arrays, Array<T> where T extends string | number, I'd imagine you should be able to pass that to a function that accepts Array<string | number> as an argument. But it is a bit tricky if that argument gets mutated by the function, e.g. pushing a number onto a string array. I think there's a fancy type-system word for this behavior?

But as I understand it, TypeScript is all structural comparison and since Pointer<T> unfurls into the union type, I'm curious where the problem lies and it seems to me like the type system should let this work...

I noticed when the generic param is P extends Pointer instead of T extends Table and then using Pointer<T>, then the code does work. But then that leads me to wonder if there's any difference between function getRecord<T extends Table>(pointer: Pointer<T>): TableToRecord[T] and function getRecord2<P extends Pointer>(pointer: P): TableToRecord[P["table"]] ...

πŸ™‚ Expected behavior

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