Skip to content

Go to definition for TypeScript literal strings #49033

Open

Description

@KATT

Feature request

Go to definition for TypeScript literal strings.

Example

Given the below code, I would really like to be able to jump to the definition of the 'foo':

const obj = {
  foo: 'bar' as const,
  hello: 'world' as const,
}

function get<T, K extends keyof T>(obj: T, path: K): T[K] {
  return obj[path]
}

const val = get(obj, 'foo')
//                   👆👆👆 Is there a way of make jump to definition work here?
//                         I really want to CMD+click `'foo'` and jump to line #2

Link to TypeScript playground.

WebStorm Demo of the same code

In WebStorm, it actually works how I wish that VSCode worked, here's a video:

nice.webstorm.mov

Activity

  1. KATT commented on May 13, 2022

    @KATT
    Author

    Cc Ryan Cavanaugh (@RyanCavanaugh) is there a way to put bug bounties on issues in order to incentivize OSS-contributors to take on tasks ahead of the core team? I'd happily put forward a few hundred $ personally for this to be solved as it's a big pain point in my API design of tRPC

  2. RyanCavanaugh commented on May 13, 2022

    @RyanCavanaugh
    Member

    Interesting question; we do not currently have a mechanism for that. Other projects have had people put up bounties on third-party sites, e.g. dotnet/runtime#36748 (comment) . I was actually just on an internal email thread about this and the general consensus was that we're OK with good-faith bounties being staked, but want to "stay neutral" in terms of e.g. deciding which of two contributors should be awarded a bounty if both put up a PR.

  3. KATT commented on May 16, 2022

    @KATT
    Author
  4. t3dotgg commented on Jun 20, 2022

    @t3dotgg

    I'll throw in another $500 on this bounty Alex / KATT (@KATT)

  5. JoshuaKGoldberg commented on Jun 28, 2022

    @JoshuaKGoldberg
    Contributor

    Mateusz Burzyński (@Andarist) and I chatted a bit about this one. We suspect it's not straightforward or trivial to implement, but hopefully doable in our time budgets.

    Edit: Ryan Cavanaugh (@RyanCavanaugh) responded immediately after this long text with answer (checker.getContextualType)

    Prior Debugging Notes

    We'd both separately done similar investigations. Take my rough notes with a grain of salt because I'm not on the TypeScript team and don't know anything for sure:

    1. The Go to Definition codefix comes from goToDefinition.ts
    2. Eventually the call stack goes to function getSymbol -> checker.getSymbolAtLocation
      • In here, the case SyntaxKind.StringLiteral case falls through to SyntaxKind.NumericLiteral
      • Nothing within those two cases gets hit, so it returns undefined

    Backing context:

    • Literals don't have rich (per-node) symbol information. There's one global symbol per literal type.
    • Individual nodes seem to store a grab-bag of information in nodeLinks, accessed via getNodeLinks.

    Potential Implementation Directions

    We discussed these three, in increasing order of likelihood to succeed:

    1. Create new symbols for the literals
    2. See what type the parent location is
    3. Node links / getNodeLinks

    Create new symbols for the literals

    Is it possible that we could have more rich symbols per literal? Maybe yes, but that'd be a pretty big breaking change in how TypeScript and its consumers understand symbols. I'm under the impression many consumers -such as typescript-eslint- assume one symbol-per-literal, not one-symbol-per-literal-and-type. 👎

    See what type the parent location is

    Is there a compiler API for "given a node, tell me what type it's expected to be"? E.g. if you're looking at a function argument, determining the intended type of the parameter? Neither of us could find one.

    If one exists: yes please, that would be an ideal solution!

    If not: ah well.

    Augment node links

    NodeLinks seems to already be a catch-all place to put information on nodes. Can we add something like a resolvedLiteralType?: Type property for literals?

    getNodeLinks is called all over the place. Neither of us have investigated where adding this information for literals would be possible, if anywhere.


    Again: don't take these notes too seriously; we might be totally off in our direction. But either way: progress! 🚀

  6. RyanCavanaugh commented on Jun 28, 2022

    @RyanCavanaugh
    Member

    Is there a compiler API for "given a node, tell me what type it's expected to be"?

    This is getContextualType

  7. RyanCavanaugh commented on Jun 28, 2022

    @RyanCavanaugh
    Member

    I'm not even sure how this can be made to work in practice. Let's consider something like

    const obj1 = {
      hello: 1
    };
    const obj2 = {
      foo: 0,
      hello: 1,
      baz: 2
    };
    
    declare let a: (keyof typeof obj1) & (keyof typeof obj2);
    a = "hello"; // <-

    Here, a has the literal type "hello". It went through intersection reduction via an identity match between "hello" from keyof typeof obj1 and keyof typeof obj2. You can't create new literal types for those keys depending on where they came from; this will have totally uncool performance characteristics (allocations, forcing intersection to do more work, and GC pressure). So you're necessarily left with just the bare literal type "hello" with no real information of where it came from.

  8. JoshuaKGoldberg commented on Jun 29, 2022

    @JoshuaKGoldberg
    Contributor

    So this is interesting. Yes, checker.getContextualType does retrieve the union type - thank you!
    But the union type doesn't have a .symbol referring back to the keyof typeof.

    In fact, literal values and operator-computed values such as keyof typeof don't have type definitions.
    Trying to Go to type definition on the following variables' names...

    • const valA = {};: highlights the {} ✅
    • const valB = true;: gives No type definition found for 'valB' ❌
    • const valC = typeof valA;: gives No type definition found for 'valC' ❌

    Is it intentional that the type doesn't have a symbol? Could we add one in as part of this change?

    declare let a: (keyof typeof obj1) & (keyof typeof obj2);

    I would think the Go to definition would be on the type annotation in full: (keyof typeof obj1) & (keyof typeof obj2). That would be the expected checker.getContextualType behavior, no?

  9. Andarist commented on Jun 29, 2022

    @Andarist
    Contributor

    I would think the Go to definition would be on the type annotation in full: (keyof typeof obj1) & (keyof typeof obj2). That would be the expected checker.getContextType behavior, no?

    That matches my thinking too.

    As to getContextualType - I was trying to play around with it before but I couldn't make any use of it. With the given simple get example if I call typeChecker.getContextualType(node) I just end up with the literal type itself. Should it be called on another object?

  10. RyanCavanaugh commented on Jun 29, 2022

    @RyanCavanaugh
    Member

    Is it intentional that the type doesn't have a symbol?

    Yes. Literal types and unions don't have symbols because there's not an originating declaration for them; both are interned.

  11. JoshuaKGoldberg commented on Jun 29, 2022

    @JoshuaKGoldberg
    Contributor

    Sorry to dig, but what does "interned" mean here?

  12. RyanCavanaugh commented on Jun 29, 2022

    @RyanCavanaugh
    Member
  13. RyanCavanaugh commented on Jun 29, 2022

    @RyanCavanaugh
    Member

    GitHub has changed Ctrl-Enter to "Close" and I am furious 👿

    Edit: Oh thank god https://twitter.com/mariorod1/status/1542189296240713730

  14. sghsri commented on Dec 21, 2022

    @sghsri

    Any updates on this, would really love to have this behavior! The use case I'm most familiar with is when I create function names using string literal types:

    image

    image

    image

    its very annoying that we're not able to go to definition on these generated functions from anywhere in the code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions