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

Index signatures for symbols and template literal strings #44512

Merged
merged 59 commits into from
Jun 21, 2021

Conversation

ahejlsberg
Copy link
Member

@ahejlsberg ahejlsberg commented Jun 8, 2021

With this PR we implement support for symbol and template literal string index signatures. We furthermore permit index signature declarations to specify union key types, provided all constituents are either string, number, symbol, or template literal types with non-generic placeholders. Some examples:

type SymbolMap<T> = {
    [key: symbol]: T;
};

type DataProps = {
    [key: `data-${string}`]: string;
};

type PropertyMap = {
    [key: string | number | symbol]: string;
};

An index signature declaration that specifies a union key type is exactly equivalent to a set of distinct index signatures for each constituent key. For example, the PropertyMap declaration above is exactly equivalent to:

type PropertyMap = {
    [key: string]: string;
    [key: number]: string;
    [key: symbol]: string;
};

Index signature declarations are not permitted to specify literal key types or generic key types. Those kinds of types can only be used with mapped types, which map literals key types to distinct properties and defer resolution of generic key types until they're instantiated with non-generic types. For example:

type Thing<T> = Record<'a' | `foo${T}` | symbol, string>;

type StringThing = Thing<string>;  // { [a: string, [x: `foo${string}`]: string, [x: symbol]: string }
type BarThing = Thing<'bar'>;  // { [a: string, foobar: string, [x: symbol]: string }

This PR supercedes #26797 which was more ambitious but had overlap between regular properties and index signatures with literal key types that is difficult to reconcile, as well as generic index signatures for which type relationships become exceedingly complex to reason about.

Fixes #1863.
Fixes #26470.
Fixes #42192.
Fixes #44675.

@shellscape
Copy link

Something is amiss with this, or it's not been released yet. Still receiving the 'ol error TS1337: An index signature parameter type cannot be a union type. Consider using a mapped object type instead. errors. @RyanCavanaugh can you confirm it's been released as of 4.3.5?

@leidegre
Copy link

Something is amiss with this, or it's not been released yet. Still receiving the 'ol error TS1337: An index signature parameter type cannot be a union type. Consider using a mapped object type instead. errors. @RyanCavanaugh can you confirm it's been released as of 4.3.5?

It's currently in the 4.4 release which is in beta scheduled to be released by the end of August.

@vepanimas
Copy link

vepanimas commented Aug 30, 2021

@ahejlsberg Could you please clarify why those cases work differently in terms of assignability (playground)? AFAIU for a type alias and anonymous type it uses an empty literal type from getTypeFromTypeLiteralOrFunctionOrConstructorTypeNode without resolving its members, but is it the expected behavior?

const sym = Symbol();

function gg3(x: { [key: string]: string }, z: { [sym]: number }) {
  x = z;  // Ok
}

interface GG4 {
    [sym]: number;
}

function gg4(x: { [key: string]: string }, z: GG4) {
  x = z; // Error
}

type GG5 = {
    [sym]: number;
}

function gg5(x: { [key: string]: string }, z: GG5) {
  x = z; // Ok ?!
}

@douglasg14b
Copy link

douglasg14b commented Sep 6, 2021

Is this in and working? I'm sitting on v4.4.2 and still receiving:

An index signature parameter type cannot be a union type. Consider using a mapped object type instead

type Test = 'one' | 'two';

export interface Foo {
    [key: Test]: any;
}

@craigphicks
Copy link

craigphicks commented Sep 28, 2021

@douglasg14b

You can do it using type and in (although not with interface).

type Test = 'one' | 'two';
type Foo= {
  [key in Test]: any;
}
//type Foo = {
//    one: any;
//    two: any;
//}

type Foo2= {
  [key in `${Test}${Test}`]: any;
}
//type Foo2 = {
//    oneone: any;
//    onetwo: any;
//    twoone: any;
//    twotwo: any;
//}

playground

I believe that because there is no "free" string or number template, this is old hat, and not related to this new feature.

@craigphicks
Copy link

@ahejlsberg - This is really a great feature, thanks!

I noticed that spaces seem to count as digits with the number type. How about not allowing that?
Of course with unquoted literal labels it doesn't matter, but with quoted string indices it does.

type TP4 = {[k:`${number}`]:null}
const tp4_1:TP4 = {'1':null}
const tp4_2:TP4 = {' ':null}   // even though there is no number present, it passes
const tp4_3:TP4 = {' 1':null}
const tp4_4:TP4 = {'1 ':null}
const tp4_5:TP4 = {' 1 ':null}

playground

@599316527
Copy link

599316527 commented Nov 22, 2021

Index signatures for template literal string with generic?

type Props<T extends string> = {
  renders: {[key in T]: any};
  [key: `render${Capitalize<T>}`]: any;
};

TS 4.5.2 says:

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.(1337)

@liuyiqi1999
Copy link

  1. If there're other keys needing restricting value type, how should I put it?
type Test = 'one' | 'two';
type Foo= {
  stringKey: string; // A mapped type may not declare properties or methods.
  numberKey: number;
  [key in Test]: any;
}

playground

  1. Can this feature be used in interfaces?
enum Test {
  ONE = 'one',
  TWO = 'two'
}
interface Foo {
  [key: Test]: any; // An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.
}

playground

@airtonix
Copy link

  1. If there're other keys needing restricting value type, how should I put it?

As two separate mapped objects

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Backlog Bug PRs that fix a backlog bug For Milestone Bug PRs that fix a bug with a specific milestone
Projects
Archived in project