Skip to content

Commit 32060d0

Browse files
authored
Add tests for field keyword semantic model and IOperation (#79945)
1 parent 0140528 commit 32060d0

File tree

3 files changed

+259
-1
lines changed

3 files changed

+259
-1
lines changed

src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Microsoft.CodeAnalysis.CSharp.Syntax;
99
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
1010
using Microsoft.CodeAnalysis.Diagnostics;
11+
using Microsoft.CodeAnalysis.Operations;
1112
using Microsoft.CodeAnalysis.Test.Utilities;
1213
using Roslyn.Test.Utilities;
1314
using System.Collections.Immutable;
@@ -12710,5 +12711,154 @@ public partial class Class1
1271012711
var comp = CreateCompilation([source1, source2]);
1271112712
comp.VerifyEmitDiagnostics();
1271212713
}
12714+
12715+
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
12716+
[InlineData("""get { return field + "a"; }""")]
12717+
[InlineData("""get => field + "a";""")]
12718+
public void PublicAPI_01(string accessor)
12719+
{
12720+
var source = $$"""
12721+
class C
12722+
{
12723+
public string Prop
12724+
{
12725+
{{accessor}}
12726+
}
12727+
}
12728+
""";
12729+
12730+
var comp = CreateCompilation(source);
12731+
comp.VerifyEmitDiagnostics();
12732+
12733+
var tree = comp.SyntaxTrees[0];
12734+
var model = comp.GetSemanticModel(tree);
12735+
var fieldExpression = tree.GetRoot().DescendantNodes().OfType<FieldExpressionSyntax>().Single();
12736+
12737+
var symbolInfo = model.GetSymbolInfo(fieldExpression);
12738+
Assert.Equal("System.String C.<Prop>k__BackingField", symbolInfo.Symbol.ToTestDisplayString());
12739+
}
12740+
12741+
[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
12742+
[InlineData("""set { field = value; }""")]
12743+
[InlineData("""set => field = value;""")]
12744+
public void PublicAPI_02(string accessor)
12745+
{
12746+
var source = $$"""
12747+
class C
12748+
{
12749+
public string Prop
12750+
{
12751+
{{accessor}}
12752+
}
12753+
}
12754+
""";
12755+
12756+
var comp = CreateCompilation(source);
12757+
comp.VerifyEmitDiagnostics();
12758+
12759+
var tree = comp.SyntaxTrees[0];
12760+
var model = comp.GetSemanticModel(tree);
12761+
var fieldExpression = tree.GetRoot().DescendantNodes().OfType<FieldExpressionSyntax>().Single();
12762+
12763+
var symbolInfo = model.GetSymbolInfo(fieldExpression);
12764+
Assert.Equal("System.String C.<Prop>k__BackingField", symbolInfo.Symbol.ToTestDisplayString());
12765+
}
12766+
12767+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
12768+
public void PublicAPI_03()
12769+
{
12770+
var source = $$"""
12771+
class C
12772+
{
12773+
public string Prop
12774+
{
12775+
get => field;
12776+
}
12777+
}
12778+
""";
12779+
12780+
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
12781+
comp.VerifyEmitDiagnostics(
12782+
// (5,16): error CS0103: The name 'field' does not exist in the current context
12783+
// get => field;
12784+
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 16));
12785+
12786+
var tree = comp.SyntaxTrees[0];
12787+
var model = comp.GetSemanticModel(tree);
12788+
Assert.Empty(tree.GetRoot().DescendantNodes().OfType<FieldExpressionSyntax>());
12789+
var fieldExpression = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(node => node.ToString() == "field").Single();
12790+
var symbolInfo = model.GetSymbolInfo(fieldExpression);
12791+
Assert.Null(symbolInfo.Symbol);
12792+
}
12793+
12794+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
12795+
public void PublicAPI_04()
12796+
{
12797+
var source = $$"""
12798+
class C
12799+
{
12800+
public string Prop
12801+
{
12802+
get => field;
12803+
}
12804+
}
12805+
""";
12806+
12807+
var comp = CreateCompilation(source);
12808+
comp.VerifyEmitDiagnostics();
12809+
comp.VerifyAnalyzerDiagnostics(analyzers: [new TestAnalyzer1()],
12810+
expected: [Diagnostic("TEST_Field", "field").WithLocation(5, 16)]);
12811+
12812+
comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
12813+
comp.VerifyEmitDiagnostics(
12814+
// (5,16): error CS0103: The name 'field' does not exist in the current context
12815+
// get => field;
12816+
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 16));
12817+
comp.VerifyAnalyzerDiagnostics(analyzers: [new TestAnalyzer1()],
12818+
expected: [Diagnostic("TEST_Invalid", "field").WithLocation(5, 16)]);
12819+
}
12820+
12821+
private class TestAnalyzer1 : DiagnosticAnalyzer
12822+
{
12823+
public static readonly DiagnosticDescriptor Descriptor_Field = new(id: "TEST_Field", title: "Test", messageFormat: "", category: "", DiagnosticSeverity.Warning, isEnabledByDefault: true);
12824+
public static readonly DiagnosticDescriptor Descriptor_Invalid = new(id: "TEST_Invalid", title: "Test", messageFormat: "", category: "", DiagnosticSeverity.Warning, isEnabledByDefault: true);
12825+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Descriptor_Field, Descriptor_Invalid];
12826+
12827+
public override void Initialize(AnalysisContext context)
12828+
{
12829+
context.RegisterOperationBlockAction(context =>
12830+
{
12831+
foreach (var block in context.OperationBlocks)
12832+
{
12833+
var walker = new OperationWalker1();
12834+
walker.Visit(block);
12835+
12836+
if (walker.FieldReference is not null)
12837+
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor_Field, walker.FieldReference.Syntax.Location));
12838+
12839+
if (walker.Invalid is not null)
12840+
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor_Invalid, walker.Invalid.Syntax.Location));
12841+
}
12842+
});
12843+
}
12844+
}
12845+
12846+
private class OperationWalker1 : OperationWalker
12847+
{
12848+
public IOperation FieldReference = null;
12849+
public IOperation Invalid = null;
12850+
12851+
public override void VisitFieldReference(IFieldReferenceOperation operation)
12852+
{
12853+
FieldReference = operation;
12854+
base.VisitFieldReference(operation);
12855+
}
12856+
12857+
public override void VisitInvalid(IInvalidOperation operation)
12858+
{
12859+
Invalid = operation;
12860+
base.VisitInvalid(operation);
12861+
}
12862+
}
1271312863
}
1271412864
}

src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IFieldReferenceExpression.cs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,5 +715,113 @@ static void testCore(CSharpCompilation comp)
715715
Assert.Equal(fieldSym.GetHashCode(), fieldReferenceOperation.Field.GetHashCode());
716716
}
717717
}
718+
719+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
720+
public void FieldKeyword_01()
721+
{
722+
var source = """
723+
class C
724+
{
725+
public string Prop
726+
{
727+
get
728+
{
729+
/*<bind>*/
730+
return field + "a";
731+
/*</bind>*/
732+
}
733+
}
734+
}
735+
""";
736+
737+
VerifyOperationTreeAndDiagnosticsForTest<StatementSyntax>(
738+
source,
739+
expectedOperationTree: """
740+
IReturnOperation (OperationKind.Return, Type: null) (Syntax: 'return field + "a";')
741+
ReturnedValue:
742+
IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: 'field + "a"')
743+
Left:
744+
IFieldReferenceOperation: System.String C.<Prop>k__BackingField (OperationKind.FieldReference, Type: System.String) (Syntax: 'field')
745+
Instance Receiver:
746+
IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'field')
747+
Right:
748+
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "a") (Syntax: '"a"')
749+
""",
750+
expectedDiagnostics: []);
751+
}
752+
753+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
754+
public void FieldKeyword_02()
755+
{
756+
var source = """
757+
class C
758+
{
759+
public string Prop
760+
{
761+
set
762+
{
763+
/*<bind>*/
764+
field = value;
765+
/*</bind>*/
766+
}
767+
}
768+
}
769+
""";
770+
771+
VerifyOperationTreeAndDiagnosticsForTest<StatementSyntax>(
772+
source,
773+
expectedOperationTree: """
774+
IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'field = value;')
775+
Expression:
776+
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'field = value')
777+
Left:
778+
IFieldReferenceOperation: System.String C.<Prop>k__BackingField (OperationKind.FieldReference, Type: System.String) (Syntax: 'field')
779+
Instance Receiver:
780+
IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'field')
781+
Right:
782+
IParameterReferenceOperation: value (OperationKind.ParameterReference, Type: System.String) (Syntax: 'value')
783+
""",
784+
expectedDiagnostics: []);
785+
}
786+
787+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
788+
public void FieldKeyword_03()
789+
{
790+
var source = """
791+
class C
792+
{
793+
public string Prop
794+
{
795+
get
796+
{
797+
/*<bind>*/
798+
return field + "a";
799+
/*</bind>*/
800+
}
801+
}
802+
}
803+
""";
804+
805+
VerifyOperationTreeAndDiagnosticsForTest<StatementSyntax>(
806+
source,
807+
expectedOperationTree: """
808+
IReturnOperation (OperationKind.Return, Type: null, IsInvalid) (Syntax: 'return field + "a";')
809+
ReturnedValue:
810+
IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsInvalid, IsImplicit) (Syntax: 'field + "a"')
811+
Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
812+
Operand:
813+
IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: ?, IsInvalid) (Syntax: 'field + "a"')
814+
Left:
815+
IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'field')
816+
Children(0)
817+
Right:
818+
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "a") (Syntax: '"a"')
819+
""",
820+
expectedDiagnostics: [
821+
// (8,20): error CS0103: The name 'field' does not exist in the current context
822+
// return field + "a";
823+
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(8, 20)],
824+
parseOptions: TestOptions.Regular13);
825+
}
718826
}
719827
}

src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2613,7 +2613,7 @@ protected static void VerifyFlowGraphForTest<TSyntaxNode>(CSharpCompilation comp
26132613
{
26142614
var tree = compilation.SyntaxTrees[0];
26152615
SyntaxNode? syntaxNode = GetSyntaxNodeOfTypeForBinding<TSyntaxNode>(GetSyntaxNodeList(tree));
2616-
Debug.Assert(syntaxNode is not null, "Did you forget to place /*<bind>*/ comments in your source?");
2616+
Debug.Assert(syntaxNode is not null, $"Ensure a /*<bind>*/ comment is used around syntax matching the type argument for '{nameof(TSyntaxNode)}'.");
26172617
VerifyFlowGraph(compilation, syntaxNode, expectedFlowGraph);
26182618
}
26192619

0 commit comments

Comments
 (0)