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

Spread keyof expression #15759

Closed
JesperTreetop opened this issue May 11, 2017 · 5 comments
Closed

Spread keyof expression #15759

JesperTreetop opened this issue May 11, 2017 · 5 comments
Labels
Duplicate An existing issue was already created

Comments

@JesperTreetop
Copy link

JesperTreetop commented May 11, 2017

Proposal

I propose an expression that would help bridge the type erasure gap by emitting an array of member names.

The following:

interface IAnimal {
  weight: number;
  length: number;
  height: number;
  name: string;
}

const animalProperties = ...keyof(IAnimal);

would generate

const animalProperties = ['weight', 'length', 'height', 'name'];

The elements included in the array by ...keyof(X) would be exactly every element considered by keyof X in a type declaration; not necessarily in any particular order.

Discussion

Encasing the ...keyof(*) expression in brackets (as may be expected from a spread operation meant to provide an array) would make it syntactically ambiguous and prevent someone from having a keyof function. As far as I know, spread is not used outside of brackets and braces aside from in parameters and left side of assignment, where it would be nonsensical anyway, so at least for now this is probably not syntactically ambiguous with ES.

This brings the keyof() expression into code space instead of type declaration space, but I couldn't think of a cleaner alternative.

There's an argument that it should generate an object with the keys being keys and the types being values; however, it is an enormous can of worms to define which objects should be used to reify the type declarations, or to which extent that should even occur or be allowed. I'm not proposing full introspection, just a way to bridge the gap and carry some remnant of interfaces and types into the generated JavaScript.

@mhegazy
Copy link
Contributor

mhegazy commented May 11, 2017

looks like a duplicate of #13542

@mhegazy mhegazy added the Duplicate An existing issue was already created label May 11, 2017
@JesperTreetop
Copy link
Author

Yes, true, although I don't quite see how string enums (proposed as the answer in that issue) would work to solve any problem this would solve unless we were to get automatic string enums with the member names of every interface/type somehow.

@mhegazy
Copy link
Contributor

mhegazy commented May 30, 2017

Automatically closing this issue for housekeeping purposes. The issue labels indicate that it is unactionable at the moment or has already been addressed.

@mhegazy mhegazy closed this as completed May 30, 2017
@wizarrc
Copy link

wizarrc commented Jul 10, 2017

@mhegazy Please reopen.

I feel that this could bring a unique capability that string enums could not that may have been overlooked. Instead of having const animalProperties = ['weight', 'length', 'height', 'name']; have a type of string[], it would instead have a type of (keyof IAnimal)[] and allow index access with type number | string. It would be very powerful for a Record type or interface where all fields are the same type. Here is an example:

interface GroupDescription {
    name: string;
    description: string;
}

const GroupKeysWorkaround: Group[] = ['red', 'blue', 'green'];

type Group = 'red' | 'blue' | 'green';
type Groups = { [K in Group]: GroupDescription };

enum GroupKeysEnum { red = 'red', blue = 'blue', green = 'green' };
type GroupsEnum = { [K in GroupKeysEnum]: GroupDescription }

// DRY issue with having to mantain two seperate lists of groups
function initGroupsWorkaround(groups: Groups) {
    for(const group of GroupKeysWorkaround) {
        const name = groups[group].name;
        processName(name); //Generically Process property
    }
}

// Ugly hack casting any twice and filtering out bad enum values
function initGroupsEnum(groups: GroupsEnum) {
    for(const group of Object.keys(GroupKeysEnum)) {
        if(Number(group) !== NaN) return; // Filter out numbers
        const groupKey: GroupKeysEnum = <any>group;
        const name: string = (<any>groups)[groupKey].name;
        processName(name); //Generically Process property
    }
}

// Prefered method
function initGroups(groups: Groups) {
    for(const group of ...Group) { // group would be type Group or 'red' | 'blue' | 'green'
        const name = groups[group].name;
        processName(name); //Generically Process property
    }
}

// Another prefered method
function initGroupsAlt(groups: Groups) {
    for(const group of ...keyof(Groups)) { // group would be type Group or 'red' | 'blue' | 'green'
        const name = groups[group].name;
        processName(name); //Generically Process property
    }
}

function processName(name: string) { }

Update - I noticed I don't need ...keyof but instead directly use the spread operator on the union type. Both seem okay to me, but having the ... directly on the union string literal seems more straightforward.

@wizarrc
Copy link

wizarrc commented Jul 10, 2017

I think const animalProperties = keyof(IAnimal); should actually generate 'weight' | 'length' | 'height' | 'name' and ...animalProperties should generate an array ['weight', 'length', 'height', 'name'] of type ('weight' | 'length' | 'height' | 'name')[]. This way you can compose however you prefer.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

3 participants