Comments (10)
createStreetLightProposed(["red", "yellow", "green"], "blue"); // Should Error, and there's no <...> way to override
But you can still always do:
const colors: ("red" | "yellow" | "green" | "blue")[] = ["red", "yellow", "green"];
createStreetLightProposed(colors, "blue");
I get the use case to control the public interface to reduce breaking changes when you decide to change the generic arguments, but you can't prevent users from providing their own types.
from typescript.
TL;DR we're not going to add something we'd immediately ban on DefinitelyTyped
Inference is inference - it is ultimately guided by heuristics that try to answer the question of what the user wanted the type parameter to be, and absolutely can be "wrong" in the sense of not what the user wanted. If your functions' default inference is "good enough" that the inference is always what the user wanted, then you don't need this feature; users don't write type arguments just to help fill up their hard drives. If the default inference isn't good enough - then what? Forcing the user to upcast or downcast because a library author decided they're not allowed to provide manual type arguments is just making a bad inference someone else's problem, and would rightly be called an antipattern.
from typescript.
Please edit to show the actual undesirable case, which looks like .attribute<string, number | string>("age", 33)
or attribute<"age", number | string>("age", 33)
or something that matches your example.
If I wanted such a thing, I'd probably write something like
public attribute<
X extends "Please don't manually specify these generic type arguments",
Name extends string, Type>(name: Name, value: Type) {
this.attributes.set(name, value);
return this as Example<Attributes & { [K in Name]: Type }>;
}
which would make any manual specification unlikely to occur unless someone really wanted to (and if they did, they'd probably find some way around any such feature, such as shown in the prev comment).
I don't know that the use case shown here is compelling enough for this feature. The only time I've wanted to prevent manual specification of type arguments is when the type parameter has a default for when inference fails (as a workaround for #58977), so you'd get something like
function foo<T = string>(f: () => T = () => ("abc" as T)): T {
return f();
}
console.log(foo(()=>123).toFixed(2)); // "123.00" π
console.log(foo().toUpperCase()); // ABC π
console.log(foo<number>().toFixed(2)); // RUNTIME ERROR! π
But I don't know that this is compelling enough either.
from typescript.
Speaking on motivation, let me chime in with an extension of the example for NoInfer
function createStreetLight<C extends string>( colors: C[], defaultColor?: NoInfer<C>, ) { // ... } createStreetLight(["red", "yellow", "green"], "red"); // OK createStreetLight(["red", "yellow", "green"], "blue"); // Error
Which allows for
createStreetLight<"red"|"yellow"|"green"|"blue">(["red", "yellow", "green"], "blue"); // OK, but... ugh
With the proposed feature
function createStreetLightProposed(
colors: (infer C extends string)[],
defaultColor?: C,
)
createStreetLightProposed(["red", "yellow", "green"], "blue"); // Should Error, and there's no <...> way to override
The parameters could still be typecast, ... but this is arguably more natural since the order of the <...>
type parameters is no longer an artifact of the API implementation.
type Lights = "red"|"yellow"|"green"|"blue";
createStreetLightProposed(["red", "yellow", "green"] as Lights[], "blue"); // OK
// note that this also works for the existing NoInfer example...
createStreetLight(["red", "yellow", "green"] as Lights[], "blue"); // OK
This feels related to the problem Java solves with ?
types. e.g., to deal with the exposed "implementation detail" in the generic type signature.
- Type Parameters:
T - the type of input elements to the reduction operation
A - the mutable accumulation type of the reduction operationβ οΈ (often hidden as an implementation detail)β οΈ
R - the result type of the reduction operation
from typescript.
Yep! That's exactly the point that the following as Lights[]
example trying to make.
@rentalhost identified a DRY opportunity in the type representation (with maybe a win for locality as well).
I'm actually a little more interested in avoiding <...>
for that reason since it encourages types to flow through the parameters themselves rather than function signatures.
from typescript.
I don't see the point. You can still change the type by having an explicitly typed variable and pass in the value that way.
const value = 33 as number | string;
foo.attribute("age", value)
from typescript.
Okay, let me try to explain my use case and the limitation that prevents me from achieving my goal. Maybe it's possible today, and you can help me, but I haven't been able to solve it yet.
TypeScript allows the use of typeof
to get the type of a parameter, but it always returns a broad type, not the specific type. Or rather, it will return the type corresponding to the parameter, and not the type of the argument. For instance, if I have something like:
function example(value: unknown): typeof value {
return value;
}
example(123); // type is unknown
example("abc"); // type is unknown
To address this, we can use generics so that TypeScript can determine the type based on the argument:
function example<T>(value: T): T {
return value;
}
example(123); // type is number 123, specifically
example("abc"); // type is string "abc", specifically
This is my goal. However, to achieve it, I need to declare generics that can be modified by the user. This adds an extra layer of care when determining how my generics will work in the future. But my intention isn't to make my function flexible enough for the user to decide what to do β I just want to use this important TypeScript feature internally.
So, I want to propose the possibility of creating 'invisible' generics, intended for local use and automatically determined by TypeScript (as he already does today, however, in a "transparent way", so to speak).
function example<internal T>(value: T): T {
return value;
}
example(123); // type is number 123, specifically
example("abc"); // type is string "abc", specifically
example<number>(123); // Error: type T is internal
Why is this important?
I donβt want to worry about how I manage my generics internally in future updates, as I'm intentionally keeping them out of the user's control. I just want to rely on what TypeScript infers.
Maybe I'm talking about an "improved typeof
", but I understand the importance of how it currently works.
Essentially, I just need the input type because I need to 'pass forward' exactly the type that was received. This is necessary because Iβm building a schema builder that works like this:
const User = schema("user")
.attribute("name", String)
.attribute("age", Number)
typeof User.getAttributes() is { name: string, age: number }
To achieve this, .attribute()
captures the name
(as generic Name
) and the type
(as generic Type
), and then returns this retyped to consider the added attributes. Something like return this as Example<T & { [K in Name]: ...<Type> }>
.
However, this forces me to use generics where I don't actually want to use them β I only need to know what is being passed in and account for that within the function to return a new type based on the userβs input.
function example<infer N extends string>(name> N): Uppercase<N>; // or
function example(name: infer N extends string): Uppercase<N>; // or
// typeof example("abc") === "ABC"
function example<T>(name: infer N extends string, type: T) { ... }
// example("abc", 123) will have generic T = number, N is internally "string" but not accessible to userland.
from typescript.
Let's say this feature existed. What would you do when someone writes const x: number = 123; example(x);
and the "internal" generic is inferred as number
and not 123
? If it's okay, then why isn't example<number>(123)
okay? If it's not okay, then the feature doesn't help you.
from typescript.
@jcalz the goal is to use the same existing feature as generics, but without exposing it directly to the user, since its use would be exclusively internal.
This approach also helps reduce the complexity of the function signature externally. Instead of seeing a signature like example<number>(value: number)
(note how number
is redundant here), we would just see example(value: number)
. In this case, number
is inferred from const x: number
from your example.
So in your example, the type would indeed be inferred as number
, not as 123
(which is not the goal anyway).
The main objective, then, is to simply pass along the type received as an argument without needing to expose the existence of a generic, which serves only that purpose.
While generics can solve this, they add an unnecessary maintenance layer in certain cases. For instance, if I define the order as <Name extends string, Type extends JsonValue>
, I wouldnβt be able to change it (whether itβs the order, adding new elements before or in between, or retyping it) without causing a breaking change, even though this flexibility was never intended for the user. Think of it as a private method/property in a class.
Using generics as they are today allows the user to provide the generic type themselves. This prevents me from continuing to maintain my function in the intended way. The function only used generics in this manner because it was the only possible way to solve my problem.
from typescript.
This issue has been marked as "Declined" and has seen no recent activity. It has been automatically closed for house-keeping purposes.
from typescript.
Related Issues (20)
- TypeScript 5.7 Iteration Plan HOT 1
- Iterator Helpers Typing HOT 7
- Template Literal Types derived from the type keys cannot be used as Indexed Access Types in generic contexts HOT 3
- Buffer type incompatibility with BinaryLike | KeyObject in crypto.createHmac() function HOT 4
- function object parameter destructure field without default value will be ignored HOT 3
- Compiler doesn't warn about incompatible signatures HOT 2
- GitHub Issue Duplicate Detector HOT 2
- Template literal type derived from string is not a subtype of the TLT derived from any
- Improve Iterator Helper Type Signatures HOT 10
- Missing Handbook Content HOT 3
- Suggest @ts-expect-error as Quick Fix
- @import suggestions for auto-imports in VS Code
- Binding element incorrectly reported as having `any` type in JS with JSDoc starting with 5.6.2 HOT 3
- React's ComponentProps type issues in TypeScript 5.6.2 HOT 11
- The function type should not inherit the built-in object type HOT 3
- Ability for compiler host to provide resolution mode for file and say it supports impledNodeFormat HOT 2
- Generated d.ts imports package that is not installed on current project HOT 2
- Organize imports preferences don't seem to work
- Types as Values (Symbols?) HOT 2
- Narrow types with boolean literal discriminants HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. πππ
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google β€οΈ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from typescript.