Skip to content

feat(auto): reference own type in type declaration #49120

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

Open
5 tasks done
Larkooo opened this issue May 15, 2022 · 10 comments
Open
5 tasks done

feat(auto): reference own type in type declaration #49120

Larkooo opened this issue May 15, 2022 · 10 comments
Labels
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

@Larkooo
Copy link

Larkooo commented May 15, 2022

Suggestion

Similarly to C++, having an auto type that would infer the type of a variable and that would be usable in its type declaration.

πŸ” Search Terms

auto in type declaration

βœ… Viability Checklist

  • 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, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Currently, there isn't a really neat and simple way of catching the type of a variable in its type declaration, which can be quite annoying when we want that variable to have other allowed types alongside with its original type, without needing to declare it as any or typing explicitely its type.

πŸ“ƒπŸ’» Example and use cases

Let's say you're working with a library that deals with a lot of types
image
In this example I'm using mongoose, and as you can see, certain type declarations don't even fit on my screen!

Now, let's say that you want to call one of the methods in this screenshot and register its value in a variable, but allow another type to go along with it, like for eg. a string, or to allow it to also be null?

The only 3 ways you could do this right now in typescript is to either;

  • Declare your variable as of the any type, which isn't really safe
  • Using the ReturnType generic to get the return type of the method you're calling, but which wouldn't even work out of the box because in this example the method we're calling is async and we're awaiting it, which returns the non promised type but... ReturnType gives us back a promise
  • Copy the entirety of the type declaration (disastrous)

Oh and this example is only for when dealing with functions, what would you do if you were dealing with an object? You can't use ReturnType so you'd have to either copy it's type name or define it as any

On top of that, writing Awaited<ReturnType<typeof fn>> is much more longer than just using auto, and readability and clarity take a hit

// can be confusing and still brings up the concern of having
// long type names
// repeated code, we would need to change typeof bar and awaited if we 
// were to call another function
let foo: Awaited<ReturnType<typeof bar>> | null = await bar();
// vs
let foo: auto | null = await bar();

Introducing auto would allow us to catch the type name of our variable and be able to mess with its type declaration

// Right now, the only way we can allow our variable x
// to be either a number or a string is 
let x: number | string = 5;
// We can't guess our type and do conditional typing at the same time
// In this example, it's not that big of a deal because we're dealing with a primite type but what if we were dealing
// with something with a large type name, or something that has multiple types?

// We could just do
let x: auto | string = 5;
// Which would also apply to our mongoose usecase
let x = auto | null = someMongooseFunctionThatReturnsA2kmWideTypeName();
@fatcerberus
Copy link

For the record:

Using the ReturnType generic to get the return type of the method you're calling, but which wouldn't even work out of the box because in this example the method we're calling is async and we're awaiting it, which returns the non promised type but... ReturnType gives us back a promise

Awaited<ReturnType<typeof fn>>

@Larkooo
Copy link
Author

Larkooo commented May 15, 2022

For the record:

Using the ReturnType generic to get the return type of the method you're calling, but which wouldn't even work out of the box because in this example the method we're calling is async and we're awaiting it, which returns the non promised type but... ReturnType gives us back a promise

Awaited<ReturnType<typeof fn>>

Fair enough, it still applies to the other reasons I've mentionned though. On top of that, writing Awaited<ReturnType<typeof fn>> is much more longer than just using auto, and readability and clarity take a hit

// can be confusing and still brings up the concern of having
// long type names
let foo: Awaited<ReturnType<typeof bar>> | null = await bar();
// vs
let foo: auto | null = await bar();

@MartinJohns
Copy link
Contributor

Duplicate of #36450.

@fatcerberus
Copy link

Not an exact duplicate of #36450 - that issue just wants an explicit keyword for type inference (i.e. not a new feature), this asks to be able to combine the auto-inferred type with other types via union, which AFAIK isn’t currently possible without writing a constrained identity function.

@MartinJohns
Copy link
Contributor

That's true, but I see no way to support this feature without such a keyword and without adding some really weird special syntax for a corner-case.

And I've just realized that this is valid syntax: const t: | number = 12

While less nice, you can get the desired feature by using a function:

function nullify<T>(val: T): T | null { return val; }
const foo = nullify(await bar());

@fatcerberus
Copy link

fatcerberus commented May 18, 2022

Indeed, nullify is the kind of thing I was referring to with β€œconstrained identity function”. It’s a very common pattern when you need to constrain a type while still allowing for type inference at the same time. Which is not as much of a corner case as you’d think. πŸ˜…

@fatcerberus
Copy link

On that note, this is probably similar in spirit to what #47920 aims to enable.

@RyanCavanaugh
Copy link
Member

This is super fraught since the contextual type of a variable's initializer is the type annotation itself. Ex:

declare function fn<T>(cb: (arg: T) => void): T;

// s: string
const a: string = fn(s => s);

If you wrote

const a: auto | string = fn(s => s);

then I'm not even sure what should happen, and you could probably construct "unsolvable" setups with this.

Another potential solution is explicit type inference (#26242), where you could (presumably?) write

type Nullable<T> = T | null;
const m: Nullable<infer> = expr;

FWIW I don't think satisfies is capable of fixing this.

The use case here makes way more sense than #36450 so I'll refrain from duping to that.

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels May 19, 2022
@fatcerberus
Copy link

fatcerberus commented May 19, 2022

Huh, I would have expected const a: string = fn(s => s) to be either an implicit-any error or else to instantiate fn<unknown> (and thus be a type error). But instead TS is instantiating a generic function based on how its return value is used, which never happens anywhere else AFAIK (e.g. overload resolution doesn't care about the return type).

Nullable<infer> seems like it should have the exact same pitfalls as just writing infer | null inline (i.e. this proposal)?

@ethanresnick
Copy link
Contributor

I think this is a duplicate of #33480? (And I still think this might be a viable way to implement it.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
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
Projects
None yet
Development

No branches or pull requests

5 participants