Is it possible to get the symbol of a class that is defined in another language?
-
Sunday, April 15, 2012 1:56 PMLets say I have a class "A" that is defined in a CSharp project "ProjectA". Class "A" derives from a class "B" which is defined in another "ProjectB" (that is included as a project reference).
Within "ProjectA" it is possible to get the symbol of "B" like this:
ProjectA.GetCompilation().GetSemanticModel().GetSemanticInfo(syntaxNode_B).Symbol
When "ProjectB" is also a CSharp project the correct symbol is returned. But when "ProjectB" is a Visual Basic project the symbol is always null. The semantic info also contains a error type with the error info "The type or namespace name 'B' could not be found (are you missing a using directive or an assembly reference?)". Is there any way to get the symbol of B?
- Edited by Deguoren Sunday, April 15, 2012 1:58 PM
All Replies
-
Sunday, April 15, 2012 9:06 PMYou can't pass a C# syntax node to a VB semantic model, or vice versa. Having said that, you can create and pass a reference to the name of the class as a VB syntax node to a VB semantic model, and the same for C#.
-
Tuesday, April 17, 2012 11:23 PMOwner
Hi - Do ProjectA and ProjectB compile without errors in the example you tried above? Could it be that the base type B is not available / accessible to the derived type A for some reason?
I tried the following cross-language example based on what you posted above and the code seems to work fine (i.e. the symbol for base type B is being retrieved correctly in the context of the C# compilation for ProjectA).
using System; using System.Collections.Generic; using System.Linq; using Roslyn.Compilers; using Roslyn.Compilers.Common; using Roslyn.Services; class Program { static void Main() { var vbCode = @" Namespace VB Public Class B Public Sub M() End Sub End Class End Namespace"; var csCode = @" using VB; public class A : B { } class Program { static void Main() { B b = new A(); b.M(); } }"; var mscorlib = new AssemblyFileReference(typeof(object).Assembly.Location); var solutionId = SolutionId.CreateNewId(); ProjectId vbProjectId, csProjectId; DocumentId vbDocumentId, csDocumentId; var solution = Solution.Create(solutionId) .AddVisualBasicProject("VB", "VB", out vbProjectId) .AddDocument(vbProjectId, "VB", vbCode, out vbDocumentId) .AddMetadataReference(vbProjectId, mscorlib) .AddCSharpProject("CS", "CS", out csProjectId) .AddDocument(csProjectId, "CS", csCode, out csDocumentId) .AddMetadataReference(csProjectId, mscorlib) .AddProjectReference(csProjectId, vbProjectId); foreach (var project in solution.Projects) { var diagnostics = project.GetCompilation().GetDiagnostics(); if (diagnostics.Any()) { PrintDiagnostics(diagnostics); } } // Get symbol for 'B' and 'A' using Compilation.GetTypeByMetadataName(). var csCompilation = solution.GetProject(csProjectId).GetCompilation(); var baseSymbol = csCompilation.GetTypeByMetadataName("VB.B"); var derivedSymbol = csCompilation.GetTypeByMetadataName("A"); PrintSymbol("baseSymbol", baseSymbol); PrintSymbol("derivedSymbol", derivedSymbol); if (derivedSymbol != null && derivedSymbol.BaseType.Equals(baseSymbol)) { Console.WriteLine("Success"); } // Get symbol for 'B' using SemanticModel.GetSemanticInfo(). var csDocument = solution.GetDocument(csDocumentId); var csRoot = csDocument.GetSyntaxTree().Root; var syntaxNode_B = csRoot.DescendentNodes() .OfType<Roslyn.Compilers.CSharp.IdentifierNameSyntax>() .First(identifier => identifier.PlainName == "B"); var symbol_B = csDocument.GetSemanticModel().GetSemanticInfo(syntaxNode_B).Symbol; PrintSymbol("symbol_B", symbol_B); if (symbol_B != null && symbol_B.Equals(baseSymbol)) { Console.WriteLine("Success"); } } private static void PrintSymbol(string prefix, ISymbol symbol) { if (symbol == null) { Console.WriteLine(prefix + ": null"); } else { Console.WriteLine(prefix + ": " + symbol.ToDisplayString() + " (" + symbol.GetType().Name + ", " + symbol.Kind + ")"); } } private static void PrintDiagnostics(IEnumerable<IDiagnostic> diagnostics) { Console.WriteLine("Diagnostics:"); foreach (var diagnostic in diagnostics) { Console.WriteLine(diagnostic.ToString()); } } }
Shyam Namboodiripad | Software Development Engineer in Test | Roslyn Compilers Team
-
Saturday, April 21, 2012 9:44 AM
Hello Shyam,
thanks for your answer. You example code works fine.
But when I create a test solution in visual studio (instead of dynamic creation in code) with the same CS and VB code the result is different. I load the test solution (var solution = Solution.Load(...)) and get diagnostic errors like "The type or namespace VB could not be found", "The type or namespace B could not be found".
In visual studio the test solution can be compiled without problems. First I thought the problem might be the root namespace that a VB project can have. But even when I set it to empty I get diagnostic errrors in Roslyn.
I uploaded my test solution here: https://docs.google.com/file/d/0BwsU_8WH6w7nWEZSWDRGako1UkU/edit?pli=1
Any idea?
-
Tuesday, April 24, 2012 9:39 PMOwner
Ah this could be because of the issue described on this post. The CTP has a known bug where the Workspace API attempts to process each project in the solution using Configuration = Debug and Platform = AnyCPU by default. However, if the solution only contains an x86 project, that results in its references not being found. This bug is fixed in our internal builds (and should therefore be fixed for the next public release).
In the case of projects created through Visual Studio, Console Application projects have Platform = x86 by default while Class Library projects have Platform = AnyCPU by default. So when you load solutions like the one you described above using the Roslyn Workspace API, the references for the C# Console Application project (with Platform = x86) are probably not being found because of the above bug.
As Dustin has pointed out in the above post, it is possible to specify the platform explicitly at the time of loading (i.e. Solution.Load("<Path>", platform:"x86") to workaround this for some solutions. However, since the solution you referred to above contains multiple projects each with a different configuration I think the workaround would probably involve switching all the projects to use the same Platform (AnyCPU). Could you try this out and see if this fixes the issue? Hope this helps!
Shyam Namboodiripad | Software Development Engineer in Test | Roslyn Compilers Team
-
Thursday, April 26, 2012 5:56 AM
This solves part of the problem. If all projects have the same configuration (e.g. Debug|AnyCPU) the references were found in the CSharp project as long as the Visual Basic project is referenced as a file reference (this didnt work before).
But when the Visual Basic project is referenced as a project reference in visual studio it is still not working. As mentioned above, the problem does not occure when both projects are CSharp projects (even when referenced as project reference).
-
Monday, May 14, 2012 5:34 AM
Hello, could you reproduce the problem when using project references in visual studio? Is it a bug or do I something wrong?
-
Sunday, May 20, 2012 6:45 PM
Shyam, I'm seeing the same results as noted by Deguoren. I'm also explicitly setting the "Debug" and "x86" configuration when loading the workspace. I've found that type symbols cannot be resolved in the referenced assembly. If I remove the reference, and add back as a file reference, it works as expected. I've debugged and looked over various properties of the compilation in both cases, and I don't see any differences. In both cases the AssemblyFileReference is in the compilation. In fact, I can call compilation.GetTypeByMetadataName with the full type name and it returns the symbol. However, the semantic model doesn't seem to know about it. For instance, calling LookSymbols produces different results with the same assembly in Project vs. File reference.
-
Friday, May 25, 2012 12:58 AM
Hopefully this gets fixed in the next release. Until then there's a fairly simple workaround. Re-adding the references seems to do the trick.
For example:
var fileRefs = compilation.References.OfType<AssemblyFileReference>(); compilation = compilation.RemoveReferences(fileRefs) .AddReferences(fileRefs.Select(r => new AssemblyFileReference(r.Path)));

