microsoft / microsoft/TypeScript

Confusing enum reverse string lookup behaviour in strict mode

Open
#29,094 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Domain: Error Messages Experience Enhancement Suggestion
Dominant language
Go
Stars
111k
Forks
14.3k
Avg merge
2d 4h
Merged PRs (30d)
132

Description

Summary

Code below.

Typescript in --strict mode will reject enum reverse lookups using arbitrary strings with this very confusing error message:

Element implicitly has an 'any' type because index expression is not of type 'number'.

A workaround is to cast the string to a subset of all possible enum keys but that's impractical with large enums (or is there an easy way to say "any key of enum X" in TS?).

My real world use case: I have a react app with a <select> filter on various items, and a bunch of <option> values, all of which are part of an integer enum. I have to use the string representation in the dom, so the state of the filter has to be the string representation. When I check the item's enum value I already know it can only be one of those enum members.

This report is three-fold:

  1. Is this behaviour intended? There are cases where it can definitely catch a reverse lookup that can fail.
  2. If it is intended, then what is the correct way to declare that the lookup will always be valid? let typeFilter: "FOO" | "BAR" = "FOO" is impractical and increases risk of errors.
  3. Regardless of the above, the error message should be fixed. The element highlighted (typeFilter) does not in fact have an "any" type, and Type[typeFilter] will not be any either (in fact, Type[typeFilter] as Type does not fix it). An error such as "Reverse lookup on enum Type may fail because typeFilter is of type 'string'" is much clearer already.

Details

TypeScript Version: 3.2.2

Search Terms: enum reverse string lookup

Code

// test.ts
// Compile with `tsc --strict test.ts`
export enum Type {
	FOO = 0,
	BAR = 1,
}

const Test = () => {
	// Change the following line to `let typeFilter: "FOO" | "BAR" = "FOO";` and it will work
	let typeFilter = "FOO";

	if (Type[typeFilter] != Type.FOO) {
		return;
	}
};

export default Test;

Actual behavior:

test.ts:9:11 - error TS7015: Element implicitly has an 'any' type because index expression is not of type 'number'.

9  if (Type[typeFilter] != Type.FOO) {
            ~~~~~~~~~~


Found 1 error.

Playground Link: Playground

Related Issues: Maybe #27297

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the report from the provided test.ts example by running tsc --strict and inspecting the diagnostic at Type[typeFilter]. Read the enum reverse-lookup type-checking path and related issue #27297 before deciding whether the behavior or only the message should change; done means the intended behavior is settled and covered by a regression case.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.