diff --git a/CSharpCodeAnalyst/board.txt b/CSharpCodeAnalyst/board.txt index 5d582db..d9aa07d 100644 --- a/CSharpCodeAnalyst/board.txt +++ b/CSharpCodeAnalyst/board.txt @@ -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 --------------------- diff --git a/CodeParser/Parser/Parser.Phase1.cs b/CodeParser/Parser/Parser.Phase1.cs index 0e5789b..cbc1444 100644 --- a/CodeParser/Parser/Parser.Phase1.cs +++ b/CodeParser/Parser/Parser.Phase1.cs @@ -9,7 +9,8 @@ public partial class Parser { private readonly List _allNamedTypesInSolution = new(); - private readonly Dictionary> _globalStatementsByAssembly = new(SymbolEqualityComparer.Default); + private readonly Dictionary> _globalStatementsByAssembly = + new(SymbolEqualityComparer.Default); private async Task BuildHierarchy(Solution solution) @@ -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(); _allNamedTypesInSolution.AddRange(types); @@ -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.) } diff --git a/CodeParser/Parser/Parser.Phase2.Properties.cs b/CodeParser/Parser/Parser.Phase2.Properties.cs index 49d014d..e67c758 100644 --- a/CodeParser/Parser/Parser.Phase2.Properties.cs +++ b/CodeParser/Parser/Parser.Phase2.Properties.cs @@ -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) { @@ -73,19 +74,11 @@ private void AnalyzePropertyBody(Solution solution, CodeElement propertyElement, } } } + private void AddPropertyDependency(CodeElement sourceElement, IPropertySymbol propertySymbol, DependencyType dependencyType, List 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) diff --git a/CodeParser/Parser/Parser.Phase2.cs b/CodeParser/Parser/Parser.Phase2.cs index 3b196f8..cc1c905 100644 --- a/CodeParser/Parser/Parser.Phase2.cs +++ b/CodeParser/Parser/Parser.Phase2.cs @@ -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) @@ -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; @@ -108,6 +109,7 @@ private void AnalyzeGlobalStatementsForAssembly(Solution solution) } } } + private void AnalyzeAttributeDependencies(CodeElement element, ISymbol symbol) { foreach (var attributeData in symbol.GetAttributes()) @@ -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 @@ -283,20 +285,10 @@ private bool IsTypeDerivedFrom(INamedTypeSymbol type, INamedTypeSymbol baseType) private void AddMethodOverrideDependency(CodeElement sourceElement, IMethodSymbol methodSymbol, List 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) @@ -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) @@ -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]); } @@ -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 - // 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: @@ -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] : []); } @@ -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) { @@ -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); } } @@ -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); } } @@ -543,27 +533,40 @@ private void AnalyzeMemberAccess(CodeElement sourceElement, MemberAccessExpressi private void AddPropertyCallDependency(CodeElement sourceElement, IPropertySymbol propertySymbol, List 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? 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; } } \ No newline at end of file diff --git a/CodeParser/Parser/Parser.cs b/CodeParser/Parser/Parser.cs index c47fa66..8ad9070 100644 --- a/CodeParser/Parser/Parser.cs +++ b/CodeParser/Parser/Parser.cs @@ -112,19 +112,19 @@ private void CollectAllFilePathInSolution(Solution solution) } } - private string GetSymbolKey(ISymbol symbol) - { - var fullName = BuildSymbolName(symbol); + //private string GetSymbolKey(ISymbol symbol) + //{ + // var fullName = BuildSymbolName(symbol); - if (symbol is IMethodSymbol methodSymbol) - { - // Overloaded methods should have a unique key - var parameters = string.Join("_ ", methodSymbol.Parameters.Select(p => p.Type.ToString())); - return $"{fullName}_{parameters}_{symbol.Kind}"; - } + // if (symbol is IMethodSymbol methodSymbol) + // { + // // Overloaded methods should have a unique key + // var parameters = string.Join("_ ", methodSymbol.Parameters.Select(p => p.Type.ToString())); + // return $"{fullName}_{parameters}_{symbol.Kind}"; + // } - return $"{fullName}_{symbol.Kind}"; - } + // return $"{fullName}_{symbol.Kind}"; + //} /// /// Since I iterate over the compilation units (to get rid of external code) @@ -169,10 +169,11 @@ private CodeElement GetOrCreateCodeElementWithNamespaceHierarchy(ISymbol symbol, return GetOrCreateCodeElement(symbol, elementType, initialParent, location); } + private CodeElement GetOrCreateCodeElement(ISymbol symbol, CodeElementType elementType, CodeElement? parent, SourceLocation? location) { - var symbolKey = GetSymbolKey(symbol); + var symbolKey = symbol.Key(); // We may encounter namespace declarations in many files. if (_symbolKeyToElementMap.TryGetValue(symbolKey, out var existingElement)) @@ -186,7 +187,7 @@ private CodeElement GetOrCreateCodeElement(ISymbol symbol, CodeElementType eleme } var name = symbol.Name; - var fullName = BuildSymbolName(symbol); + var fullName = symbol.BuildSymbolName(); var newId = Guid.NewGuid().ToString(); var element = new CodeElement(newId, elementType, name, fullName, parent); @@ -198,51 +199,12 @@ private CodeElement GetOrCreateCodeElement(ISymbol symbol, CodeElementType eleme parent?.Children.Add(element); _codeGraph.Nodes[element.Id] = element; _symbolKeyToElementMap[symbolKey] = element; + + // We need the symbol in phase2 when analyzing the dependencies. _elementIdToSymbolMap[element.Id] = symbol; return element; } - /// - /// Sometimes when walking up the parent chain: - /// After the global namespace the containing symbol is not reliable. - /// If we do not end up at an assembly it is added manually. - /// - public static string BuildSymbolName(ISymbol symbol) - { - var parts = new List(); - - var currentSymbol = symbol; - ISymbol? lastKnownSymbol = null; - - while (currentSymbol != null) - { - if (currentSymbol is IModuleSymbol) - { - // Skip the module symbol - currentSymbol = currentSymbol.ContainingSymbol; - } - - lastKnownSymbol = currentSymbol; - - var name = currentSymbol.Name; - parts.Add(name); - currentSymbol = currentSymbol.ContainingSymbol; - } - - if (lastKnownSymbol is not IAssemblySymbol) - { - // global namespace has the ContainingCompilation set. - Debug.Assert(lastKnownSymbol is INamespaceSymbol { IsGlobalNamespace: true }); - var namespaceSymbol = lastKnownSymbol as INamespaceSymbol; - var assemblySymbol = namespaceSymbol.ContainingCompilation.Assembly; - parts.Add(assemblySymbol.Name); - } - - parts.Reverse(); - var fullName = string.Join(".", parts.Where(p => !string.IsNullOrEmpty(p))); - return fullName; - } - /// /// Get the source location of a syntax node /// diff --git a/CodeParser/Parser/SymbolExtensions.cs b/CodeParser/Parser/SymbolExtensions.cs new file mode 100644 index 0000000..54ee5ae --- /dev/null +++ b/CodeParser/Parser/SymbolExtensions.cs @@ -0,0 +1,113 @@ +using System.Diagnostics; +using Microsoft.CodeAnalysis; + +namespace CodeParser.Parser; + +public static class SymbolExtensions +{ + /// + /// Sometimes when walking up the parent chain: + /// After the global namespace the containing symbol is not reliable. + /// If we do not end up at an assembly it is added manually. + /// + public static string BuildSymbolName(this ISymbol symbol) + { + var parts = new List(); + + var currentSymbol = symbol; + ISymbol? lastKnownSymbol = null; + + while (currentSymbol != null) + { + if (currentSymbol is IModuleSymbol) + { + // Skip the module symbol + currentSymbol = currentSymbol.ContainingSymbol; + } + + lastKnownSymbol = currentSymbol; + + var name = currentSymbol.Name; + parts.Add(name); + currentSymbol = currentSymbol.ContainingSymbol; + } + + if (lastKnownSymbol is not IAssemblySymbol) + { + // global namespace has the ContainingCompilation set. + Debug.Assert(lastKnownSymbol is INamespaceSymbol { IsGlobalNamespace: true }); + var namespaceSymbol = lastKnownSymbol as INamespaceSymbol; + var assemblySymbol = namespaceSymbol.ContainingCompilation.Assembly; + parts.Add(assemblySymbol.Name); + } + + parts.Reverse(); + var fullName = string.Join(".", parts.Where(p => !string.IsNullOrEmpty(p))); + return fullName; + } + + /// + /// Returns a unique key for the symbol + /// We may have for example multiple symbols for the same namespace (X.Y.Z vs X.Y{Z}) + /// See INamespaceSymbol.ConstituentNamespaces + /// Method overloads and generics are considered in the key. + /// This key more or less replaces the SymbolEqualityComparer not useful for this application. + /// + public static string Key(this ISymbol symbol) + { + var fullName = BuildSymbolName(symbol); + var genericPart = GetGenericPart(symbol); + var kind = symbol.Kind.ToString(); + + if (symbol is IMethodSymbol methodSymbol) + { + var parameters = string.Join("_", methodSymbol.Parameters.Select(p => p.Type.ToDisplayString())); + return $"{fullName}{genericPart}_{parameters}_{kind}"; + } + + return $"{fullName}{genericPart}_{kind}"; + } + + private static string GetGenericPart(ISymbol symbol) + { + var result = string.Empty; + switch (symbol) + { + case INamedTypeSymbol namedTypeSymbol: + if (namedTypeSymbol.IsGenericType) + { + if (!namedTypeSymbol.IsDefinition) + { + // This is a constructed type (e.g., List) + result = $"<{string.Join(",", namedTypeSymbol.TypeArguments.Select(t => t.ToDisplayString()))}>"; + } + else + { + // This is a generic type definition (e.g., List) + // When processing the solution hierarchy or dependency to original definition symbol + result = $"<{string.Join(",", namedTypeSymbol.TypeParameters.Select(t => t.Name))}>"; + } + } + + break; + + case IMethodSymbol methodSymbol: + if (methodSymbol.IsGenericMethod) + { + if (!methodSymbol.IsDefinition) + { + // This is a constructed generic method + result = $"<{string.Join(",", methodSymbol.TypeArguments.Select(t => t.ToDisplayString()))}>"; + } + else + { + // This is a generic method definition + result = $"<{string.Join(",", methodSymbol.TypeParameters.Select(t => t.Name))}>"; + } + } + break; + } + + return result; + } +} \ No newline at end of file diff --git a/CodeParserTests/CodeParserApprovalTests.cs b/CodeParserTests/CodeParserApprovalTests.cs index 9b3ce09..a77b590 100644 --- a/CodeParserTests/CodeParserApprovalTests.cs +++ b/CodeParserTests/CodeParserApprovalTests.cs @@ -50,10 +50,11 @@ public void FindsAllFunctions() var methods = codeElements .Where(n => n.ElementType == CodeElementType.Method) - .Select(m => m.FullName); + .Select(m => m.FullName).ToList(); - var expectedMethods = new HashSet + // No HashSet because some methods are overloaded + var expectedMethods = new List { "ModuleLevel0.ModuleLevel0.Bootstrapper.Run", "ModuleLevel1.ModuleLevel1.FactoryC.Create", "ModuleLevel1.ModuleLevel1.IServiceC.Do", "ModuleLevel1.ModuleLevel1.Model.ModelA..ctor", @@ -84,7 +85,14 @@ public void FindsAllFunctions() "CSharpLanguage.CSharpLanguage.Extensions.Slice", // Called by extension method - "CSharpLanguage.CSharpLanguage.TheExtendedType.Do" + "CSharpLanguage.CSharpLanguage.TheExtendedType.Do", + + // Generic method calls + "CSharpLanguage.CSharpLanguage.MoreGenerics.M1", + "CSharpLanguage.CSharpLanguage.MoreGenerics.M1", + "CSharpLanguage.CSharpLanguage.MoreGenerics.M2", + "CSharpLanguage.CSharpLanguage.MoreGenerics.M2", + "CSharpLanguage.CSharpLanguage.MoreGenerics.Run" }; diff --git a/Documentation/Roslyn/roslyn-guide.md b/Documentation/Roslyn/roslyn-guide.md new file mode 100644 index 0000000..6147bf2 --- /dev/null +++ b/Documentation/Roslyn/roslyn-guide.md @@ -0,0 +1,152 @@ +# Guide to Roslyn Concepts (Generated via Claude.ai) + +## Table of Contents +[TOC] + +## GetSymbolInfo vs GetDeclaredSymbol + +### GetSymbolInfo +- **Use Case**: Get information about a symbol being referenced or used. +- **Typical Scenarios**: Attribute usages, method invocations, type references, variable usages. +- **Returns**: `SymbolInfo` struct, which may contain the symbol if successfully bound, or information about why the symbol couldn't be determined. +- **Example**: + ```csharp + var invocation = node as InvocationExpressionSyntax; + var symbolInfo = semanticModel.GetSymbolInfo(invocation); + var methodSymbol = symbolInfo.Symbol as IMethodSymbol; + ``` + +### GetDeclaredSymbol +- **Use Case**: Get the symbol for a declaration in your code. +- **Typical Scenarios**: Class declarations, method declarations, property declarations, variable declarations. +- **Returns**: The `ISymbol` directly if the node represents a declaration, or null if it doesn't. +- **Example**: + ```csharp + var classDeclaration = node as ClassDeclarationSyntax; + var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol; + ``` + +### Key Differences +1. **Usage vs Declaration**: + - `GetSymbolInfo` is for uses of symbols. + - `GetDeclaredSymbol` is for declarations of symbols. + +2. **Return Type**: + - `GetSymbolInfo` returns a `SymbolInfo` struct. + - `GetDeclaredSymbol` returns an `ISymbol` or null. + +3. **Binding Information**: + - `GetSymbolInfo` can provide information even if the symbol couldn't be determined (e.g., in case of errors). + - `GetDeclaredSymbol` only returns a symbol if it's a valid declaration. + +### In the Context of Parsing +- Use `GetDeclaredSymbol` in the hierarchy-building phase when processing declarations. +- Use `GetSymbolInfo` in the dependency-analysis phase when examining usages, including attributes. + +### Example in Context +```csharp +// In hierarchy-building phase (for declarations) +case ClassDeclarationSyntax classDeclaration: + symbol = semanticModel.GetDeclaredSymbol(classDeclaration); + elementType = CodeElementType.Class; + break; + +// In dependency-analysis phase (for usages, including attributes) +var attributeSymbol = semanticModel.GetSymbolInfo(attribute).Symbol; +if (attributeSymbol is IMethodSymbol attributeCtorSymbol) +{ + // Process attribute usage +} +``` + +## OriginalDefinition vs ConstructedFrom + +### OriginalDefinition +- **Available On**: Various symbol types (INamedTypeSymbol, IMethodSymbol, etc.) +- **Purpose**: Returns the original definition of a symbol, regardless of whether it's generic or not. +- **Behavior**: + - For non-generic types/methods: Returns the symbol itself. + - For generic types/methods (open and closed): Returns the unconstructed, open generic form. +- **Examples**: + - `List` → `List` + - `List` → `List` + - `MyClass` (non-generic) → `MyClass` + +### ConstructedFrom +- **Available On**: INamedTypeSymbol and IMethodSymbol only +- **Purpose**: Relevant only for constructed generic types or methods (closed generics). +- **Behavior**: + - For constructed generic types/methods: Returns the open generic form. + - For non-generic or unconstructed generic types/methods: Returns null. +- **Examples**: + - `List` → `List` + - `List` → null + - `MyClass` (non-generic) → null + +### Key Differences +1. **Availability**: + - `OriginalDefinition` is more widely available. + - `ConstructedFrom` is limited to certain symbol types. + +2. **Behavior with Non-Generics**: + - `OriginalDefinition` returns the symbol itself. + - `ConstructedFrom` returns null. + +3. **Behavior with Open Generics**: + - `OriginalDefinition` returns the symbol itself. + - `ConstructedFrom` returns null. + +4. **Purpose**: + - `OriginalDefinition` is universal, always giving the "template" form of a symbol. + - `ConstructedFrom` is specific for navigating from closed to open generic forms. + +### In the Context of Parsing +- `OriginalDefinition` is generally more useful for mapping any type back to its original definition in your codebase. +- `ConstructedFrom` is helpful when you need to distinguish between open and closed generics. + +For handling generic base classes, `OriginalDefinition` is usually the more appropriate choice, as it works consistently for both generic and non-generic types. + +## TypeParameters vs TypeArguments + +### TypeParameters +- **Definition**: Represent the generic type parameters declared on a method or type. +- **Nature**: Placeholders for types that will be specified when the method is called or the type is used. +- **Example Declaration**: + ```csharp + public T MyMethod(T input) { ... } + ``` + Here, `T` is a TypeParameter. + +### TypeArguments +- **Definition**: Represent the actual types used when a generic method is invoked or a generic type is constructed. +- **Nature**: Concrete types that replace the type parameters at the call site or usage point. +- **Example Usage**: + ```csharp + int result = MyMethod(5); + ``` + Here, `int` is a TypeArgument. + +### In the Context of Roslyn's IMethodSymbol +- `TypeParameters` property: + - Contains the list of type parameters declared on the method. + - Always present, regardless of whether you're examining a declaration or invocation. + +- `TypeArguments` property: + - Populated for method invocations or when the generic method is part of a constructed type. + - Typically empty for method declarations. + +### Key Points +- For method declarations: + - `TypeParameters` will be populated. + - `TypeArguments` will typically be empty. + +- For method invocations: + - Both `TypeParameters` and `TypeArguments` will be populated. + - `TypeParameters` contains the original type parameters. + - `TypeArguments` contains the concrete types used in the invocation. + +### Implications for Code Analysis +- When analyzing method declarations, focus on `TypeParameters`. +- When analyzing method invocations, focus on `TypeArguments` to see the concrete types being used. + +Understanding these distinctions is crucial for correctly analyzing dependencies and usage of generic methods in code parsing and analysis tasks. diff --git a/Documentation/Roslyn/roslyn-symbol-retrieval-guide.md b/Documentation/Roslyn/roslyn-symbol-retrieval-guide.md deleted file mode 100644 index 37a2df7..0000000 --- a/Documentation/Roslyn/roslyn-symbol-retrieval-guide.md +++ /dev/null @@ -1,71 +0,0 @@ -# GetSymbolInfo vs GetDeclaredSymbol in Roslyn - -## GetSymbolInfo - -- **Use Case**: Use `GetSymbolInfo` when you want to get information about a symbol that is being referenced or used. -- **Typical Scenarios**: - - Attribute usages - - Method invocations - - Type references - - Variable usages -- **Returns**: `SymbolInfo` struct, which may contain the symbol if successfully bound, or information about why the symbol couldn't be determined. -- **Example**: - ```csharp - var invocation = node as InvocationExpressionSyntax; - var symbolInfo = semanticModel.GetSymbolInfo(invocation); - var methodSymbol = symbolInfo.Symbol as IMethodSymbol; - ``` - -## GetDeclaredSymbol - -- **Use Case**: Use `GetDeclaredSymbol` when you want to get the symbol for a declaration in your code. -- **Typical Scenarios**: - - Class declarations - - Method declarations - - Property declarations - - Variable declarations -- **Returns**: The `ISymbol` directly if the node represents a declaration, or null if it doesn't. -- **Example**: - ```csharp - var classDeclaration = node as ClassDeclarationSyntax; - var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol; - ``` - -## Key Differences - -1. **Usage vs Declaration**: - - `GetSymbolInfo` is for uses of symbols. - - `GetDeclaredSymbol` is for declarations of symbols. - -2. **Return Type**: - - `GetSymbolInfo` returns a `SymbolInfo` struct. - - `GetDeclaredSymbol` returns an `ISymbol` or null. - -3. **Binding Information**: - - `GetSymbolInfo` can provide information even if the symbol couldn't be determined (e.g., in case of errors). - - `GetDeclaredSymbol` only returns a symbol if it's a valid declaration. - -## In Your Parser - -- Use `GetDeclaredSymbol` in the hierarchy-building phase when processing declarations. -- Use `GetSymbolInfo` in the dependency-analysis phase when examining usages, including attributes. - -## Example in Context - -```csharp -// In hierarchy-building phase (for declarations) -case ClassDeclarationSyntax classDeclaration: - symbol = semanticModel.GetDeclaredSymbol(classDeclaration); - elementType = CodeElementType.Class; - break; - -// In dependency-analysis phase (for usages, including attributes) -var attributeSymbol = semanticModel.GetSymbolInfo(attribute).Symbol; -if (attributeSymbol is IMethodSymbol attributeCtorSymbol) -{ - // Process attribute usage -} -``` - -Remember, always use `GetDeclaredSymbol` for nodes that represent declarations, and `GetSymbolInfo` for nodes that represent usages or references. - diff --git a/SampleProject/CSharpLanguage/MoreGenerics.cs b/SampleProject/CSharpLanguage/MoreGenerics.cs new file mode 100644 index 0000000..2071e33 --- /dev/null +++ b/SampleProject/CSharpLanguage/MoreGenerics.cs @@ -0,0 +1,41 @@ +namespace CSharpLanguage; + +internal class MoreGenerics +{ + private void Run() + { + var f1 = new Foo(); + var f2 = new Foo(); + + M1(3); + M1(3); + M2(2, "2"); + M2("2", 2); + } + + private void M1(T obj) + { + } + + private void M1(T obj) + { + } + + + private void M2(int i, T obj) + { + } + + private void M2(T obj, int i) + { + } + + + internal class Foo + { + } + + internal class Foo + { + } +} \ No newline at end of file