Repository: TypeScript
Issue: Allow identifiers in types to resolve to unit-typed values
Labels: Suggestion,Awaiting More Feedback

## Search Terms

constant type literal, Cannot find name, type identifier, unit value

## Suggestion

When declaring a constant like `const UNSPECIFIED = "UNSPECIFIED";`, I'd like the identifier `UNSPECIFIED` to be usable as a type (equivalent to the `"UNSPECIFIED"` literal type), just like how you can use `null`, string literals, and enum values as types. There are lots of workarounds and alternatives, but all of them have caused confusion or unease within my team, and I think it's possible to make this straightforward syntax work.

As mentioned in https://github.com/Microsoft/TypeScript/issues/6151#issuecomment-165840507 , simply declaring `UNSPECIFIED` in the type namespace would be a breaking change, so my suggestion is to instead extend type name resolution to *look in the value namespace as a fallback*, which is backcompat.

Here's my attempt at formalizing this behavior:
* When resolving an identifier node in a type context, let `ID` be the identifier's name.
  * First search the type namespace for the name `ID`. If a type with that name exists, use it.
  * If no type matched, then search the value namespace to see a value exists with name `ID`.
    * If a value matched, determine the value's type.
      * If the type of `ID` is a unit type (string literal, enum value, etc), then treat this node as if it was written `typeof ID`.
      * If `ID`'s type is not a unit type, give the error message "Cannot use non-unit value ID as a type".
    * If no value matched, give the error message "Cannot find name ID".

(I understand that there may be implementation roadblocks with this approach, just throwing it out there as a suggestion in case it is reasonable like I'm hoping.)

Some related improvements that would also be nice:
* Simply improving the error message from `Cannot find name "UNSPECIFIED"` to something like `"UNSPECIFIED" is a value, not a type"` would be really helpful. This is already filed as https://github.com/Microsoft/TypeScript/issues/27630 . It would be especially helpful if the error message pointed to an officially-recommended workaround.
* In addition to changing identifier resolution, ideally more complex expressions could also be resolved in this way, particularly property accesses. In my example in https://github.com/Microsoft/TypeScript/issues/17592#issuecomment-449440944 , ideally `Events.Pause` would be usable as a type, and if it was, there would be a pattern for a "union enum" without special language support. It would also allow accessing constants from nested objects (as long as the constant has a literal type) and `import *` statements. You could even go so far as to say "any expresssion `E` is a shorthand for `typeof E`", though I think for complex expressions like function invocations, it could be confusing.

This proposal requires the relevant values to have literal types, which is mostly true already. Some prior discussions and future suggestions around that: https://github.com/Microsoft/TypeScript/issues/6167, https://github.com/Microsoft/TypeScript/pull/10676, https://github.com/Microsoft/TypeScript/issues/26979 .

## Use Cases

See the code snippets below for concrete use cases of literal types in general, though I imagine those are fairly well-understood.

My team is in the process of everyone learning TypeScript and porting a large JavaScript codebase to TypeScript, and this has been one of the bigger issues we've run into. The general advice we've concluded on is "TypeScript doesn't handle constants as types very well; enums work a lot better".

### Current approaches and their shortcomings

#### Use an enum

Switching from enum-style constants to actual enums is helpful, but there are some shortcomings:
* This requires changing all usages, including JS files using these constants. We prefer to keep emitted code changes to a minimum when porting JS to TS, and having to update all usages introduces risk.
* It's awkward to make a one-value enum, like with the `UNSPECIFIED` example. We won't always want a list of choices, sometimes we just want a single special value that we can compare against.
* Since enum values are always written as a property access, they're not as concise as constants.

#### Use `typeof UNSPECIFIED` as the type

For example, you could write `type MaybeFolder = Folder | typeof UNSPECIFIED`. This works, but has been confusing enough in practice that I think it's best to avoid, mostly because people feel that `typeof UNSPECIFIED` should be `string`. It generally has a feel of advanced/mysterious TypeScript trickery. It makes sense if you have deep knowledge of how TypeScript works, but it's not intuitive or clear.

#### Explicitly define both a value and a type

There are a few ways to write it, but here's one example:

```typescript
export const UNSPECIFIED = "UNSPECIFIED";
export type UNSPECIFIED = typeof UNSPECIFIED;
```

This is nice in that it's self-contained, but it's doubly-confusing because not only does it use the `typeof` trick, it uses the surprising fact that you can export the same name twice, once in the value namespace and once in the type namespace. We're currently using this pattern for a small number of shared constants, but it seems awkward for people to write new instances of it.

#### Always use a string literal type instead of a constant value

Just using `"UNSPECIFIED"` as the type works out pretty well, but has some disadvantages:
* It doesn't work as well with editor tooling like jump-to-definition.
* It *feels* wrong/unsafe/ugly, especially to someone not as familiar with TypeScript.

#### Use `null`

In some cases, `null` can be used to denote an alternate value, e.g. `Folder | null`. This is concise, but makes the intentions less clear.

## Examples

This can be used to concisely create a safer `null` variant:

```typescript
interface Folder {
  name: string;
}
const NO_ACCESS = "NO_ACCESS";

function formatFolder(folder: Folder | NO_ACCESS): string {
  if (folder === NO_ACCESS) {
    return "(Insufficient permissions)"
  } else {
    return folder.name;
  }
}
```

Similarly, it can be used to extend enums with ad-hoc values:

```typescript
enum ColorOption {
  RED = "RED",
  GREEN = "GREEN",
  BLUE = "BLUE",
}

const UNSPECIFIED = "UNSPECIFIED";

interface ColorPickerUIState {
  colorChoice: ColorOption | UNSPECIFIED;
}
```

It can also be used for enum-like use cases for legacy code that doesn't yet use enums:

```typescript
const RED = "RED";
const GREEN = "GREEN";
const BLUE = "BLUE";
const ORANGE = "ORANGE";
const YELLOW = "YELLOW";
const PURPLE = "PURPLE";

type PrimaryColor = RED | GREEN | BLUE;
```

To demonstrate a few examples of name resolution:
```typescript
const number = true;
// "number" refers to plain number type, since type names always take precedence.
const x: number = 3;

type T1 = string;
type T2 = string;

function foo() {
  const T2 = "T2";
  const T3 = "T3";
  
  // Param type resolves to string | string | "T3".
  function f(val: T1 | T2 | T3) {}
}
```

## Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).