Get Symbol for ReferenceLocation - c#

I'm using the SymbolFinder to find all references to a certain type in my solution like this:
ISymbol typeOfInterest = compilation.GetTypeByMetadataName(
"System.Reflection.PropertyInfo");
var references = SymbolFinder.FindReferencesAsync(typeOfInterest, solution).Result;
foreach (var reference in references)
{
// reference.Locations => symbol?
}
This part is working fine, the SymbolFinder returns correct ReferenceLocations (upon manual inspection). I'm actually interested in the symbols at these locations to get more (semantic) information about the references, so I can filter upon / work with it (e.g. only work on properties).
There seems to be very little public information on Roslyn yet and I couldn't find anything working with the results of SymbolFinder in the samples of the SDK Preview. So here is my question: Is it possible to get the symbol corresponding to a ReferenceLocation? How?

So, there isn't strictly a "symbol" at any of these locations, at least no innate concept of that. What you can do is take that Location, and find the enclosing symbol. You can take the location's SyntaxTree and get a Document. From there, call GetSemanticModelAsync, and then call ISemanticModel.GetEnclosingSymbol.
As an example, here's some (internal) code that does this for FAR itself: https://github.com/dotnet/roslyn/blob/748d6ab1b504ceee0c29f132fdcbe2a777aa88ea/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs#L67-L101

Related

In-memory CSharpCompilation cannot resolve attributes

I am trying to run C# source generators in-memory using the following code snippet:
var syntaxTree = await SyntaxTreeFromRelativeFile("testdata/IMyInterface.cs");
var compilation = CSharpCompilation.Create("compilation", ImmutableArray.Create(syntaxTree), References);
var generator = new ProxyGenerator();
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGenerators(compilation);
References is set to the necessary sources to compile the code:
public static readonly ImmutableArray<MetadataReference> References = ImmutableArray.Create<MetadataReference>(
// System
MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
MetadataReference.CreateFromFile(typeof(GCSettings).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location),
MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location),
// JetBrains
MetadataReference.CreateFromFile(typeof(UsedImplicitlyAttribute).Assembly.Location),
// some custom ones
);
While the generator runs just fine this way, it sadly doesn't have the desired affect, because the source generator relies on an attribute to know whether to generate source for the specified type. The exact source is something along the following:
[MyAttribute]
public interface IMyInterface { /* ... */ }
The source generator picks up the attribute correctly, but it gets resolved to an ExtendedErrorTypeSymbol with the result kind being NotAnAttributeType. However, the extended error type symbol also has a candidate symbol, which is the exact symbol I expect it to match.
This is surprising to me, because clearly the type is an attribute, and running the source generator as part of the normal compilation actually does generate all the right types. This seems to imply that there is something strange going on because of the in-memory nature of this run specifically.
As far as I can tell, my list of References covers everything that is needed to correctly realise that something is an attribute (mscorlib, System.Runtime, netstandard, and System.Core), though perhaps there is another MetadataReference missing?
I did find this GitHub issue which seems to describe a very similar, if not the same problem.
I'd love to know if I did something wrong here, if there are other references I am missing, or whether I am missing something completely else altogether.
By decompiling the source and stepping through how the attribute become an ExtendedErrorTypeSymbol, I found out that alongside the resultKind and candidate type, you could also find a DiagnosticBag. In this bag, the actual problem was shown:
error CS0012: The type 'Attribute' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
I was under the impression that my code added that correctly, but using this error I was luckily able to further my search, and ran into this Stackoverflow question. This seems to imply that for some reason (I am not 100% sure why), the following code will not actually add the right reference to System.Runtime:
MetadataReference.CreateFromFile(typeof(GCSettings).Assembly.Location)
Instead, I followed the example of the answer linked above, and changed the code to:
private static readonly string dotNetAssemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location);
public static readonly ImmutableArray<MetadataReference> References = ImmutableArray.Create<MetadataReference>(
// .NET assemblies are finicky and need to be loaded in a special way.
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "mscorlib.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Core.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Private.CoreLib.dll")),
MetadataReference.CreateFromFile(Path.Combine(dotNetAssemblyPath, "System.Runtime.dll")),
// more references, loaded as before
);
For some reason, this made all the difference. I had to also add the reference to System.Private.CoreLib.dll, but now that I knew where to find the diagnostics bag with additional information about what is actually wrong, this was an easy fix.

Can Roslyn recognize all tokens that correspond to a given variable?

I am trying to use Roslyn to determine information about objects being added to a collection. The general pattern of the code is that an IList is passed in to a method (which I am able to get the MethodDeclarationSytax for) and then a number of objects are added to that collection. I need to be able to tell where the Add method is being called on the passed in collection, which is always the only parameter passed to the method.
I have only been able to figure out how to do this on string matching to tokens rather than being able to directly recognize that a given token is the same variable based on scope.
How can I get information out of Roslyn that will indicate that the tokens are, in fact, actually references to the same variable based on scope? Is there an option for this in the semantic view? Is string comparison and manual scope checking the only option available to me?
public void AddColumns(IList<Column> columns)
{
Column newColumn = new Column("something");
columns.Add(newColumn);
newColumn = new Column("somethingElse");
columns.Add(newColumn);
Column anotherNewColumn = new Column("something else");
anotherNewColumn.MakeSomeChanges("123");
columns.Add(anotherNewColumn );
}
In this code for example, I have the MethodDeclarationSyntax for AddColumns. I need to be able to identify lines 4, 7 and 11 as being places where the Add is happening and, preferably, will further need to determine where newColumn and anotherNewColumn come from (lines 3, 6 and 9).
I can fall back to string based checking if I have to, but I'm worried about missing cases that don't match what I expect since there are also examples where methods are used in adding columns, so it will get really complicated really fast.
You need to use the SemanticModel API that you can get from a Compilation object for a specific SyntaxTree that is part of the compilation. With the SemanticModel you can get the symbols referenced at specific points in the source, by calling GetSymbolInfo for the expression for the parameter name (probably an IdentifierNameSyntax).

Find all method calls for a specific method using Roslyn

I am working on a code analyser using Roslyn and my current task is to find all internal methods which are unused in the assembly.
I start with a MethodDeclarationSyntax and get the symbol from that. I then use the FindCallersAsync method in SymbolFinder, but it returns an empty collection even when I am making a call to the method in question somewhere in the assembly. See the code below.
protected override void Analyze(SyntaxNodeAnalysisContext context)
{
NodeToAnalyze = context.Node;
var methodDeclaration = NodeToAnalyze as MethodDeclarationSyntax;
if (methodDeclaration == null)
return;
var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodDeclaration) as ISymbol;
if (methodSymbol.DeclaredAccessibility != Accessibility.Internal)
return;
var solutionPath = GetSolutionPath();
var msWorkspace = MSBuildWorkspace.Create();
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
var callers = SymbolFinder.FindCallersAsync(symbol, solution).Result; // Returns empty collection.
...
}
I have seen similar code here, but in that example the method symbol is obtained using GetSymbolInfo on an InvocationExpressionSyntax:
//Get the syntax node for the first invocation to M()
var methodInvocation = doc.GetSyntaxRootAsync().Result.DescendantNodes().OfType<InvocationExpressionSyntax>().First();
var methodSymbol = model.GetSymbolInfo(methodInvocation).Symbol;
//Finds all references to M()
var referencesToM = SymbolFinder.FindReferencesAsync(methodSymbol, doc.Project.Solution).Result;
However, in my case, I need to find the invocations (if any) from a declaration. If I do get the invocation first and pass in the symbol from GetSymbolInfo the calls to the method are returned correctly - so the issue seems to be with the symbol parameter and not solution.
Since I am trying to get the underlying symbol of a declaration, I cannot use GetSymbolInfo, but use GetDeclaredSymbol instead (as suggested here).
My understanding from this article is that the symbols returned from GetDeclaredSymbol and GetSymbolInfo should be the same. However, a simple comparison using Equals returns false.
Does anyone have any idea of what the difference is between the two symbols returned and how I can get the 'correct' one which works? Or perhaps there is a better approach entirely? All my research seems to point to FindCallersAsync, but I just can't get it to work.
My understanding from this article is that the symbols returned from GetDeclaredSymbol and GetSymbolInfo should be the same. However, a simple comparison using Equals returns false.
This is because they're not the same symbol; they are coming from entirely different compilations which might or might not be different. One is coming from the compiler that is actively compiling, one is coming from MSBuildWorkspace.
Fundamentally, using MSBuildWorkspace in an analyzer is unsupported. Completely. Don't do that. Not only would that be really slow, but it also has various correctness issues, especially if you're running your analyzer in Visual Studio. If your goal is to find unused methods anywhere in a solution, that's something we don't really support implementing as an analyzer either, since that involves cross-project analysis.

Using roslyn for hover over data for source tree symbols

QUESTION:
How do I apply my personal DocumentationProvider to source tree symbols? Which is the type of symbol i get when i use the SymbolFinder.FindSymbolAtPosition()
Specifically I want to override the GetDocumentationForSymbol() function. I have it overridden for my autocomplete symbols but not the symbols i get from hover over.
BACKGROUND:
Hi, I am using roslyn to gather intellisense for a text editor i am creating. One of the things i need to make is quick info or tool tips. I have it working for the autocomplete suggestions. by using a snippet that looks like this
compilation = CSharpCompilation.Create(
"MyIntellisense",
new[] { CSharpSyntaxTree.ParseText(dotNetCode) },
assemblies
.Select(i => MetadataReference
.CreateFromFile(i.Location, MetadataReferenceProperties.Assembly,
new DotNetDocumentationProvider(
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
this uses my own personal DotNetDocumentationProvider which parses XML and documentation the way I need it. This works for assembly symbols which are the types of symbols I have when i use Recommender.GetRecommendedSymbolsAtPosition().
EDIT: Just wanted to give more background :)
I get symbols in two different ways.
1) One way is when I call
var symbols = Recommender.GetRecommendedSymbolsAtPosition(semanticModel, offset, solution.Workspace);
I use this when the user asks for auto-complete information
With these symbols I can go through and for each one call:
var information = symbol.GetDocumentationCommentXml();
This eventually calls a function I have overridden from the class DocumentationProvider :
protected override string GetDocumentationForSymbol(string documentationMemberID, CultureInfo preferredCulture, CancellationToken cancellationToken = default(CancellationToken))
2) The second way is for when the user hovers over
var symbol = SymbolFinder.FindSymbolAtPosition(semanticModel, offset, workspace, cancellationToken);
I call the exact same function (from the same line of code actually, keeping it
DRY)
var information = symbol.GetDocumentationCommentXml();
But this does not invoke my overridden GetDocumentationCommentXml() instead the default Roslyn one is called.
Thanks!
Not finding all of the symbols I need,How to find more symbols using the Roslyn API
Another question I asked, when I solved this issue it then solved the problem i was having here.
The problem was that i thought
_workspace.CurrentSolution.AddMetadataReferences(_currentProject.Id,_compilation.References);
updated the workspace i was working in. but it does not it returns a solution with the references added. i needed to use
_workspace.TryApplyChanges(referenceSolution);
to save it.
Thanks to Jason for answering my other question found at the link. If you post here i will mark it as an answer.

Obtain case label constant in Roslyn

I'm trying to gather the switch section label constants from a SwitchStatement with Roslyn. But while I can see in the Syntax Visualizer that the CaseSwitchLabelSyntax has a Value property with the corresponding constant and the declared symbol (SourceLabelSymbol) has a SwitchCaseLabelConstant property, I cannot seem to get that information from what I have in my code.
// SwitchStatementSyntax node;
// SemanticModel model;
foreach (var section in node.Sections) {
foreach (var label in section.Labels) {
var labelSymbol = model.GetDeclaredSymbol(label);
// Here I'm stuck
}
}
I could probably look whether the SwitchLabelSyntax is a CaseSwitchLabelSyntax or a DefaultSwitchLabelSyntax and cast accordingly. SourceLabelSymbol is actually internal, so I cannot access its properties. model.GetConstantValue(label) returns null.
But given that Roslyn always hands out interfaces I believe that there's a reason for that and wildly casting around feels a bit hacky to me. Is there a better option?
Note: I'm doing this to translate C# syntax into another language. Technically, first into a separate AST that is then converted to text again. Above code is from within a CSharpSyntaxWalker and I could probably just store my partially converted switch statement away, continue visiting its descendants and build it up piecewise.
But that means having more state, building statements in half a dozen distinct locations which leads to hard-to-read and -follow code. I'd rather avoid it here, if possible.
Closest from API is semanticModel.GetConstantValue method, but still you need to pass Value node to it like this:
section.Labels
.OfType<CaseSwitchLabelSyntax>()
.Select(l => semanticModel.GetConstantValue(l.Value))
.ToArray()
As you can see filtering out CaseSwitchLabelSyntax is required anyway.

Categories