Skip to content

Spread keyof expression #15759

Closed
Closed

Description

@JesperTreetop

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.

Activity

  1. mhegazy commented on May 11, 2017

    @mhegazy
    Contributor

    looks like a duplicate of #13542

  2. JesperTreetop commented on May 12, 2017

    @JesperTreetop
    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.

  3. mhegazy commented on May 30, 2017

    @mhegazy
    Contributor

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

  4. wizarrc commented on Jul 10, 2017

    @wizarrc

    Mohamed Hegazy (@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.

  5. wizarrc commented on Jul 10, 2017

    @wizarrc

    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.

  6. locked and limited conversation to collaborators on Jun 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions