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

naive type infer for key in number & keyof T #31575

Closed
worudso opened this issue May 24, 2019 · 4 comments
Closed

naive type infer for key in number & keyof T #31575

worudso opened this issue May 24, 2019 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@worudso
Copy link

worudso commented May 24, 2019

TypeScript Version: 3.5.0-dev.20190523

Search Terms:
generic, keyof ,indexed access type
Code

type G1<T extends { [key: number]: any }> = { [key in keyof T]: T[key][] };
type G2<T extends { [key: number]: any }> = { [key in number & keyof T]: T[key][] };

type A = G1<["a", "b"]>; // [number[], string[]]
type B = G2<["a", "b"]>; // { [x:number]: (number | string)[] }

Expected behavior:
B is the same type as A.
Actual behavior:
B is a naiver one.
Playground Link: https://www.typescriptlang.org/play/#src=type%20G1%3CT%20extends%20%7B%20%5Bkey%3A%20number%5D%3A%20any%20%7D%3E%20%3D%20%7B%20%5Bkey%20in%20keyof%20T%5D%3A%20T%5Bkey%5D%5B%5D%20%7D%3B%0D%0Atype%20G2%3CT%20extends%20%7B%20%5Bkey%3A%20number%5D%3A%20any%20%7D%3E%20%3D%20%7B%20%5Bkey%20in%20number%20%26%20keyof%20T%5D%3A%20T%5Bkey%5D%5B%5D%20%7D%3B%0D%0A%0D%0Atype%20A%20%3D%20G1%3C%5B%22a%22%2C%20%22b%22%5D%3E%3B%20%2F%2F%20%5Bnumber%5B%5D%2C%20string%5B%5D%5D%0D%0Atype%20B%20%3D%20G2%3C%5B%22a%22%2C%20%22b%22%5D%3E%3B%20%2F%2F%20%7B%20%5Bx%3Anumber%5D%3A%20(number%20%7C%20string)%5B%5D%20%7D

Related Issues:
#30769 maybe?

@worudso worudso changed the title naive type infer for key in number & keyof T naive type infer for [key in number & keyof T] May 24, 2019
@worudso worudso changed the title naive type infer for [key in number & keyof T] naive type infer for key in number & keyof T May 24, 2019
@worudso
Copy link
Author

worudso commented May 24, 2019

type A = number & (1 | 2)
type B = number & keyof {1: any, 2: any}

Both A and B is 1 | 2 not number.
But it seems they work differently with key in.

@ahejlsberg
Copy link
Member

This is working as intended. Homomorphic mapped types (such as G1) handle arrays and tuples differently than regular mapped types. See #26063.

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label May 24, 2019
@worudso
Copy link
Author

worudso commented May 25, 2019

@ahejlsberg If it's much harder or proven to be impossible to deal with non-homomorphic mapped type same as with homomorphic one for some technical issues, then I don't have anything more to ask for.

#31385 (comment), it sounds like that the current behavior is for less verbosity and keeping the old behavior, though we can claim more explicit constraints on key in ....

And I read in #26063

Previously, we would treat array and tuple types like regular object types and transform all properties (including methods) of the arrays and tuples. This behavior is rarely if ever desired.

Can [key in number & keyof T] solve that problem(when it works as I asked for)? So the choice(let homomorphic mapped type work differently) was only for less verbosity? not because it's impossible to narrow [key in number & keyof T] well?

If then, I want to introduce a possible use case of explicit narrowing with number or string or symbol.
I've been writing a draft of typed graphql query builder that looks below.

type Human = {
    firstName: string;
    lastName: string
    age: number;
}

function queryHuman<T extends {readonly [key: number]: keyof Human} & {[key: string]: keyof Human}>
    (fields: T): {[key in T[number & keyof T]]: key} & {[key in string & keyof T]: T[key]} {
    return {} as any;
}

/*
human {
    firstName
    age
}
*/
queryHuman(["firstName", "age"] as const); // not working
/*
human {
    a: firstName
    firstName
    lastName
}
*/
queryHuman({
    a: "firstName",
    firstName: "firstName", // verbose
    lastName: "lastName", // verbose
}); // working
/*
human {
    a: firstName
    firstName
    lastName
}
*/
queryHuman({
    a: "firstName",
    ...["firstName", "lastName"] as const,
}); // not working

It's not working because of the current behavior of key in number & keyof T .
This is the case that number key property and string key property has a different semantic, so if they are distinguishable in type level, more powerful typed expression can be allowed.
Also, I think symbol key properties are used for special semantic in most of the case.

To be honest, I can't strongly insist that this change can give a rich improvement in various domains.
But if the tradeoff is verbosity and breaking, I think this change is worth considering.

@typescript-bot
Copy link
Collaborator

This issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants