Skip to content

More robust generic parsing #3

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

Merged
merged 1 commit into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CSharpCodeAnalyst/board.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
IMPROVEMENTS
---------------------

- SybolEqualityComparer does not work for this application well. Remove it where it is used.
- Check if the map key -> Symbol is unique. If not use a list of symbols!
_elementIdToSymbolMap. Are all locations captured in this case?


- Highlight of aggregated edges takes too long. If quick help not visible skip?
- Performance in general

- Attributes are caught at class or method level. Not for the parameters like [CallerMemberName]
- Main method is required


BUGS
---------------------
Expand Down
6 changes: 4 additions & 2 deletions CodeParser/Parser/Parser.Phase1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public partial class Parser
{
private readonly List<INamedTypeSymbol> _allNamedTypesInSolution = new();

private readonly Dictionary<IAssemblySymbol, List<GlobalStatementSyntax>> _globalStatementsByAssembly = new(SymbolEqualityComparer.Default);
private readonly Dictionary<IAssemblySymbol, List<GlobalStatementSyntax>> _globalStatementsByAssembly =
new(SymbolEqualityComparer.Default);


private async Task BuildHierarchy(Solution solution)
Expand All @@ -30,6 +31,7 @@ private async Task BuildHierarchy(Solution solution)

// Build also a list of all named types in the solution
// We need this in phase 2 to resolve dependencies
// Constructed types are not contained in this list!
var types = compilation.GetSymbolsWithName(_ => true, SymbolFilter.Type).OfType<INamedTypeSymbol>();
_allNamedTypesInSolution.AddRange(types);

Expand Down Expand Up @@ -155,7 +157,7 @@ private void ProcessNodeForHierarchy(SyntaxNode node, SemanticModel semanticMode
var assemblySymbol = semanticModel.Compilation.Assembly;
_globalStatementsByAssembly[assemblySymbol].Add(globalStatementSyntax);
return; // We'll handle these collectively later

// Add more cases as needed (e.g., for events, delegates, etc.)
}

Expand Down
15 changes: 4 additions & 11 deletions CodeParser/Parser/Parser.Phase2.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement,
{
if (propertyDeclaration.ExpressionBody != null)
{
AnalyzeMethodBody(propertyElement, propertyDeclaration.ExpressionBody.Expression, semanticModel);
AnalyzeMethodBody(propertyElement, propertyDeclaration.ExpressionBody.Expression,
semanticModel);
}
else if (propertyDeclaration.AccessorList != null)
{
Expand All @@ -73,19 +74,11 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement,
}
}
}

private void AddPropertyDependency(CodeElement sourceElement, IPropertySymbol propertySymbol,
DependencyType dependencyType, List<SourceLocation> locations)
{
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol), out var targetElement))
{
AddDependency(sourceElement, dependencyType, targetElement, locations);
}
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol.ContainingType),
out var containingTypeElement))
{
// If we don't have the property itself in our map, add a dependency to its containing type
AddDependency(sourceElement, dependencyType, containingTypeElement, locations);
}
AddDependencyWithFallbackToContainingType(sourceElement, propertySymbol, dependencyType, locations);
}

private IPropertySymbol? GetImplementedInterfaceProperty(IPropertySymbol propertySymbol)
Expand Down
167 changes: 85 additions & 82 deletions CodeParser/Parser/Parser.Phase2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ private void AnalyzeDependencies(Solution solution)
var loop = 0;
foreach (var element in _codeGraph.Nodes.Values)
{
// TODO atr Analyze if we can have more than one symbol!
var symbol = _elementIdToSymbolMap[element.Id];

if (symbol is IEventSymbol eventSymbol)
Expand Down Expand Up @@ -75,13 +76,13 @@ private void AnalyzeGlobalStatementsForAssembly(Solution solution)
}

// Find the existing assembly element
var symbolKey = GetSymbolKey(assemblySymbol);
var symbolKey = assemblySymbol.Key();
var assemblyElement = _symbolKeyToElementMap[symbolKey];

// Create a dummy class for this assembly's global statements
var dummyClassId = Guid.NewGuid().ToString();
var dummyClassName = "GlobalStatements";
var dummyClassFullName = BuildSymbolName(assemblySymbol) + "." + dummyClassName;
var dummyClassFullName = assemblySymbol.BuildSymbolName() + "." + dummyClassName;
var dummyClass = new CodeElement(dummyClassId, CodeElementType.Class, dummyClassName, dummyClassFullName,
assemblyElement);
_codeGraph.Nodes[dummyClassId] = dummyClass;
Expand All @@ -108,6 +109,7 @@ private void AnalyzeGlobalStatementsForAssembly(Solution solution)
}
}
}

private void AnalyzeAttributeDependencies(CodeElement element, ISymbol symbol)
{
foreach (var attributeData in symbol.GetAttributes())
Expand Down Expand Up @@ -238,7 +240,7 @@ private void FindImplementations(CodeElement methodElement, IMethodSymbol method
var implementingMethod = implementingType.FindImplementationForInterfaceMember(methodSymbol);
if (implementingMethod != null)
{
var implementingElement = _symbolKeyToElementMap.GetValueOrDefault(GetSymbolKey(implementingMethod));
var implementingElement = _symbolKeyToElementMap.GetValueOrDefault(implementingMethod.Key());
if (implementingElement != null)
{
// Note: Implementations for external methods are not in our map
Expand Down Expand Up @@ -283,20 +285,10 @@ private bool IsTypeDerivedFrom(INamedTypeSymbol type, INamedTypeSymbol baseType)
private void AddMethodOverrideDependency(CodeElement sourceElement, IMethodSymbol methodSymbol,
List<SourceLocation> locations)
{
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol), out var targetElement))
{
AddDependency(sourceElement, DependencyType.Overrides, targetElement, locations);
}
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol.ContainingType),
out var containingTypeElement))
{
// Trace.WriteLine("Method override not captured. It is likely that the base method is generic or external code.");

// If we don't have the method itself in our map, add a dependency to its containing type
// Maybe we override a framework method. Happens also if the base method is a generic one.
// In this case the GetSymbolKey is different. One uses T, the overriding method uses the actual type.
AddDependency(sourceElement, DependencyType.Overrides, containingTypeElement, locations);
}
// If we don't have the method itself in our map, add a dependency to its containing type
// Maybe we override a framework method. Happens also if the base method is a generic one.
// In this case the GetSymbolKey is different. One uses T, the overriding method uses the actual type.
AddDependencyWithFallbackToContainingType(sourceElement, methodSymbol, DependencyType.Overrides, locations);
}

private void AnalyzeFieldDependencies(CodeElement fieldElement, IFieldSymbol fieldSymbol)
Expand Down Expand Up @@ -378,20 +370,13 @@ private void AnalyzeMethodBody(CodeElement sourceElement, SyntaxNode node, Seman

private void AddEventUsageDependency(CodeElement sourceElement, IEventSymbol eventSymbol)
{
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(eventSymbol), out var eventElement))
{
AddDependency(sourceElement, DependencyType.Uses, eventElement, []);
}
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(eventSymbol.ContainingType),
out var containingTypeElement))
{
// If we don't have the event itself in our map, add a dependency to its containing type
AddDependency(sourceElement, DependencyType.Uses, containingTypeElement, []);
}
// If we don't have the event itself in our map, add a dependency to its containing type
AddDependencyWithFallbackToContainingType(sourceElement, eventSymbol, DependencyType.Uses, []);
}

private void AddCallsDependency(CodeElement sourceElement, IMethodSymbol methodSymbol, SourceLocation location)
{
//Debug.Assert(FindCodeElement(methodSymbol)!= null);
//Trace.WriteLine($"Adding call dependency: {sourceElement.Name} -> {methodSymbol.Name}");

if (methodSymbol.IsExtensionMethod)
Expand All @@ -400,16 +385,13 @@ private void AddCallsDependency(CodeElement sourceElement, IMethodSymbol methodS
methodSymbol = methodSymbol.ReducedFrom ?? methodSymbol;
}

if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol), out var targetElement))
if (methodSymbol.IsGenericMethod && FindCodeElement(methodSymbol) is null)
{
AddDependency(sourceElement, DependencyType.Calls, targetElement, [location]);
methodSymbol = methodSymbol.OriginalDefinition;
}

// If the method is not in our map, we might want to add a dependency to its containing type
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol.ContainingType),
out var containingTypeElement))
{
AddDependency(sourceElement, DependencyType.Calls, containingTypeElement, [location]);
}
AddDependencyWithFallbackToContainingType(sourceElement, methodSymbol, DependencyType.Calls, [location]);
}


Expand Down Expand Up @@ -442,38 +424,8 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol
break;

case INamedTypeSymbol namedTypeSymbol:
var symbolKey = GetSymbolKey(namedTypeSymbol);
if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var targetElement))
{
// The type is internal (part of our codebase)
AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []);

if (namedTypeSymbol.IsGenericType)
{
// Add "Uses" dependencies to type arguments
foreach (var typeArg in namedTypeSymbol.TypeArguments)
{
AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location);
}
}
}
else
{
// The type is external

// Optionally, you might want to track external dependencies
// AddExternalDependency(sourceElement, namedTypeSymbol, dependencyType, location);
if (namedTypeSymbol.IsGenericType)
{
// For example List<MyType>
// Add "Uses" dependencies to type arguments, which might be internal
foreach (var typeArg in namedTypeSymbol.TypeArguments)
{
AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location);
}
}
}

AddNamedTypeDependency(sourceElement, namedTypeSymbol, dependencyType, location);
break;

case IPointerTypeSymbol pointerTypeSymbol:
Expand All @@ -491,8 +443,8 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol
break;
default:
// Handle other type symbols (e.g., type parameters)
symbolKey = GetSymbolKey(typeSymbol);
if (_symbolKeyToElementMap.TryGetValue(symbolKey, out targetElement))
var symbolKey = typeSymbol.Key();
if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var targetElement))
{
AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []);
}
Expand All @@ -501,6 +453,44 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol
}
}

private void AddNamedTypeDependency(CodeElement sourceElement, INamedTypeSymbol namedTypeSymbol,
DependencyType dependencyType,
SourceLocation? location)
{
var targetElement = FindCodeElement(namedTypeSymbol);
if (targetElement != null)
{
// The type is internal (part of our codebase)
AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []);
}
else
{
// The type is external or a constructed generic type
// Note the constructed type is not in our CodeElement map!
// It is not found in phase1 the way we parse it but the original definition is.
var originalDefinition = namedTypeSymbol.OriginalDefinition;
var originalSymbolKey = originalDefinition.Key();

if (_symbolKeyToElementMap.TryGetValue(originalSymbolKey, out var originalTargetElement))
{
// We found the original definition, add dependency to it
AddDependency(sourceElement, dependencyType, originalTargetElement, location != null ? [location] : []);
}
// The type is truly external, you might want to log this or handle it differently
// AddExternalDependency(sourceElement, namedTypeSymbol, dependencyType, location);
}

if (namedTypeSymbol.IsGenericType)
{
// Add "Uses" dependencies to type arguments
foreach (var typeArg in namedTypeSymbol.TypeArguments)
{
AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location);
}
}
}


private void AnalyzeIdentifier(CodeElement sourceElement, IdentifierNameSyntax identifierSyntax,
SemanticModel semanticModel)
{
Expand All @@ -517,7 +507,7 @@ private void AnalyzeIdentifier(CodeElement sourceElement, IdentifierNameSyntax i

if (symbolInfo.Symbol is IFieldSymbol fieldSymbol)
{
AddFieldDependency(sourceElement, fieldSymbol, DependencyType.Uses);
AddDependencyWithFallbackToContainingType(sourceElement, fieldSymbol, DependencyType.Uses);
}
}

Expand All @@ -533,7 +523,7 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi
}
else if (symbolInfo.Symbol is IFieldSymbol fieldSymbol)
{
AddFieldDependency(sourceElement, fieldSymbol, DependencyType.Uses);
AddDependencyWithFallbackToContainingType(sourceElement, fieldSymbol, DependencyType.Uses);
}
}

Expand All @@ -543,27 +533,40 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi
private void AddPropertyCallDependency(CodeElement sourceElement, IPropertySymbol propertySymbol,
List<SourceLocation> locations)
{
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol), out var targetElement))
AddDependencyWithFallbackToContainingType(sourceElement, propertySymbol, DependencyType.Calls, locations);
}

private void AddDependencyWithFallbackToContainingType(CodeElement sourceElement, ISymbol symbol,
DependencyType dependencyType, List<SourceLocation>? locations = null)
{
// If we don't have the property itself in our map, add a dependency to its containing type
if (locations == null)
{
locations = [];
}

var targetElement = FindCodeElement(symbol);
if (targetElement != null)
{
AddDependency(sourceElement, DependencyType.Calls, targetElement, locations);
AddDependency(sourceElement, dependencyType, targetElement, locations);
return;
}
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol.ContainingType),
out var containingTypeElement))

var containingTypeElement = FindCodeElement(symbol.ContainingType);
if (containingTypeElement != null)
{
AddDependency(sourceElement, DependencyType.Calls, containingTypeElement, locations);
AddDependency(sourceElement, dependencyType, containingTypeElement, locations);
}
}

private void AddFieldDependency(CodeElement sourceElement, IFieldSymbol fieldSymbol, DependencyType dependencyType)
private CodeElement? FindCodeElement(ISymbol? symbol)
{
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(fieldSymbol), out var targetElement))
if (symbol is null)
{
AddDependency(sourceElement, dependencyType, targetElement, []);
}
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(fieldSymbol.ContainingType),
out var containingTypeElement))
{
AddDependency(sourceElement, dependencyType, containingTypeElement, []);
return null;
}

_symbolKeyToElementMap.TryGetValue(symbol.Key(), out var element);
return element;
}
}
Loading
Loading