Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using System.Collections.Immutable;
Expand Down Expand Up @@ -12710,5 +12711,154 @@ public partial class Class1
var comp = CreateCompilation([source1, source2]);
comp.VerifyEmitDiagnostics();
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
[InlineData("""get { return field + "a"; }""")]
[InlineData("""get => field + "a";""")]
public void PublicAPI_01(string accessor)
{
var source = $$"""
class C
{
public string Prop
{
{{accessor}}
}
}
""";

var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics();

var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var fieldExpression = tree.GetRoot().DescendantNodes().OfType<FieldExpressionSyntax>().Single();

var symbolInfo = model.GetSymbolInfo(fieldExpression);
Assert.Equal("System.String C.<Prop>k__BackingField", symbolInfo.Symbol.ToTestDisplayString());
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
[InlineData("""set { field = value; }""")]
[InlineData("""set => field = value;""")]
public void PublicAPI_02(string accessor)
{
var source = $$"""
class C
{
public string Prop
{
{{accessor}}
}
}
""";

var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics();

var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
var fieldExpression = tree.GetRoot().DescendantNodes().OfType<FieldExpressionSyntax>().Single();

var symbolInfo = model.GetSymbolInfo(fieldExpression);
Assert.Equal("System.String C.<Prop>k__BackingField", symbolInfo.Symbol.ToTestDisplayString());
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
public void PublicAPI_03()
{
var source = $$"""
class C
{
public string Prop
{
get => field;
}
}
""";

var comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
comp.VerifyEmitDiagnostics(
// (5,16): error CS0103: The name 'field' does not exist in the current context
// get => field;
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 16));

var tree = comp.SyntaxTrees[0];
var model = comp.GetSemanticModel(tree);
Assert.Empty(tree.GetRoot().DescendantNodes().OfType<FieldExpressionSyntax>());
var fieldExpression = tree.GetRoot().DescendantNodes().OfType<IdentifierNameSyntax>().Where(node => node.ToString() == "field").Single();
var symbolInfo = model.GetSymbolInfo(fieldExpression);
Assert.Null(symbolInfo.Symbol);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
public void PublicAPI_04()
{
var source = $$"""
class C
{
public string Prop
{
get => field;
}
}
""";

var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics();
comp.VerifyAnalyzerDiagnostics(analyzers: [new TestAnalyzer1()],
expected: [Diagnostic("TEST_Field", "field").WithLocation(5, 16)]);

comp = CreateCompilation(source, parseOptions: TestOptions.Regular13);
comp.VerifyEmitDiagnostics(
// (5,16): error CS0103: The name 'field' does not exist in the current context
// get => field;
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 16));
comp.VerifyAnalyzerDiagnostics(analyzers: [new TestAnalyzer1()],
expected: [Diagnostic("TEST_Invalid", "field").WithLocation(5, 16)]);
}

private class TestAnalyzer1 : DiagnosticAnalyzer
{
public static readonly DiagnosticDescriptor Descriptor_Field = new(id: "TEST_Field", title: "Test", messageFormat: "", category: "", DiagnosticSeverity.Warning, isEnabledByDefault: true);
public static readonly DiagnosticDescriptor Descriptor_Invalid = new(id: "TEST_Invalid", title: "Test", messageFormat: "", category: "", DiagnosticSeverity.Warning, isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Descriptor_Field, Descriptor_Invalid];

public override void Initialize(AnalysisContext context)
{
context.RegisterOperationBlockAction(context =>
{
foreach (var block in context.OperationBlocks)
{
var walker = new OperationWalker1();
walker.Visit(block);

if (walker.FieldReference is not null)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor_Field, walker.FieldReference.Syntax.Location));

if (walker.Invalid is not null)
context.ReportDiagnostic(CodeAnalysis.Diagnostic.Create(Descriptor_Invalid, walker.Invalid.Syntax.Location));
}
});
}
}

private class OperationWalker1 : OperationWalker
{
public IOperation FieldReference = null;
public IOperation Invalid = null;

public override void VisitFieldReference(IFieldReferenceOperation operation)
{
FieldReference = operation;
base.VisitFieldReference(operation);
}

public override void VisitInvalid(IInvalidOperation operation)
{
Invalid = operation;
base.VisitInvalid(operation);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -715,5 +715,113 @@ static void testCore(CSharpCompilation comp)
Assert.Equal(fieldSym.GetHashCode(), fieldReferenceOperation.Field.GetHashCode());
}
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
public void FieldKeyword_01()
{
var source = """
class C
{
public string Prop
{
get
{
/*<bind>*/
return field + "a";
/*</bind>*/
}
}
}
""";

VerifyOperationTreeAndDiagnosticsForTest<StatementSyntax>(
source,
expectedOperationTree: """
IReturnOperation (OperationKind.Return, Type: null) (Syntax: 'return field + "a";')
ReturnedValue:
IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: System.String) (Syntax: 'field + "a"')
Left:
IFieldReferenceOperation: System.String C.<Prop>k__BackingField (OperationKind.FieldReference, Type: System.String) (Syntax: 'field')
Instance Receiver:
IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'field')
Right:
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "a") (Syntax: '"a"')
""",
expectedDiagnostics: []);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
public void FieldKeyword_02()
{
var source = """
class C
{
public string Prop
{
set
{
/*<bind>*/
field = value;
/*</bind>*/
}
}
}
""";

VerifyOperationTreeAndDiagnosticsForTest<StatementSyntax>(
source,
expectedOperationTree: """
IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: 'field = value;')
Expression:
ISimpleAssignmentOperation (OperationKind.SimpleAssignment, Type: System.String) (Syntax: 'field = value')
Left:
IFieldReferenceOperation: System.String C.<Prop>k__BackingField (OperationKind.FieldReference, Type: System.String) (Syntax: 'field')
Instance Receiver:
IInstanceReferenceOperation (ReferenceKind: ContainingTypeInstance) (OperationKind.InstanceReference, Type: C, IsImplicit) (Syntax: 'field')
Right:
IParameterReferenceOperation: value (OperationKind.ParameterReference, Type: System.String) (Syntax: 'value')
""",
expectedDiagnostics: []);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/79201")]
public void FieldKeyword_03()
{
var source = """
class C
{
public string Prop
{
get
{
/*<bind>*/
return field + "a";
/*</bind>*/
}
}
}
""";

VerifyOperationTreeAndDiagnosticsForTest<StatementSyntax>(
source,
expectedOperationTree: """
IReturnOperation (OperationKind.Return, Type: null, IsInvalid) (Syntax: 'return field + "a";')
ReturnedValue:
IConversionOperation (TryCast: False, Unchecked) (OperationKind.Conversion, Type: System.String, IsInvalid, IsImplicit) (Syntax: 'field + "a"')
Conversion: CommonConversion (Exists: False, IsIdentity: False, IsNumeric: False, IsReference: False, IsUserDefined: False) (MethodSymbol: null)
Operand:
IBinaryOperation (BinaryOperatorKind.Add) (OperationKind.Binary, Type: ?, IsInvalid) (Syntax: 'field + "a"')
Left:
IInvalidOperation (OperationKind.Invalid, Type: ?, IsInvalid) (Syntax: 'field')
Children(0)
Right:
ILiteralOperation (OperationKind.Literal, Type: System.String, Constant: "a") (Syntax: '"a"')
""",
expectedDiagnostics: [
// (8,20): error CS0103: The name 'field' does not exist in the current context
// return field + "a";
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(8, 20)],
parseOptions: TestOptions.Regular13);
}
}
}
2 changes: 1 addition & 1 deletion src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2613,7 +2613,7 @@ protected static void VerifyFlowGraphForTest<TSyntaxNode>(CSharpCompilation comp
{
var tree = compilation.SyntaxTrees[0];
SyntaxNode? syntaxNode = GetSyntaxNodeOfTypeForBinding<TSyntaxNode>(GetSyntaxNodeList(tree));
Debug.Assert(syntaxNode is not null, "Did you forget to place /*<bind>*/ comments in your source?");
Debug.Assert(syntaxNode is not null, $"Ensure a /*<bind>*/ comment is used around syntax matching the type argument for '{nameof(TSyntaxNode)}'.");
VerifyFlowGraph(compilation, syntaxNode, expectedFlowGraph);
}

Expand Down