Const function generic typescript

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

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

Suggestion: Const contexts for generic type inference #30680

Suggestion: Const contexts for generic type inference #30680

Comments

Search Terms

const contexts; literals; narrowing; generics;

Suggestion

Allow const contexts for generic type parameter inference.

Often I’ve written and seen generic functions where the type parameter inference is intended to be as narrow as possible. Accomplishing this involves several «tricks» with generic constraints. It is more reminiscent of alchemy than I would like, and places quite a burden on the function signature and anyone with the misfortune of having to read and understand it:

// 🧙 what sorcery is this?? 🧙‍ type Narrowable = string | number | boolean | symbol | object | undefined | void | null | >; declare function fooN extends Narrowable, T extends  [k: string]: N | T | [] >>(x: T): T; foo( a: 1, b: "c", d: ["e", 2, true,  f: "g" >] >); // T inferred as < a: 1; b: "c"; d: ["e", 2, true, < f: "g"; >]; >

Const contexts (#29510) are exactly the knob we want to turn here, and the » as const » syntax
is succinct, understandable, and non-mind-bending. Unfortunately, the only way to use this in
generic functions is from the caller’s side, which is hard to guarantee:

declare function barT extends object>(x: T): T; bar( a: 1, b: "c", d: ["e", 2, true,  f: "g" >] > as const); // burden on function caller // T inferred as  // readonly a: 1; readonly b: "c"; d: readonly ["e", 2, true, < readonly f: "g"; >]; // > bar( a: 1, b: "c", d: ["e", 2, true,  f: "g" >] >); // oops!! // T inferred as  // a: number; b: string; d: (string | number | boolean | < f: string; >)[]; // > 😢

The suggestion here is to get the best of both worlds by allowing a const context to be specified in the generic type parameter declaration:

declare function bazT extends const object>(x: T): T; // 🤔 declare function bazT extends object as const>(x: T): T; // 🤷 declare function bazconst T extends object>(x: T): T; // 😵 declare function bazconst T const extends readonly object as const>(x: T): T; // 🧠💥🤪 // also possible, see #46937: declare function bazT extends object>(x: const T): T; baz( a: 1, b: "c", d: ["e", 2, true,  f: "g" >] >); // T inferred as  // readonly a: 1; readonly b: "c"; d: readonly ["e", 2, true, < readonly f: "g"; >]; // > ❤🎉

#10676: generics infer literals «T extends string | number | boolean»
#27179: generics infer tuples «T extends U[] | [U]»
#13347: probably can’t make generics infer readonly

#16896: please narrow all object literals as much as possible

Checklist

My suggestion meets these guidelines:

  • 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, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

EDIT: mentioning slightly different approach from #46937 where modifier goes on the type parameter instead of its constraint. It’s not obvious to me if one has an advantage over the other.

The text was updated successfully, but these errors were encountered:

Источник

Saved searches

Use saved searches to filter your results more quickly

You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.

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

Allow inferring generic function types «as const» #38968

Allow inferring generic function types «as const» #38968

Awaiting More Feedback This means we’d like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

Search Terms

const generic inference nested

Suggestion

Let generic function signature specify that type parameters should be inferred as const

Use Cases

Allow writing library functions that specify how they want their generic parameters to be inferred. Right now, this is only possible with generics like (value: T) => . , which doesn’t cover objects — only literals.

Examples

Let’s say you have a basic runtime type-matching function:

const getMatcher = T>(sample: T) => U>(value: U): value is T & U =>  // implementation details not important - just a very basic deep-equals if (sample && typeof sample === 'object')  return Object.entries(sample).every(([k, v]) => k in value && getMatcher(v)((value as any)[k])) > return sample === (value as any) >

If I try to use this to make a string matcher, it doesn’t keep track of literal types:

const fooMatcher = getMatcher('foo') // T is inferred as `string`, not literal `"foo"` const s = Math.random()  0.5 ? 'foo' : 'bar' if (fooMatcher(s))  const foo: 'foo' = s // Error: Type '"foo" | "bar"' is not assignable to type '"foo"'. >

This makes sense as default behaviour, since ‘foo’ was passed in as a string. And it can be fixed at the call site with getMatch(‘foo’ as const) . But this only works for typescript programs, and if we’re writing a library, javascript users of that library won’t get useful type inference. And it’s not really intuitive. Even typescript users of the library will need to know somehow that this trick is available.

It could also be fixed by changing the function signature:

const getMatcher = T extends string>(sample: T) => .

But getMatcher is now limited to only work with strings. This doesn’t cover the case of deeply nested objects:

const commentMatcher = getMatcher( type: 'comment', payload: sender: 'abc'>, >)

As far as I know, there’s no way to tweak the extends constraint for T which makes this keep track of the literal type: ‘comment’ and sender: ‘abc’ values. So this feature request is to allow us to effectively ask the compiler to add as const to any inferred type parameters:

const getMatcher = T as const>(sample: T) => U>(value: U): value is T & U =>  . >

(syntax is just an idea which reuses the as const pattern in the generic typedef)

If we could do this, it’d make it easy to use the matcher to write useful type guards:

type Request = | type: 'comment'; payload: sender: string; issueId: number; body: string>> | type: 'issue'; payload: sender: string; id: number; title: string; body: string>> const commentMatcher = getMatcher( type: 'comment', payload: sender: 'abc'>, >) const handleRequest = (request: Request) =>  if (commentMatcher(request))  console.log('parent: ' + request.payload.issueId) // request would be correctly inferred as a comment, because `type: 'comment'` was inferred as a literal > >

Checklist

My suggestion meets these guidelines:

  • 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, etc.)
  • This feature would agree with the rest of TypeScript’s Design Goals.

The text was updated successfully, but these errors were encountered:

@robbiespeed yes, but I’m interested in inferring objects with string/number properties as literals, not just string/number values. This solution helps, but doesn’t cover the example in the OP:

Here, the generic will be inferred as < type: string >, whereas I want

@mmkal my mistake I missed the part where it was about properties, doesn’t help that I forgot < foo: 'bar' >as const is valid syntax for recursively making a objects properties readonly and inferred as literal.

I did just stumble across how to make it work for properties, I thought it would only restrict the type going into getMatcher to a const, but it actually inferred all the properties as literal.

Edit
I was able to greatly simplify the getMatcher type, it doesn’t require overloads, and no longer only accepts literals as the sample or as properties of the sample. Still will infer them as literal if possible. Tested to make sure it worked with the request example you gave in the OP.

type ConstRecord T> =  [P in keyof T]: T[P] extends string ? string extends T[P] ? string : T[P] : T[P] extends number ? number extends T[P] ? number : T[P] : T[P] extends boolean ? boolean extends T[P] ? boolean : T[P] : ConstRecordT[P]>; > type Const T> = T extends string ? T : T extends number ? T : T extends boolean ? T : ConstRecordT> function getMatcher T> (sample: ConstT>): (value: unknown) => value is T  return function (value: any): value is T  if (sample && typeof sample === 'object' && typeof value === 'object')  return Object.entries(sample).every(([k, v]) => k in value && getMatcher(v as any)(value[k])) > return (sample as any) === value; > > type APIRequest = | type: 'comment'; payload: sender: string; issueId: number; body: string>> | type: 'issue'; payload: sender: string; id: number; title: string; body: string>> const commentMatcher = getMatcher( type: 'comment', payload: sender: 'abc'>, >) // infers all nested properties as literal const fooMatcher = getMatcher('foo') // infers 'foo' as literal const handleRequest = (request: APIRequest) =>  if (commentMatcher(request))  console.log('parent: ' + request.payload.issueId) // request is correctly inferred as a comment > >
type ConstRecord T> =  [P in keyof T]: T[P] extends string ? string extends T[P] ? never : T[P] : T[P] extends number ? number extends T[P] ? never : T[P] : T[P] extends boolean ? boolean extends T[P] ? never : T[P] : ConstRecordT[P]>; > type ConstValue B, T extends B> = B extends T ? never : T; function getMatcher T extends string> (sample: ConstValuestring, T>): U> (value: U) => value is T & U function getMatcher T extends number> (sample: ConstValuenumber, T>): U> (value: U) => value is T & U function getMatcher T extends boolean> (sample: ConstValueboolean, T>): U> (value: U) => value is T & U function getMatcher T> (sample: ConstRecordT>): U> (value: U) => value is T & U function getMatcher T> (sample: ConstRecordT>)  return function U> (value: U): value is T & U  if (sample && typeof sample === 'object' && typeof value === 'object')  return Object.entries(sample).every(([k, v]) => k in value && getMatcher(v as any)((value as any)[k])) > return (sample as any) === value; > > const oMatcher = getMatcher( foo: true > as const); // type infered as const oMatcherB = getMatcher( foo: true >); // type also infered as const oMatcherC = getMatcher( foo:  bar: true > >); // works on nested props too! const b = Math.random()  0.5 ?  foo: true > : 'bar'; const barMatcher = getMatcher('bar'); if (oMatcherB(b))  b; // type guarded to < foo: true >, although it's not properly reducing the type and has a full type of: // ( // foo: true; // > & string) | ( // foo: true; // > & // foo: boolean; // >) // It might be possible to fix this with some tweaking > else if (barMatcher(b))  b; // type correctly guarded to 'bar' >

Источник

Читайте также:  Html href page link
Оцените статью