Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic types should be compatible #54892

Open
ccorcos opened this issue Jul 5, 2023 · 3 comments Β· May be fixed by #55130
Open

Generic types should be compatible #54892

ccorcos opened this issue Jul 5, 2023 · 3 comments Β· May be fixed by #55130
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@ccorcos
Copy link

ccorcos commented Jul 5, 2023

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

@jcalz
Copy link
Contributor

jcalz commented Jul 6, 2023

Info: Pointer<T> is a distributive object type as coined in #47109

I think that if F<T> is a distributive object type, then F<T> is covariant in T (at least if T is only allowed to be unit types or unions of unit types). TS seems to recognize that F<T> extends F<U> when T extends U if U is generic, but not if U is specific. Not sure if this is a design limitation or a missing feature or something intentional.

Anyway I hope we can amend the title to something more specific?

@RyanCavanaugh
Copy link
Member

I think this is likely just a bug somewhere. The workaround, which has no semantic effect but makes the error go away, is

function run<T extends Table>(pointer: Pointer<T & Table>) {

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript Help Wanted You can do this labels Jul 6, 2023
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Jul 6, 2023
@poorna-prakash-sr
Copy link

Can I work on this issue @RyanCavanaugh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants