Skip to content

Introduce "must-infer" generics to restrict user-specified type overrides #59814

Closed as not planned

Description

🔍 Search Terms

must-infer generic
immutable generic
avoid override generic

✅ Viability Checklist

⭐ Suggestion

TypeScript currently allows developers to specify generic types explicitly, which can override TypeScript’s inferred types. While this flexibility is powerful, there are cases where it is essential to prevent the user from altering the generic type, ensuring that the type inference mechanism retains full control.

I propose adding a "must-infer" modifier for generics, where the TypeScript compiler would infer the type without allowing any user overrides. The syntax could be as follows:

function test<infer X>(value: X): X { 
  return value; 
}

This approach enforces that X is determined solely by the TypeScript compiler based on the input value, and it cannot be explicitly set by the user.

For example:

test<number>(123); // Error: X cannot be set by the user

📃 Motivating Example

This feature enhances type safety by ensuring that the generic type is always determined by TypeScript’s inference system, which is particularly useful in cases where the generic type must match the input type exactly. This prevents potential misuse or errors that could arise from explicitly overriding the inferred type.

💻 Use Cases

In my real-world scenario, I have a method that needs to capture the string representing the name of an attribute and the type of its value. To achieve this, I do the following:

class Example<Attributes extends object = object> {
  private attributes = new Map<string, any>()

  public attribute<Name extends string, Type>(name: Name, value: Type) {
    this.attributes.set(name, value);

    return this as Example<Attributes & { [K in Name]: Type }>;
  }
}

const example = new Example()
  .attribute("user", "John Doe")
  .attribute("age", 33);

// typeof example ~= Example<{ user: string; age: number }>

However, note that I can "subvert" the type by overriding the generic:

.attribute<number | string>("age", 33)

// typeof example ~= Example<{ user: string; age: number | string }>

This is not the expected behavior. Even worse, it allows the user to manipulate my generics, while I intended to use them solely to "pass through" the input type to the output. This restricts future maintenance; if I ever want to change or remove a generic, I wouldn’t be able to do so.

In this case, the proposal would be something like:

public attribute<infer Name extends string, infer Type>(name: Name, value: Type) { ... }

From there, TypeScript itself would infer the types of Name and Type, and the user wouldn’t be able to modify them. This would allow me to maintain the freedom to change my generics without worrying about breaking changes.

My other idea was to simply use typeof, but that doesn’t work well because it expands the type too broadly. For example:

public attribute(name: string, value: any) {
  this.attributes.set(name, value);

  return this as Example<Attributes & { [K in typeof name]: typeof value }>;
}

// typeof example ~= Example<{ [x: string]: any }>

This doesn’t achieve the desired precision, as it leads to overly generalized types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    DeclinedThe issue was declined as something which matches the TypeScript visionSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions