Description
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:
- (least risk) what I've already mentioned.
infer Self
has to be in the beginning of the type definition. - (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.
- (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.