From b7133960d44622f5fbba284601935e5030cf8a50 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:56:05 +0000 Subject: [PATCH 01/15] Initial plan From aa899b40c8cdf0f3e520a9b41c3b6c0a27ec0526 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:27:45 +0000 Subject: [PATCH 02/15] Add VisitQueryClause to suppress property checking in query clauses Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com> --- .../CSharp/Portable/Compiler/MethodCompiler.cs | 10 ++++++++++ .../Test/Semantic/Semantics/QueryTests.cs | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index 1b1e92887475d..b48bb725323da 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -2430,6 +2430,16 @@ private WasPropertyBackingFieldAccessChecked() } } + public override BoundNode? VisitQueryClause(BoundQueryClause node) + { + // Suppress checking for property accesses inside query clauses because they may be + // cloned/rewritten during query translation, which resets the WasPropertyBackingFieldAccessChecked flag. + using (new ChangeSuppression(this, suppressChecking: true)) + { + return base.VisitQueryClause(node); + } + } + public override BoundNode? VisitAssignmentOperator(BoundAssignmentOperator node) { using (new ChangeSuppression(this, suppressChecking: false)) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs index 50847516eefa9..f6962979b22ce 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs @@ -4786,5 +4786,22 @@ public static class F var comp = CreateCompilation(code); CompileAndVerify(code, expectedOutput: "ran").VerifyDiagnostics(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80008")] + public void PropertyAccess_AnonymousType_GroupBy() + { + var code = """ + using System.Linq; + + var x = new { A = new[] { new { B = 1 } } }; + + var y = from a in x.A + group a by a.B into g + select 1; + """; + + var comp = CreateCompilation(code); + comp.VerifyDiagnostics(); + } } } From 296c549218ffddaabc322034e925abb3c242245d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 20:31:36 +0000 Subject: [PATCH 03/15] Fix: Only set WasPropertyBackingFieldAccessChecked flag if not already set Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com> --- src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index a2d6516952e51..b5245bc93038c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -776,7 +776,10 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind } } #if DEBUG - expr.WasPropertyBackingFieldAccessChecked = true; + if (!expr.WasPropertyBackingFieldAccessChecked) + { + expr.WasPropertyBackingFieldAccessChecked = true; + } #endif break; From 8f78c88e5aa5769d8b56567dc7418d382c492394 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 22:11:55 +0000 Subject: [PATCH 04/15] Fix root cause: pass receiverIsCheckedForRValue: true for optimized GroupBy call Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com> --- src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs | 5 +---- src/Compilers/CSharp/Portable/Binder/Binder_Query.cs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index b5245bc93038c..a2d6516952e51 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -776,10 +776,7 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind } } #if DEBUG - if (!expr.WasPropertyBackingFieldAccessChecked) - { - expr.WasPropertyBackingFieldAccessChecked = true; - } + expr.WasPropertyBackingFieldAccessChecked = true; #endif break; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 8034339586531..d6b0100f4688b 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -276,7 +276,7 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia { // The optimized form. We store the unoptimized form for analysis unoptimizedForm = result; - result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "GroupBy", lambdaLeft, diagnostics + result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: true, "GroupBy", lambdaLeft, diagnostics #if DEBUG , state.nextInvokedMethodName #endif From ed8e09650d6d695db0501ffa9cde7c4727f5b670 Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Fri, 26 Dec 2025 11:56:28 -0800 Subject: [PATCH 05/15] Revert unnecessary change --- .../CSharp/Portable/Compiler/MethodCompiler.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index b48bb725323da..1b1e92887475d 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -2430,16 +2430,6 @@ private WasPropertyBackingFieldAccessChecked() } } - public override BoundNode? VisitQueryClause(BoundQueryClause node) - { - // Suppress checking for property accesses inside query clauses because they may be - // cloned/rewritten during query translation, which resets the WasPropertyBackingFieldAccessChecked flag. - using (new ChangeSuppression(this, suppressChecking: true)) - { - return base.VisitQueryClause(node); - } - } - public override BoundNode? VisitAssignmentOperator(BoundAssignmentOperator node) { using (new ChangeSuppression(this, suppressChecking: false)) From 24384da0f62577010610f5eaf35630f46fd100e5 Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Fri, 26 Dec 2025 12:07:26 -0800 Subject: [PATCH 06/15] Fix issue link --- src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs index f6962979b22ce..69d65d2146f42 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs @@ -4787,7 +4787,7 @@ public static class F CompileAndVerify(code, expectedOutput: "ran").VerifyDiagnostics(); } - [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80008")] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81709")] public void PropertyAccess_AnonymousType_GroupBy() { var code = """ From ea83009228a1b77993d9fcbcc7a3fdb7df90fd97 Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Mon, 29 Dec 2025 10:12:24 -0800 Subject: [PATCH 07/15] Add a test for the missing diagnostics, ensure that diagnostics for `CheckValue` are retained. --- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../CSharp/Portable/Binder/Binder_Query.cs | 26 +++++++++++++----- .../Test/Semantic/Semantics/QueryTests.cs | 27 +++++++++++++++++++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 8324d4f1a0396..14658fa8c3706 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7755,7 +7755,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #else /// diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index d6b0100f4688b..4944d911fb7dc 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -259,8 +259,18 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia // this is the unoptimized form (when v is not the identifier x) var d = BindingDiagnosticBag.GetInstance(diagnostics); + var checkValueDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics); BoundExpression lambdaRight = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v, diagnostics.AccumulatesDependencies); - result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "GroupBy", ImmutableArray.Create(lambdaLeft, lambdaRight), d + result = MakeQueryInvocation( + state.selectOrGroup, + e, + receiverIsCheckedForRValue: false, + methodName: "GroupBy", + typeArgsSyntax: default, + typeArgs: default, + args: ImmutableArray.Create(lambdaLeft, lambdaRight), + diagnostics: d, + checkValueDiagnostics: checkValueDiagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -281,6 +291,8 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia , state.nextInvokedMethodName #endif ); + + diagnostics.AddRange(checkValueDiagnostics); #if DEBUG state.nextInvokedMethodName = null; #endif @@ -289,9 +301,11 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia else { diagnostics.AddRange(d); + diagnostics.AddRange(checkValueDiagnostics); } d.Free(); + checkValueDiagnostics.Free(); return MakeQueryClause(groupClause, result, queryInvocation: result, unoptimizedForm: unoptimizedForm); } default: @@ -888,7 +902,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), ImmutableArray.Create(arg), diagnostics + return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), ImmutableArray.Create(arg), diagnostics, checkValueDiagnostics: diagnostics #if DEBUG , expectedMethodName #endif @@ -901,7 +915,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), args, diagnostics + return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), args, diagnostics, checkValueDiagnostics: diagnostics #if DEBUG , expectedMethodName #endif @@ -914,14 +928,14 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, new SeparatedSyntaxList(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray.Empty, diagnostics + return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, new SeparatedSyntaxList(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray.Empty, diagnostics, checkValueDiagnostics: diagnostics #if DEBUG , expectedMethodName #endif ); } - protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, SeparatedSyntaxList typeArgsSyntax, ImmutableArray typeArgs, ImmutableArray args, BindingDiagnosticBag diagnostics + protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, SeparatedSyntaxList typeArgsSyntax, ImmutableArray typeArgs, ImmutableArray args, BindingDiagnosticBag diagnostics, BindingDiagnosticBag checkValueDiagnostics #if DEBUG , string? expectedMethodName #endif @@ -995,7 +1009,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r { if (!receiverIsCheckedForRValue) { - var checkedUltimateReceiver = CheckValue(ultimateReceiver, BindValueKind.RValue, diagnostics); + var checkedUltimateReceiver = CheckValue(ultimateReceiver, BindValueKind.RValue, checkValueDiagnostics); if (checkedUltimateReceiver != ultimateReceiver) { receiver = updateUltimateReceiver(receiver, ultimateReceiver, checkedUltimateReceiver); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs index 69d65d2146f42..d3b5cd7872e6c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs @@ -4480,6 +4480,33 @@ public void SetOnlyProperty() var test = from i in c.Prop select i; +public class C { + public IEnumerable Prop + { + set {} + } +} +", options: TestOptions.ReleaseExe); + + comp.VerifyDiagnostics( + // (6,22): error CS0154: The property or indexer 'C.Prop' cannot be used in this context because it lacks the get accessor + // var test = from i in c.Prop + Diagnostic(ErrorCode.ERR_PropertyLacksGet, "c.Prop").WithArguments("C.Prop").WithLocation(6, 22) + ); + } + + [Fact, WorkItem(50316, "https://github.com/dotnet/roslyn/issues/50316")] + public void SetOnlyProperty_GroupByContinuation() + { + var comp = CreateCompilation(@" +using System.Collections.Generic; +using System.Linq; + +var c = new C(); +var test = from i in c.Prop + group i by i into g + select g; + public class C { public IEnumerable Prop { From 767c483f3453432e55401664333d8f3761bfcba0 Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Mon, 29 Dec 2025 15:23:41 -0800 Subject: [PATCH 08/15] Revert back to upstream/main --- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../CSharp/Portable/Binder/Binder_Query.cs | 28 +++++-------------- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 14658fa8c3706..8324d4f1a0396 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7755,7 +7755,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #else /// diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 4944d911fb7dc..8034339586531 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -259,18 +259,8 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia // this is the unoptimized form (when v is not the identifier x) var d = BindingDiagnosticBag.GetInstance(diagnostics); - var checkValueDiagnostics = BindingDiagnosticBag.GetInstance(diagnostics); BoundExpression lambdaRight = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v, diagnostics.AccumulatesDependencies); - result = MakeQueryInvocation( - state.selectOrGroup, - e, - receiverIsCheckedForRValue: false, - methodName: "GroupBy", - typeArgsSyntax: default, - typeArgs: default, - args: ImmutableArray.Create(lambdaLeft, lambdaRight), - diagnostics: d, - checkValueDiagnostics: checkValueDiagnostics + result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "GroupBy", ImmutableArray.Create(lambdaLeft, lambdaRight), d #if DEBUG , state.nextInvokedMethodName #endif @@ -286,13 +276,11 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia { // The optimized form. We store the unoptimized form for analysis unoptimizedForm = result; - result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: true, "GroupBy", lambdaLeft, diagnostics + result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "GroupBy", lambdaLeft, diagnostics #if DEBUG , state.nextInvokedMethodName #endif ); - - diagnostics.AddRange(checkValueDiagnostics); #if DEBUG state.nextInvokedMethodName = null; #endif @@ -301,11 +289,9 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia else { diagnostics.AddRange(d); - diagnostics.AddRange(checkValueDiagnostics); } d.Free(); - checkValueDiagnostics.Free(); return MakeQueryClause(groupClause, result, queryInvocation: result, unoptimizedForm: unoptimizedForm); } default: @@ -902,7 +888,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), ImmutableArray.Create(arg), diagnostics, checkValueDiagnostics: diagnostics + return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), ImmutableArray.Create(arg), diagnostics #if DEBUG , expectedMethodName #endif @@ -915,7 +901,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), args, diagnostics, checkValueDiagnostics: diagnostics + return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), args, diagnostics #if DEBUG , expectedMethodName #endif @@ -928,14 +914,14 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, new SeparatedSyntaxList(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray.Empty, diagnostics, checkValueDiagnostics: diagnostics + return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, new SeparatedSyntaxList(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray.Empty, diagnostics #if DEBUG , expectedMethodName #endif ); } - protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, SeparatedSyntaxList typeArgsSyntax, ImmutableArray typeArgs, ImmutableArray args, BindingDiagnosticBag diagnostics, BindingDiagnosticBag checkValueDiagnostics + protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, SeparatedSyntaxList typeArgsSyntax, ImmutableArray typeArgs, ImmutableArray args, BindingDiagnosticBag diagnostics #if DEBUG , string? expectedMethodName #endif @@ -1009,7 +995,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r { if (!receiverIsCheckedForRValue) { - var checkedUltimateReceiver = CheckValue(ultimateReceiver, BindValueKind.RValue, checkValueDiagnostics); + var checkedUltimateReceiver = CheckValue(ultimateReceiver, BindValueKind.RValue, diagnostics); if (checkedUltimateReceiver != ultimateReceiver) { receiver = updateUltimateReceiver(receiver, ultimateReceiver, checkedUltimateReceiver); From a5d62003a6dea29ccbb89c858b45464bea5f3f8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:33:31 +0000 Subject: [PATCH 09/15] Fix broken cref to MakeQueryInvocation after signature change Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com> --- src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 8324d4f1a0396..9887ae2d49050 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7764,7 +7764,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #endif private BoundExpression BindMemberAccessWithBoundLeft( From 4b7d1881ac908e17d33a9b328c248febd93dd014 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 29 Dec 2025 23:47:47 +0000 Subject: [PATCH 10/15] Apply refactoring: CheckValue creates new node under DEBUG, MakeQueryInvocation always calls CheckValue Per @AlekseyTs's suggestion: - Modified CheckValue to create a new node under DEBUG instead of mutating the original - Removed conditional logic in MakeQueryInvocation to always call CheckValue unconditionally - This allows the same node to be passed to CheckValue multiple times safely Co-authored-by: 333fred <2371880+333fred@users.noreply.github.com> --- .../Portable/Binder/Binder.ValueChecks.cs | 69 ++++++++++++++++--- .../CSharp/Portable/Binder/Binder_Query.cs | 13 +--- 2 files changed, 62 insertions(+), 20 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index a2d6516952e51..384961b6ce3de 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -759,25 +759,74 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind return expr; case BoundKind.PropertyAccess: - if (!InAttributeArgument) { - // If the property has a synthesized backing field, record the accessor kind of the property - // access for determining whether the property access can use the backing field directly. var propertyAccess = (BoundPropertyAccess)expr; - if (HasSynthesizedBackingField(propertyAccess.PropertySymbol, out _)) + if (!InAttributeArgument) { - expr = propertyAccess.Update( + // If the property has a synthesized backing field, record the accessor kind of the property + // access for determining whether the property access can use the backing field directly. + if (HasSynthesizedBackingField(propertyAccess.PropertySymbol, out _)) + { + expr = propertyAccess.Update( + propertyAccess.ReceiverOpt, + propertyAccess.InitialBindingReceiverIsSubjectToCloning, + propertyAccess.PropertySymbol, + autoPropertyAccessorKind: GetAccessorKind(valueKind), + propertyAccess.ResultKind, + propertyAccess.Type); + } +#if DEBUG + else + { + // Under DEBUG, create a new node to mark as checked, rather than mutating the original. + // This allows the original node to be passed to CheckValue multiple times safely. + // To ensure a new node is created, we temporarily toggle InitialBindingReceiverIsSubjectToCloning + // and then toggle it back. + var toggled = propertyAccess.InitialBindingReceiverIsSubjectToCloning == ThreeState.True + ? ThreeState.False + : ThreeState.True; + var temp = propertyAccess.Update( + propertyAccess.ReceiverOpt, + toggled, + propertyAccess.PropertySymbol, + propertyAccess.AutoPropertyAccessorKind, + propertyAccess.ResultKind, + propertyAccess.Type); + expr = temp.Update( + temp.ReceiverOpt, + propertyAccess.InitialBindingReceiverIsSubjectToCloning, + temp.PropertySymbol, + temp.AutoPropertyAccessorKind, + temp.ResultKind, + temp.Type); + } +#endif + } +#if DEBUG + else + { + // In attribute arguments, still create a new node to mark as checked. + var toggled = propertyAccess.InitialBindingReceiverIsSubjectToCloning == ThreeState.True + ? ThreeState.False + : ThreeState.True; + var temp = propertyAccess.Update( propertyAccess.ReceiverOpt, - propertyAccess.InitialBindingReceiverIsSubjectToCloning, + toggled, propertyAccess.PropertySymbol, - autoPropertyAccessorKind: GetAccessorKind(valueKind), + propertyAccess.AutoPropertyAccessorKind, propertyAccess.ResultKind, propertyAccess.Type); + expr = temp.Update( + temp.ReceiverOpt, + propertyAccess.InitialBindingReceiverIsSubjectToCloning, + temp.PropertySymbol, + temp.AutoPropertyAccessorKind, + temp.ResultKind, + temp.Type); } - } -#if DEBUG - expr.WasPropertyBackingFieldAccessChecked = true; + expr.WasPropertyBackingFieldAccessChecked = true; #endif + } break; case BoundKind.IndexerAccess: diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 8034339586531..3c3d4aec38b21 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -993,17 +993,10 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r } else { - if (!receiverIsCheckedForRValue) + var checkedUltimateReceiver = CheckValue(ultimateReceiver, BindValueKind.RValue, diagnostics); + if (checkedUltimateReceiver != ultimateReceiver) { - var checkedUltimateReceiver = CheckValue(ultimateReceiver, BindValueKind.RValue, diagnostics); - if (checkedUltimateReceiver != ultimateReceiver) - { - receiver = updateUltimateReceiver(receiver, ultimateReceiver, checkedUltimateReceiver); - } - } - else - { - Debug.Assert(ultimateReceiver is not BoundQueryClause); + receiver = updateUltimateReceiver(receiver, ultimateReceiver, checkedUltimateReceiver); } } From 266f4a961f64c0dde68dc14d16b6527c33d5b8fa Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Mon, 29 Dec 2025 16:25:34 -0800 Subject: [PATCH 11/15] Simplify cloning --- .../Portable/Binder/Binder.ValueChecks.cs | 41 ++----------------- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../Portable/BoundTree/BoundExpression.cs | 3 ++ 3 files changed, 8 insertions(+), 38 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 384961b6ce3de..e3fb08783dd76 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -780,49 +780,16 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind { // Under DEBUG, create a new node to mark as checked, rather than mutating the original. // This allows the original node to be passed to CheckValue multiple times safely. - // To ensure a new node is created, we temporarily toggle InitialBindingReceiverIsSubjectToCloning - // and then toggle it back. - var toggled = propertyAccess.InitialBindingReceiverIsSubjectToCloning == ThreeState.True - ? ThreeState.False - : ThreeState.True; - var temp = propertyAccess.Update( - propertyAccess.ReceiverOpt, - toggled, - propertyAccess.PropertySymbol, - propertyAccess.AutoPropertyAccessorKind, - propertyAccess.ResultKind, - propertyAccess.Type); - expr = temp.Update( - temp.ReceiverOpt, - propertyAccess.InitialBindingReceiverIsSubjectToCloning, - temp.PropertySymbol, - temp.AutoPropertyAccessorKind, - temp.ResultKind, - temp.Type); + expr = propertyAccess.Clone(); } #endif } #if DEBUG else { - // In attribute arguments, still create a new node to mark as checked. - var toggled = propertyAccess.InitialBindingReceiverIsSubjectToCloning == ThreeState.True - ? ThreeState.False - : ThreeState.True; - var temp = propertyAccess.Update( - propertyAccess.ReceiverOpt, - toggled, - propertyAccess.PropertySymbol, - propertyAccess.AutoPropertyAccessorKind, - propertyAccess.ResultKind, - propertyAccess.Type); - expr = temp.Update( - temp.ReceiverOpt, - propertyAccess.InitialBindingReceiverIsSubjectToCloning, - temp.PropertySymbol, - temp.AutoPropertyAccessorKind, - temp.ResultKind, - temp.Type); + // Under DEBUG, create a new node to mark as checked, rather than mutating the original. + // This allows the original node to be passed to CheckValue multiple times safely. + expr = propertyAccess.Clone(); } expr.WasPropertyBackingFieldAccessChecked = true; #endif diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 9887ae2d49050..8324d4f1a0396 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7764,7 +7764,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #endif private BoundExpression BindMemberAccessWithBoundLeft( diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs index baaf9fb3386cb..9b61e88d12f9a 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs @@ -325,6 +325,9 @@ public override Symbol? ExpressionSymbol { get { return this.PropertySymbol; } } + + public BoundPropertyAccess Clone() + => (BoundPropertyAccess)this.MemberwiseClone(); } internal enum AccessorKind : byte From 0a99f68835c9230da6670d427423e757cc57f38a Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Mon, 29 Dec 2025 16:26:53 -0800 Subject: [PATCH 12/15] Remove unneeded brace add --- .../Portable/Binder/Binder.ValueChecks.cs | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index e3fb08783dd76..183dc25450306 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -759,30 +759,20 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind return expr; case BoundKind.PropertyAccess: + var propertyAccess = (BoundPropertyAccess)expr; + if (!InAttributeArgument) { - var propertyAccess = (BoundPropertyAccess)expr; - if (!InAttributeArgument) + // If the property has a synthesized backing field, record the accessor kind of the property + // access for determining whether the property access can use the backing field directly. + if (HasSynthesizedBackingField(propertyAccess.PropertySymbol, out _)) { - // If the property has a synthesized backing field, record the accessor kind of the property - // access for determining whether the property access can use the backing field directly. - if (HasSynthesizedBackingField(propertyAccess.PropertySymbol, out _)) - { - expr = propertyAccess.Update( - propertyAccess.ReceiverOpt, - propertyAccess.InitialBindingReceiverIsSubjectToCloning, - propertyAccess.PropertySymbol, - autoPropertyAccessorKind: GetAccessorKind(valueKind), - propertyAccess.ResultKind, - propertyAccess.Type); - } -#if DEBUG - else - { - // Under DEBUG, create a new node to mark as checked, rather than mutating the original. - // This allows the original node to be passed to CheckValue multiple times safely. - expr = propertyAccess.Clone(); - } -#endif + expr = propertyAccess.Update( + propertyAccess.ReceiverOpt, + propertyAccess.InitialBindingReceiverIsSubjectToCloning, + propertyAccess.PropertySymbol, + autoPropertyAccessorKind: GetAccessorKind(valueKind), + propertyAccess.ResultKind, + propertyAccess.Type); } #if DEBUG else @@ -791,9 +781,17 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind // This allows the original node to be passed to CheckValue multiple times safely. expr = propertyAccess.Clone(); } - expr.WasPropertyBackingFieldAccessChecked = true; #endif } +#if DEBUG + else + { + // Under DEBUG, create a new node to mark as checked, rather than mutating the original. + // This allows the original node to be passed to CheckValue multiple times safely. + expr = propertyAccess.Clone(); + } + expr.WasPropertyBackingFieldAccessChecked = true; +#endif break; case BoundKind.IndexerAccess: From fb3c16caea156c2152b27d062bd89d52b668bcfe Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Mon, 29 Dec 2025 16:38:57 -0800 Subject: [PATCH 13/15] Remove unused parameter --- .../Portable/Binder/Binder_Expressions.cs | 4 +- .../CSharp/Portable/Binder/Binder_Query.cs | 38 ++++++++----------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 8324d4f1a0396..ceff894323eb0 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7755,7 +7755,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #else /// @@ -7764,7 +7764,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #endif private BoundExpression BindMemberAccessWithBoundLeft( diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index 3c3d4aec38b21..a1ee18f8439cf 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -54,7 +54,7 @@ internal BoundExpression BindQuery(QueryExpressionSyntax node, BindingDiagnostic if (fromClause.Type != null) { var typeRestriction = BindTypeArgument(fromClause.Type, diagnostics); - cast = MakeQueryInvocation(fromClause, state.fromExpression, receiverIsCheckedForRValue: false, "Cast", fromClause.Type, typeRestriction, diagnostics + cast = MakeQueryInvocation(fromClause, state.fromExpression, "Cast", fromClause.Type, typeRestriction, diagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -230,7 +230,7 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia var e = state.fromExpression; var v = selectClause.Expression; var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v, diagnostics.AccumulatesDependencies); - var result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "Select", lambda, diagnostics + var result = MakeQueryInvocation(state.selectOrGroup, e, "Select", lambda, diagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -260,7 +260,7 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia // this is the unoptimized form (when v is not the identifier x) var d = BindingDiagnosticBag.GetInstance(diagnostics); BoundExpression lambdaRight = MakeQueryUnboundLambda(state.RangeVariableMap(), x, v, diagnostics.AccumulatesDependencies); - result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "GroupBy", ImmutableArray.Create(lambdaLeft, lambdaRight), d + result = MakeQueryInvocation(state.selectOrGroup, e, "GroupBy", ImmutableArray.Create(lambdaLeft, lambdaRight), d #if DEBUG , state.nextInvokedMethodName #endif @@ -276,7 +276,7 @@ private BoundExpression FinalTranslation(QueryTranslationState state, BindingDia { // The optimized form. We store the unoptimized form for analysis unoptimizedForm = result; - result = MakeQueryInvocation(state.selectOrGroup, e, receiverIsCheckedForRValue: false, "GroupBy", lambdaLeft, diagnostics + result = MakeQueryInvocation(state.selectOrGroup, e, "GroupBy", lambdaLeft, diagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -364,7 +364,7 @@ private void ReduceWhere(WhereClauseSyntax where, QueryTranslationState state, B // is translated into // from x in ( e ) . Where ( x => f ) var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), state.rangeVariable, where.Condition, diagnostics.AccumulatesDependencies); - var invocation = MakeQueryInvocation(where, state.fromExpression, receiverIsCheckedForRValue: false, "Where", lambda, diagnostics + var invocation = MakeQueryInvocation(where, state.fromExpression, "Where", lambda, diagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -395,7 +395,7 @@ private void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, Bind // is translated into // join x in ( e ) . Cast < T > ( ) on k1 equals k2 var castType = BindTypeArgument(join.Type, diagnostics); - castInvocation = MakeQueryInvocation(join, inExpression, receiverIsCheckedForRValue: false, "Cast", join.Type, castType, diagnostics + castInvocation = MakeQueryInvocation(join, inExpression, "Cast", join.Type, castType, diagnostics #if DEBUG , expectedMethodName: null #endif @@ -426,7 +426,6 @@ private void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, Bind invocation = MakeQueryInvocation( join, state.fromExpression, - receiverIsCheckedForRValue: false, "Join", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics @@ -455,7 +454,6 @@ private void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, Bind invocation = MakeQueryInvocation( join, state.fromExpression, - receiverIsCheckedForRValue: false, "GroupJoin", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics @@ -496,7 +494,6 @@ private void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, Bind invocation = MakeQueryInvocation( join, state.fromExpression, - receiverIsCheckedForRValue: false, "Join", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics @@ -527,7 +524,6 @@ private void ReduceJoin(JoinClauseSyntax join, QueryTranslationState state, Bind invocation = MakeQueryInvocation( join, state.fromExpression, - receiverIsCheckedForRValue: false, "GroupJoin", ImmutableArray.Create(inExpression, outerKeySelectorLambda, innerKeySelectorLambda, resultSelectorLambda), diagnostics @@ -569,7 +565,7 @@ private void ReduceOrderBy(OrderByClauseSyntax orderby, QueryTranslationState st { string methodName = (first ? "OrderBy" : "ThenBy") + (ordering.IsKind(SyntaxKind.DescendingOrdering) ? "Descending" : ""); var lambda = MakeQueryUnboundLambda(state.RangeVariableMap(), state.rangeVariable, ordering.Expression, diagnostics.AccumulatesDependencies); - var invocation = MakeQueryInvocation(ordering, state.fromExpression, receiverIsCheckedForRValue: false, methodName, lambda, diagnostics + var invocation = MakeQueryInvocation(ordering, state.fromExpression, methodName, lambda, diagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -615,7 +611,6 @@ private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, Bind var invocation = MakeQueryInvocation( from, state.fromExpression, - receiverIsCheckedForRValue: false, "SelectMany", ImmutableArray.Create(collectionSelectorLambda, resultSelectorLambda), diagnostics @@ -663,7 +658,6 @@ private void ReduceFrom(FromClauseSyntax from, QueryTranslationState state, Bind var invocation = MakeQueryInvocation( from, state.fromExpression, - receiverIsCheckedForRValue: false, "SelectMany", ImmutableArray.Create(collectionSelectorLambda, resultSelectorLambda), diagnostics @@ -765,7 +759,7 @@ private void ReduceLet(LetClauseSyntax let, QueryTranslationState state, Binding state.AddTransparentIdentifier(x.Name); var y = state.AddRangeVariable(this, let.Identifier, diagnostics); state.allRangeVariables[y].Add(let.Identifier.ValueText); - var invocation = MakeQueryInvocation(let, state.fromExpression, receiverIsCheckedForRValue: false, "Select", lambda, diagnostics + var invocation = MakeQueryInvocation(let, state.fromExpression, "Select", lambda, diagnostics #if DEBUG , state.nextInvokedMethodName #endif @@ -858,7 +852,7 @@ private UnboundLambda MakeQueryUnboundLambdaWithCast(RangeVariableMap qvm, Range BoundExpression boundExpression = lambdaBodyBinder.BindValue(expression, diagnostics, BindValueKind.RValue); // We transform the expression from "expr" to "expr.Cast()". - boundExpression = lambdaBodyBinder.MakeQueryInvocation(expression, boundExpression, receiverIsCheckedForRValue: true, "Cast", castTypeSyntax, castType, diagnostics + boundExpression = lambdaBodyBinder.MakeQueryInvocation(expression, boundExpression, "Cast", castTypeSyntax, castType, diagnostics #if DEBUG , expectedMethodName: null #endif @@ -882,46 +876,46 @@ private static UnboundLambda MakeQueryUnboundLambda(CSharpSyntaxNode node, Query return lambda; } - protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, BoundExpression arg, BindingDiagnosticBag diagnostics + protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, BoundExpression arg, BindingDiagnosticBag diagnostics #if DEBUG , string? expectedMethodName #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), ImmutableArray.Create(arg), diagnostics + return MakeQueryInvocation(node, receiver, methodName, default(SeparatedSyntaxList), default(ImmutableArray), ImmutableArray.Create(arg), diagnostics #if DEBUG , expectedMethodName #endif ); } - protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, ImmutableArray args, BindingDiagnosticBag diagnostics + protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, ImmutableArray args, BindingDiagnosticBag diagnostics #if DEBUG , string? expectedMethodName #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, default(SeparatedSyntaxList), default(ImmutableArray), args, diagnostics + return MakeQueryInvocation(node, receiver, methodName, default(SeparatedSyntaxList), default(ImmutableArray), args, diagnostics #if DEBUG , expectedMethodName #endif ); } - protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, TypeSyntax typeArgSyntax, TypeWithAnnotations typeArg, BindingDiagnosticBag diagnostics + protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, TypeSyntax typeArgSyntax, TypeWithAnnotations typeArg, BindingDiagnosticBag diagnostics #if DEBUG , string? expectedMethodName #endif ) { - return MakeQueryInvocation(node, receiver, receiverIsCheckedForRValue, methodName, new SeparatedSyntaxList(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray.Empty, diagnostics + return MakeQueryInvocation(node, receiver, methodName, new SeparatedSyntaxList(new SyntaxNodeOrTokenList(typeArgSyntax, 0)), ImmutableArray.Create(typeArg), ImmutableArray.Empty, diagnostics #if DEBUG , expectedMethodName #endif ); } - protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, bool receiverIsCheckedForRValue, string methodName, SeparatedSyntaxList typeArgsSyntax, ImmutableArray typeArgs, ImmutableArray args, BindingDiagnosticBag diagnostics + protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression receiver, string methodName, SeparatedSyntaxList typeArgsSyntax, ImmutableArray typeArgs, ImmutableArray args, BindingDiagnosticBag diagnostics #if DEBUG , string? expectedMethodName #endif From 8ba5089a70d8c375a3bc9488756621121d7c9809 Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Tue, 30 Dec 2025 09:16:49 -0800 Subject: [PATCH 14/15] Fixes --- src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs | 6 ++++-- src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 183dc25450306..8880087fc8068 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -778,7 +778,8 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind else { // Under DEBUG, create a new node to mark as checked, rather than mutating the original. - // This allows the original node to be passed to CheckValue multiple times safely. + // This allows the original node to be passed to CheckValue multiple times safely, without + // asserting in WasPropertyBackingFieldAccessChecked. expr = propertyAccess.Clone(); } #endif @@ -787,7 +788,8 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind else { // Under DEBUG, create a new node to mark as checked, rather than mutating the original. - // This allows the original node to be passed to CheckValue multiple times safely. + // This allows the original node to be passed to CheckValue multiple times safely, without + // asserting in WasPropertyBackingFieldAccessChecked. expr = propertyAccess.Clone(); } expr.WasPropertyBackingFieldAccessChecked = true; diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index ceff894323eb0..2f6f240289113 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -7764,7 +7764,7 @@ private BoundExpression BindDynamicMemberAccess( /// /// /// If new checks are added to this method, they will also need to be added to - /// . + /// . /// #endif private BoundExpression BindMemberAccessWithBoundLeft( From 7c60e9c5a74e61766405f04d4ddf5cc090721b34 Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Tue, 6 Jan 2026 13:32:31 -0800 Subject: [PATCH 15/15] Move function to a separate file --- .../CSharp/Portable/BoundTree/BoundExpression.cs | 3 --- .../CSharp/Portable/BoundTree/BoundPropertyAccess.cs | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 src/Compilers/CSharp/Portable/BoundTree/BoundPropertyAccess.cs diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs index 9b61e88d12f9a..baaf9fb3386cb 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundExpression.cs @@ -325,9 +325,6 @@ public override Symbol? ExpressionSymbol { get { return this.PropertySymbol; } } - - public BoundPropertyAccess Clone() - => (BoundPropertyAccess)this.MemberwiseClone(); } internal enum AccessorKind : byte diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundPropertyAccess.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundPropertyAccess.cs new file mode 100644 index 0000000000000..ee105b0647d80 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundPropertyAccess.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CSharp; + +internal partial class BoundPropertyAccess +{ + public BoundPropertyAccess Clone() + => (BoundPropertyAccess)this.MemberwiseClone(); +}