Skip to content

Go to definition for TypeScript literal strings #49033

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
KATT opened this issue May 9, 2022 · 14 comments · May be fixed by #57729
Open

Go to definition for TypeScript literal strings #49033

KATT opened this issue May 9, 2022 · 14 comments · May be fixed by #57729
Labels
Domain: Symbol Navigation Relates to go-to-definition, find-all-references, highlighting/occurrences. Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Milestone

Comments

@KATT
Copy link

KATT commented May 9, 2022

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
@mjbvz mjbvz transferred this issue from microsoft/vscode May 9, 2022
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Help Wanted You can do this Experience Enhancement Noncontroversial enhancements Domain: Symbol Navigation Relates to go-to-definition, find-all-references, highlighting/occurrences. labels May 12, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone May 12, 2022
@KATT
Copy link
Author

KATT commented May 13, 2022

Cc @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

@RyanCavanaugh
Copy link
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.

@KATT

This comment was marked as outdated.

@t3dotgg
Copy link

t3dotgg commented Jun 20, 2022

I'll throw in another $500 on this bounty @KATT

@JoshuaKGoldberg
Copy link
Contributor

JoshuaKGoldberg commented Jun 28, 2022

@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: @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! 🚀

@RyanCavanaugh
Copy link
Member

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

This is getContextualType

@RyanCavanaugh
Copy link
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.

@JoshuaKGoldberg
Copy link
Contributor

JoshuaKGoldberg commented Jun 29, 2022

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?

@Andarist
Copy link
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?

@RyanCavanaugh
Copy link
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.

@JoshuaKGoldberg
Copy link
Contributor

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

@RyanCavanaugh
Copy link
Member

https://en.wikipedia.org/wiki/Interning_(computer_science)

@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jun 29, 2022

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

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

@sghsri
Copy link

sghsri commented Dec 21, 2022

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

zardoy added a commit to zardoy/typescript-vscode-plugins that referenced this issue Jan 17, 2023
…with "static" generics) like:

type B = { foo: 5, bar }
const a = <T extends keyof B>(a: T) => {}
a('foo')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: Symbol Navigation Relates to go-to-definition, find-all-references, highlighting/occurrences. Experience Enhancement Noncontroversial enhancements Help Wanted You can do this Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants