diff --git a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
index 2029d8d5aa669..04ac043860e7d 100644
--- a/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
+++ b/src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
@@ -258,6 +258,24 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder)
public override BoundNode? Visit(BoundNode? node)
{
#if DEBUG
+ TrackVisit(node);
+#endif
+ return base.Visit(node);
+ }
+
+#if DEBUG
+ protected override void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node)
+ {
+ TrackVisit(node);
+ }
+
+ protected override void BeforeVisitingSkippedBoundCallChildren(BoundCall node)
+ {
+ TrackVisit(node);
+ }
+
+ private void TrackVisit(BoundNode? node)
+ {
if (node is BoundValuePlaceholderBase placeholder)
{
Debug.Assert(ContainsPlaceholderScope(placeholder));
@@ -267,14 +285,11 @@ private bool ContainsPlaceholderScope(BoundValuePlaceholderBase placeholder)
if (_visited is { } && _visited.Count <= MaxTrackVisited)
{
bool added = _visited.Add(expr);
- Debug.Assert(added);
+ Debug.Assert(added, $"Expression {expr} `{expr.Syntax}` visited more than once.");
}
}
-#endif
- return base.Visit(node);
}
-#if DEBUG
private void AssertVisited(BoundExpression expr)
{
if (expr is BoundValuePlaceholderBase placeholder)
@@ -283,7 +298,7 @@ private void AssertVisited(BoundExpression expr)
}
else if (_visited is { } && _visited.Count <= MaxTrackVisited)
{
- Debug.Assert(_visited.Contains(expr));
+ Debug.Assert(_visited.Contains(expr), $"Expected {expr} `{expr.Syntax}` to be visited.");
}
}
#endif
@@ -659,13 +674,6 @@ protected override void VisitArguments(BoundCall node)
_localScopeDepth,
_diagnostics);
}
-
-#if DEBUG
- if (_visited is { } && _visited.Count <= MaxTrackVisited)
- {
- _visited.Add(node);
- }
-#endif
}
private void GetInterpolatedStringPlaceholders(
diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs
index a8ed41a6f003c..8f0df23b1e56c 100644
--- a/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs
+++ b/src/Compilers/CSharp/Portable/BoundTree/BoundTreeWalker.cs
@@ -114,6 +114,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
var binary = (BoundBinaryOperator)node.Left;
+ BeforeVisitingSkippedBoundBinaryOperatorChildren(binary);
rightOperands.Push(binary.Right);
BoundExpression current = binary.Left;
@@ -121,6 +122,7 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
while (current.Kind == BoundKind.BinaryOperator)
{
binary = (BoundBinaryOperator)current;
+ BeforeVisitingSkippedBoundBinaryOperatorChildren(binary);
rightOperands.Push(binary.Right);
current = binary.Left;
}
@@ -136,6 +138,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
return null;
}
+ protected virtual void BeforeVisitingSkippedBoundBinaryOperatorChildren(BoundBinaryOperator node)
+ {
+ }
+
public sealed override BoundNode? VisitCall(BoundCall node)
{
if (node.ReceiverOpt is BoundCall receiver1)
@@ -147,10 +153,13 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
node = receiver1;
while (node.ReceiverOpt is BoundCall receiver2)
{
+ BeforeVisitingSkippedBoundCallChildren(node);
calls.Push(node);
node = receiver2;
}
+ BeforeVisitingSkippedBoundCallChildren(node);
+
VisitReceiver(node);
do
@@ -170,6 +179,10 @@ protected BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator
return null;
}
+ protected virtual void BeforeVisitingSkippedBoundCallChildren(BoundCall node)
+ {
+ }
+
///
/// Called only for the first (in evaluation order) in the chain.
///
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
index 6da08b9784005..0cd3ee133280d 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefEscapingTests.cs
@@ -7871,6 +7871,37 @@ ref struct S
Diagnostic(ErrorCode.ERR_EscapeVariable, "s").WithArguments("s").WithLocation(47, 16));
}
+ [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")]
+ public void UserDefinedBinaryOperator_RefStruct_Nested()
+ {
+ var source = """
+ class C
+ {
+ S M()
+ {
+ S s;
+ s = default(S) + 100 + 200;
+ return s;
+ }
+ }
+
+ ref struct S
+ {
+ public static S operator+(S y, in int x) => throw null;
+ }
+ """;
+ CreateCompilation(source).VerifyDiagnostics(
+ // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'x' outside of their declaration scope
+ // s = default(S) + 100 + 200;
+ Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100").WithArguments("S.operator +(S, in int)", "x").WithLocation(6, 13),
+ // (6,13): error CS8347: Cannot use a result of 'S.operator +(S, in int)' in this context because it may expose variables referenced by parameter 'y' outside of their declaration scope
+ // s = default(S) + 100 + 200;
+ Diagnostic(ErrorCode.ERR_EscapeCall, "default(S) + 100 + 200").WithArguments("S.operator +(S, in int)", "y").WithLocation(6, 13),
+ // (6,26): error CS8156: An expression cannot be used in this context because it may not be passed or returned by reference
+ // s = default(S) + 100 + 200;
+ Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "100").WithLocation(6, 26));
+ }
+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/71773")]
public void UserDefinedBinaryOperator_RefStruct_Scoped_Left()
{
@@ -8531,5 +8562,16 @@ static R F2()
// return new R(1) | new R(2);
Diagnostic(ErrorCode.ERR_RefReturnLvalueExpected, "1").WithLocation(18, 22));
}
+
+ [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72873")]
+ public void Utf8Addition()
+ {
+ var code = """
+ using System;
+ ReadOnlySpan x = "Hello"u8 + " "u8 + "World!"u8;
+ Console.WriteLine(x.Length);
+ """;
+ CreateCompilation(code, targetFramework: TargetFramework.Net70).VerifyDiagnostics();
+ }
}
}