镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Index signature parameter type should allow for enums #2491

Description

@jhlange

Typescript requires that enums have number value types (hopefully soon, this will also include string value types).

Attempting to use an enum as a key type for a hash results in this error: "Index signature parameter type much be 'string' or 'number' ".-- An enum is actually a number type.-- This shouldn't be an error.

Enums are a convenient way of defining the domain of number and string value types, in cases such as

export interface UserInterfaceColors {
    [index: UserInterfaceElement]: ColorInfo;
}
export interface ColorInfo {
    r: number;
    g: number;
    b: number;
    a: number;
}
export enum UserInterfaceElement {
    ActiveTitleBar = 0,
    InactiveTitleBar = 1,
}

Activity

  1. bgever commented on May 21, 2015

    @bgever

    +1, this is a great way for type-safe dictionaries.

  2. jbondc commented on May 31, 2015

    @jbondc
    Contributor

    +1

    If the TS team embraces 'set types' #3105, this would work too:

    interface SomeArrayLikeThing {
        [index: 0...255]: number;
    }
    
    enum foo {
     [prop: string]: 0...255
     a,
     b,
     c
    }
    
    interface SomeArrayLikeThingByEnum {
        [index: foo]: number;
    }
    
  3. jbondc commented on May 31, 2015

    @jbondc
    Contributor

    Would likely require at the minimum in the compiler something like:

    export interface ObjectIndex {
           valueType: Type        // any
           keyType?: Type         // string|number or any subset type
    }
    export interface ResolvedType extends ObjectType, UnionType {
           members: SymbolTable;              // Properties by name
           properties: Symbol[];              // Properties
           callSignatures: Signature[];       // Call signatures of type
           constructSignatures: Signature[];  // Construct signatures of type
           stringIndex?: ObjectIndex;            // String index
           numberIndex?: ObjectIndex;        // Numeric index
    }
    
  4. added
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    and removed on Aug 5, 2015
  5. RyanCavanaugh commented on Aug 5, 2015

    @RyanCavanaugh
    Member

    This does seem very useful. Going back to the original example, we had some questions about the intended semantics. Given some types:

    enum Color { red, green, blue }
    enum Size { small, medium, large }
    interface Alpha {
      [c: Color]: string;
    }
    interface Beta {
      [c: Size]: string;
    }
    interface Gamma {
      [c: number]: string;
    }
    interface Delta {
      [c: Color]: string;
      [c: Size]: string;
    }

    Obvious things:

    • You can't index an Alpha by a Size

    Non-obvious things:

    • Is Alpha assignable to Beta ?
    • Is Gamma assignable to Alpha ? Vice versa?
    • Can I index an Alpha by a number ?
    • Is Delta a legal declaration? (strongly leaning toward no)
  6. jbondc commented on Aug 28, 2015

    @jbondc
    Contributor

    (a)

    Is Alpha assignable to Beta ?
    Is Delta a legal declaration?

    No

    (b)

    Can I index an Alpha by a number ?
    Is Gamma assignable to Alpha ? Vice versa?

    Yes (unfortunately), follow isTypeAssignableTo()

    const enum Color { red, green, blue }
    let a: number;
    let b: Color;
    a = 4;
    b = 4; // unfortunately (c)
    
    interface Alpha {
      [c: Color]: string;
    }
    let c: Alpha;
    c[Color.red];
    c[0];
    c[4]; // unfortunately (c)
    

    I'd be happy if (c) changed eventually so that const enums only accept constants or types within their range of values.

  7. added and removed
    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.
    on Aug 28, 2015
  8. added
    DeclinedThe issue was declined as something which matches the TypeScript vision
    Too ComplexAn issue which adding support for may be too complex for the value it adds
    on Oct 5, 2015
  9. 24 remaining items

  10. jkillian commented on Nov 16, 2016

    @jkillian

    Mohamed Hegazy (@mhegazy): it seems like it might be a little trickier than that in practice?

    export enum MyEnum {
        First = 0,
        Second = 1,
    }
    
    export type EnumMap = {
        [P in MyEnum]: string;
    }
    
    // [ts] Type '{ [x: number]: string; }' is not assignable to type 'EnumMap'.
    //      Property '0' is missing in type '{ [x: number]: string; }'.
    const doesntWork: EnumMap = {
        [MyEnum.First]: "first",
        [MyEnum.Second]: "second",
    };
    
    const works: EnumMap = {
        0: "first",
        1: "second",
    };
    
    // but then this is allowed, should it be?
    works["foo"] = "bar";
    // but then: 
    // [ts] Property 'foo' does not exist on type 'EnumMap'.
    type test = typeof works["foo"]
  11. icholy commented on Dec 14, 2016

    @icholy

    You can use any type as the key for Map and if you really want type safe object keys, you can wrap it.

    class DictValues<V> {
        [key: string]: V;
        [key: number]: V;
    }
    
    interface ToStringer {
      toString(): string;
    }
    
    class Dict<K extends ToStringer, V> {
      private _values: DictValues<V> = Object.create(null);
    
      get(key: K): V {
        return this._values[key.toString()];
      }
    
      set(key: K, value: V): void {
        this._values[key.toString()] = value;
      }
    
      has(key: K): boolean {
        return this._values.hasOwnProperty(key.toString());
      }
    
      values(): DictValues<V> {
        return this._values;
      }
    }
  12. ChiriVulpes commented on Dec 19, 2016

    @ChiriVulpes
    Contributor

    Mohamed Hegazy (@mhegazy): I get Type 'UserInterfaceColors' is not assignable to type 'string' when using the code that should work:

    export type UserInterfaceColors = {
        [P in UserInterfaceElement]: ColorInfo;
    }

    Coupled with the bits Jason Killian (@jkillian) mentioned makes me think this should be reopened (or at least the Fixed tag removed)

  13. mhegazy commented on Dec 19, 2016

    @mhegazy
    Contributor

    This was broken by #12425. would you mind logging a new issue to allow numeric literals as constraints of mapped types?

  14. mattiLeBlanc commented on Mar 3, 2017

    @mattiLeBlanc

    Hi, I would also benefit from being able to use a declared type as index, so that I can restrict which kind of indexes are being used.

    For example:

    declare type validationTypes = 'email' | 'number' | 'abn';
    
    interface IField {
      label:                string;
      model:                string;
      type:                 fieldType;
      placeholder?:         string;
      addon?:               string;
    }
    
    interface IEmailValidation {
      email:              string;
    }
    interface IRequiredValidation {
      required:             string;
    }
    
    
    interface IEmailField extends IField {
      type:                'email';
      validation:        IDictionary<IEmailValidation | IRequiredValidation>
    }
    

    What I try to achieve is that the validation will be a dictionary and has the key 'email' and the value type of string, and a required validation.

    A text field would only have Required validation.

    The Javascript object looks like this:

    const emailField =        {
              "label":                "Email Address",
              "model":               "emailaddress",
              "type":                 "email",
              "placeholder":          "Please enter an email address",
              "validation": {
                "required":          "An email address is required",
                "email":              "Please enter a valid email address"
              }
            }
    

    Right now I can only allow all possible validations but I can't restrict the set of validations per field type. This is because of the index must to be string or number restriction.

    If it would be possible to define a dictionary like this:

    export interface IDictionary<K,V> {
      [index: key]: V;
    }
    then K could be a declare type of 'email' | 'required' for an email field and only 'required' for a text field.
    

    Any thoughts?

  15. apetrushin commented on Apr 21, 2017

    @apetrushin

    This seems to be quite frequent pattern, would be nice to support it.

    type keys = 'a' | 'b' | 'c'
    let map: { [key: keys]: string } = {}
    
  16. Xample commented on Apr 27, 2017

    @Xample

    apetrushin you can use "in" for this

    type keys = 'a' | 'b';
    let anObject: { 
    	[index in keys]: string;
    }
    anObject.a = "correct";
    anObject.t = "error";
    
  17. apetrushin commented on Apr 28, 2017

    @apetrushin

    Thanks, but it's still allows to do anObject['non-existing-key'] = 'value'

  18. mattiLeBlanc commented on Apr 28, 2017

    @mattiLeBlanc

    Have a look at my post just above. That would be an awesome addition. Right now we can only defined the index of a dictionary to be of a string but not restrict to a list of string types.
    Any change this can become a feature?

  19. nevir commented on Jun 29, 2017

    @nevir

    This appears broken again as of TS 2.4 (2.x?)

    enum Things { ONE, TWO }
    // Error: Type 'Things' is not assignable to type 'string'. 
    type ThingMap = {[TKey in Things]: boolean};
  20. aselbie commented on Aug 2, 2017

    @aselbie

    It appears this is a known regression with a fix on the roadmap: #13042

  21. yordis commented on Dec 8, 2017

    @yordis

    Ian MacLeod (@nevir) I am using 2.6 and it is working fine for me but because I match the enum keys to some string

    enum Things { ONE = 'one', TWO = 'two' }

    Should be that the default behavior? or should be just doing the enum without any mapping?

  22. locked and limited conversation to collaborators on Jun 18, 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

    Fix AvailableA PR has been opened for this issueSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions