From 7938a0cbb3a52005a361de0e338098f18babeb42 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 13 Apr 2026 20:07:36 -0400 Subject: [PATCH 01/12] Update ClangSharp.PInvokeGenerator --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 @@ - + From 78b8ec4a6ace41b3331ebca8fda05d8f4e599829 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 13 Apr 2026 21:48:16 -0400 Subject: [PATCH 02/12] Update comment on ModifyAllReferencesAsync in TransformHandles --- sources/SilkTouch/SilkTouch/Mods/TransformHandles.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, From a11ef49ae9ee31e49c560d0d1090c1bfbd1d24c0 Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 13 Apr 2026 23:32:04 -0400 Subject: [PATCH 03/12] Format files using CSharpier These were written before I installed the CSharpier plugin so they were not formatted. --- .../IdentifierRenamingTransformer.cs | 23 ++++++--- .../LocationTransformationRewriter.cs | 19 +++++-- .../LocationTransformationUtils.cs | 51 ++++++++++++------- .../LocationTransformer.cs | 5 +- 4 files changed, 67 insertions(+), 31 deletions(-) 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..a5ae9006c6 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -18,7 +18,10 @@ namespace Silk.NET.SilkTouch.Mods.LocationTransformation; /// /// Symbols to search for. /// Transformers to use on each found symbol reference. -public class LocationTransformationRewriter(HashSet symbols, List transformers) : CSharpSyntaxRewriter +public class LocationTransformationRewriter( + HashSet symbols, + List transformers +) : CSharpSyntaxRewriter { // Symbols can also be referenced within XML doc, which are trivia nodes. /// @@ -66,7 +69,8 @@ public void Initialize(SemanticModel semanticModel) { // Apply deferred transformer var deferredTransformer = transformers[transformation.TransformerIndex]; - modifiedNode = deferredTransformer.Visit(modifiedNode) + modifiedNode = deferredTransformer + .Visit(modifiedNode) .WithLeadingTrivia(unmodifiedNode.GetLeadingTrivia().Select(VisitTrivia)) .WithTrailingTrivia(unmodifiedNode.GetTrailingTrivia()); } @@ -105,13 +109,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()); } @@ -247,7 +255,8 @@ public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSynta /// public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { - var symbol = semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetTypeInfo(node).Type; + var symbol = + semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetTypeInfo(node).Type; ReportSymbol(node, symbol); return base.VisitIdentifierName(node)!; diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs index 8ae9303d72..025c282b38 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,45 @@ 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 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); + var semanticModel = await originalDocument.GetSemanticModelAsync(ct); - if (originalRoot == null || semanticModel == null) - { - return; - } + if (originalRoot == null || semanticModel == null) + { + return; + } - // 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. From 9cf0637d92da248814f0ba1aa9b4cc319580e8ef Mon Sep 17 00:00:00 2001 From: William Chen Date: Mon, 13 Apr 2026 23:56:52 -0400 Subject: [PATCH 04/12] Remove GetTypeInfo call GetTypeInfo should be unnecessary since it gets the type of an expression. For non-type expressions (eg: `5` is an int), this isn't useful for us. This is arguably an indirect reference to int, but the current Silk generator only cares about modifying direct references. For type expressions (eg: `int` refers to int), GetSymbolInfo will also return the same symbol. --- .../LocationTransformation/LocationTransformationRewriter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs index a5ae9006c6..a19e065d33 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -255,8 +255,7 @@ public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSynta /// public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { - var symbol = - semanticModel.GetSymbolInfo(node).Symbol ?? semanticModel.GetTypeInfo(node).Type; + var symbol = semanticModel.GetSymbolInfo(node).Symbol; ReportSymbol(node, symbol); return base.VisitIdentifierName(node)!; From 8c05ec7270578797c1fd3c5c3b64b094e74e4abc Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 00:13:16 -0400 Subject: [PATCH 05/12] Get the compilation once* *Roslyn already caches this internally. This is mainly for avoiding the async method call. Difference is probably not measurable, but theoretically this is faster. --- .../LocationTransformationUtils.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs index 025c282b38..745230ea4f 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationUtils.cs @@ -45,6 +45,12 @@ public static async Task ModifyAllReferencesAsync( ]; 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( @@ -59,13 +65,13 @@ await Parallel.ForEachAsync( } var originalRoot = await originalDocument.GetSyntaxRootAsync(ct); - var semanticModel = await originalDocument.GetSemanticModelAsync(ct); - - if (originalRoot == null || semanticModel == null) + if (originalRoot == 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, From fe50783b8cdd1de953dc415d5d6b3fe8283d2b8b Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 00:19:49 -0400 Subject: [PATCH 06/12] Compare against _relevantIdentifiers set before calling GetSymbolInfo --- .../LocationTransformationRewriter.cs | 85 +++++++++++-------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs index a19e065d33..22dbfaffc0 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -14,39 +14,47 @@ 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; + _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")] @@ -63,12 +71,12 @@ 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]; + var deferredTransformer = _transformers[transformation.TransformerIndex]; modifiedNode = deferredTransformer .Visit(modifiedNode) .WithLeadingTrivia(unmodifiedNode.GetLeadingTrivia().Select(VisitTrivia)) @@ -76,12 +84,12 @@ public void Initialize(SemanticModel semanticModel) } // 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(); @@ -109,7 +117,7 @@ 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( + _queuedTransformations.Add( selectedNode, new QueuedTransformation(transformation.Symbol, i) ); @@ -130,12 +138,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 ----- @@ -143,7 +151,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)!; @@ -152,7 +160,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)!; @@ -161,7 +169,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)!; @@ -170,7 +178,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)!; @@ -179,7 +187,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)!; @@ -188,7 +196,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)!; @@ -199,7 +207,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)!; @@ -208,7 +216,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)!; @@ -217,7 +225,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)!; @@ -226,7 +234,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)!; @@ -235,7 +243,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)!; @@ -244,7 +252,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)!; @@ -255,7 +263,12 @@ public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSynta /// public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node) { - var symbol = semanticModel.GetSymbolInfo(node).Symbol; + if (!_relevantIdentifiers.Contains(node.Identifier.Text)) + { + return node; + } + + var symbol = _semanticModel.GetSymbolInfo(node).Symbol; ReportSymbol(node, symbol); return base.VisitIdentifierName(node)!; @@ -265,7 +278,7 @@ 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)!; From 9cb75e1102a4051c786448d0e7cf97b46738a0ef Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 00:19:57 -0400 Subject: [PATCH 07/12] Skip using directives --- .../LocationTransformation/LocationTransformationRewriter.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs index 22dbfaffc0..200eea8202 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -283,4 +283,9 @@ public override SyntaxNode VisitVariableDeclarator(VariableDeclaratorSyntax node return base.VisitVariableDeclarator(node)!; } + + // ----- Skipped nodes ----- + + /// + public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node) => node; } From 56382211b52b83e958b228073a4ddcd2023c4339 Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 00:29:17 -0400 Subject: [PATCH 08/12] Add ProfilingScope --- .../SilkTouch/Profiling/ProfilingScope.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs diff --git a/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs new file mode 100644 index 0000000000..d7393a5afc --- /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) + { + this.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 + ); + } +} From a0ea115d7dd1907ed3c1bf60f3751485596b2148 Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 00:45:47 -0400 Subject: [PATCH 09/12] Add comment on _relevantIdentifiers --- .../LocationTransformation/LocationTransformationRewriter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs index 200eea8202..e0a921de92 100644 --- a/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs +++ b/sources/SilkTouch/SilkTouch/Mods/LocationTransformation/LocationTransformationRewriter.cs @@ -48,6 +48,9 @@ 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(); } From ed941db1377cef64b6c00085dada8ad1d15a054e Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 14:33:30 -0400 Subject: [PATCH 10/12] Fix field naming convention in ProfilingScope --- .../SilkTouch/SilkTouch/Profiling/ProfilingScope.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs index d7393a5afc..03989bda03 100644 --- a/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs +++ b/sources/SilkTouch/SilkTouch/Profiling/ProfilingScope.cs @@ -10,21 +10,21 @@ namespace Silk.NET.SilkTouch.Profiling; ///
internal readonly struct ProfilingScope : IDisposable { - private readonly string name; - private readonly long timestamp; + private readonly string _name; + private readonly long _timestamp; public ProfilingScope(string name) { - this.name = name; - timestamp = Stopwatch.GetTimestamp(); + _name = name; + _timestamp = Stopwatch.GetTimestamp(); } public void Dispose() { - var elapsed = Stopwatch.GetElapsedTime(timestamp); + var elapsed = Stopwatch.GetElapsedTime(_timestamp); Console.WriteLine( "Elapsed time for scope \"{0}\": {1:F3} ms", - name, + _name, elapsed.TotalMilliseconds ); } From 8d5b978ec39d48223e50045e593e408edd4be614 Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 14:46:08 -0400 Subject: [PATCH 11/12] Optimize document renaming in PrettifyNames Conflict checking was O(n), now it is O(1). --- .../SilkTouch/SilkTouch/Mods/PrettifyNames.cs | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs index 100f43ad25..bcca591f5f 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.FilePath) + .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,23 @@ await proj.GetCompilationAsync(ct) continue; } + // Rename doc and update path var originalName = doc.Name; + var originalPath = doc.FilePath; doc = doc.ReplaceNameAndPath(oldName, newName); - 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(doc.FilePath!)) { 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; From 878a90f1b5e456abb3999911ca0ac7edc20a5bb7 Mon Sep 17 00:00:00 2001 From: William Chen Date: Tue, 14 Apr 2026 14:58:56 -0400 Subject: [PATCH 12/12] Use normalized relative paths during conflict checking This is to prevent false negatives from mismatched path formats. --- sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs index bcca591f5f..da23b4a6dc 100644 --- a/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs +++ b/sources/SilkTouch/SilkTouch/Mods/PrettifyNames.cs @@ -200,7 +200,7 @@ await proj.GetCompilationAsync(ct) var typeNamesLongestFirst = typeNames.OrderByDescending(x => x.Key.Length).ToArray(); var documentPaths = proj - .Documents.Select(d => d.FilePath) + .Documents.Select(d => d.RelativePath()) .Where(d => d != null) .ToHashSet(); @@ -223,12 +223,13 @@ await proj.GetCompilationAsync(ct) // Rename doc and update path var originalName = doc.Name; - var originalPath = doc.FilePath; + var originalPath = doc.RelativePath(); doc = doc.ReplaceNameAndPath(oldName, newName); + var newPath = doc.RelativePath(); // Check for path conflict documentPaths.Remove(originalPath); - if (!documentPaths.Add(doc.FilePath!)) + if (!documentPaths.Add(newPath)) { logger.LogError( $"{originalName} -> {doc.Name} failed to rename file as a file already exists at {doc.FilePath}"