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

Add public API to retrieve metadata token from ISymbol #53486

Closed
tmat opened this issue May 18, 2021 · 15 comments · Fixed by #55913
Closed

Add public API to retrieve metadata token from ISymbol #53486

tmat opened this issue May 18, 2021 · 15 comments · Fixed by #55913
Labels
api-approved API was approved in API review, it can be implemented Area-Compilers Concept-API This issue involves adding, removing, clarification, or modification of an API. Feature Request

Comments

@tmat
Copy link
Member

tmat commented May 18, 2021

Scenario

Implement improvements in Go To Definition when navigating to a metadata symbol (see #24349).

When the user invokes GTD on a metadata symbol we plan to identify the source file where the symbol is defined based on information stored in the PDB (combination of MethodDebugInformation, CustomDebugInformation and Document PDB tables).
Using debugger provided service we can first retrieve the PDB, find the source info in the PDB tables, then use Source Link (assuming it's present in the PDB), download the source file(s) that define the symbol from the source server, open these files in the editor and finally navigate to the requested symbol.

Problem

Currently ISymbol that represents a metadata symbol does not expose its metadata token. It is possible to find the token within the metadata using MetadataReader APIs but it would involve reimplementing the metadata signature decoder logic that's already implemented in the compiler. Instead we propose to expose the token from ISymbol directly.

Proposal

interface ISymbol
{
   ...
   /// <summary>
   /// If this symbol represents a metadata entity returns its metadata token, otherwise returns 0.
   /// </summary>
   int MetadataToken { get; }
   ...
}

The API would return 0 in all cases when the symbol is not backed by a metadata entity (internally implemented as a PE symbol).

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels May 18, 2021
@jcouv jcouv added the Concept-API This issue involves adding, removing, clarification, or modification of an API. label May 18, 2021
@jaredpar
Copy link
Member

Is zero every a valid value for a metadata token?

@jaredpar jaredpar added Feature Request and removed untriaged Issues and PRs which have not yet been triaged by a lead labels May 18, 2021
@CyrusNajmabadi
Copy link
Member

Would it be better to add this to Location? Location​ already has IsInSource and IsInMetadata. And if it's from source, exposes the SyntaxTree/Span. For metadata it exposes the MetadataModule so it feels like a natural fit that it could also expose the MetadataToken.

Then, you'd just get this from ISymbol.Locations in a consistent fashion. Thoughts?

@tmat
Copy link
Member Author

tmat commented May 18, 2021

We could. However we currently reuse a single ImmutableArray<Location> for all symbols from given PE module. We would need to allocate a new location (and an array) for each metadata symbol.

@tmat
Copy link
Member Author

tmat commented May 18, 2021

Is zero every a valid value for a metadata token?

It's not.

@333fred
Copy link
Member

333fred commented May 18, 2021

@AlekseyTs as API owner.

@tmat
Copy link
Member Author

tmat commented Jun 9, 2021

@AlekseyTs Does the proposed design look good, or should we access the token thru Location API with the downside that we would allocate a lot of Location objects?

@AlekseyTs
Copy link
Contributor

@tmat, it looks like internally we don't have a token as an int, but rather have an instance of opaque System.Reflection.Metadata.MethodDefinitionHandle. What would be the way for us to get the underlying value? Shold we instead expose an instance of System.Reflection.Metadata.Handle?

I agree that adding the information to Location might introduce undesired allocations.

@siegfriedpammer
Copy link
Contributor

What would be the way for us to get the underlying value? Shold we instead expose an instance of System.Reflection.Metadata.Handle?

Just my 50c. As a user of both Roslyn and SRM, I would be perfectly fine with having access to the strongly typed Handle. The literal value can be easily obtained by calling System.Reflection.Metadata.Ecma335.MetadataTokens.GetToken() and other extensions in that class. If you are interested in accessing raw metadata you probably have knowledge of the internal structure of the metadata tables and the SRM API anyway, so this should be no problem.

I am really looking forward to this!

@tmat
Copy link
Member Author

tmat commented Jun 24, 2021

What would be the way for us to get the underlying value? Shold we instead expose an instance of System.Reflection.Metadata.Handle

I wouldn't expose dependency on SRM in public API. The underlying token can be retrieved via MetadataTokens.GetToken(Handle), as @siegfriedpammer pointed out.

Returning int would also be consistent with Reflection API (MemberInfo.MetadataToken) which also returns int.

@333fred
Copy link
Member

333fred commented Jun 24, 2021

I wouldn't expose dependency on SRM in public API.

We already have such dependencies in our public API, such as around calling conventions and one older API that I can't remember off the top of my head. So I don't think there's any issue in another similar API. That said, if we think an int is better for other reasons, that's fine.

@tmat
Copy link
Member Author

tmat commented Jun 24, 2021

@333fred Oh, interesting I did not know that. If that is so then returning EntityHandle would be fine.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jun 25, 2021

I think we expose System.Reflection types from public APIs, but I am not sure if we actually expose any System.Reflection.Metadata types from public APIs today. Keeping that sounds compelling to me.

@AlekseyTs AlekseyTs added the api-ready-for-review API is ready for review, it is NOT ready for implementation label Jun 25, 2021
@333fred
Copy link
Member

333fred commented Jun 25, 2021

but I am not sure if we actually expose any System.Reflection.Metadata types from public APIs today

https://sourceroslyn.io/#Microsoft.CodeAnalysis/Symbols/IMethodSymbol.cs,220 is from SRM.

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jun 25, 2021

You are correct there is a precedent. However, that is an enum, effectively an int. Doesn't really say how we read metadata.

@333fred
Copy link
Member

333fred commented Jun 30, 2021

API Review

API Approved

  • Adding to Location, while somewhat tempting, is concerning from a perf perspective. Metadata symbols can share Location arrays for all symbols from the same metadata symbol, this would prevent us from doing that and potentially incur a large number of allocations.
  • While we do have precedent for exposing System.Runtime.Metadata types from our APIs (IModuleSymbol.GetMetadata().GetMetadataReader()), an int is a good abstraction here that is aligned with ECMA 335 and can be used by both System.Reflection and System.Reflection.Emit. We'd also want to hide the handle property on derived types to expose ParameterHandle/PropertyHandle/etc strongly-typed properties, which can be painful.

@333fred 333fred added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jun 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented Area-Compilers Concept-API This issue involves adding, removing, clarification, or modification of an API. Feature Request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants