Skip to content
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

infer Self #53690

Closed
henrikeliq opened this issue Apr 6, 2023 · 5 comments
Closed

infer Self #53690

henrikeliq opened this issue Apr 6, 2023 · 5 comments

Comments

@henrikeliq
Copy link

henrikeliq commented Apr 6, 2023

Suggestion

πŸ” Search Terms

Automatic generic parameter
Generic type self reference
Generic Self keyword
Infer Self
Exclude from any

βœ… Viability Checklist

My suggestion meets these guidelines:

[x] (I think) This wouldn't be a breaking change in existing TypeScript/JavaScript code
[x] (if done right) This wouldn't change the runtime behavior of existing JavaScript code
[x] This could be implemented without emitting different JS based on the types of the expressions
[x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
[x] This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Instead of writing

type DoesTheThing<T> = T extends Something<T> ? T : never

and then

const something = aLongVariableName as DoesTheThing<typeof aLongVariableName>

it would be nice to have a way to infer the generic type in these instances.

Like this:

type DoesTheThing = infer Self extends Something<Self> ? Self : never

which could then be used like this:

const something: DoesTheThing = aLongVariableName

With the risk of being off-topic another thing that would be nice, albeit not as nice as the above, is the ability to Exclude from any.

type NotAString = Exclude<any, string> // anything that is not a string

I have three variations of my main suggestion, infer Self, that carry varying risks:

  1. (least risk) what I've already mentioned. infer Self has to be in the beginning of the type definition.
  2. (low or no risk) You don't have to write Self but can write any name as long as it's in the start of the expression.
  3. (most risky) You have to write Self (or another keyword) but you can use the keyword in other places as well.

πŸ“ƒ Motivating Example

const aString = "some string"
const aFunction = (test: string) => "string"

// This is how we have to do it now
export type NotFunction<T> = Exclude<T, (...args: any) => any>

const old1 = aString as NotFunction<typeof aString>
//    ^"some string"
const old2 = aFunction as NotFunction<typeof aFunction>
//    ^never

// Least risky change
export type NiceNotFunction = infer Self extends Exclude<Self, (...args: any) => any> ? Self : never

// Maybe more risky (?) 
export type AlsoNiceNotFunction = infer ThisIsAlsoSelf extends Exclude<ThisIsAlsoSelf, (...args: any) => any> ? Self : never

const new1: NiceNotFunction = aString
// ^ Should extend string but is any currently

const new2: NiceNotFunction = aFunction
// ^ Should be never but is any currently


// Most risky/difficult change.
// InferSelf would be overrideable so that it doesn't mess up old codebases.
// It could also be a different keyword that would be a syntax error before this change, 
// for example by including a space.
export type NicerNotFunction = Exclude<InferSelf, (...args: any) => any>

const new3: NicerNotFunction = aString
// ^ Should extend string but is any currently

const new4: NicerNotFunction = aFunction
// ^ Should be never but is any currently

πŸ’» Use Cases

Google jsonable typescript to get lots of examples, the top result being this. There's also been another typescript suggestion to add JSON as a default type.

In the article, the author suggests doing this:

type JSONValue =
    | string
    | number
    | boolean
    | JSONObject
    | JSONArray;

interface JSONObject {
    [x: string]: JSONValue;
}

interface JSONArray extends Array<JSONValue> { }

This works but it's a bit verbose for what I wanted. I also wanted to allow undefined and null, and in that case basically anything can be JSON serialized except for functions and symbols if I'm not mistaken.

So I went on to google on how to make a NotFunction type. This was the top result, and they suggest doing this:

type NotFunction<T> = T extends Function ? never : T;

Which brings me to what I've already said.

@RyanCavanaugh
Copy link
Member

I have a hard time figuring out from the examples what you're trying to do. Are you looking for the satisfies operator, or negated types?

Like, for this example:

type DoesTheThing<T> = T extends Something ? T : never
const something = aLongVariableName as DoesTheThing<typeof aLongVariableName>
type DoesTheThing = infer Self extends Something ? Self : never
const something: DoesTheThing = aLongVariableName

You could have written this?

const something: Something = aLongVariableName

@henrikeliq
Copy link
Author

henrikeliq commented Apr 6, 2023

I have a hard time figuring out from the examples what you're trying to do. Are you looking for the satisfies operator, or negated types?

Like, for this example:

type DoesTheThing<T> = T extends Something ? T : never
const something = aLongVariableName as DoesTheThing<typeof aLongVariableName>
type DoesTheThing = infer Self extends Something ? Self : never
const something: DoesTheThing = aLongVariableName

You could have written this?

const something: Something = aLongVariableName

Sorry I made a mistake here.

I meant:

T extends Something<T> ? T : never

I will correct it.

@RyanCavanaugh
Copy link
Member

So that's

const something = aLongVariableName satisfies DoesTheThing<unknown>;

or

interface Something<T> {
  x: T
}
const aLongVariableName = { x: 3 } satisfies Something<unknown>;

@Andarist
Copy link
Contributor

Andarist commented Apr 7, 2023

The feature request sounds a lot like what has been PRed here

@henrikeliq
Copy link
Author

henrikeliq commented Apr 8, 2023

@Andarist That does indeed seem very similar. I should've looked more carefully. With that being said I did come up with some different variations of the same general idea which might be useful. I've posted a comment on that PR.

@henrikeliq henrikeliq reopened this Apr 8, 2023
@henrikeliq henrikeliq closed this as not planned Won't fix, can't repro, duplicate, stale Apr 8, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants