Skip to content

Better support for enumerating properties in generics using string enums #18409

Closed
@trevorade

Description

@trevorade

TypeScript Version: 2.4.0 / nightly (2.5.0-dev.201xxxxx)

Code

Example:

/// An examination of string enums when used to enumerate values and properties.

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

enum AltVals {
    UNO = 'one',
}

/// First, a look at how string enums are treated as the value of a property in
/// a property in a generic interface.

// Note: Note: In this case, `extends string` is unnecessary.
interface LimitedVals<TVals extends string> {
    readonly val: TVals;
}

// The errors in limited1 and limited3 make sense.
const limited1: LimitedVals<Vals> = { val: 'one' };                     // Error
const limited2: LimitedVals<Vals> = { val: Vals.ONE };
const limited3: LimitedVals<Vals> = { val: AltVals.UNO };               // Error

/// Next, a look at how string enums are treated as possible properties in a
/// generic interface.

// Perhaps `extends string` isn't the most appropriate for string enums.
// Presumably, it's just defining the properties as strings.
interface LimitedProps<TProps extends string> {
  readonly values: {[prop in TProps]: number};
}

// It may be desirable for the same errors as found in limited1 above to occur
// for limProps0 and limProps1.
const limProps0: LimitedProps<Vals> =
    { values: { one: 1, two: 2 } };
const limProps1: LimitedProps<Vals> =
    { values: { 'one': 1, 'two': 2 } };
// Computed properties should work and should probably be the preferred syntax.
const limProps2: LimitedProps<Vals> =                                   // Error
    { values: { [Vals.ONE]: 1, [Vals.TWO]: 2 } };
// Should still result in an error but not because of the computed property.
// AltVals.UNO is from the wrong enum even though it has the same string value.
const limProps3: LimitedProps<Vals> =                                   // Error
    { values: { [AltVals.UNO]: 1, [Vals.TWO]: 2 } };

// Again, these two property reference styles are inconsistent with the error in
// limited1.
limProps1.values.one;
limProps1.values['one'];
// Arguably, this should be the preferred syntax.
limProps1.values[Vals.ONE];
// This shouldn't work as it's the wrong enum type.
limProps1.values[AltVals.UNO];

Expected behavior:
A generic interface should be able to take a string enum as a template type to define properties. When using an instance of this type generic interface, property type checking should occur. See the example above for suggestions of what should and should not work.

It's possible that problem is the usage of extends string for the generic type in the interface. I'm guessing that the compiler loses the fact that the properties should be the type of the string enum and instead just sees string properties. If that's the case, perhaps we need a better way to tell the type checker that we expect a string enum here. Like <TProps extends enum<string>> or something.

Actual behavior:
When creating a property that is an instance of a generic interface using a string enum as type, the "enumness" of the type is lost. It's just strings at this point. See the above example for current behavior.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions