diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
index a2d6516952e51..8880087fc8068 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
@@ -759,11 +759,11 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind
return expr;
case BoundKind.PropertyAccess:
+ 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.
- var propertyAccess = (BoundPropertyAccess)expr;
if (HasSynthesizedBackingField(propertyAccess.PropertySymbol, out _))
{
expr = propertyAccess.Update(
@@ -774,8 +774,24 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind 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, without
+ // asserting in WasPropertyBackingFieldAccessChecked.
+ expr = propertyAccess.Clone();
+ }
+#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, without
+ // asserting in WasPropertyBackingFieldAccessChecked.
+ expr = propertyAccess.Clone();
+ }
expr.WasPropertyBackingFieldAccessChecked = true;
#endif
break;
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
index 8324d4f1a0396..2f6f240289113 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 8034339586531..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
@@ -993,17 +987,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);
}
}
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();
+}
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs
index 50847516eefa9..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
{
@@ -4786,5 +4813,22 @@ public static class F
var comp = CreateCompilation(code);
CompileAndVerify(code, expectedOutput: "ran").VerifyDiagnostics();
}
+
+ [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81709")]
+ 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();
+ }
}
}