Skip to content

Commit b64ee87

Browse files
committed
Overwrite placholder regions temporarily
1 parent cab096c commit b64ee87

File tree

3 files changed

+37
-21
lines changed

3 files changed

+37
-21
lines changed

src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4480,7 +4480,7 @@ internal SafeContext GetValEscape(BoundExpression expr, SafeContext scopeOfTheCo
44804480
return scopeOfTheContainingExpression;
44814481

44824482
case BoundKind.InterpolatedStringHandlerPlaceholder:
4483-
if (_placeholderScopes?.TryGetValue((BoundInterpolatedStringHandlerPlaceholder)expr, out var scope) == true)
4483+
if (_placeholderScopes?.TryPeek((BoundInterpolatedStringHandlerPlaceholder)expr, out var scope) == true)
44844484
{
44854485
return scope;
44864486
}
@@ -5710,7 +5710,7 @@ private bool CheckInterpolatedStringHandlerInvocationArgMixingParts(BoundInterpo
57105710
{
57115711
if (part is BoundCall { Method.Name: BoundInterpolatedString.AppendFormattedMethod } call)
57125712
{
5713-
using var _ = new PlaceholderRegion(this, [((BoundInterpolatedStringHandlerPlaceholder)call.ReceiverOpt, escapeTo)]);
5713+
using var _ = new PlaceholderRegion(this, [((BoundInterpolatedStringHandlerPlaceholder)call.ReceiverOpt, escapeTo)], overwriteExistingTemporarily: true);
57145714
result &= CheckInvocationArgMixing(
57155715
call.Syntax,
57165716
MethodInfo.Create(call.Method),

src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.Immutable;
88
using System.Diagnostics;
99
using System.Linq;
10+
using Microsoft.CodeAnalysis.Collections;
1011
using Microsoft.CodeAnalysis.CSharp.Symbols;
1112
using Microsoft.CodeAnalysis.PooledObjects;
1213
using Roslyn.Utilities;
@@ -61,7 +62,7 @@ private static bool InUnsafeMethod(Symbol symbol)
6162
private bool _inUnsafeRegion;
6263
private SafeContext _localScopeDepth;
6364
private Dictionary<LocalSymbol, (SafeContext RefEscapeScope, SafeContext ValEscapeScope)>? _localEscapeScopes;
64-
private Dictionary<BoundValuePlaceholderBase, SafeContext>? _placeholderScopes;
65+
private KeyedStack<BoundValuePlaceholderBase, SafeContext>? _placeholderScopes;
6566
private SafeContext _patternInputValEscape;
6667
#if DEBUG
6768
private const int MaxTrackVisited = 100; // Avoid tracking if too many expressions.
@@ -152,22 +153,24 @@ private ref struct PlaceholderRegion
152153
{
153154
private readonly RefSafetyAnalysis _analysis;
154155
private readonly ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> _placeholders;
156+
private readonly bool _overwriteExistingTemporarily;
155157

156-
public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders)
158+
public PlaceholderRegion(RefSafetyAnalysis analysis, ArrayBuilder<(BoundValuePlaceholderBase, SafeContext)> placeholders, bool overwriteExistingTemporarily = false)
157159
{
158160
_analysis = analysis;
159161
_placeholders = placeholders;
162+
_overwriteExistingTemporarily = overwriteExistingTemporarily;
160163
foreach (var (placeholder, valEscapeScope) in placeholders)
161164
{
162-
_analysis.AddPlaceholderScope(placeholder, valEscapeScope);
165+
_analysis.AddPlaceholderScope(placeholder, valEscapeScope, canExist: overwriteExistingTemporarily);
163166
}
164167
}
165168

166169
public void Dispose()
167170
{
168171
foreach (var (placeholder, _) in _placeholders)
169172
{
170-
_analysis.RemovePlaceholderScope(placeholder);
173+
_analysis.RemovePlaceholderScope(placeholder, forcePop: _overwriteExistingTemporarily);
171174
}
172175
_placeholders.Free();
173176
}
@@ -189,33 +192,34 @@ private void SetLocalScopes(LocalSymbol local, SafeContext refEscapeScope, SafeC
189192
AddOrSetLocalScopes(local, refEscapeScope, valEscapeScope);
190193
}
191194

192-
private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, SafeContext valEscapeScope)
195+
private void AddPlaceholderScope(BoundValuePlaceholderBase placeholder, SafeContext valEscapeScope, bool canExist = false)
193196
{
194-
Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) != true);
197+
Debug.Assert(canExist || _placeholderScopes?.ContainsKey(placeholder) != true);
195198

196199
// Consider not adding the placeholder to the dictionary if the escape scope is
197200
// CallingMethod, and simply fallback to that value in GetPlaceholderScope().
198201

199-
_placeholderScopes ??= new Dictionary<BoundValuePlaceholderBase, SafeContext>();
200-
_placeholderScopes[placeholder] = valEscapeScope;
202+
_placeholderScopes ??= new KeyedStack<BoundValuePlaceholderBase, SafeContext>();
203+
_placeholderScopes.Push(placeholder, valEscapeScope);
201204
}
202205

203-
#pragma warning disable IDE0060
204-
private void RemovePlaceholderScope(BoundValuePlaceholderBase placeholder)
206+
private void RemovePlaceholderScope(BoundValuePlaceholderBase placeholder, bool forcePop = false)
205207
{
206208
Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) == true);
207209

208210
// https://github.com/dotnet/roslyn/issues/65961: Currently, analysis may require subsequent calls
209211
// to GetRefEscape(), etc. for the same expression so we cannot remove placeholders eagerly.
210-
//_placeholderScopes.Remove(placeholder);
212+
if (forcePop)
213+
{
214+
_placeholderScopes?.TryPop(placeholder, out _);
215+
}
211216
}
212-
#pragma warning restore IDE0060
213217

214218
private SafeContext GetPlaceholderScope(BoundValuePlaceholderBase placeholder)
215219
{
216220
Debug.Assert(_placeholderScopes?.ContainsKey(placeholder) == true);
217221

218-
return _placeholderScopes?.TryGetValue(placeholder, out var scope) == true
222+
return _placeholderScopes?.TryPeek(placeholder, out var scope) == true
219223
? scope
220224
: SafeContext.CallingMethod;
221225
}

src/Compilers/Core/Portable/Collections/KeyedStack.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
65
using System.Collections.Generic;
76
using System.Diagnostics.CodeAnalysis;
8-
using System.Linq;
9-
using System.Text;
10-
using System.Threading.Tasks;
11-
using Microsoft.CodeAnalysis.Text;
127

138
namespace Microsoft.CodeAnalysis.Collections
149
{
15-
internal class KeyedStack<T, R>
10+
internal sealed class KeyedStack<T, R>
1611
where T : notnull
1712
{
1813
private readonly Dictionary<T, Stack<R>> _dict = new Dictionary<T, Stack<R>>();
1914

15+
public bool ContainsKey(T key)
16+
{
17+
return _dict.ContainsKey(key);
18+
}
19+
2020
public void Push(T key, R value)
2121
{
2222
Stack<R>? store;
@@ -29,6 +29,18 @@ public void Push(T key, R value)
2929
store.Push(value);
3030
}
3131

32+
public bool TryPeek(T key, [MaybeNullWhen(returnValue: false)] out R value)
33+
{
34+
if (_dict.TryGetValue(key, out var stack) && stack.Count > 0)
35+
{
36+
value = stack.Peek();
37+
return true;
38+
}
39+
40+
value = default;
41+
return false;
42+
}
43+
3244
public bool TryPop(T key, [MaybeNullWhen(returnValue: false)] out R value)
3345
{
3446
Stack<R>? store;

0 commit comments

Comments
 (0)