Skip to content

Commit 5652a43

Browse files
authored
More robust generic parsing (#3)
1 parent 11fdf86 commit 5652a43

File tree

10 files changed

+431
-225
lines changed

10 files changed

+431
-225
lines changed

CSharpCodeAnalyst/board.txt

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
IMPROVEMENTS
22
---------------------
33

4+
- SybolEqualityComparer does not work for this application well. Remove it where it is used.
5+
- Check if the map key -> Symbol is unique. If not use a list of symbols!
6+
_elementIdToSymbolMap. Are all locations captured in this case?
7+
8+
49
- Highlight of aggregated edges takes too long. If quick help not visible skip?
510
- Performance in general
611

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

1114
BUGS
1215
---------------------

CodeParser/Parser/Parser.Phase1.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ public partial class Parser
99
{
1010
private readonly List<INamedTypeSymbol> _allNamedTypesInSolution = new();
1111

12-
private readonly Dictionary<IAssemblySymbol, List<GlobalStatementSyntax>> _globalStatementsByAssembly = new(SymbolEqualityComparer.Default);
12+
private readonly Dictionary<IAssemblySymbol, List<GlobalStatementSyntax>> _globalStatementsByAssembly =
13+
new(SymbolEqualityComparer.Default);
1314

1415

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

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

@@ -155,7 +157,7 @@ private void ProcessNodeForHierarchy(SyntaxNode node, SemanticModel semanticMode
155157
var assemblySymbol = semanticModel.Compilation.Assembly;
156158
_globalStatementsByAssembly[assemblySymbol].Add(globalStatementSyntax);
157159
return; // We'll handle these collectively later
158-
160+
159161
// Add more cases as needed (e.g., for events, delegates, etc.)
160162
}
161163

CodeParser/Parser/Parser.Phase2.Properties.cs

+4-11
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement,
5353
{
5454
if (propertyDeclaration.ExpressionBody != null)
5555
{
56-
AnalyzeMethodBody(propertyElement, propertyDeclaration.ExpressionBody.Expression, semanticModel);
56+
AnalyzeMethodBody(propertyElement, propertyDeclaration.ExpressionBody.Expression,
57+
semanticModel);
5758
}
5859
else if (propertyDeclaration.AccessorList != null)
5960
{
@@ -73,19 +74,11 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement,
7374
}
7475
}
7576
}
77+
7678
private void AddPropertyDependency(CodeElement sourceElement, IPropertySymbol propertySymbol,
7779
DependencyType dependencyType, List<SourceLocation> locations)
7880
{
79-
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol), out var targetElement))
80-
{
81-
AddDependency(sourceElement, dependencyType, targetElement, locations);
82-
}
83-
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol.ContainingType),
84-
out var containingTypeElement))
85-
{
86-
// If we don't have the property itself in our map, add a dependency to its containing type
87-
AddDependency(sourceElement, dependencyType, containingTypeElement, locations);
88-
}
81+
AddDependencyWithFallbackToContainingType(sourceElement, propertySymbol, dependencyType, locations);
8982
}
9083

9184
private IPropertySymbol? GetImplementedInterfaceProperty(IPropertySymbol propertySymbol)

CodeParser/Parser/Parser.Phase2.cs

+85-82
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ private void AnalyzeDependencies(Solution solution)
1616
var loop = 0;
1717
foreach (var element in _codeGraph.Nodes.Values)
1818
{
19+
// TODO atr Analyze if we can have more than one symbol!
1920
var symbol = _elementIdToSymbolMap[element.Id];
2021

2122
if (symbol is IEventSymbol eventSymbol)
@@ -75,13 +76,13 @@ private void AnalyzeGlobalStatementsForAssembly(Solution solution)
7576
}
7677

7778
// Find the existing assembly element
78-
var symbolKey = GetSymbolKey(assemblySymbol);
79+
var symbolKey = assemblySymbol.Key();
7980
var assemblyElement = _symbolKeyToElementMap[symbolKey];
8081

8182
// Create a dummy class for this assembly's global statements
8283
var dummyClassId = Guid.NewGuid().ToString();
8384
var dummyClassName = "GlobalStatements";
84-
var dummyClassFullName = BuildSymbolName(assemblySymbol) + "." + dummyClassName;
85+
var dummyClassFullName = assemblySymbol.BuildSymbolName() + "." + dummyClassName;
8586
var dummyClass = new CodeElement(dummyClassId, CodeElementType.Class, dummyClassName, dummyClassFullName,
8687
assemblyElement);
8788
_codeGraph.Nodes[dummyClassId] = dummyClass;
@@ -108,6 +109,7 @@ private void AnalyzeGlobalStatementsForAssembly(Solution solution)
108109
}
109110
}
110111
}
112+
111113
private void AnalyzeAttributeDependencies(CodeElement element, ISymbol symbol)
112114
{
113115
foreach (var attributeData in symbol.GetAttributes())
@@ -238,7 +240,7 @@ private void FindImplementations(CodeElement methodElement, IMethodSymbol method
238240
var implementingMethod = implementingType.FindImplementationForInterfaceMember(methodSymbol);
239241
if (implementingMethod != null)
240242
{
241-
var implementingElement = _symbolKeyToElementMap.GetValueOrDefault(GetSymbolKey(implementingMethod));
243+
var implementingElement = _symbolKeyToElementMap.GetValueOrDefault(implementingMethod.Key());
242244
if (implementingElement != null)
243245
{
244246
// Note: Implementations for external methods are not in our map
@@ -283,20 +285,10 @@ private bool IsTypeDerivedFrom(INamedTypeSymbol type, INamedTypeSymbol baseType)
283285
private void AddMethodOverrideDependency(CodeElement sourceElement, IMethodSymbol methodSymbol,
284286
List<SourceLocation> locations)
285287
{
286-
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol), out var targetElement))
287-
{
288-
AddDependency(sourceElement, DependencyType.Overrides, targetElement, locations);
289-
}
290-
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol.ContainingType),
291-
out var containingTypeElement))
292-
{
293-
// Trace.WriteLine("Method override not captured. It is likely that the base method is generic or external code.");
294-
295-
// If we don't have the method itself in our map, add a dependency to its containing type
296-
// Maybe we override a framework method. Happens also if the base method is a generic one.
297-
// In this case the GetSymbolKey is different. One uses T, the overriding method uses the actual type.
298-
AddDependency(sourceElement, DependencyType.Overrides, containingTypeElement, locations);
299-
}
288+
// If we don't have the method itself in our map, add a dependency to its containing type
289+
// Maybe we override a framework method. Happens also if the base method is a generic one.
290+
// In this case the GetSymbolKey is different. One uses T, the overriding method uses the actual type.
291+
AddDependencyWithFallbackToContainingType(sourceElement, methodSymbol, DependencyType.Overrides, locations);
300292
}
301293

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

379371
private void AddEventUsageDependency(CodeElement sourceElement, IEventSymbol eventSymbol)
380372
{
381-
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(eventSymbol), out var eventElement))
382-
{
383-
AddDependency(sourceElement, DependencyType.Uses, eventElement, []);
384-
}
385-
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(eventSymbol.ContainingType),
386-
out var containingTypeElement))
387-
{
388-
// If we don't have the event itself in our map, add a dependency to its containing type
389-
AddDependency(sourceElement, DependencyType.Uses, containingTypeElement, []);
390-
}
373+
// If we don't have the event itself in our map, add a dependency to its containing type
374+
AddDependencyWithFallbackToContainingType(sourceElement, eventSymbol, DependencyType.Uses, []);
391375
}
392376

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

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

403-
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol), out var targetElement))
388+
if (methodSymbol.IsGenericMethod && FindCodeElement(methodSymbol) is null)
404389
{
405-
AddDependency(sourceElement, DependencyType.Calls, targetElement, [location]);
390+
methodSymbol = methodSymbol.OriginalDefinition;
406391
}
392+
407393
// If the method is not in our map, we might want to add a dependency to its containing type
408-
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(methodSymbol.ContainingType),
409-
out var containingTypeElement))
410-
{
411-
AddDependency(sourceElement, DependencyType.Calls, containingTypeElement, [location]);
412-
}
394+
AddDependencyWithFallbackToContainingType(sourceElement, methodSymbol, DependencyType.Calls, [location]);
413395
}
414396

415397

@@ -442,38 +424,8 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol
442424
break;
443425

444426
case INamedTypeSymbol namedTypeSymbol:
445-
var symbolKey = GetSymbolKey(namedTypeSymbol);
446-
if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var targetElement))
447-
{
448-
// The type is internal (part of our codebase)
449-
AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []);
450-
451-
if (namedTypeSymbol.IsGenericType)
452-
{
453-
// Add "Uses" dependencies to type arguments
454-
foreach (var typeArg in namedTypeSymbol.TypeArguments)
455-
{
456-
AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location);
457-
}
458-
}
459-
}
460-
else
461-
{
462-
// The type is external
463-
464-
// Optionally, you might want to track external dependencies
465-
// AddExternalDependency(sourceElement, namedTypeSymbol, dependencyType, location);
466-
if (namedTypeSymbol.IsGenericType)
467-
{
468-
// For example List<MyType>
469-
// Add "Uses" dependencies to type arguments, which might be internal
470-
foreach (var typeArg in namedTypeSymbol.TypeArguments)
471-
{
472-
AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location);
473-
}
474-
}
475-
}
476427

428+
AddNamedTypeDependency(sourceElement, namedTypeSymbol, dependencyType, location);
477429
break;
478430

479431
case IPointerTypeSymbol pointerTypeSymbol:
@@ -491,8 +443,8 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol
491443
break;
492444
default:
493445
// Handle other type symbols (e.g., type parameters)
494-
symbolKey = GetSymbolKey(typeSymbol);
495-
if (_symbolKeyToElementMap.TryGetValue(symbolKey, out targetElement))
446+
var symbolKey = typeSymbol.Key();
447+
if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var targetElement))
496448
{
497449
AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []);
498450
}
@@ -501,6 +453,44 @@ private void AddTypeDependency(CodeElement sourceElement, ITypeSymbol typeSymbol
501453
}
502454
}
503455

456+
private void AddNamedTypeDependency(CodeElement sourceElement, INamedTypeSymbol namedTypeSymbol,
457+
DependencyType dependencyType,
458+
SourceLocation? location)
459+
{
460+
var targetElement = FindCodeElement(namedTypeSymbol);
461+
if (targetElement != null)
462+
{
463+
// The type is internal (part of our codebase)
464+
AddDependency(sourceElement, dependencyType, targetElement, location != null ? [location] : []);
465+
}
466+
else
467+
{
468+
// The type is external or a constructed generic type
469+
// Note the constructed type is not in our CodeElement map!
470+
// It is not found in phase1 the way we parse it but the original definition is.
471+
var originalDefinition = namedTypeSymbol.OriginalDefinition;
472+
var originalSymbolKey = originalDefinition.Key();
473+
474+
if (_symbolKeyToElementMap.TryGetValue(originalSymbolKey, out var originalTargetElement))
475+
{
476+
// We found the original definition, add dependency to it
477+
AddDependency(sourceElement, dependencyType, originalTargetElement, location != null ? [location] : []);
478+
}
479+
// The type is truly external, you might want to log this or handle it differently
480+
// AddExternalDependency(sourceElement, namedTypeSymbol, dependencyType, location);
481+
}
482+
483+
if (namedTypeSymbol.IsGenericType)
484+
{
485+
// Add "Uses" dependencies to type arguments
486+
foreach (var typeArg in namedTypeSymbol.TypeArguments)
487+
{
488+
AddTypeDependency(sourceElement, typeArg, DependencyType.Uses, location);
489+
}
490+
}
491+
}
492+
493+
504494
private void AnalyzeIdentifier(CodeElement sourceElement, IdentifierNameSyntax identifierSyntax,
505495
SemanticModel semanticModel)
506496
{
@@ -517,7 +507,7 @@ private void AnalyzeIdentifier(CodeElement sourceElement, IdentifierNameSyntax i
517507

518508
if (symbolInfo.Symbol is IFieldSymbol fieldSymbol)
519509
{
520-
AddFieldDependency(sourceElement, fieldSymbol, DependencyType.Uses);
510+
AddDependencyWithFallbackToContainingType(sourceElement, fieldSymbol, DependencyType.Uses);
521511
}
522512
}
523513

@@ -533,7 +523,7 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi
533523
}
534524
else if (symbolInfo.Symbol is IFieldSymbol fieldSymbol)
535525
{
536-
AddFieldDependency(sourceElement, fieldSymbol, DependencyType.Uses);
526+
AddDependencyWithFallbackToContainingType(sourceElement, fieldSymbol, DependencyType.Uses);
537527
}
538528
}
539529

@@ -543,27 +533,40 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi
543533
private void AddPropertyCallDependency(CodeElement sourceElement, IPropertySymbol propertySymbol,
544534
List<SourceLocation> locations)
545535
{
546-
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol), out var targetElement))
536+
AddDependencyWithFallbackToContainingType(sourceElement, propertySymbol, DependencyType.Calls, locations);
537+
}
538+
539+
private void AddDependencyWithFallbackToContainingType(CodeElement sourceElement, ISymbol symbol,
540+
DependencyType dependencyType, List<SourceLocation>? locations = null)
541+
{
542+
// If we don't have the property itself in our map, add a dependency to its containing type
543+
if (locations == null)
544+
{
545+
locations = [];
546+
}
547+
548+
var targetElement = FindCodeElement(symbol);
549+
if (targetElement != null)
547550
{
548-
AddDependency(sourceElement, DependencyType.Calls, targetElement, locations);
551+
AddDependency(sourceElement, dependencyType, targetElement, locations);
552+
return;
549553
}
550-
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(propertySymbol.ContainingType),
551-
out var containingTypeElement))
554+
555+
var containingTypeElement = FindCodeElement(symbol.ContainingType);
556+
if (containingTypeElement != null)
552557
{
553-
AddDependency(sourceElement, DependencyType.Calls, containingTypeElement, locations);
558+
AddDependency(sourceElement, dependencyType, containingTypeElement, locations);
554559
}
555560
}
556561

557-
private void AddFieldDependency(CodeElement sourceElement, IFieldSymbol fieldSymbol, DependencyType dependencyType)
562+
private CodeElement? FindCodeElement(ISymbol? symbol)
558563
{
559-
if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(fieldSymbol), out var targetElement))
564+
if (symbol is null)
560565
{
561-
AddDependency(sourceElement, dependencyType, targetElement, []);
562-
}
563-
else if (_symbolKeyToElementMap.TryGetValue(GetSymbolKey(fieldSymbol.ContainingType),
564-
out var containingTypeElement))
565-
{
566-
AddDependency(sourceElement, dependencyType, containingTypeElement, []);
566+
return null;
567567
}
568+
569+
_symbolKeyToElementMap.TryGetValue(symbol.Key(), out var element);
570+
return element;
568571
}
569572
}

0 commit comments

Comments
 (0)