Skip to content

Proposal: Enhance String interface definition to support type inference for string literalsΒ #60456

Open
@Akindin

Description

@Akindin

πŸ” Search Terms

string generic methods, string concat, checked domain literal types

Related issues #44268

βœ… Viability Checklist

⭐ Suggestion

Current Behavior:
Currently, any manipulation on string other then assigning to literals and using ${templates} doesn't infer the type from literal string. Basic toString and valueOf will lose type of literal string in the process.
Desired Behavior:
I propose enhancing the type definitions for String interface so that it can infer the exact type when used with string literals and templates. This would improve type safety and developer experience when working with string manipulation. At least valueOf, toString, toUpperCase, toLowerCase can be implemented without changing something other than the definition of String interface.

Example of Current Issue:

const result = 'hello'.concat(' ', 'world'); // TypeScript infers 'string' instead of 'helloworld'

Proposed Solution:

Introduce a new type definition for concat that uses variadic tuple types to infer the correct concatenated string literal type:

type Join<S extends string[], D extends string> = 
    S extends [] ? '' :
    S extends [infer First extends string, ...infer Rest extends string[]] ? 
        `${First}${Rest extends [] ? '' : D}${Join<Rest, D>}` : string;

interface String {
    concat<This extends string, S extends string[]>(this: This, ...strings: S): `${This}${Join<S, ''>}`;
}


const c = 'qwery'.concat("123", 'abcd')

Benefits:

  • Improved Type Inference: Developers will get precise types when concatenating string literals.
  • Better Code Completion and Error Detection: IDEs can provide better suggestions and catch more errors at compile-time.
  • Consistency: Aligns with TypeScript's goal of providing accurate and useful type information.

Potential Drawbacks:

  • Complexity: This might increase the complexity of TypeScript's type system for string operations.
  • Performance: There could be an impact on type-checking performance for very complex string concatenations.
  • Error on reassign There could be a problems when concat used in let variable initialization
type Join<S extends string[], D extends string> = 
    S extends [] ? '' :
    S extends [infer First extends string, ...infer Rest extends string[]] ? 
        `${First}${Rest extends [] ? '' : D}${Join<Rest, D>}` : string;

interface String {
    concat<This extends string, S extends string[]>(this: This, ...strings: S): `${This}${Join<S, ''>}`;
    toString<This extends string>(this: This): This;
    toUpperCase<This extends string>(this: This): Uppercase<This>;
    toLowerCase<This extends string>(this: This): Lowercase<This>;
    valueOf<This extends string>(this: This): This;
}

let a = "123".concat("qwerty");

a = "something else"; // this will result in error if implemented as interface modification because of inferred type "123qwerty"

Additional Context:
This change would particularly benefit scenarios where string templates or literal string concatenation are heavily used, enhancing the robustness of TypeScript's type system in string manipulation contexts.

Playground


πŸ“ƒ Motivating Example

In TypeScript, while working with string literals, certain operations like concatenation or transformations (e.g., toUpperCase, toLowerCase) typically result in the loss of specific literal types, being inferred as a general string. This can lead to a loss of valuable type information, resulting in less strict compile-time checks and the need for manual type assertions or annotations.

Consider the following example:

const basePath = "/api";
const usersPath = "/users";
const fullPath = basePath.concat(usersPath);  // Inferred as `string`

Here, despite knowing that basePath is "/api" and usersPath is "/users", TypeScript loses the literal type information after concatenation, inferring fullPath as string, rather than "/api/users". This loss of precision means we can't rely on TypeScript to enforce strict types when building paths or identifiers, leading to potential runtime errors.

πŸ’» Use Cases

  1. What do you want to use this for?
    To get rid of boilerplate when transforming strings and ensure that result of transformation satisfies the constraints
  2. What shortcomings exist with current approaches?
    Explicit type declaration and cast after manipulations
  3. What workarounds are you using in the meantime?
    Create a bunch of utils functions that work as a Proxy for calling built in methods

Metadata

Metadata

Assignees

No one assigned

    Labels

    Awaiting More FeedbackThis means we'd like to hear from more people who would be helped by this featureSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions