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

[BUG]: Generic as interface key does not result in corresponding value #57372

Closed
jxn-30 opened this issue Feb 11, 2024 · 5 comments
Closed

[BUG]: Generic as interface key does not result in corresponding value #57372

jxn-30 opened this issue Feb 11, 2024 · 5 comments

Comments

@jxn-30
Copy link

jxn-30 commented Feb 11, 2024

πŸ”Ž Search Terms

generic, widen, interface

πŸ•— Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about Generics and Interfaces

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.3#code/JYOwLgpgTgZghgYwgAgJLIN4Chm+XALmQCUIEB7KAEwB4QBXAWwCNoAaZAZzClAHMA2gF0AfAG4ceZkVIVqdJqygcGLaMPFYAvliwUQ3ZFXIBlcowhgAFv2QBeZDQDSyCAA9IIKp2QBrCACe5DBoIgAU-gFEThwAbnAANvQQRKgCTkIAlPYimJK4wCERgfZ2DgDkcOXZ2Hh1yPqc5AkQAHQJ5HzFAZliyAD0-X4lwD7WKAnAkFCJyJXlyIAo5IDwf-n1jc1tHV3xSRC9a-VHuIPIAHoXFyRklLSqShzcvCCCosgAPtdyd4rsyPfqUSHY71U7uAAOZEgVBkN3kAOUXB4-A0ay0rgSnBQtWOGxa7U63V6AyGkWQo2Q42Qk2ms3KzAWK2BuDxW0Ju2SBxB3NOlyusluCjUiKeKLenwF8N+iIRqO5ILBbkhCGhsO+Qoe-2lcrwOi0QA

πŸ’» Code

interface I {
    a: Record<number, string[]>;
    b: Record<number, number[]>;
}

const doSomething = <K extends keyof I>(key: K, value: I[K]) => {
    if (key === 'a') {
        console.log(key); // key is the literal 'a' βœ”οΈ
        console.log(value);
                 // ^^^^^ Record<number, string[]> | Record<number, number[]>
                 // expected: Record<number, string[]>
    } else {
        console.log(key); // key is the literal 'b' βœ”οΈ
        console.log(value);
                 // ^^^^^ Record<number, string[]> | Record<number, number[]>
                 // expected: Record<number, number[]>
    }
}

πŸ™ Actual behavior

value in line 9 (when key is 'a') is considered to be type Record<number, string[]> | Record<number, number[]>.
accordingly, value in line 14 (when key is 'b') is also considered to be type Record<number, string[]> | Record<number, number[]>

πŸ™‚ Expected behavior

value in line 9 (when key is 'a') should be considered to be type Record<number, string[]>.
accordingly, value in line 14 (when key is 'b') should be considered to be type Record<number, number[]>

Additional information about the issue

No response

@MartinJohns
Copy link
Contributor

This is working as intended.

It's perfectly fine to call the generic function this way:

const rec: Record<number, number[] > = { 0: [0, 1] };
doSomething<'a' | 'b'>('a', rec);

@whzx5byb
Copy link

whzx5byb commented Feb 11, 2024

Another "extends oneof" issue, see also #27808.

You can use the distribution behavior as a workaround:

const doSomething = <K extends keyof I>(...[key, value]: K extends unknown ? [key: K, value: I[K]] : never) => {
    if (key === 'a') {
        console.log(key); // key is the literal 'a' βœ”οΈ
        console.log(value);
                 // ^ Record<number, string[]> βœ”οΈ
    } else {
        console.log(key); // key is the literal 'b' βœ”οΈ
        console.log(value);
                 // ^ Record<number, number[]> βœ”οΈ
    }
}

@jxn-30
Copy link
Author

jxn-30 commented Feb 12, 2024

This is working as intended.

It's perfectly fine to call the generic function this way:

const rec: Record<number, number[] > = { 0: [0, 1] };
doSomething<'a' | 'b'>('a', rec);

You're indeed right. I did not think of this case.

Another "extends oneof" issue, see also #27808.

You can use the distribution behavior as a workaround:

const doSomething = <K extends keyof I>(...[key, value]: K extends unknown ? [key: K, value: I[K]] : never) => {
    if (key === 'a') {
        console.log(key); // key is the literal 'a' βœ”οΈ
        console.log(value);
                 // ^ Record<number, string[]> βœ”οΈ
    } else {
        console.log(key); // key is the literal 'b' βœ”οΈ
        console.log(value);
                 // ^ Record<number, number[]> βœ”οΈ
    }
}

Thanks for providing this example on how to fullfill my needs and also linking the "extends oneof" issue! 😊

@fatcerberus
Copy link

You're indeed right. I did not think of this case.

It's not just you; a lot of people don't think of this. The type-theoretical implications of untagged union and intersection types are... quite interesting, but most mainstream languages don't have such a concept so they're easy to overlook.

A good way to think about union types, I've found, is to consider a | to be a point of uncertainty about exactly which type you have - and in case of a type parameter T extends... where T is instantiated with a union type, you copy this axis of uncertainty to every place you use T. Of course this makes it really hard to ensure correlation - hence the extends oneof issue πŸ˜‰

@paulleonartcalvo
Copy link

paulleonartcalvo commented Feb 14, 2024

Another "extends oneof" issue, see also #27808.

You can use the distribution behavior as a workaround:

const doSomething = <K extends keyof I>(...[key, value]: K extends unknown ? [key: K, value: I[K]] : never) => {
    if (key === 'a') {
        console.log(key); // key is the literal 'a' βœ”οΈ
        console.log(value);
                 // ^ Record<number, string[]> βœ”οΈ
    } else {
        console.log(key); // key is the literal 'b' βœ”οΈ
        console.log(value);
                 // ^ Record<number, number[]> βœ”οΈ
    }
}

In this example, how might i define the returned type as dependent on K? I have a mapped type D whose keys are K, but where i mark the return as D[K] i get the following: intersection was reduced to 'never' because property 'type' has conflicting types in some constituents.. The values mapped by the type are types which are discriminated by a property type which is a string literal unique to each union member.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants