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

[WIP] Proposal for type hierarchy #346

Closed
wants to merge 1 commit into from

Conversation

gorkem
Copy link

@gorkem gorkem commented May 5, 2018

Add new textDocument/subTypes and textDocument/superTypes
requests. Introduces SymbolNode.

Fixes microsoft/language-server-protocol#136

add new textDocument/subTypes and textDocument/superTypes
requests. Introduces SymbolNode.
@gorkem gorkem changed the title Proposal for type hierarchy [WIP] Proposal for type hierarchy May 5, 2018
/**
* Immediate descendants of this node
*/
descendants? : SymbolNode[]
Copy link
Contributor

Choose a reason for hiding this comment

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

If it's the immediate descendants, it should be called children

Copy link
Author

Choose a reason for hiding this comment

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

The doc is incorrect needs to be updated.

I am not sure about children either. This can actually hold hierarchies with depth larger than one. Probably needs a better name.

Copy link
Contributor

Choose a reason for hiding this comment

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

Even if it's depth larger than one, wouldn't the elements of this particular array still be the immediate children?

Copy link
Author

Choose a reason for hiding this comment

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

We use this same data structure for both subTypes and superTypes calls on the superTypes call they are actually the ancestors/parents. We probably need a new name to reflect that.

Copy link
Contributor

@svenefftinge svenefftinge May 9, 2018

Choose a reason for hiding this comment

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

Talking about children would be natural for me, as the symbol nodes will typically be presented in tree-like structures in the client. Also, it matches with hasChildrenbelow.

Copy link

Choose a reason for hiding this comment

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

It may sometimes be useful to expand multiple levels in one request, This is how ccls and cquery do.

@@ -0,0 +1,77 @@

#### Type Hierarchy
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason this proposal is using the term "type" everywhere and not "symbol"? For example in TypeScript, those are two very different concepts

Copy link
Author

Choose a reason for hiding this comment

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

I think those are very different concepts on most languages.


_Response_

* result: `SymbolNode[] | null`
Copy link
Contributor

Choose a reason for hiding this comment

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

So a type can have multiple parent types? If that is true, how would it be displayed in a tree UI (vs a graph)?

Copy link
Author

Choose a reason for hiding this comment

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

multiple-inheritance has been around and there have been tree based representations of it implemented before. The most generic approach I have seen is to replicate the same Type hierarchy tree under all parent nodes. We can add additional information to this data structure if it is going to help with its representation.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should reclarify the goal of this proposal, I misunderstood it. The linked issue describes a symbol hierarchy (e.g. namespaces -> classes -> members), not inheritance

Copy link
Author

Choose a reason for hiding this comment

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

Also take a look at microsoft/language-server-protocol#472 I think that should include the symbol hirearchy

@felixfbecker
Copy link
Contributor

I don't think this is fixes microsoft/language-server-protocol#136 - that issue is about a symbol hierachy tree in a workspace, but this seems to be about getting the inheritance hierachy for definition at a given location.

@gorkem
Copy link
Author

gorkem commented May 5, 2018

microsoft/language-server-protocol#136 refers to inheritance on object-oriented languages, I know because I have written it. I think you are thinking about a way to solve the problem created by the very insufficient SymbolInfomation#containerName field, which is not this one.

@felixfbecker
Copy link
Contributor

Okay, I must have remembered it wrong. Sorry for the confusion. I always had a workspace symbol request in my mind.

@dbaeumer
Copy link
Member

dbaeumer commented May 7, 2018

Some first comments:

  • shouldn't the API be more level based. What do you expect such a request returns if ask on a huge Java system for the type hierarchy of Object.
  • this is somehow foreseen with the hasChildren but from the proposal it is unclear to me how a client would resolve the next level of the children.

Some ideas:

  • we have textDocument/subTypes & textDocument/superTypes requests
  • we have symbolNode/resolve requests for the next level.

We should also see how this aligns with the document symbol hierarchy request to generate the outline of a document.

@gorkem
Copy link
Author

gorkem commented May 7, 2018

The idea was to make another call to textDocument/subTypes with the information from the Location information from SymbolNode. There isn't any generic enough information that can be passed to symbolNode/resolve that is helpful and different fromtextDocument/subTypes.

Another idea is to pass SymbolInformation to textDocument/subTypes and textDocument/superTypes calls instead of current TextDocumentPositionParams.
A new param type like below. We can also amend SymbolInformation with an optional data field similar to CompletionItem that servers would retrieve back to skip the symbol search. The downside is we need to always retrieve the SymbolInformation prior to hierarchy calls.

export interface HierarchyParams {
  /**
   * The symbol for the call.
   */
	symbol: SymbolInformation
}

@dbaeumer
Copy link
Member

dbaeumer commented May 8, 2018

We should use the same data property mechanism that we use for document links and completion items to allow the server to identify the object in the resolve call.

I am wondering whether it makes sense to have a Tree and Graph instead of a SymbolNode in the protocol. Then the request would return a Graph. We could further classify the edges in the tree / graph. This would then work for hierarchical document symbols, type hierarchies, namespace hierarchies or even reference search.

@tsmaeder
Copy link
Contributor

tsmaeder commented May 8, 2018

@dbaeumer I don't quite get what you mean with your last comment. Both about the "data property mechanism" and what you would like to do with trees and graphs.

@dbaeumer
Copy link
Member

dbaeumer commented May 8, 2018

@tsmaeder a completion item and a document link can carry generic data property that the server can fill in and that the client preserves when calling the resolve request. That way the server can uniquely identify the completion item instance when it receives a resolve request. In JDT speak that data filed could contain the Java model identifier to identify the symbol when send back to the server for resolving children.

SymbolInformation literals are already returned in arrays. We didn't add a nextpointer to it :-) So instead if defining the tree / graph structure inside SymbolInformation or a sub type of it we could define a TreeNode and GraphNode in the LSP and then return a GraphNode for the type hierarchy. We could also type the edges to make the graph useful for containment, call hierarchies, sub type / super type, ....

_Response_

* result: `SymbolNode[] | null`
* error: code and message set in case an exception happens during the 'textDocument/subTypes' request
Copy link
Contributor

Choose a reason for hiding this comment

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

When I open a type hierarchy on a certain position in the text, I would like to see the SymbolNode for that token together with its subtypes. How do I obtain the SymbolNode I am opening the hierarchy on?

Copy link

@MaskRay MaskRay Jun 8, 2018

Choose a reason for hiding this comment

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

There can be multiple dimensions of sub-super relations: template parameters related to a template, derived types related to a base, specializations. Is there some way to differentiate them in this proposal?

In ccls, both derived types and specializations are stored in the IndexType::derived field.

@tsmaeder
Copy link
Contributor

@dbaeumer So the GraphNode thing is more about not linking it to the SymbolInformation than any functional difference? Making it more general, but structurally it would be similar?
Got it about the "userData".

@dbaeumer
Copy link
Member

@tsmaeder yes that is the idea.

@gorkem
Copy link
Author

gorkem commented Jun 26, 2018

FYI since this has stalled a bit because I got new responsibilities @tsmaeder will be taking over this PR, so stay tuned

@dbaeumer
Copy link
Member

dbaeumer commented Jul 4, 2018

@tsmaeder in VS Code we experimented with a GraphNode and we gave up on it :-(. Mainly because it was hard to construct a graph without adding a builder or some other kind of help methods. If you have a nice idea I am still open to it. Otherwise we found that it is better to have specific return types for every request. So in the type hierarchy case something like a TypeHierarchyNode.

@simark
Copy link

simark commented Aug 27, 2018

A use case I would like to see supported is calling the type hierarchy request on a method (declaration, definition or call) and seeing which nodes implement that method. In Eclipse JDT and CDT, for example (probably others too), you can get something like this:

screenshot_2018-08-27_10-47-37

This gives the hierarchy from the point of view of the Child2 class, but also tells you that the method I called "type hierarchy" has an definition in the Parent class, but not in the Child class.

Protocol-wise, I think that calling "type hierarchy" on a method could be equivalent to doing the request on the type of the object in which the method is declared/defined or on which the method is called. But the hierarchy nodes could also include a flag (only useful if the target of the request is a method) indicating if the node implements that method. In addition, we would need to get information about what the user has called "type hierarchy" on (name, type, etc).

* Represents hierarchical information about programming constructs like variables, classes,
* interfaces etc.
*/
export interface SymbolNode extends SymbolInformation {

Choose a reason for hiding this comment

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

As of LSP 3.10.0, we can use the DocumentSymbol as is, right? It has the children property.

Choose a reason for hiding this comment

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

We need one structural change, and we could simplify this PR:

The DocumentSymbol should be

export class DocumentSymbol {
	name: string;
	detail?: string;
	kind: SymbolKind;
	deprecated?: boolean;
	range: Range | Location;
	selectionRange: Range | Location;
	children?: DocumentSymbol[];
}

instead of the current state:

export class DocumentSymbol {
	name: string;
	detail?: string;
	kind: SymbolKind;
	deprecated?: boolean;
	range: Range;
	selectionRange: Range;
	children?: DocumentSymbol[];
}

When the range and the selectionRange has the type Range then the DocumentUri is inferred. For example, when calling textDocument/documentSymbol, the DocumentUri can be inferred as DocumentSymbolParams.textDocument.uri from the param. If the range and the selectionRange are Location types, then the DocumentUri is explicitly defined (Location.uri).

If we accept this structural change in the DocumentSymbol, we can defined our two new methods as textDocument/subTypes and textDocument/superTypes with DocumentSymbol[] return type.

We can even stick to the children name for the textDocument/superTypes; the supertypes will be real children of the DocumentSymbol node, only the way of the traversal differs.

CC: @tsmaeder (since you have taken over the PR)

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 See related #420 (comment).
Btw. the documentation for DocumentSymbol#selectionRange says

The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
	 * Must be contained by the `range`.

So with this contract it is enough if the range can alternatively be a Location.

Copy link
Contributor

@svenefftinge svenefftinge Oct 8, 2018

Choose a reason for hiding this comment

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

Actually, I would vote for adding an optional uri: URI property to DocumentSymbol, because the property name range suggests that the datatype is a Range, too. Also, it will not break existing clients.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kittaakos
Copy link

kittaakos commented Oct 11, 2018

I am done with a reference server implementation for Java, also with the client for Eclipse Theia. (The Java implementation of the extended LSP is here).

Type Hierarchy in action:

screencast 2018-10-11 15-18-32


Besides declaring the textDocument/subTypes and textDocument/superTypes methods, I had to add an optional URI (uri?: DocumentUri) property to the DocumentSymbol. No other changes were invovled.


Since I am not allowed to make any changes in this PR, how should we proceed to speed things up a bit? Is it OK if I create a separate PR with the proposed changes? (CC: @dbaeumer, @tsmaeder)

Update:
Here is the follow-up PR: #426

@dbaeumer
Copy link
Member

dbaeumer commented Nov 20, 2018

Sorry for the long delay on this. From the experiences I gained with the LSP so far I would opt to not overload any existing types. We are better off introducing new once even if they look similar or at least introduce a type alias. Otherwise it will be hard to evolve them separately. In this regard I would opt for the following:

  • have a type TypeHierarchyItem
  • it should be possible to lazy resolve a type hierarchy.

Given the above to I would end up with something like this:

interface TypeHierarchyItem {
	name: string;
	detail?: string;
	kind: SymbolKind;
	deprecated?: boolean;
	uri: string;
	range: Range;
	selectionRange: Range;

	parents?: TypeHierarchyItem[];
	children?: TypeHierarchyItem[];

	data?: any
}

A first request would return a TypeHierarchyItem for a given cursor location. The request would also allow to specify if the item should be resolved and whether sub or super types are to be resolved. Something like this:

interface TypeHierarchyParams extends TextDocumentPositionParams {
    resolve?: number; // the levels to resolve. 0 indicates no level.
    direction?: 'parents' | 'children' | 'both';
}

We also need another request to resolve an unresolved TypeHierarchyItem (which is indicated if parents or children is undefined. If resolved and no parents or children are available then an empty array is returned). The request would look the same as resolving a compeletion item with the same resolve and direction params as above. So something like this

interface ResolveTypeHierarchyItemParams {
    item: TypeHierarchyItem;
    resolve: number; // the levels to resolve. 0 indicates no level.
    direction: 'parents' | 'children' | 'both';
}

The optional data field can be used to identify a hierarchy item in a resolve request.

Does this make sense?

@jrieken any comments / objections on this design from a VS Code API perspective.

@dbaeumer
Copy link
Member

Closing in favour of #426

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants