Description
🔍 Search Terms
must-infer generic
immutable generic
avoid override generic
✅ Viability Checklist
- 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 isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
⭐ 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.