Skip to content

Type-level narrowing constraints (like as const) #53813

Open
@dimitropoulos

Description

@dimitropoulos

Suggestion

In userland code, you can use as const, but as a library author, I often want to provide the best possible experience, and when there's an opportunity for a literal value to be produced, I would like to infer that value for my users without them having to use (or remember to use) as const.

🔍 Search Terms

constant literals, Math.random, require as const, infer literals, readonly by default

✅ Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Provide an ability to narrow types at the type level (i.e. without JavaScript where you can use as const).

📃 Motivating Example

const notLiteral = () => "A";

A type like this produces () => string. I can improve the situation by specifying as const, which will cause it to produce () => "A".

Note: I'm not totally sure how related this is, but I've seen > () => Math.random() > 0.5 ? "A" : "B" which produces () => "A" | "B", and it's not clear to me what the difference is from the example above

However, in the context of a library, a library author doesn't have the option to do this in the type level. Something for this use-case would be a nice improvement:

////// LIBRARY CODE
const UploadThingServerHelper = <ValidRoutes,>(
  route: {
    readonly [Route in keyof ValidRoutes]: {
      middleware: () => ValidRoutes[Route]; // how can a library author force a more specific type here?
      onUpload: (response: { metadata: ValidRoutes[Route] }) => void;
    };
  }
) => {};

////// END USER CODE
const FileRouter = UploadThingServerHelper({
  example: {
    middleware: () => "someValue", // and end-user must remember to put `as const` here to force it to return a literal
    onUpload: response => {
      response.metadata; // the result is that the type here is not as narrow as it could be for the best user experience
      //       ^?
    },
  },
});

💻 Use Cases

This is useful for libraries trying to provide a great user experience with great inferencing. It should be a backwards compatible change. There are no workarounds that I'm aware of (from the code of a library).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs ProposalThis issue needs a plan that clarifies the finer details of how it could be implemented.SuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions