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

Instantiable tuple labels #55452

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Andarist
Copy link
Contributor

closes #44939

A somewhat big drawback of this feature is that an instantiated label is only usable if it instantiates to a string literal type. This means that instantiations that end up being template literal types and even unions of strings are just discarded. I'm open to discussing other possible designs.

I think it's a reasonable tradeoff since this feature is primarily an editor experience improvement. Labels don't have any effect on type checking itself.

@typescript-bot typescript-bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Aug 21, 2023
@typescript-bot
Copy link
Collaborator

The TypeScript team hasn't accepted the linked issue #44939. If you can get it accepted, this PR will have a better chance of being reviewed.

@typescript-bot
Copy link
Collaborator

Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page.

Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up.

const index = pos - paramCount;
const associatedName = associatedNames?.[index];
const isRestTupleElement = !!associatedName?.dotDotDotToken;

if (associatedName) {
Debug.assert(isIdentifier(associatedName.name));
// TODO: figure out the non-identifier case here
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MariaSolOs what kind of inlay hint interactivity is expected out of a parameter name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected behaviour is to be able to click on an argument of a function call and have that be a "go to definition" to the respective parameter in the function signature.

@jakebailey
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 21, 2023

Heya @jakebailey, I've started to run the tarball bundle task on this PR at f6e2298. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 21, 2023

Hey @jakebailey, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/156768/artifacts?artifactName=tgz&fileId=6FB00385121F5003E3E1F9DDD8A0AA954C0219A751C825B601C106BC71C81A7602&fileName=/typescript-5.3.0-insiders.20230821.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@Andarist Andarist marked this pull request as draft August 22, 2023 08:19
@DanielRosenwasser
Copy link
Member

@typescript-bot pack this

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 25, 2023

Heya @DanielRosenwasser, I've started to run the tarball bundle task on this PR at 724dc6c. You can monitor the build here.

@typescript-bot
Copy link
Collaborator

typescript-bot commented Aug 25, 2023

Hey @DanielRosenwasser, I've packed this into an installable tgz. You can install it for testing by referencing it in your package.json like so:

{
    "devDependencies": {
        "typescript": "https://typescript.visualstudio.com/cf7ac146-d525-443c-b23c-0d58337efebc/_apis/build/builds/156960/artifacts?artifactName=tgz&fileId=9E4A90143F2EDBAD1CD4D428FC793DEC39A94254D5CF1A3CB84CD746D32918E502&fileName=/typescript-5.3.0-insiders.20230825.tgz"
    }
}

and then running npm install.


There is also a playground for this build and an npm module you can use via "typescript": "npm:@typescript-deploys/[email protected]".;

@Andarist
Copy link
Contributor Author

Andarist commented Aug 25, 2023

@DanielRosenwasser this is still a draft that I put up since I wanted to share this with some people who asked for this feature.

Since then I tested this more and it's not working correctly yet for some recursive cases etc. I have some work in progress locally that fixes some of those issues. While investigating them I learned a bunch of new things about how type references, mappers, etc are implemented under the hood 😉

Those learnings make it harder to implement this feature - or to rephrase: I've hoped for a simpler implementation but at the end of the day I can't avoid instantiating those labels sooner. This raises some data structure/internal API questions. Since those things are also consumed programmatically I'd like to ensure that an implementation design won't be too disruptive for those consumers. I have some ideas/some open questions related to that. If the team would be open to discussing those concerns before I sit down to implement an enhanced proof of concept - I would highly appreciate it. It would be also quite nice if the team could comment on general level of your interest in this feature :)

@DanielRosenwasser
Copy link
Member

Yeah, I think that's why I packed it - I want to gauge appetite/interest. Of course, even if we're interested, a big part of this will hinge on the complexity/maintainability/limitations of the implementation.

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Aug 25, 2023

Actually, I'm having a hard time imagining a reasonable implementation of InsertTupleNames from the orignal issue. I think you have some very basic first-order types that are being used as labels, but I still don't quite get the motivation - what people want to write, how feasible/practical it is, and whether it's worth it.

@Andarist
Copy link
Contributor Author

I can't truly comment on InsertTupleNames right now - would have to read that issue more carefully. The motivating example that I'd like to make work is close to something like this (TS playground):

type ToNamedTuple<
  TAbiParameters extends readonly { name: string; type: unknown }[],
  Results extends any[] = [],
> = TAbiParameters extends readonly [
  infer Head extends { name: string; type: unknown },
  ...infer Tail extends readonly { name: string; type: unknown }[],
]
  ? ToNamedTuple<
      Tail,
      [...Results, `${Head['name']}`: Head['type']]
    >
  : Results

type Result = ToNamedTuple<
  [
    { name: 'foo'; type: string },
    { name: 'bar'; type: bigint },
    { name: 'baz'; type: number },
  ]
>

This is definitely implementable, just a matter of juggling type parameters and things.

In the beginning, I thought that .mapper would always be there and that it would be able to resolve all type parameters. That would allow me to have those labels pretty lazily evaluated. That's not how it works though.

Then I tried to store a global mapping between the types and mappers but that didn't pan out because it turned out that types are heavily cached backed on their type arguments. So that made the labels to "leak" between tuple instantiations with the same element types.

There is no good caching unit that would allow me to keep labels/mappers around - other than the type itself. Currently labels of tuples are Nodes (well, Identifiers). Changing that labeledElementDeclarations' type to (Node | Type)[] would be bizarre and I discarded this option right away.

Types created based on template literal types at labels positions should be somehow included in the type's ID. This way new types could be created based on different label instantiations and things would "just work". This has an obvious drawback of creating more tuple types with the same element types. I don't think that's a big issue from the memory PoV but the checker should learn how to compare such tuple tupes effectively - labels are purely decorative and they shouldn't affect the assignability rules between same element tuples.

Alternatively, wrapper types could be introduced with .target that would reference the underlying tuple type that would hold the actual element types and the wrapper would only be used to store the instantiated labels. I'm not yet sure how I feel about it. On one level it feels like a complication but it has some potential of making the implementation simpler at the expense of having one extra type of a type to care about. Note that I already found a potential use case for an alternative "tuple type" in the past - currently reversed mapped tuple types have subtle differences from reverse mapped object types since they are instantiated at different times. Introducing a ReversedTupleType (or something akin to that) could potentially fix this by making it possible to defer their instantiations so they could behave in the same way as object ones.

@rexfordessilfie
Copy link

Got some other motivating examples for you (potentially). I ran into a similar issue where I think I could benefit from the changes in this PR and from something such as InsertTupleNames - as I understand it.

Here are the examples:

  1. Retyping the parameters of a function. I think I have an immediate use case for this here.

    type Result = RetypeParameters<[a: string, b: number], [number, string]> // [a: number, b: string]
  2. Named tuple to Object Type. Kind of the reverse of your example.

    type Result = ToObject<[a: string, b: number]> // { a: string, b: number }
  3. Using infer over named tuples. (Not sure if this will be addressed by your PR, but my thinking is this would allow for the above two to be easy to implement?)

    type InferNames<NamedTuple extends any[]> = 
        NamedTuple extends [infer Name: any, ...infer Rest] ? 
        [Name, ...InferNames<Rest>] : never 

What do you think?

Apologies if this is not the right avenue to post any of these. Happy to move over to a new issue, or to the original issue your PR is solving. Thanks for driving this forward!

@sentialx
Copy link

sentialx commented Jun 23, 2024

Looking forward to this feature, my use case is converting function parameters to an object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
For Uncommitted Bug PR for untriaged, rejected, closed or missing bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Dynamically named tuples
7 participants