From e7af83e69ea999ecb27775e81652a743dd314e52 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 24 Apr 2025 16:28:18 +0200 Subject: [PATCH 1/2] Extensions: analyzer actions --- .../CSharpDeclarationComputer.cs | 2 +- .../Test/Emit3/Semantics/ExtensionTests.cs | 165 ++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs b/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs index b422629ed39d..85c78efd1a2f 100644 --- a/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs +++ b/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs @@ -102,7 +102,6 @@ private static void ComputeDeclarations( case SyntaxKind.StructDeclaration: case SyntaxKind.RecordDeclaration: case SyntaxKind.RecordStructDeclaration: - // Tracked by https://github.com/dotnet/roslyn/issues/76130 : likely needs work for analyzers { if (associatedSymbol is IMethodSymbol ctor) { @@ -123,6 +122,7 @@ private static void ComputeDeclarations( goto case SyntaxKind.InterfaceDeclaration; } case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ExtensionDeclaration: { var t = (TypeDeclarationSyntax)node; foreach (var decl in t.Members) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index af139c7a228f..26e80a19b056 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -5,12 +5,15 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.VisualBasic; @@ -36601,4 +36604,166 @@ static unsafe class E Assert.False(verifier.HasLocalsInit("E.M")); Assert.True(verifier.HasLocalsInit("E.M2")); } + + [Fact] + public void AnalyzerActions_01() + { + var src = """ +static class E +{ + extension([Attr] T t) + { + [Attr2] + public void M() { } + + [Attr3] + public int P => 0; + } +} +"""; + + var analyzer = new AnalyzerActions_01_Analyzer(); + var comp = CreateCompilation(src); + comp.GetAnalyzerDiagnostics([analyzer], null).Verify(); + + AssertEx.SetEqual([ + "Attr2 -> void E.<>E__0.M()", + "M -> void E.<>E__0.M()", + "Attr3 -> System.Int32 E.<>E__0.P { get; }", + "P -> System.Int32 E.<>E__0.P { get; }", + "T -> E.<>E__0", + "Attr -> E.<>E__0", + "extension -> E.<>E__0"], + analyzer._results.ToArray()); + } + + private class AnalyzerActions_01_Analyzer : DiagnosticAnalyzer + { + public ConcurrentQueue _results = new ConcurrentQueue(); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics => [Descriptor]; + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(handle, SyntaxKind.ExtensionDeclaration); + context.RegisterSyntaxNodeAction(handle, SyntaxKind.IdentifierName); + context.RegisterSyntaxNodeAction(handle, SyntaxKind.MethodDeclaration); + context.RegisterSyntaxNodeAction(handle, SyntaxKind.PropertyDeclaration); + + void handle(SyntaxNodeAnalysisContext context) + { + _results.Enqueue(print(context)); + Assert.Same(context.Node.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + static string print(SyntaxNodeAnalysisContext context) + { + var syntaxString = context.Node switch + { + ExtensionDeclarationSyntax => "extension", + MethodDeclarationSyntax method => method.Identifier.ValueText, + PropertyDeclarationSyntax property => property.Identifier.ValueText, + _ => context.Node.ToString() + }; + + return $"{syntaxString} -> {context.ContainingSymbol.ToTestDisplayString()}"; + } + } + } + + [Fact] + public void AnalyzerActions_02() + { + var src = """ +static class E +{ + extension(T t) + { + public void M() { } + public int P => 0; + } +} +"""; + + var analyzer = new AnalyzerActions_02_Analyzer(); + var comp = CreateCompilation(src); + comp.GetAnalyzerDiagnostics([analyzer], null).Verify(); + + AssertEx.SetEqual([ + "System.Int32 E.<>E__0.P.get", + "void E.<>E__0.M()", + "E.<>E__0", + "System.Int32 E.<>E__0.P { get; }", + "E"], + analyzer._results.ToArray()); + } + + private class AnalyzerActions_02_Analyzer : DiagnosticAnalyzer + { + public ConcurrentQueue _results = new ConcurrentQueue(); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics => [Descriptor]; + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(handle, SymbolKind.NamedType); + context.RegisterSymbolAction(handle, SymbolKind.Method); + context.RegisterSymbolAction(handle, SymbolKind.Property); + + void handle(SymbolAnalysisContext context) + { + _results.Enqueue(context.Symbol.ToTestDisplayString()); + } + } + } + + [Fact] + public void AnalyzerActions_03() + { + var src = """ +static class E +{ + extension(T t) + { + public void M() { } + public int P { get { return 0; } } + } +} +"""; + + var analyzer = new AnalyzerActions_03_Analyzer(); + var comp = CreateCompilation(src); + comp.GetAnalyzerDiagnostics([analyzer], null).Verify(); + + AssertEx.SetEqual([ + "public void M() { } -> void E.<>E__0.M()", + "get { return 0; } -> System.Int32 E.<>E__0.P.get"], + analyzer._results.ToArray()); + } + + private class AnalyzerActions_03_Analyzer : DiagnosticAnalyzer + { + public ConcurrentQueue _results = new ConcurrentQueue(); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics => [Descriptor]; + + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationAction(handle, OperationKind.MethodBody); + + void handle(OperationAnalysisContext context) + { + _results.Enqueue($"{context.Operation.Syntax.ToString()} -> {context.ContainingSymbol.ToTestDisplayString()}"); + } + } + } } From 61c58d1ce1a7bc6c8ceb81f014d2b8ceaaca2cf7 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 8 May 2025 20:06:15 -0700 Subject: [PATCH 2/2] Add test for Start/End actions. Fix RegisterSymbolAction for parameters --- .../Test/Emit3/Semantics/ExtensionTests.cs | 79 ++++++++++++++++++- .../DiagnosticStartAnalysisScope.cs | 11 ++- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs index 9672a44dbeab..9f5f6d970615 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/ExtensionTests.cs @@ -37403,9 +37403,11 @@ static class E { extension(T t) { - public void M() { } + public void M(int i) { } public int P => 0; } + extension(__arglist) { } + extension(object o1, object o2) { } } """; @@ -37414,11 +37416,16 @@ public void M() { } comp.GetAnalyzerDiagnostics([analyzer], null).Verify(); AssertEx.SetEqual([ - "System.Int32 E.<>E__0.P.get", - "void E.<>E__0.M()", + "E", "E.<>E__0", "System.Int32 E.<>E__0.P { get; }", - "E"], + "T t", + "E.<>E__1", + "E.<>E__2", + "System.Object o1", + "void E.<>E__0.M(System.Int32 i)", + "System.Int32 i", + "System.Int32 E.<>E__0.P.get"], analyzer._results.ToArray()); } @@ -37434,6 +37441,8 @@ private class AnalyzerActions_02_Analyzer : DiagnosticAnalyzer public override void Initialize(AnalysisContext context) { context.RegisterSymbolAction(handle, SymbolKind.NamedType); + context.RegisterSymbolAction(handle, SymbolKind.Parameter); + context.RegisterSymbolAction(handle, SymbolKind.TypeParameter); context.RegisterSymbolAction(handle, SymbolKind.Method); context.RegisterSymbolAction(handle, SymbolKind.Property); @@ -37487,4 +37496,66 @@ void handle(OperationAnalysisContext context) } } } + + [Fact] + public void AnalyzerActions_04() + { + var src = """ +static class E +{ + extension(T t) + { + public void M(int i) { } + public int P { get { return 0; } } + } +} +"""; + + var analyzer = new AnalyzerActions_04_Analyzer(); + var comp = CreateCompilation(src); + comp.GetAnalyzerDiagnostics([analyzer], null).Verify(); + + AssertEx.SetEqual([ + "Start: E", + "Start: E.<>E__0", + "Start: void E.<>E__0.M(System.Int32 i)", + "Start: System.Int32 E.<>E__0.P { get; }", + "Start: System.Int32 E.<>E__0.P.get", + "End: System.Int32 E.<>E__0.P { get; }", + "End: System.Int32 E.<>E__0.P.get", + "End: void E.<>E__0.M(System.Int32 i)", + "End: E.<>E__0", + "End: E"], + analyzer._results.ToArray()); + } + + private class AnalyzerActions_04_Analyzer : DiagnosticAnalyzer + { + public ConcurrentQueue _results = new ConcurrentQueue(); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics => [Descriptor]; + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolStartAction(handleStart, SymbolKind.NamedType); + context.RegisterSymbolStartAction(handleStart, SymbolKind.Method); + context.RegisterSymbolStartAction(handleStart, SymbolKind.Property); + context.RegisterSymbolStartAction(handleStart, SymbolKind.Parameter); + context.RegisterSymbolStartAction(handleStart, SymbolKind.TypeParameter); + + void handleStart(SymbolStartAnalysisContext context) + { + _results.Enqueue($"Start: {context.Symbol.ToTestDisplayString()}"); + context.RegisterSymbolEndAction(handleEnd); + } + + void handleEnd(SymbolAnalysisContext context) + { + _results.Enqueue($"End: {context.Symbol.ToTestDisplayString()}"); + } + } + } } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs index ba5874c3d1d0..db530e231f9b 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/DiagnosticStartAnalysisScope.cs @@ -549,8 +549,15 @@ public void RegisterSymbolAction(Action action, Immutable break; case SymbolKind.NamedType: var namedType = (INamedTypeSymbol)context.Symbol; - var delegateInvokeMethod = namedType.DelegateInvokeMethod; - parameters = delegateInvokeMethod?.Parameters ?? ImmutableArray.Create(); + if (namedType.IsExtension) + { + parameters = namedType.ExtensionParameter is { } extensionParameter ? [extensionParameter] : []; + } + else + { + var delegateInvokeMethod = namedType.DelegateInvokeMethod; + parameters = delegateInvokeMethod?.Parameters ?? ImmutableArray.Create(); + } break; default: throw new ArgumentException($"{context.Symbol.Kind} is not supported.", nameof(context));