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

[Proposal] GetAllTypesByMetadataName #57802

Closed
333fred opened this issue Nov 16, 2021 · 8 comments · Fixed by #58366
Closed

[Proposal] GetAllTypesByMetadataName #57802

333fred opened this issue Nov 16, 2021 · 8 comments · Fixed by #58366
Assignees
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
Milestone

Comments

@333fred
Copy link
Member

333fred commented Nov 16, 2021

Background and Motivation

We've had a number of requests over the years for a GetTypeByMetadataName that works differently. The IDE has a GetBestTypeByMetadataName extension method that works well for their purposes, and #52399 requested making it a public API on Compilation itself. However, this API is just a different heuristic for picking the "best" among all potential types exposed for a given metadata name in a compilation; it's one that works well for the IDE, but it's not a one-size-fits all algorithm. Rather than making another heuristic API and trying to determine if we made it extensible enough for any given use case, we instead propose an API that will get all types that match a given metadata name in a Compilation and all referenced assemblies: the caller can than look through those instances and decide which one it would like to use.

Proposed API

namespace Microsoft.CodeAnalysis
{
    public class Compilation
    {
+        public ImmutableArray<INamedTypeSymbol> GetTypesByMetadataName(string fullyQualifiedMetadataName);
    }
}

Usage Examples

// Fetching `System.Runtime.CompilerServices.IsExternalInit` from the current project, if it exists
var isExternalInit = compilation.GetTypesByMetadataName("System.Runtime.CompilerServices.IsExternalInit").FirstOrDefault();
if (ReferenceEquals(isExternalInit?.ContainingAssembly, compilation.Assembly) { ... }
// Reimplementing the IDE's GetBestTypeByMetadataName on top of this API
public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
{
    INamedTypeSymbol type = null;

    foreach (var currentType in compilation.GetTypesByMetadataName(fullyQualifiedMetadataName));
    {
        switch (currentType.GetResultantVisibility())
        {
            case Utilities.SymbolVisibility.Public:
            case Utilities.SymbolVisibility.Internal when currentType.ContainingAssembly.GivesAccessTo(compilation.Assembly):
                break;

            default:
                continue;
        }

        if (ReferenceEquals(currentType.ContainingAssembly, compilation.ContainingAssembly))
        {
            return currentType;
        }

        if (type is not null)
        {
            // Multiple visible types with the same metadata name are present
            return null;
        }

        type = currentType;
    }

    return type;
}

Alternative Designs

I've opted to add a few extensions to try address what I view as the common use case for this API, but it's possible we don't really see those extensions being useful/needed, so we can choose to omit them if needed.

We could soften our "no heuristics" stance slightly by adding a default bool parameter includeInaccessibleTypes that would control whether the returned enumerable includes types that the match the requested FQN, but are not visible to the current Compilation (because they are internal without IVT/private). This could be a common enough heuristic that it would make sense to include in the API, but there's definitely a slippery slope of how far down this rabbit hole do we want to go.

Risks

Any new API adds risks, of course, but I believe the approach here leads to less risk for both the compiler and consumers, and the heuristics expected are clearly defined by the consumer's code.

@333fred 333fred added Concept-API This issue involves adding, removing, clarification, or modification of an API. Area-Compilers Feature Request api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Nov 16, 2021
@333fred 333fred self-assigned this Nov 16, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged Issues and PRs which have not yet been triaged by a lead label Nov 16, 2021
@333fred
Copy link
Member Author

333fred commented Nov 16, 2021

@AlekseyTs would you take a look at this API and let me know your thoughts? I've marked it needs-work for now until we agree that it's ready to go to full design review.

@333fred 333fred added this to the 17.1 milestone Nov 16, 2021
@jaredpar jaredpar removed the untriaged Issues and PRs which have not yet been triaged by a lead label Nov 16, 2021
@AlekseyTs
Copy link
Contributor

would you take a look at this API and let me know your thoughts?

The proposal looks good to me. I don't particularly like the name of the structure, but we can discuss that during review. Also, I would use "GetTypesByMetadataName" as a name for the API.

@333fred
Copy link
Member Author

333fred commented Nov 17, 2021

Also, I would use "GetTypesByMetadataName" as a name for the API.

FWIW, I did consider this, but I felt it was too similar to GetTypeByMetadataName. Definitely something we can discuss during API review though, we'll try to get to it tomorrow.

@333fred 333fred added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Nov 17, 2021
@CyrusNajmabadi
Copy link
Member

Definitely something we can discuss during API review though, we'll try to get to it tomorrow.

FWIW< that doesn't bother me. You'll get very distinct types back, so it should be readily apparent if you're doing something wrong. I would not like it if the signatures were the same (including return type) and tehre was a one letter difference like this :)

@CyrusNajmabadi
Copy link
Member

I'd call MetadataNameEnumerable TypesWithMetadataName instead. I do appreciate the idea that you can use this efficiently without alloc.

@333fred 333fred added api-needs-work API needs work before it is approved, it is NOT ready for implementation and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Nov 18, 2021
@333fred
Copy link
Member Author

333fred commented Nov 18, 2021

API Review

We had a lengthy discussion about our philosophies in this area today. We generally think an API shaped similar to this is a good primitive to have for building heuristics, and there is some concern about adding heuristics to the compiler layer itself. We do, however, need to consider how we want to help users with common heuristics, like the equivalent of the compiler's GetWellKnownType internal API. Now that we've agreed on this API concept, we can look at specifics of the API itself in the next design meeting. Some initial comments:

  • GetTypesByMetadataName seems to be the preferred naming
  • MetadataNameEnumerable implies that we are enumerating metadata names. Perhaps TypesByMetadataNameEnumerable would be a better name.
  • We might consider adding a filter delegate to the enumerable, but we will have to carefully consider how that works with arguments and potentially avoiding performance issues. It might be best to simply let perf scenarios filter with a foreach, and non-perf scenarios use LINQ.

@333fred
Copy link
Member Author

333fred commented Dec 16, 2021

I've updated the proposal here: in particular, I've removed the custom lazy enumerable MetadataNameEnumerable. This is because we know that GetTypeByMetadataName can be hit repeatedly in the same Compilation for the same type by analyzers today. If we make this method return a lazy enumerable, attempting to perform the same caching strategy is going to result in a very complex implementation that I don't think is worth it to maintain. We have no current indications that the performance of GetTypeByMetadataName is a significant problem, and since that API is already effectively doing the same implementation (it needs to search all referenced assemblies to determine if there are duplicates, even after the first match is found) I don't think the extra laziness complexity is worth any effort here.

Similarly, while we could add filtering overloads, we can't effectively cache their results, and thus the filtering methods would have to do one of two things:

  1. Rerun the search with the filter. This doesn't really speed anything up, we have to look through all the referenced assemblies.
  2. Call the unfiltered method to get the full results, and then filter the full results. The user can just do this on the other end.

@333fred 333fred added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-needs-work API needs work before it is approved, it is NOT ready for implementation labels Dec 16, 2021
333fred added a commit to 333fred/roslyn that referenced this issue Dec 16, 2021
Closes dotnet#57802. Implements the GetTypesByMetadataName API, which returns all named types in the current compilation and referenced assemblies that match the given CLR metatdata name. Also updates the IDE's GetBestTypeByMetadataName to use this API under the hood, rather than the existing manual walk they were doing, simplyfing that codepath and proving out the API itself.
@333fred
Copy link
Member Author

333fred commented Jan 6, 2022

API Review

API is approved. We should additionally update the docs on GetTypeByMetadataName to clarify the ambiguity scenarios, and look into an analyzer to warn when the result of GetTypeByMetadataName is unconditionally accessed.

@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 Jan 6, 2022
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.

4 participants