From 5d1ba27f533402dc9b9616765ecc23b29d81abfa Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Tue, 28 Jul 2020 15:31:31 -0700 Subject: [PATCH] Adjust records behavior around IOperations and analyzer actions. (#46308) --- .../CSharpDeclarationComputer.cs | 22 +- .../Compilation/CSharpSemanticModel.cs | 29 +- .../Compilation/MemberSemanticModel.cs | 5 + .../Compilation/SyntaxTreeSemanticModel.cs | 111 +- .../Test/Semantic/Semantics/RecordTests.cs | 1842 ++++++++++++++++- .../AnalyzerDriver/DeclarationComputer.cs | 5 + .../DiagnosticAnalyzer/AnalyzerDriver.cs | 5 + .../DiagnosticAnalyzer/AnalyzerExecutor.cs | 15 +- .../Test/Utilities/CSharp/CSharpTestBase.cs | 10 +- 9 files changed, 1999 insertions(+), 45 deletions(-) diff --git a/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs b/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs index 49f95ca0644..91ca602a7e1 100644 --- a/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs +++ b/src/Compilers/CSharp/CSharpAnalyzerDriver/CSharpDeclarationComputer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -95,10 +96,29 @@ private static bool InvalidLevel(int? level) return; } + case SyntaxKind.RecordDeclaration: + { + if (associatedSymbol is IMethodSymbol ctor) + { + var recordDeclaration = (RecordDeclarationSyntax)node; + Debug.Assert(ctor.MethodKind == MethodKind.Constructor && recordDeclaration.ParameterList is object); + + var codeBlocks = GetParameterListInitializersAndAttributes(recordDeclaration.ParameterList); + + if (recordDeclaration.BaseList?.Types.FirstOrDefault() is PrimaryConstructorBaseTypeSyntax initializer) + { + codeBlocks = codeBlocks.Concat(initializer); + } + + builder.Add(GetDeclarationInfo(node, associatedSymbol, codeBlocks)); + return; + } + + goto case SyntaxKind.ClassDeclaration; + } case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.RecordDeclaration: { var t = (TypeDeclarationSyntax)node; foreach (var decl in t.Members) diff --git a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs index f6b23bd155d..5f5a7d5a12f 100644 --- a/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs @@ -5106,34 +5106,7 @@ internal override void ComputeDeclarationsInNode(SyntaxNode node, ISymbol associ CSharpDeclarationComputer.ComputeDeclarationsInNode(this, associatedSymbol, node, getSymbol, builder, cancellationToken, levelsToCompute); } - internal override Func GetSyntaxNodesToAnalyzeFilter(SyntaxNode declaredNode, ISymbol declaredSymbol) - { - if (declaredNode is CompilationUnitSyntax unit && SimpleProgramNamedTypeSymbol.GetSimpleProgramEntryPoint(Compilation, unit, fallbackToMainEntryPoint: false) is SynthesizedSimpleProgramEntryPointSymbol entryPoint) - { - switch (declaredSymbol.Kind) - { - case SymbolKind.Namespace: - Debug.Assert(((INamespaceSymbol)declaredSymbol).IsGlobalNamespace); - // Do not include top level global statements into a global namespace - return (node) => node.Kind() != SyntaxKind.GlobalStatement || node.Parent != unit; - - case SymbolKind.Method: - Debug.Assert((object)declaredSymbol.GetSymbol() == (object)entryPoint); - // Include only global statements at the top level - return (node) => node.Parent != unit || node.Kind() == SyntaxKind.GlobalStatement; - - case SymbolKind.NamedType: - Debug.Assert((object)declaredSymbol.GetSymbol() == (object)entryPoint.ContainingSymbol); - return (node) => false; - - default: - ExceptionUtilities.UnexpectedValue(declaredSymbol.Kind); - break; - } - } - - return base.GetSyntaxNodesToAnalyzeFilter(declaredNode, declaredSymbol); - } + internal abstract override Func GetSyntaxNodesToAnalyzeFilter(SyntaxNode declaredNode, ISymbol declaredSymbol); protected internal override SyntaxNode GetTopmostNodeForDiagnosticAnalysis(ISymbol symbol, SyntaxNode declaringSyntax) { diff --git a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs index 5452ff3bb4f..ada231c608d 100644 --- a/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs @@ -2323,6 +2323,11 @@ internal override Symbol RemapSymbolIfNecessaryCore(Symbol symbol) } } + internal sealed override Func GetSyntaxNodesToAnalyzeFilter(SyntaxNode declaredNode, ISymbol declaredSymbol) + { + throw ExceptionUtilities.Unreachable; + } + /// /// The incremental binder is used when binding statements. Whenever a statement /// is bound, it checks the bound node cache to see if that statement was bound, diff --git a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs index 1c5c44a5064..e65a222115a 100644 --- a/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs +++ b/src/Compilers/CSharp/Portable/Compilation/SyntaxTreeSemanticModel.cs @@ -178,7 +178,9 @@ internal override IOperation GetOperationWorker(CSharpSyntaxNode node, Cancellat case AccessorDeclarationSyntax accessor: model = (accessor.Body != null || accessor.ExpressionBody != null) ? GetOrAddModel(node) : null; break; - + case RecordDeclarationSyntax { ParameterList: { }, PrimaryConstructorBaseType: { } } recordDeclaration when TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor ctor: + model = GetOrAddModel(recordDeclaration); + break; default: model = this.GetMemberModel(node); break; @@ -1087,10 +1089,9 @@ private MemberSemanticModel CreateMemberModel(CSharpSyntaxNode node) case SyntaxKind.RecordDeclaration: { - var recordType = GetDeclaredSymbol((TypeDeclarationSyntax)node).GetSymbol(); - var symbol = recordType.GetMembersUnordered().OfType().SingleOrDefault(); + SynthesizedRecordConstructor symbol = TryGetSynthesizedRecordConstructor((RecordDeclarationSyntax)node); - if (symbol?.GetSyntax() != node) + if (symbol is null) { return null; } @@ -1250,6 +1251,19 @@ MemberSemanticModel createMethodBodySemanticModel(CSharpSyntaxNode memberDecl, S } } + private SynthesizedRecordConstructor TryGetSynthesizedRecordConstructor(RecordDeclarationSyntax node) + { + NamedTypeSymbol recordType = GetDeclaredType(node); + var symbol = recordType.GetMembersUnordered().OfType().SingleOrDefault(); + + if (symbol?.GetSyntax() != node) + { + return null; + } + + return symbol; + } + private AttributeSemanticModel CreateModelForAttribute(Binder enclosingBinder, AttributeSyntax attribute, MemberSemanticModel containingModel) { AliasSymbol aliasOpt; @@ -2006,7 +2020,14 @@ internal override ImmutableArray GetDeclaredSymbols(BaseFieldDeclaratio MethodSymbol method; - method = (GetDeclaredSymbol(memberDecl, cancellationToken) as IMethodSymbol).GetSymbol(); + if (memberDecl is RecordDeclarationSyntax recordDecl && recordDecl.ParameterList == paramList) + { + method = TryGetSynthesizedRecordConstructor(recordDecl); + } + else + { + method = (GetDeclaredSymbol(memberDecl, cancellationToken) as IMethodSymbol).GetSymbol(); + } if ((object)method == null) { @@ -2334,5 +2355,85 @@ internal override Symbol RemapSymbolIfNecessaryCore(Symbol symbol) var memberModel = GetMemberModel(position); return memberModel?.RemapSymbolIfNecessaryCore(symbol) ?? symbol; } + + internal override Func GetSyntaxNodesToAnalyzeFilter(SyntaxNode declaredNode, ISymbol declaredSymbol) + { + switch (declaredNode) + { + case CompilationUnitSyntax unit when SimpleProgramNamedTypeSymbol.GetSimpleProgramEntryPoint(Compilation, unit, fallbackToMainEntryPoint: false) is SynthesizedSimpleProgramEntryPointSymbol entryPoint: + switch (declaredSymbol.Kind) + { + case SymbolKind.Namespace: + Debug.Assert(((INamespaceSymbol)declaredSymbol).IsGlobalNamespace); + // Do not include top level global statements into a global namespace + return (node) => node.Kind() != SyntaxKind.GlobalStatement || node.Parent != unit; + + case SymbolKind.Method: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)entryPoint); + // Include only global statements at the top level + return (node) => node.Parent != unit || node.Kind() == SyntaxKind.GlobalStatement; + + case SymbolKind.NamedType: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)entryPoint.ContainingSymbol); + return (node) => false; + + default: + ExceptionUtilities.UnexpectedValue(declaredSymbol.Kind); + break; + } + break; + + case RecordDeclarationSyntax recordDeclaration when TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor ctor: + switch (declaredSymbol.Kind) + { + case SymbolKind.Method: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor); + return (node) => + { + // Accept only nodes that either match, or above/below of a 'parameter list'/'base arguments list'. + if (node.Parent == recordDeclaration) + { + return node == recordDeclaration.ParameterList || node == recordDeclaration.BaseList; + } + else if (node.Parent is BaseListSyntax baseList) + { + return node == recordDeclaration.PrimaryConstructorBaseType; + } + else if (node.Parent is PrimaryConstructorBaseTypeSyntax baseType && baseType == recordDeclaration.PrimaryConstructorBaseType) + { + return node == baseType.ArgumentList; + } + + return true; + }; + + case SymbolKind.NamedType: + Debug.Assert((object)declaredSymbol.GetSymbol() == (object)ctor.ContainingSymbol); + // Accept nodes that do not match a 'parameter list'/'base arguments list'. + return (node) => node != recordDeclaration.ParameterList && + !(node.Kind() == SyntaxKind.ArgumentList && node == recordDeclaration.PrimaryConstructorBaseType?.ArgumentList); + + default: + ExceptionUtilities.UnexpectedValue(declaredSymbol.Kind); + break; + } + break; + + case PrimaryConstructorBaseTypeSyntax { Parent: BaseListSyntax { Parent: RecordDeclarationSyntax recordDeclaration } } baseType + when recordDeclaration.PrimaryConstructorBaseType == declaredNode && TryGetSynthesizedRecordConstructor(recordDeclaration) is SynthesizedRecordConstructor ctor: + if ((object)declaredSymbol.GetSymbol() == (object)ctor) + { + // Only 'base arguments list' or nodes below it + return (node) => node != baseType.Type; + } + break; + + case ParameterSyntax param when declaredSymbol.Kind == SymbolKind.Property && param.Parent?.Parent is RecordDeclarationSyntax recordDeclaration && recordDeclaration.ParameterList == param.Parent: + Debug.Assert(declaredSymbol.GetSymbol() is SynthesizedRecordPropertySymbol); + return (node) => false; + } + + return null; + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 341be6e752d..d4d053833a8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -8,9 +8,13 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.FlowAnalysis; +using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.Test.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; @@ -5755,7 +5759,6 @@ static void Main() } }"; comp = CreateCompilation(sourceB, references: new[] { refA }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); - comp.VerifyDiagnostics(); var actualMembers = GetProperties(comp, "C").ToTestDisplayStrings(); AssertEx.Equal(new[] { "System.Type C.EqualityContract { get; }" }, actualMembers); @@ -14823,7 +14826,7 @@ public Base(int X, int Y) public Base() {} } -record C(int X, int Y) : Base(X, Y) +record C(int X, int Y = 123) : Base(X, Y) { int Z = 123; public static void Main() @@ -14832,7 +14835,7 @@ public static void Main() Console.WriteLine(c.Z); } - C(int X, int Y, int Z) : this(X, Y) {} + C(int X, int Y, int Z = 124) : this(X, Y) {} }"; var verifier = CompileAndVerify(src, expectedOutput: @" 1 @@ -14872,7 +14875,7 @@ .maxstack 3 var symbol = model.GetSymbolInfo(x).Symbol; Assert.Equal(SymbolKind.Parameter, symbol!.Kind); Assert.Equal("System.Int32 X", symbol.ToTestDisplayString()); - Assert.Equal("C..ctor(System.Int32 X, System.Int32 Y)", symbol.ContainingSymbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X, [System.Int32 Y = 123])", symbol.ContainingSymbol.ToTestDisplayString()); Assert.Equal(Accessibility.Public, symbol.ContainingSymbol.DeclaredAccessibility); Assert.Same(symbol.ContainingSymbol, model.GetEnclosingSymbol(x.SpanStart)); Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); @@ -14902,17 +14905,153 @@ .maxstack 3 model = comp.GetSemanticModel(tree); Assert.Empty(model.GetMemberGroup(baseWithargs)); model = comp.GetSemanticModel(tree); + +#nullable disable + var operation = model.GetOperation(baseWithargs); + + VerifyOperationTree(comp, operation, +@" +IInvocationOperation ( Base..ctor(System.Int32 X, System.Int32 Y)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Base(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: Base, IsImplicit) (Syntax: 'Base(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"); + + Assert.Null(model.GetOperation(baseWithargs.Type)); + Assert.Null(model.GetOperation(baseWithargs.Parent)); + Assert.Same(operation.Parent.Parent, model.GetOperation(baseWithargs.Parent.Parent)); + Assert.Equal(SyntaxKind.RecordDeclaration, baseWithargs.Parent.Parent.Kind()); + + VerifyOperationTree(comp, operation.Parent.Parent, +@" +IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'record C(in ... }') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: 'Base(X, Y)') + Expression: + IInvocationOperation ( Base..ctor(System.Int32 X, System.Int32 Y)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Base(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: Base, IsImplicit) (Syntax: 'Base(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'record C(in ... }') + ExpressionBody: + null +"); + + Assert.Null(operation.Parent.Parent.Parent); + ControlFlowGraphVerifier.VerifyGraph(comp, +@" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] +Block[B1] - Block + Predecessors: [B0] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: 'Base(X, Y)') + Expression: + IInvocationOperation ( Base..ctor(System.Int32 X, System.Int32 Y)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Base(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: Base, IsImplicit) (Syntax: 'Base(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + Next (Regular) Block[B2] +Block[B2] - Exit + Predecessors: [B1] + Statements (0) +", ControlFlowGraph.Create((IConstructorBodyOperation)operation.Parent.Parent)); + + var equalsValue = tree.GetRoot().DescendantNodes().OfType().First(); + + Assert.Equal("= 123", equalsValue.ToString()); + model.VerifyOperationTree(equalsValue, +@" +IParameterInitializerOperation (Parameter: [System.Int32 Y = 123]) (OperationKind.ParameterInitializer, Type: null) (Syntax: '= 123') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 123) (Syntax: '123') +"); +#nullable enable } { var baseWithargs = tree.GetRoot().DescendantNodes().OfType().Single(); Assert.Equal(": this(X, Y)", baseWithargs.ToString()); - Assert.Equal("C..ctor(System.Int32 X, System.Int32 Y)", model.GetSymbolInfo((SyntaxNode)baseWithargs).Symbol.ToTestDisplayString()); - Assert.Equal("C..ctor(System.Int32 X, System.Int32 Y)", model.GetSymbolInfo(baseWithargs).Symbol.ToTestDisplayString()); - Assert.Equal("C..ctor(System.Int32 X, System.Int32 Y)", CSharpExtensions.GetSymbolInfo(model, baseWithargs).Symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X, [System.Int32 Y = 123])", model.GetSymbolInfo((SyntaxNode)baseWithargs).Symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X, [System.Int32 Y = 123])", model.GetSymbolInfo(baseWithargs).Symbol.ToTestDisplayString()); + Assert.Equal("C..ctor(System.Int32 X, [System.Int32 Y = 123])", CSharpExtensions.GetSymbolInfo(model, baseWithargs).Symbol.ToTestDisplayString()); Assert.Empty(model.GetMemberGroup((SyntaxNode)baseWithargs).Select(m => m.ToTestDisplayString())); Assert.Empty(model.GetMemberGroup(baseWithargs).Select(m => m.ToTestDisplayString())); Assert.Empty(CSharpExtensions.GetMemberGroup(model, baseWithargs).Select(m => m.ToTestDisplayString())); + + model.VerifyOperationTree(baseWithargs, +@" +IInvocationOperation ( C..ctor(System.Int32 X, [System.Int32 Y = 123])) (OperationKind.Invocation, Type: System.Void) (Syntax: ': this(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: ': this(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"); + + var equalsValue = tree.GetRoot().DescendantNodes().OfType().Last(); + + Assert.Equal("= 124", equalsValue.ToString()); + model.VerifyOperationTree(equalsValue, +@" +IParameterInitializerOperation (Parameter: [System.Int32 Z = 124]) (OperationKind.ParameterInitializer, Type: null) (Syntax: '= 124') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 124) (Syntax: '124') +"); + + model.VerifyOperationTree(baseWithargs.Parent, +@" +IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'C(int X, in ... is(X, Y) {}') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: ': this(X, Y)') + Expression: + IInvocationOperation ( C..ctor(System.Int32 X, [System.Int32 Y = 123])) (OperationKind.Invocation, Type: System.Void) (Syntax: ': this(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: ': this(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null) (Syntax: '{}') + ExpressionBody: + null +"); } } @@ -15082,6 +15221,13 @@ public Base(int X, int Y) Assert.Same("", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); Assert.Empty(model.LookupSymbols(x.SpanStart, name: "X")); Assert.DoesNotContain("X", model.LookupNames(x.SpanStart)); + + var recordDeclarations = tree.GetRoot().DescendantNodes().OfType().Skip(1).ToArray(); + + Assert.Equal("C", recordDeclarations[0].Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclarations[0])); + Assert.Equal("C", recordDeclarations[1].Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclarations[1])); } [Fact] @@ -15136,6 +15282,14 @@ public Base(int X, int Y) Assert.Empty(model.LookupSymbols(x.SpanStart, name: "X")); Assert.DoesNotContain("X", model.LookupNames(x.SpanStart)); } + + var recordDeclarations = tree.GetRoot().DescendantNodes().OfType().Skip(1).ToArray(); + + Assert.Equal("C", recordDeclarations[0].Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclarations[0])); + + Assert.Equal("C", recordDeclarations[1].Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclarations[1])); } [Fact] @@ -15196,6 +15350,35 @@ public Base(int X, int Y) Assert.Same("", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); Assert.Empty(model.LookupSymbols(x.SpanStart, name: "X")); Assert.DoesNotContain("X", model.LookupNames(x.SpanStart)); + + var recordDeclarations = tree.GetRoot().DescendantNodes().OfType().Skip(1).ToArray(); + + Assert.Equal("C", recordDeclarations[0].Identifier.ValueText); + model.VerifyOperationTree(recordDeclarations[0], +@" +IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'partial rec ... }') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: 'Base(X, Y)') + Expression: + IInvocationOperation ( Base..ctor(System.Int32 X, System.Int32 Y)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Base(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: Base, IsImplicit) (Syntax: 'Base(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'partial rec ... }') + ExpressionBody: + null +"); + Assert.Equal("C", recordDeclarations[1].Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclarations[1])); } [Fact] @@ -15256,6 +15439,36 @@ public Base(int X, int Y) Assert.Same("", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); Assert.Empty(model.LookupSymbols(x.SpanStart, name: "X")); Assert.DoesNotContain("X", model.LookupNames(x.SpanStart)); + + var recordDeclarations = tree.GetRoot().DescendantNodes().OfType().Skip(1).ToArray(); + + Assert.Equal("C", recordDeclarations[0].Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclarations[0])); + + Assert.Equal("C", recordDeclarations[1].Identifier.ValueText); + model.VerifyOperationTree(recordDeclarations[1], +@" +IConstructorBodyOperation (OperationKind.ConstructorBody, Type: null) (Syntax: 'partial rec ... }') + Initializer: + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsImplicit) (Syntax: 'Base(X, Y)') + Expression: + IInvocationOperation ( Base..ctor(System.Int32 X, System.Int32 Y)) (OperationKind.Invocation, Type: System.Void) (Syntax: 'Base(X, Y)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: Base, IsImplicit) (Syntax: 'Base(X, Y)') + Arguments(2): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: 'X') + IParameterReferenceOperation: X (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'X') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: Y) (OperationKind.Argument, Type: null) (Syntax: 'Y') + IParameterReferenceOperation: Y (OperationKind.ParameterReference, Type: System.Int32) (Syntax: 'Y') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + BlockBody: + IBlockOperation (0 statements) (OperationKind.Block, Type: null, IsImplicit) (Syntax: 'partial rec ... }') + ExpressionBody: + null +"); } [Fact] @@ -16395,6 +16608,13 @@ static void Main() } }"; var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var recordDeclaration = tree.GetRoot().DescendantNodes().OfType().First(); + Assert.Null(model.GetOperation(recordDeclaration)); + var verifier = CompileAndVerify(comp, expectedOutput: @"True True @@ -16792,6 +17012,14 @@ static void Main() } }"; var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview, options: TestOptions.ReleaseExe); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var recordDeclaration = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + Assert.Equal("B", recordDeclaration.Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclaration)); + var verifier = CompileAndVerify(comp, expectedOutput: @"True False @@ -17926,6 +18154,10 @@ public static void Main() Assert.Equal("System.Int32 C.Z", model.GetEnclosingSymbol(x.SpanStart).ToTestDisplayString()); Assert.Contains(symbol, model.LookupSymbols(x.SpanStart, name: "X")); Assert.Contains("X", model.LookupNames(x.SpanStart)); + + var recordDeclaration = tree.GetRoot().DescendantNodes().OfType().Single(); + Assert.Equal("C", recordDeclaration.Identifier.ValueText); + Assert.Null(model.GetOperation(recordDeclaration)); } [Fact] @@ -18887,5 +19119,1601 @@ public static void Main() Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "int").WithArguments("R2", "T", "int").WithLocation(10, 20) ); } + + [Fact] + public void AnalyzerActions_01() + { + var text1 = @" +record A([Attr1]int X = 0) : I1 +{} + +record B([Attr2]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3]int Z = 4) : base(5) + {} +} + +interface I1 {} + +class Attr1 : System.Attribute {} +class Attr2 : System.Attribute {} +class Attr3 : System.Attribute {} +"; + + var analyzer = new AnalyzerActions_01_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount0); + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + Assert.Equal(1, analyzer.FireCount10); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + Assert.Equal(1, analyzer.FireCount13); + Assert.Equal(1, analyzer.FireCount14); + Assert.Equal(1, analyzer.FireCount15); + Assert.Equal(1, analyzer.FireCount16); + Assert.Equal(1, analyzer.FireCount17); + Assert.Equal(1, analyzer.FireCount18); + Assert.Equal(1, analyzer.FireCount19); + Assert.Equal(1, analyzer.FireCount20); + Assert.Equal(1, analyzer.FireCount21); + Assert.Equal(1, analyzer.FireCount22); + Assert.Equal(1, analyzer.FireCount23); + Assert.Equal(1, analyzer.FireCount24); + Assert.Equal(1, analyzer.FireCount25); + Assert.Equal(1, analyzer.FireCount26); + Assert.Equal(1, analyzer.FireCount27); + Assert.Equal(1, analyzer.FireCount28); + Assert.Equal(1, analyzer.FireCount29); + Assert.Equal(1, analyzer.FireCount30); + Assert.Equal(1, analyzer.FireCount31); + } + + private class AnalyzerActions_01_Analyzer : DiagnosticAnalyzer + { + public int FireCount0; + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + public int FireCount8; + public int FireCount9; + public int FireCount10; + public int FireCount11; + public int FireCount12; + public int FireCount13; + public int FireCount14; + public int FireCount15; + public int FireCount16; + public int FireCount17; + public int FireCount18; + public int FireCount19; + public int FireCount20; + public int FireCount21; + public int FireCount22; + public int FireCount23; + public int FireCount24; + public int FireCount25; + public int FireCount26; + public int FireCount27; + public int FireCount28; + public int FireCount29; + public int FireCount30; + public int FireCount31; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(Handle1, SyntaxKind.NumericLiteralExpression); + context.RegisterSyntaxNodeAction(Handle2, SyntaxKind.EqualsValueClause); + context.RegisterSyntaxNodeAction(Handle3, SyntaxKind.BaseConstructorInitializer); + context.RegisterSyntaxNodeAction(Handle4, SyntaxKind.ConstructorDeclaration); + context.RegisterSyntaxNodeAction(Handle5, SyntaxKind.PrimaryConstructorBaseType); + context.RegisterSyntaxNodeAction(Handle6, SyntaxKind.RecordDeclaration); + context.RegisterSyntaxNodeAction(Handle7, SyntaxKind.IdentifierName); + context.RegisterSyntaxNodeAction(Handle8, SyntaxKind.SimpleBaseType); + context.RegisterSyntaxNodeAction(Handle9, SyntaxKind.ParameterList); + context.RegisterSyntaxNodeAction(Handle10, SyntaxKind.ArgumentList); + } + + protected void Handle1(SyntaxNodeAnalysisContext context) + { + var literal = (LiteralExpressionSyntax)context.Node; + + switch (literal.ToString()) + { + case "0": + Interlocked.Increment(ref FireCount0); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "1": + Interlocked.Increment(ref FireCount1); + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "2": + Interlocked.Increment(ref FireCount2); + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + break; + + case "3": + Interlocked.Increment(ref FireCount3); + Assert.Equal("System.Int32 B.M()", context.ContainingSymbol.ToTestDisplayString()); + break; + case "4": + Interlocked.Increment(ref FireCount4); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "5": + Interlocked.Increment(ref FireCount5); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(literal.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle2(SyntaxNodeAnalysisContext context) + { + var equalsValue = (EqualsValueClauseSyntax)context.Node; + + switch (equalsValue.ToString()) + { + case "= 0": + Interlocked.Increment(ref FireCount15); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "= 1": + Interlocked.Increment(ref FireCount16); + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "= 4": + Interlocked.Increment(ref FireCount6); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(equalsValue.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle3(SyntaxNodeAnalysisContext context) + { + var initializer = (ConstructorInitializerSyntax)context.Node; + + switch (initializer.ToString()) + { + case ": base(5)": + Interlocked.Increment(ref FireCount7); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(initializer.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle4(SyntaxNodeAnalysisContext context) + { + Interlocked.Increment(ref FireCount8); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + } + + protected void Handle5(SyntaxNodeAnalysisContext context) + { + var baseType = (PrimaryConstructorBaseTypeSyntax)context.Node; + + switch (baseType.ToString()) + { + case "A(2)": + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount9); + break; + case "B": + Interlocked.Increment(ref FireCount17); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + + Assert.Same(baseType.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle6(SyntaxNodeAnalysisContext context) + { + var record = (RecordDeclarationSyntax)context.Node; + + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount10); + break; + case "B": + Interlocked.Increment(ref FireCount11); + break; + case "A": + Interlocked.Increment(ref FireCount12); + break; + case "C": + Interlocked.Increment(ref FireCount13); + break; + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount14); + break; + default: + Assert.True(false); + break; + } + + Assert.Same(record.SyntaxTree, context.ContainingSymbol!.DeclaringSyntaxReferences.Single().SyntaxTree); + } + + protected void Handle7(SyntaxNodeAnalysisContext context) + { + var identifier = (IdentifierNameSyntax)context.Node; + + switch (identifier.Identifier.ValueText) + { + case "A": + switch (identifier.Parent!.ToString()) + { + case "A(2)": + Interlocked.Increment(ref FireCount18); + Assert.Equal("B", context.ContainingSymbol.ToTestDisplayString()); + break; + case "A": + Interlocked.Increment(ref FireCount19); + Assert.Equal(SyntaxKind.SimpleBaseType, identifier.Parent.Kind()); + Assert.Equal("C", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + break; + case "Attr1": + Interlocked.Increment(ref FireCount24); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "Attr2": + Interlocked.Increment(ref FireCount25); + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "Attr3": + Interlocked.Increment(ref FireCount26); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + } + } + + protected void Handle8(SyntaxNodeAnalysisContext context) + { + var baseType = (SimpleBaseTypeSyntax)context.Node; + + switch (baseType.ToString()) + { + case "I1": + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "A": + Interlocked.Increment(ref FireCount20); + break; + case "B": + Interlocked.Increment(ref FireCount21); + break; + case "C": + Interlocked.Increment(ref FireCount22); + break; + default: + Assert.True(false); + break; + } + break; + case "A": + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "C": + Interlocked.Increment(ref FireCount23); + break; + default: + Assert.True(false); + break; + } + break; + + case "System.Attribute": + break; + + default: + Assert.True(false); + break; + } + } + + protected void Handle9(SyntaxNodeAnalysisContext context) + { + var parameterList = (ParameterListSyntax)context.Node; + + switch (parameterList.ToString()) + { + case "([Attr1]int X = 0)": + Interlocked.Increment(ref FireCount27); + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "([Attr2]int Y = 1)": + Interlocked.Increment(ref FireCount28); + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "([Attr3]int Z = 4)": + Interlocked.Increment(ref FireCount29); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "()": + break; + default: + Assert.True(false); + break; + } + } + + protected void Handle10(SyntaxNodeAnalysisContext context) + { + var argumentList = (ArgumentListSyntax)context.Node; + + switch (argumentList.ToString()) + { + case "(2)": + Interlocked.Increment(ref FireCount30); + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + break; + case "(5)": + Interlocked.Increment(ref FireCount31); + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_02() + { + var text1 = @" +record A(int X = 0) +{} + +record C +{ + C(int Z = 4) + {} +} +"; + + var analyzer = new AnalyzerActions_02_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + } + + private class AnalyzerActions_02_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(Handle, SymbolKind.Method); + context.RegisterSymbolAction(Handle, SymbolKind.Property); + context.RegisterSymbolAction(Handle, SymbolKind.Parameter); + context.RegisterSymbolAction(Handle, SymbolKind.NamedType); + } + + private void Handle(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + break; + case "System.Int32 A.X { get; init; }": + Interlocked.Increment(ref FireCount2); + break; + case "[System.Int32 X = 0]": + Interlocked.Increment(ref FireCount3); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount4); + break; + case "[System.Int32 Z = 4]": + Interlocked.Increment(ref FireCount5); + break; + case "A": + Interlocked.Increment(ref FireCount6); + break; + case "C": + Interlocked.Increment(ref FireCount7); + break; + case "System.Runtime.CompilerServices.IsExternalInit": + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_03() + { + var text1 = @" +record A(int X = 0) +{} + +record C +{ + C(int Z = 4) + {} +} +"; + + var analyzer = new AnalyzerActions_03_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(0, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(0, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + Assert.Equal(1, analyzer.FireCount10); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + } + + private class AnalyzerActions_03_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + public int FireCount8; + public int FireCount9; + public int FireCount10; + public int FireCount11; + public int FireCount12; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolStartAction(Handle1, SymbolKind.Method); + context.RegisterSymbolStartAction(Handle1, SymbolKind.Property); + context.RegisterSymbolStartAction(Handle1, SymbolKind.Parameter); + context.RegisterSymbolStartAction(Handle1, SymbolKind.NamedType); + } + + private void Handle1(SymbolStartAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + context.RegisterSymbolEndAction(Handle2); + break; + case "System.Int32 A.X { get; init; }": + Interlocked.Increment(ref FireCount2); + context.RegisterSymbolEndAction(Handle3); + break; + case "[System.Int32 X = 0]": + Interlocked.Increment(ref FireCount3); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount4); + context.RegisterSymbolEndAction(Handle4); + break; + case "[System.Int32 Z = 4]": + Interlocked.Increment(ref FireCount5); + break; + case "A": + Interlocked.Increment(ref FireCount9); + + Assert.Equal(0, FireCount1); + Assert.Equal(0, FireCount2); + Assert.Equal(0, FireCount6); + Assert.Equal(0, FireCount7); + + context.RegisterSymbolEndAction(Handle5); + break; + case "C": + Interlocked.Increment(ref FireCount10); + + Assert.Equal(0, FireCount4); + Assert.Equal(0, FireCount8); + + context.RegisterSymbolEndAction(Handle6); + break; + case "System.Runtime.CompilerServices.IsExternalInit": + break; + default: + Assert.True(false); + break; + } + } + + private void Handle2(SymbolAnalysisContext context) + { + Assert.Equal("A..ctor([System.Int32 X = 0])", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount6); + } + + private void Handle3(SymbolAnalysisContext context) + { + Assert.Equal("System.Int32 A.X { get; init; }", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount7); + } + + private void Handle4(SymbolAnalysisContext context) + { + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount8); + } + + private void Handle5(SymbolAnalysisContext context) + { + Assert.Equal("A", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount11); + + Assert.Equal(1, FireCount1); + Assert.Equal(1, FireCount2); + Assert.Equal(1, FireCount6); + Assert.Equal(1, FireCount7); + } + + private void Handle6(SymbolAnalysisContext context) + { + Assert.Equal("C", context.Symbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount12); + + Assert.Equal(1, FireCount4); + Assert.Equal(1, FireCount8); + } + } + + [Fact] + public void AnalyzerActions_04() + { + var text1 = @" +record A([Attr1(100)]int X = 0) : I1 +{} + +record B([Attr2(200)]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3(300)]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_04_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(0, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + Assert.Equal(1, analyzer.FireCount10); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + Assert.Equal(1, analyzer.FireCount13); + Assert.Equal(1, analyzer.FireCount14); + Assert.Equal(1, analyzer.FireCount15); + Assert.Equal(1, analyzer.FireCount16); + Assert.Equal(1, analyzer.FireCount17); + } + + private class AnalyzerActions_04_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + public int FireCount8; + public int FireCount9; + public int FireCount10; + public int FireCount11; + public int FireCount12; + public int FireCount13; + public int FireCount14; + public int FireCount15; + public int FireCount16; + public int FireCount17; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationAction(Handle1, OperationKind.ConstructorBody); + context.RegisterOperationAction(Handle2, OperationKind.Invocation); + context.RegisterOperationAction(Handle3, OperationKind.Literal); + context.RegisterOperationAction(Handle4, OperationKind.ParameterInitializer); + context.RegisterOperationAction(Handle5, OperationKind.PropertyInitializer); + context.RegisterOperationAction(Handle5, OperationKind.FieldInitializer); + } + + protected void Handle1(OperationAnalysisContext context) + { + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + Assert.Equal(SyntaxKind.RecordDeclaration, context.Operation.Syntax.Kind()); + break; + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount2); + Assert.Equal(SyntaxKind.RecordDeclaration, context.Operation.Syntax.Kind()); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount3); + Assert.Equal(SyntaxKind.ConstructorDeclaration, context.Operation.Syntax.Kind()); + break; + default: + Assert.True(false); + break; + } + } + + protected void Handle2(OperationAnalysisContext context) + { + switch (context.ContainingSymbol.ToTestDisplayString()) + { + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount4); + Assert.Equal(SyntaxKind.PrimaryConstructorBaseType, context.Operation.Syntax.Kind()); + VerifyOperationTree((CSharpCompilation)context.Compilation, context.Operation, +@" +IInvocationOperation ( A..ctor([System.Int32 X = 0])) (OperationKind.Invocation, Type: System.Void) (Syntax: 'A(2)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: 'A(2)') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: '2') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount5); + Assert.Equal(SyntaxKind.BaseConstructorInitializer, context.Operation.Syntax.Kind()); + VerifyOperationTree((CSharpCompilation)context.Compilation, context.Operation, +@" +IInvocationOperation ( A..ctor([System.Int32 X = 0])) (OperationKind.Invocation, Type: System.Void) (Syntax: ': base(5)') + Instance Receiver: + IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: A, IsImplicit) (Syntax: ': base(5)') + Arguments(1): + IArgumentOperation (ArgumentKind.Explicit, Matching Parameter: X) (OperationKind.Argument, Type: null) (Syntax: '5') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5) (Syntax: '5') + InConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) + OutConversion: CommonConversion (Exists: True, IsIdentity: True, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null) +"); + break; + default: + Assert.True(false); + break; + } + } + + protected void Handle3(OperationAnalysisContext context) + { + switch (context.Operation.Syntax.ToString()) + { + case "100": + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount6); + break; + case "0": + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount7); + break; + case "200": + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount8); + break; + case "1": + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount9); + break; + case "2": + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount10); + break; + case "300": + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount11); + break; + case "4": + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount12); + break; + case "5": + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount13); + break; + case "3": + Assert.Equal("System.Int32 B.M()", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount17); + break; + default: + Assert.True(false); + break; + } + } + + protected void Handle4(OperationAnalysisContext context) + { + switch (context.Operation.Syntax.ToString()) + { + case "= 0": + Assert.Equal("A..ctor([System.Int32 X = 0])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount14); + break; + case "= 1": + Assert.Equal("B..ctor([System.Int32 Y = 1])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount15); + break; + case "= 4": + Assert.Equal("C..ctor([System.Int32 Z = 4])", context.ContainingSymbol.ToTestDisplayString()); + Interlocked.Increment(ref FireCount16); + break; + default: + Assert.True(false); + break; + } + } + + protected void Handle5(OperationAnalysisContext context) + { + Assert.True(false); + } + } + + [Fact] + public void AnalyzerActions_05() + { + var text1 = @" +record A([Attr1(100)]int X = 0) : I1 +{} + +record B([Attr2(200)]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3(300)]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_05_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + } + + private class AnalyzerActions_05_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationBlockAction(Handle); + } + + private void Handle(OperationBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + Assert.Equal(2, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 0", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr1(100)", context.OperationBlocks[1].Syntax.ToString()); + + break; + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount2); + Assert.Equal(3, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 1", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr2(200)", context.OperationBlocks[1].Syntax.ToString()); + + Assert.Equal(OperationKind.Invocation, context.OperationBlocks[2].Kind); + Assert.Equal("A(2)", context.OperationBlocks[2].Syntax.ToString()); + + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount3); + Assert.Equal(4, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 4", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr3(300)", context.OperationBlocks[1].Syntax.ToString()); + + Assert.Equal(OperationKind.Block, context.OperationBlocks[2].Kind); + + Assert.Equal(OperationKind.Invocation, context.OperationBlocks[3].Kind); + Assert.Equal(": base(5)", context.OperationBlocks[3].Syntax.ToString()); + + break; + case "System.Int32 B.M()": + Interlocked.Increment(ref FireCount4); + Assert.Equal(1, context.OperationBlocks.Length); + Assert.Equal(OperationKind.Block, context.OperationBlocks[0].Kind); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_06() + { + var text1 = @" +record A([Attr1(100)]int X = 0) : I1 +{} + +record B([Attr2(200)]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3(300)]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_06_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount100); + Assert.Equal(1, analyzer.FireCount200); + Assert.Equal(1, analyzer.FireCount300); + Assert.Equal(1, analyzer.FireCount400); + + Assert.Equal(0, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + Assert.Equal(1, analyzer.FireCount10); + Assert.Equal(1, analyzer.FireCount11); + Assert.Equal(1, analyzer.FireCount12); + Assert.Equal(1, analyzer.FireCount13); + Assert.Equal(1, analyzer.FireCount14); + Assert.Equal(1, analyzer.FireCount15); + Assert.Equal(1, analyzer.FireCount16); + Assert.Equal(1, analyzer.FireCount17); + + Assert.Equal(1, analyzer.FireCount1000); + Assert.Equal(1, analyzer.FireCount2000); + Assert.Equal(1, analyzer.FireCount3000); + Assert.Equal(1, analyzer.FireCount4000); + } + + private class AnalyzerActions_06_Analyzer : AnalyzerActions_04_Analyzer + { + public int FireCount100; + public int FireCount200; + public int FireCount300; + public int FireCount400; + + public int FireCount1000; + public int FireCount2000; + public int FireCount3000; + public int FireCount4000; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterOperationBlockStartAction(Handle); + } + + private void Handle(OperationBlockStartAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount100); + Assert.Equal(2, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 0", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr1(100)", context.OperationBlocks[1].Syntax.ToString()); + + RegisterOperationAction(context); + context.RegisterOperationBlockEndAction(Handle6); + break; + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount200); + Assert.Equal(3, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 1", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr2(200)", context.OperationBlocks[1].Syntax.ToString()); + + Assert.Equal(OperationKind.Invocation, context.OperationBlocks[2].Kind); + Assert.Equal("A(2)", context.OperationBlocks[2].Syntax.ToString()); + + RegisterOperationAction(context); + context.RegisterOperationBlockEndAction(Handle6); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount300); + Assert.Equal(4, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 4", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr3(300)", context.OperationBlocks[1].Syntax.ToString()); + + Assert.Equal(OperationKind.Block, context.OperationBlocks[2].Kind); + + Assert.Equal(OperationKind.Invocation, context.OperationBlocks[3].Kind); + Assert.Equal(": base(5)", context.OperationBlocks[3].Syntax.ToString()); + + RegisterOperationAction(context); + context.RegisterOperationBlockEndAction(Handle6); + break; + case "System.Int32 B.M()": + Interlocked.Increment(ref FireCount400); + Assert.Equal(1, context.OperationBlocks.Length); + Assert.Equal(OperationKind.Block, context.OperationBlocks[0].Kind); + RegisterOperationAction(context); + context.RegisterOperationBlockEndAction(Handle6); + break; + default: + Assert.True(false); + break; + } + } + + private void RegisterOperationAction(OperationBlockStartAnalysisContext context) + { + context.RegisterOperationAction(Handle1, OperationKind.ConstructorBody); + context.RegisterOperationAction(Handle2, OperationKind.Invocation); + context.RegisterOperationAction(Handle3, OperationKind.Literal); + context.RegisterOperationAction(Handle4, OperationKind.ParameterInitializer); + context.RegisterOperationAction(Handle5, OperationKind.PropertyInitializer); + context.RegisterOperationAction(Handle5, OperationKind.FieldInitializer); + } + + private void Handle6(OperationBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1000); + Assert.Equal(2, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 0", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr1(100)", context.OperationBlocks[1].Syntax.ToString()); + + break; + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount2000); + Assert.Equal(3, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 1", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr2(200)", context.OperationBlocks[1].Syntax.ToString()); + + Assert.Equal(OperationKind.Invocation, context.OperationBlocks[2].Kind); + Assert.Equal("A(2)", context.OperationBlocks[2].Syntax.ToString()); + + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount3000); + Assert.Equal(4, context.OperationBlocks.Length); + + Assert.Equal(OperationKind.ParameterInitializer, context.OperationBlocks[0].Kind); + Assert.Equal("= 4", context.OperationBlocks[0].Syntax.ToString()); + + Assert.Equal(OperationKind.None, context.OperationBlocks[1].Kind); + Assert.Equal("Attr3(300)", context.OperationBlocks[1].Syntax.ToString()); + + Assert.Equal(OperationKind.Block, context.OperationBlocks[2].Kind); + + Assert.Equal(OperationKind.Invocation, context.OperationBlocks[3].Kind); + Assert.Equal(": base(5)", context.OperationBlocks[3].Syntax.ToString()); + + break; + case "System.Int32 B.M()": + Interlocked.Increment(ref FireCount4000); + Assert.Equal(1, context.OperationBlocks.Length); + Assert.Equal(OperationKind.Block, context.OperationBlocks[0].Kind); + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_07() + { + var text1 = @" +record A([Attr1(100)]int X = 0) : I1 +{} + +record B([Attr2(200)]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3(300)]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_07_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + } + + private class AnalyzerActions_07_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterCodeBlockAction(Handle); + } + + private void Handle(CodeBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount1); + break; + default: + Assert.True(false); + break; + } + break; + case "B..ctor([System.Int32 Y = 1])": + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "B" } }: + Interlocked.Increment(ref FireCount2); + break; + default: + Assert.True(false); + break; + } + break; + case "C..ctor([System.Int32 Z = 4])": + switch (context.CodeBlock) + { + case ConstructorDeclarationSyntax { Identifier: { ValueText: "C" } }: + Interlocked.Increment(ref FireCount3); + break; + default: + Assert.True(false); + break; + } + break; + case "System.Int32 B.M()": + switch (context.CodeBlock) + { + case MethodDeclarationSyntax { Identifier: { ValueText: "M" } }: + Interlocked.Increment(ref FireCount4); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_08() + { + var text1 = @" +record A([Attr1]int X = 0) : I1 +{} + +record B([Attr2]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_08_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount100); + Assert.Equal(1, analyzer.FireCount200); + Assert.Equal(1, analyzer.FireCount300); + Assert.Equal(1, analyzer.FireCount400); + + Assert.Equal(1, analyzer.FireCount0); + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(0, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + Assert.Equal(0, analyzer.FireCount10); + Assert.Equal(0, analyzer.FireCount11); + Assert.Equal(0, analyzer.FireCount12); + Assert.Equal(0, analyzer.FireCount13); + Assert.Equal(0, analyzer.FireCount14); + Assert.Equal(1, analyzer.FireCount15); + Assert.Equal(1, analyzer.FireCount16); + Assert.Equal(0, analyzer.FireCount17); + Assert.Equal(0, analyzer.FireCount18); + Assert.Equal(0, analyzer.FireCount19); + Assert.Equal(0, analyzer.FireCount20); + Assert.Equal(0, analyzer.FireCount21); + Assert.Equal(0, analyzer.FireCount22); + Assert.Equal(0, analyzer.FireCount23); + Assert.Equal(1, analyzer.FireCount24); + Assert.Equal(1, analyzer.FireCount25); + Assert.Equal(1, analyzer.FireCount26); + Assert.Equal(0, analyzer.FireCount27); + Assert.Equal(0, analyzer.FireCount28); + Assert.Equal(0, analyzer.FireCount29); + Assert.Equal(1, analyzer.FireCount30); + Assert.Equal(1, analyzer.FireCount31); + + Assert.Equal(1, analyzer.FireCount1000); + Assert.Equal(1, analyzer.FireCount2000); + Assert.Equal(1, analyzer.FireCount3000); + Assert.Equal(1, analyzer.FireCount4000); + } + + private class AnalyzerActions_08_Analyzer : AnalyzerActions_01_Analyzer + { + public int FireCount100; + public int FireCount200; + public int FireCount300; + public int FireCount400; + + public int FireCount1000; + public int FireCount2000; + public int FireCount3000; + public int FireCount4000; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterCodeBlockStartAction(Handle); + } + + private void Handle(CodeBlockStartAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount100); + break; + default: + Assert.True(false); + break; + } + break; + case "B..ctor([System.Int32 Y = 1])": + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "B" } }: + Interlocked.Increment(ref FireCount200); + break; + default: + Assert.True(false); + break; + } + break; + case "C..ctor([System.Int32 Z = 4])": + switch (context.CodeBlock) + { + case ConstructorDeclarationSyntax { Identifier: { ValueText: "C" } }: + Interlocked.Increment(ref FireCount300); + break; + default: + Assert.True(false); + break; + } + break; + case "System.Int32 B.M()": + switch (context.CodeBlock) + { + case MethodDeclarationSyntax { Identifier: { ValueText: "M" } }: + Interlocked.Increment(ref FireCount400); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + + context.RegisterSyntaxNodeAction(Handle1, SyntaxKind.NumericLiteralExpression); + context.RegisterSyntaxNodeAction(Handle2, SyntaxKind.EqualsValueClause); + context.RegisterSyntaxNodeAction(Handle3, SyntaxKind.BaseConstructorInitializer); + context.RegisterSyntaxNodeAction(Handle4, SyntaxKind.ConstructorDeclaration); + context.RegisterSyntaxNodeAction(Handle5, SyntaxKind.PrimaryConstructorBaseType); + context.RegisterSyntaxNodeAction(Handle6, SyntaxKind.RecordDeclaration); + context.RegisterSyntaxNodeAction(Handle7, SyntaxKind.IdentifierName); + context.RegisterSyntaxNodeAction(Handle8, SyntaxKind.SimpleBaseType); + context.RegisterSyntaxNodeAction(Handle9, SyntaxKind.ParameterList); + context.RegisterSyntaxNodeAction(Handle10, SyntaxKind.ArgumentList); + + context.RegisterCodeBlockEndAction(Handle11); + } + + private void Handle11(CodeBlockAnalysisContext context) + { + switch (context.OwningSymbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "A" } }: + Interlocked.Increment(ref FireCount1000); + break; + default: + Assert.True(false); + break; + } + break; + case "B..ctor([System.Int32 Y = 1])": + switch (context.CodeBlock) + { + case RecordDeclarationSyntax { Identifier: { ValueText: "B" } }: + Interlocked.Increment(ref FireCount2000); + break; + default: + Assert.True(false); + break; + } + break; + case "C..ctor([System.Int32 Z = 4])": + switch (context.CodeBlock) + { + case ConstructorDeclarationSyntax { Identifier: { ValueText: "C" } }: + Interlocked.Increment(ref FireCount3000); + break; + default: + Assert.True(false); + break; + } + break; + case "System.Int32 B.M()": + switch (context.CodeBlock) + { + case MethodDeclarationSyntax { Identifier: { ValueText: "M" } }: + Interlocked.Increment(ref FireCount4000); + break; + default: + Assert.True(false); + break; + } + break; + default: + Assert.True(false); + break; + } + } + } + + [Fact] + public void AnalyzerActions_09() + { + var text1 = @" +record A([Attr1(100)]int X = 0) : I1 +{} + +record B([Attr2(200)]int Y = 1) : A(2), I1 +{ + int M() => 3; +} + +record C : A, I1 +{ + C([Attr3(300)]int Z = 4) : base(5) + {} +} + +interface I1 {} +"; + + var analyzer = new AnalyzerActions_09_Analyzer(); + var comp = CreateCompilation(text1); + comp.GetAnalyzerDiagnostics(new[] { analyzer }, null).Verify(); + + Assert.Equal(1, analyzer.FireCount1); + Assert.Equal(1, analyzer.FireCount2); + Assert.Equal(1, analyzer.FireCount3); + Assert.Equal(1, analyzer.FireCount4); + Assert.Equal(1, analyzer.FireCount5); + Assert.Equal(1, analyzer.FireCount6); + Assert.Equal(1, analyzer.FireCount7); + Assert.Equal(1, analyzer.FireCount8); + Assert.Equal(1, analyzer.FireCount9); + } + + private class AnalyzerActions_09_Analyzer : DiagnosticAnalyzer + { + public int FireCount1; + public int FireCount2; + public int FireCount3; + public int FireCount4; + public int FireCount5; + public int FireCount6; + public int FireCount7; + public int FireCount8; + public int FireCount9; + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor("XY0000", "Test", "Test", "Test", DiagnosticSeverity.Warning, true, "Test", "Test"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(Descriptor); + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(Handle1, SymbolKind.Method); + context.RegisterSymbolAction(Handle2, SymbolKind.Property); + context.RegisterSymbolAction(Handle3, SymbolKind.Parameter); + } + + private void Handle1(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "A..ctor([System.Int32 X = 0])": + Interlocked.Increment(ref FireCount1); + break; + case "B..ctor([System.Int32 Y = 1])": + Interlocked.Increment(ref FireCount2); + break; + case "C..ctor([System.Int32 Z = 4])": + Interlocked.Increment(ref FireCount3); + break; + case "System.Int32 B.M()": + Interlocked.Increment(ref FireCount4); + break; + default: + Assert.True(false); + break; + } + } + + private void Handle2(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "System.Int32 A.X { get; init; }": + Interlocked.Increment(ref FireCount5); + break; + case "System.Int32 B.Y { get; init; }": + Interlocked.Increment(ref FireCount6); + break; + default: + Assert.True(false); + break; + } + } + + private void Handle3(SymbolAnalysisContext context) + { + switch (context.Symbol.ToTestDisplayString()) + { + case "[System.Int32 X = 0]": + Interlocked.Increment(ref FireCount7); + break; + case "[System.Int32 Y = 1]": + Interlocked.Increment(ref FireCount8); + break; + case "[System.Int32 Z = 4]": + Interlocked.Increment(ref FireCount9); + break; + default: + Assert.True(false); + break; + } + } + } } } diff --git a/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs b/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs index 753de2b1efa..ce4a57eb6c9 100644 --- a/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs +++ b/src/Compilers/Core/AnalyzerDriver/DeclarationComputer.cs @@ -18,6 +18,11 @@ internal class DeclarationComputer internal static DeclarationInfo GetDeclarationInfo(SemanticModel model, SyntaxNode node, bool getSymbol, IEnumerable? executableCodeBlocks, CancellationToken cancellationToken) { var declaredSymbol = GetDeclaredSymbol(model, node, getSymbol, cancellationToken); + return GetDeclarationInfo(node, declaredSymbol, executableCodeBlocks); + } + + internal static DeclarationInfo GetDeclarationInfo(SyntaxNode node, ISymbol? declaredSymbol, IEnumerable? executableCodeBlocks) + { var codeBlocks = executableCodeBlocks?.Where(c => c != null).AsImmutableOrEmpty() ?? ImmutableArray.Empty; return new DeclarationInfo(node, codeBlocks, declaredSymbol); } diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index 3ed956d7c6e..2d241b0e221 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -2767,6 +2767,7 @@ private static bool IsEquivalentSymbol(ISymbol declaredSymbol, ISymbol otherSymb { case OperationKind.MethodBody: case OperationKind.ConstructorBody: + Debug.Assert(!operationBlock.Parent.IsImplicit); operationsToAnalyze.Add(operationBlock.Parent); break; @@ -2776,6 +2777,10 @@ private static bool IsEquivalentSymbol(ISymbol declaredSymbol, ISymbol otherSymb Debug.Assert(operationBlock.Parent.IsImplicit); Debug.Assert(operationBlock.Parent.Parent is IConstructorBodyOperation ctorBody && ctorBody.Initializer == operationBlock.Parent); + Debug.Assert(!operationBlock.Parent.Parent.IsImplicit); + + operationsToAnalyze.Add(operationBlock.Parent.Parent); + break; default: diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs index 1a0856b8abf..ad1408f9e6e 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerExecutor.cs @@ -943,7 +943,20 @@ public void MarkSymbolEndAnalysisComplete(ISymbol symbol, DiagnosticAnalyzer ana { ExecuteBlockActionsCore, CodeBlockAnalyzerAction, SyntaxNodeAnalyzerAction, SyntaxNodeAnalyzerStateData, SyntaxNode, TLanguageKindEnum>( codeBlockStartActions, codeBlockActions, codeBlockEndActions, analyzer, - declaredNode, declaredSymbol, executableCodeBlocks, (codeBlocks) => codeBlocks.SelectMany(cb => cb.DescendantNodesAndSelf()), + declaredNode, declaredSymbol, executableCodeBlocks, (codeBlocks) => codeBlocks.SelectMany( + cb => + { + var filter = semanticModel.GetSyntaxNodesToAnalyzeFilter(cb, declaredSymbol); + + if (filter is object) + { + return cb.DescendantNodesAndSelf(descendIntoChildren: filter).Where(filter); + } + else + { + return cb.DescendantNodesAndSelf(); + } + }), semanticModel, getKind, analyzerStateOpt?.CodeBlockAnalysisState, isGeneratedCode); return true; } diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index 6765faee117..3cc56f22578 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -1769,9 +1769,13 @@ protected static IOperation VerifyOperationTreeForTest(CSharpCompil protected static void VerifyOperationTreeForNode(CSharpCompilation compilation, SemanticModel model, SyntaxNode syntaxNode, string expectedOperationTree) { - var actualOperation = model.GetOperation(syntaxNode); - Assert.NotNull(actualOperation); - var actualOperationTree = GetOperationTreeForTest(compilation, actualOperation); + VerifyOperationTree(compilation, model.GetOperation(syntaxNode), expectedOperationTree); + } + + protected static void VerifyOperationTree(CSharpCompilation compilation, IOperation operation, string expectedOperationTree) + { + Assert.NotNull(operation); + var actualOperationTree = GetOperationTreeForTest(compilation, operation); OperationTreeVerifier.Verify(expectedOperationTree, actualOperationTree); } -- GitLab