Skip to content

infer SelfΒ #53690

Closed as not planned
Closed as not planned
@henrikeliq

Description

@henrikeliq

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions