-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Const Type Constraint #41114
Comments
Can you provide a real world example where this would be useful? |
function tuple<T extends Array<string | number>>(...args: T) {
return args;
}
const t1 = tuple('a', 'b'); // ['a', 'b']
let bar = 'bar';
const t2 = tuple('foo', bar); // ['foo', string] For intellisense, parameter must be literal. |
Why not to use just: function tuple<T extends Array<string | number>>(...args: T) {
return args;
}
const t1 = tuple('a', 'b'); // ['a', 'b']
let bar = 'bar' as const;
const t2 = tuple('foo', bar); // ['foo', 'bar'] ? |
|
But you didn't provide an example where this would be useful. |
This could be accomplished with the conditionally assignable types discussed in #40779. type StrictSubtype<T> = match U extends T => T extends U ? false : true;
type ConstString = StrictSubtype<string>; |
The main use case––seems to be––to help library developers ensure that consumers are passing narrowed objects: // BAD
acceptsNarrowed({
a: "A",
b: true,
c: 3,
});
// GOOD
acceptsNarrowed({
a: "A",
b: true,
c: 3,
} as const); This feature would really shine! I've run into this problem many times. The following would be incredibly useful to me personally. declare function acceptsNarrowed<O extends Constant<Record<PropertyKey, any>>>(o: O): void;
// or
declare function acceptsNarrowed<O extends const Record<PropertyKey, any>>(o: O): void; |
I have to agree with @harrysolovay. I run into problems around this regularly, usually with some complex type-converting breaking down when it resieves non-literal Also, i think this could solve many of the issues related to the notoriously strange
This would make it possible to give symbols to an API that can be referenced at a later point, both internally and by the API consumer.
|
After a few thinking, I think there are still some unresolved problems in the proposal.
const foo = (arr: Constant<Array<string>>) => arr; If object member extends Use this instead. const bar = (arr: Array<Constant<string>>) => arr; What about We need more discussion.
@tjjfvi That will be a greet solution! I think generic is much better than a new syntax. |
I have to be nitpickey here, but this is also new syntax. I also don't think this would address the issue in this thread. They do have overlap tho.
I don't see a reason why this couldn't be called. I would interpret that as: the function only takes arrays with a known length (so eg. no
I have now idea how There has been a want for something like this (or like the other proposal) for quite some time. There was a suggestion about extending the |
😜Thanks much! I mean no need for another new syntax. https://github.com/tc39/proposal-record-tuple |
@RyanCavanaugh what's the process for adding this to the language by the external contributor? is this proposal non-destructive enough that a PR could be made and there would be a high chance of merging it after refining the details during the code review process? |
I'm trying to do something that I believe the above suggestion would allow. Let's say I want to use https://github.com/vultix/ts-results, but I'd like to:
If I wrap function double(a: number) {
if (a < 0.0) {
return Err({type: 'ERROR_LESS_THAN_ZERO' as const});
} else if (a > 100.0) {
return Err({type: 'ERROR_MORE_THAN_ONE_HUNDRED' as const});
}
return Ok(a * 2.0);
} ...and I am able to get completion and exhaustive checking of those function double(a: number): MyErr<{
type: "ERROR_LESS_THAN_ZERO";
}> | MyErr<{
type: "ERROR_MORE_THAN_ONE_HUNDRED";
}> | TSResults.Ok<number> But the problem is if I forget to add an function double(a: number): MyErr<{
type: string;
}> | TSResults.Ok<number> ...and I've lost the discriminated union of type tags. I'd love some way to say |
@malcolmstill You can use the type Narrow<T> =
| (T extends infer U ? U : never)
| Extract<T, number | string | boolean | bigint | symbol | null | undefined | []>
| ([T] extends [[]] ? [] : { [K in keyof T]: Narrow<T[K]> })
declare function Err<E extends { type: string }>(error: Narrow<E>): E;
const e = Err({ type: 'ERROR_LESS_THAN_ZERO' }) // no `as const` needed
// ^? - { type: "ERROR_LESS_THAN_ZERO" } |
@tjjfvi thanks, as you say function double(a: number, someString: string) {
if (a < 0.0) {
return Err({type: 'ERROR_LESS_THAN_ZERO' as const});
} else if (a > 100.0) {
return Err({type: 'ERROR_MORE_THAN_ONE_HUNDRED' as const});
} else if (a > 50.0) {
return Err({type: someString}); // I would like this to be a compile error because I want to enforce that type is const
}
return Ok(a * 2.0);
} The above compiles, but collapses the return type to: function double(a: number): MyErr<{
type: string;
}> | TSResults.Ok<number> ...I want it instead to not compile, and force me to make |
Basically it's hard to infer from strings when the input isn't a const. Linked code
|
I have a use case for this where I provide a const literal as a function parameter which decodes data into the format which the literal specifies - for example |
(Centralizing the conversation from #51745) I want to define an API for users to specify a fixed path in an object, consider type Obj = {
a: {
b: {
c: "123"
}
}
}
type Get<T, K> = K extends keyof T ? NonNullable<T[K]> : never
type GetPath<T, P> = P extends PathKey[]
? P extends []
? T // If P is empty, return T.
: P extends [infer K, ...infer Rest]
? GetPath<Get<T, K>, Rest> // Else, recurse.
: never
: never
function set<T, P extends string[]>(obj: T, path: P, value: GetPath<T, P>) {}
set(
obj,
["a", "b", "c"],
value
) Currently, typescript would infer this This however, works when function set<T, P extends string>(obj: T, path: [P], value: unknown) {}
function set<T, P1 extends string, P2 extends string>(obj: T, path: [P1, P2], value: unknown) {}
function set<T, P1 extends string, P2 extends string, P3 extends string>(obj: T, path: [P1, P2, P3], value: unknown) {}
// ... This feels both cumbersome and not scalable. With the proposed change, it'll look like funciton set<T, P extends const string[]>(obj: T, path: P, value: GetPath<T, P> {} On the other hand, sometimes people do want their generics to be inferred as Allowing a |
@zen0wu This one actually can be done quite easily even today: The only drawback to this is that I have no idea how to make autocomplete work for this - without computing all possible paths eagerly. |
@Andarist good to know, thank you! just so that i understand correctly, doing |
Yes
Not sure. It's what I do though :P it seems to me like a way better approach than the older |
@Andarist Actually I tried this out. It has this weird behavior where.. it works when the definition of the variable and the type is in the same file, but when they're in different files, this breaks (TS falls back to |
Not sure if I understand - this only impacts how If you'd like to ensure that you can't pass any values that are not literals etc - then this might get more tricky. You can try to use this remapping technique with the presented In more complex scenarios it might be tricky to actually validate your things this way but usually you can get good results |
Search Terms
Const Type Constraint Literal
Suggestion
Add new syntax
const T
or a global generictype Constant<T = any> = T extends const T ? T : never;
type ConstString = const string
is same astype ConstString = Constant<string>
.T
extendsstring | number | boolean | symbol | bigint | null | undefined
, requiresT
to be a constant.T
isobject
orArray
, requires every member ofT
to be a constant.T
isenum
, nothing to do, becauseenum
is same asconst enum
The difference with union literal is that type
'foo' | 'bar'
can only be 'foo' or 'bar', but typeconst string
can be any constant string.Use Cases
Examples
In fact,
const
only affects the assignment behavior, and aconst
type is considered to be the same as a nonconst
type when read it.A
const type
doesn't mean immutable, just means every primitive value should be literal, so maybe should call itliteral type
.Alternatives
extends const
syntax, and add global generictype Constant<T = any> = T extends const ? T : never;
finally
orstatic
orliteral
(not exist in current keywords) keyword instead ofconst
type Literal
instead oftype Constant
.Related issues
#30680 Suggestion: Const contexts for generic type inference
Checklist
My suggestion meets these guidelines:
The text was updated successfully, but these errors were encountered: