diff --git a/Directory.Packages.props b/Directory.Packages.props index 0c0cf4d13b..385845b466 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -21,7 +21,7 @@ - + diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs index 3dcd4a281a..d2d459e11b 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/IdentifierRenamingTransformer.cs @@ -14,19 +14,25 @@ public class IdentifierRenamingTransformer : LocationTransformer { private ISymbol symbol = null!; - private readonly IReadOnlyDictionary> newNameLookup; + private readonly IReadOnlyDictionary< + string, + List<(ISymbol Symbol, string NewName)> + > newNameLookup; /// /// Creates a new IdentifierRenamingTransformer. /// /// The new names for each symbol - public IdentifierRenamingTransformer(IEnumerable<(ISymbol Symbol, string NewName)> newNames) : this(CreateNameLookup(newNames)) {} + public IdentifierRenamingTransformer(IEnumerable<(ISymbol Symbol, string NewName)> newNames) + : this(CreateNameLookup(newNames)) { } /// /// Creates a new IdentifierRenamingTransformer. /// /// The new names for each symbol grouped by symbol name. - public IdentifierRenamingTransformer(IReadOnlyDictionary> newNameLookup) + public IdentifierRenamingTransformer( + IReadOnlyDictionary> newNameLookup + ) { this.newNameLookup = newNameLookup; } @@ -34,9 +40,14 @@ public IdentifierRenamingTransformer(IReadOnlyDictionary /// Creates a name lookup dictionary designed for . /// - public static IReadOnlyDictionary> CreateNameLookup(IEnumerable<(ISymbol Symbol, string NewName)> names) + public static IReadOnlyDictionary< + string, + List<(ISymbol Symbol, string NewName)> + > CreateNameLookup(IEnumerable<(ISymbol Symbol, string NewName)> names) { - return names.GroupBy(t => t.Symbol.Name).ToDictionary(group => group.Key, group => group.ToList()); + return names + .GroupBy(t => t.Symbol.Name) + .ToDictionary(group => group.Key, group => group.ToList()); } /// @@ -68,7 +79,7 @@ private SyntaxToken GetRenamed(ISymbol symbol, SyntaxToken currentNameIdentifier return currentNameIdentifier; } - // ----- Types ----- + // ----- Types ----- /// public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs index 47f48c8014..e0a921de92 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -14,36 +14,50 @@ namespace Silk.NET.SilkTouch.Mods.LocationTransformation; /// and modifies the nodes only when coming back up. ///
/// Modifying nodes cause them to be detached from the semantic model (meaning no symbol information), -/// so this ensures that we gather all of the data we need before making changes. +/// so this ensures that we gather all the data we need before making changes. /// -/// Symbols to search for. -/// Transformers to use on each found symbol reference. -public class LocationTransformationRewriter(HashSet symbols, List transformers) : CSharpSyntaxRewriter +public class LocationTransformationRewriter : CSharpSyntaxRewriter { // Symbols can also be referenced within XML doc, which are trivia nodes. /// public override bool VisitIntoStructuredTrivia => true; - private readonly Dictionary queuedTransformations = new(); + private readonly Dictionary _queuedTransformations = new(); /// The symbol for the node. /// The index of the transformer that should be used when continuing the transformation process. private record struct QueuedTransformation(ISymbol Symbol, int TransformerIndex); - private readonly List tempNodeList = new(); + private readonly List _tempNodeList = new(); /// /// The semantic model of the currently processed document. /// - private SemanticModel semanticModel = null!; + private SemanticModel _semanticModel = null!; + + private readonly HashSet _symbols; + private readonly List _transformers; + private readonly HashSet _relevantIdentifiers; + + /// Symbols to search for. + /// Transformers to use on each found symbol reference. + public LocationTransformationRewriter( + HashSet symbols, + List transformers + ) + { + _symbols = symbols; + _transformers = transformers; + + // Used to skip symbol lookups + // Does not handle the omission of the "-Attribute" suffix, but generally, we don't need to transform attributes + _relevantIdentifiers = _symbols.Select(s => s.Name).ToHashSet(); + } /// /// Initializes the renamer to work for a new document. Must be called before visiting any nodes. /// - public void Initialize(SemanticModel semanticModel) - { - this.semanticModel = semanticModel; - } + public void Initialize(SemanticModel semanticModel) => _semanticModel = semanticModel; /// [return: NotNullIfNotNull("unmodifiedNode")] @@ -60,24 +74,25 @@ public void Initialize(SemanticModel semanticModel) // Check for queued transformation // To apply a transformation, we must be in the same level in the hierarchy as the selected node // We also must apply transformations when going back up in the hierarchy so we don't overwrite previous transformations - if (queuedTransformations.Remove(unmodifiedNode, out var transformation)) + if (_queuedTransformations.Remove(unmodifiedNode, out var transformation)) { if (transformation.TransformerIndex >= 0) { // Apply deferred transformer - var deferredTransformer = transformers[transformation.TransformerIndex]; - modifiedNode = deferredTransformer.Visit(modifiedNode) + var deferredTransformer = _transformers[transformation.TransformerIndex]; + modifiedNode = deferredTransformer + .Visit(modifiedNode) .WithLeadingTrivia(unmodifiedNode.GetLeadingTrivia().Select(VisitTrivia)) .WithTrailingTrivia(unmodifiedNode.GetTrailingTrivia()); } // Continue applying remaining transformers - for (var i = transformation.TransformerIndex + 1; i < transformers.Count; i++) + for (var i = transformation.TransformerIndex + 1; i < _transformers.Count; i++) { - var transformer = transformers[i]; + var transformer = _transformers[i]; // Calculate hierarchy - var hierarchy = tempNodeList; + var hierarchy = _tempNodeList; { hierarchy.Clear(); @@ -105,13 +120,17 @@ public void Initialize(SemanticModel semanticModel) { // We can't directly transform the node since we are at the wrong place in the hierarchy // Defer it so it is processed later - queuedTransformations.Add(selectedNode, new QueuedTransformation(transformation.Symbol, i)); + _queuedTransformations.Add( + selectedNode, + new QueuedTransformation(transformation.Symbol, i) + ); break; } // Transform the node - modifiedNode = transformer.Visit(modifiedNode) + modifiedNode = transformer + .Visit(modifiedNode) .WithLeadingTrivia(unmodifiedNode.GetLeadingTrivia().Select(VisitTrivia)) .WithTrailingTrivia(unmodifiedNode.GetTrailingTrivia()); } @@ -122,12 +141,12 @@ public void Initialize(SemanticModel semanticModel) private void ReportSymbol(SyntaxNode node, ISymbol? symbol) { - if (symbol == null || !symbols.Contains(symbol)) + if (symbol == null || !_symbols.Contains(symbol)) { return; } - queuedTransformations.Add(node, new QueuedTransformation(symbol, -1)); + _queuedTransformations.Add(node, new QueuedTransformation(symbol, -1)); } // ----- Types ----- @@ -135,7 +154,7 @@ private void ReportSymbol(SyntaxNode node, ISymbol? symbol) /// public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitClassDeclaration(node)!; @@ -144,7 +163,7 @@ public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) /// public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitStructDeclaration(node)!; @@ -153,7 +172,7 @@ public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node) /// public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitInterfaceDeclaration(node)!; @@ -162,7 +181,7 @@ public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax /// public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitRecordDeclaration(node)!; @@ -171,7 +190,7 @@ public override SyntaxNode VisitRecordDeclaration(RecordDeclarationSyntax node) /// public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitDelegateDeclaration(node)!; @@ -180,7 +199,7 @@ public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax no /// public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitEnumDeclaration(node)!; @@ -191,7 +210,7 @@ public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node) /// public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitEnumMemberDeclaration(node)!; @@ -200,7 +219,7 @@ public override SyntaxNode VisitEnumMemberDeclaration(EnumMemberDeclarationSynta /// public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitPropertyDeclaration(node)!; @@ -209,7 +228,7 @@ public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax no /// public override SyntaxNode VisitEventDeclaration(EventDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitEventDeclaration(node)!; @@ -218,7 +237,7 @@ public override SyntaxNode VisitEventDeclaration(EventDeclarationSyntax node) /// public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitMethodDeclaration(node)!; @@ -227,7 +246,7 @@ public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node) /// public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitConstructorDeclaration(node)!; @@ -236,7 +255,7 @@ public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyn /// public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitDestructorDeclaration(node)!; @@ -247,7 +266,12 @@ public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSynta /// public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { - var symbol = semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetTypeInfo(node).Type; + if (!_relevantIdentifiers.Contains(node.Identifier.Text)) + { + return node; + } + + var symbol = _semanticModel.GetSymbolInfo(node).Symbol; ReportSymbol(node, symbol); return base.VisitIdentifierName(node)!; @@ -257,9 +281,14 @@ public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) /// public override SyntaxNode VisitVariableDeclarator(VariableDeclaratorSyntax node) { - var symbol = semanticModel.GetDeclaredSymbol(node); + var symbol = _semanticModel.GetDeclaredSymbol(node); ReportSymbol(node, symbol); return base.VisitVariableDeclarator(node)!; } + + // ----- Skipped nodes ----- + + /// + public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) => node; } diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs index 8ae9303d72..745230ea4f 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs @@ -26,7 +26,8 @@ public static async Task ModifyAllReferencesAsync( IEnumerable symbols, IEnumerable transformers, ILogger? logger = null, - CancellationToken ct = default) + CancellationToken ct = default + ) { var sourceProject = ctx.SourceProject; if (sourceProject == null) @@ -37,33 +38,51 @@ public static async Task ModifyAllReferencesAsync( // We need to track both the original solution and modified solution // The original is where we retrieve documents and semantic models // The modified solution is where we place the results - IReadOnlyList documentIds = [.. sourceProject.DocumentIds, .. ctx.TestProject?.DocumentIds ?? []]; + IReadOnlyList documentIds = + [ + .. sourceProject.DocumentIds, + .. ctx.TestProject?.DocumentIds ?? [], + ]; var originalSolution = sourceProject.Solution; + var compilation = await sourceProject.GetCompilationAsync(ct); + if (compilation == null) + { + return; + } + var newDocuments = new ConcurrentDictionary(); var symbolSet = new HashSet(symbols, SymbolEqualityComparer.Default); - await Parallel.ForEachAsync(documentIds, ct, async (documentId, _) => { - var originalDocument = originalSolution.GetDocument(documentId); - if (originalDocument == null) + await Parallel.ForEachAsync( + documentIds, + ct, + async (documentId, _) => { - return; - } + var originalDocument = originalSolution.GetDocument(documentId); + if (originalDocument == null) + { + return; + } - var originalRoot = await originalDocument.GetSyntaxRootAsync(ct); - var semanticModel = await originalDocument.GetSemanticModelAsync(ct); + var originalRoot = await originalDocument.GetSyntaxRootAsync(ct); + if (originalRoot == null) + { + return; + } - if (originalRoot == null || semanticModel == null) - { - return; - } + var semanticModel = compilation.GetSemanticModel(originalRoot.SyntaxTree); - // Since this is multithreaded, each thread needs their own copy of the rewriter and transformers - var rewriter = new LocationTransformationRewriter(symbolSet, [..transformers.Select(t => t.GetThreadSafeCopy())]); - rewriter.Initialize(semanticModel); + // Since this is multithreaded, each thread needs their own copy of the rewriter and transformers + var rewriter = new LocationTransformationRewriter( + symbolSet, + [.. transformers.Select(t => t.GetThreadSafeCopy())] + ); - var newRoot = rewriter.Visit(originalRoot); - newDocuments.TryAdd(documentId, newRoot); - }); + rewriter.Initialize(semanticModel); + var newRoot = rewriter.Visit(originalRoot); + newDocuments.TryAdd(documentId, newRoot); + } + ); var modifiedSolution = sourceProject.Solution; foreach (var (documentId, newRoot) in newDocuments) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs index 56c9884937..fa647abd8d 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformer.cs @@ -24,7 +24,10 @@ public abstract class LocationTransformer : CSharpSyntaxRewriter /// The node hierarchy as a reversed stack. Index 0 is the current node. Index 1 is its parent and so on. /// The symbol that is associated with this node. /// The given node, another node, or null. - public abstract SyntaxNode? GetNodeToModify(IReadOnlyList hierarchy, ISymbol symbol); + public abstract SyntaxNode? GetNodeToModify( + IReadOnlyList hierarchy, + ISymbol symbol + ); /// /// Clone this location transformer for purposes of thread safety. diff --git a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs index 100f43ad25..da23b4a6dc 100644 --- a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs +++ b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs @@ -195,9 +195,15 @@ await proj.GetCompilationAsync(ct) // Change the filenames where appropriate. proj = ctx.SourceProject; + var typeNames = newNames.GetValueOrDefault("", []); var typeNamesLongestFirst = typeNames.OrderByDescending(x => x.Key.Length).ToArray(); + var documentPaths = proj + .Documents.Select(d => d.RelativePath()) + .Where(d => d != null) + .ToHashSet(); + foreach (var docId in proj.DocumentIds) { var doc = proj.GetDocument(docId); @@ -206,6 +212,7 @@ await proj.GetCompilationAsync(ct) continue; } + // Find best matching document for renamed types var firstMatch = typeNamesLongestFirst.FirstOrDefault(x => doc.FilePath.Contains(x.Key) || doc.Name.Contains(x.Key) ); @@ -214,43 +221,24 @@ await proj.GetCompilationAsync(ct) continue; } + // Rename doc and update path var originalName = doc.Name; + var originalPath = doc.RelativePath(); doc = doc.ReplaceNameAndPath(oldName, newName); + var newPath = doc.RelativePath(); - var found = false; - if (doc.FilePath is not null) - { - foreach (var checkDocId in proj.DocumentIds) - { - if (checkDocId == docId) - { - continue; - } - - var checkDoc = proj.GetDocument(checkDocId); - if (checkDoc?.FilePath is null) - { - continue; - } - - if (checkDoc.FilePath == doc.FilePath) - { - found = true; - break; - } - } - } - - if (found) + // Check for path conflict + documentPaths.Remove(originalPath); + if (!documentPaths.Add(newPath)) { logger.LogError( $"{originalName} -> {doc.Name} failed to rename file as a file already exists at {doc.FilePath}" ); + + continue; } - else - { - proj = doc.Project; - } + + proj = doc.Project; } ctx.SourceProject = proj; diff --git a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs index 04feaa8cd0..722f1a3b94 100644 --- a/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs +++ b/sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs @@ -86,8 +86,8 @@ public override async Task ExecuteAsync(IModContext ctx, CancellationToken ct = } } - // Do the two following transformation to all references of the handle types: - // 2. Reduce pointer dimensions + // Reduce pointer dimensions + // The -Handle suffix will be applied later by PrettifyNames if the user configures it to do so ctx.SourceProject = project; await LocationTransformationUtils.ModifyAllReferencesAsync( ctx, diff --git a/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs new file mode 100644 index 0000000000..03989bda03 --- /dev/null +++ b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +namespace Silk.NET.SilkTouch.Profiling; + +/// +/// Lightweight way to profile a section of code. +/// +internal readonly struct ProfilingScope : IDisposable +{ + private readonly string _name; + private readonly long _timestamp; + + public ProfilingScope(string name) + { + _name = name; + _timestamp = Stopwatch.GetTimestamp(); + } + + public void Dispose() + { + var elapsed = Stopwatch.GetElapsedTime(_timestamp); + Console.WriteLine( + "Elapsed time for scope \"{0}\": {1:F3} ms", + _name, + elapsed.TotalMilliseconds + ); + } +}