Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update underline-reassigned-variable to understand suppressed variables as well #58428

Merged
merged 3 commits into from
Dec 20, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.UnitTests.ReassignedVariable;
using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ReassignedVariable
Expand Down Expand Up @@ -652,6 +653,23 @@ void M()
}");
}

[Fact]
public async Task TestReadonlyRefLocalWithNoReassignment1()
{
await TestAsync(
@"
using System;
class C
{
void M()
{
int p = 0;
ref readonly int refP = ref p!;
Console.WriteLine(p);
}
}");
}

[Fact]
public async Task TestPointerCausingPossibleReassignment()
{
Expand Down Expand Up @@ -994,6 +1012,22 @@ void M(int p, int p)
{
p = 1;
}
}");
}

[Fact, WorkItem(58161, "https://github.com/dotnet/roslyn/issues/58161")]
public async Task TestRefToSuppression1()
{
await TestAsync(
@"#nullable enable

using System.Diagnostics.CodeAnalysis;
using System.Threading;

class C
{
public static T EnsureInitialized<T>([NotNull] ref T? [|target|]) where T : class
=> Volatile.Read(ref [|target|]!);
}");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,27 @@ public static bool IsInOutContext(this ExpressionSyntax expression)
}

public static bool IsInRefContext(this ExpressionSyntax expression)
=> expression.IsParentKind(SyntaxKind.RefExpression) ||
(expression?.Parent as ArgumentSyntax)?.RefOrOutKeyword.Kind() == SyntaxKind.RefKeyword;
=> IsInRefContext(expression, out _);

/// <summary>
/// Returns true if this expression is in some <c>ref</c> keyword context. If <see langword="true"/> then
/// <paramref name="refParent"/> will be the node containing the <see langword="ref"/> keyword.
/// </summary>
public static bool IsInRefContext(this ExpressionSyntax expression, [NotNullWhen(true)] out SyntaxNode? refParent)
{
while (expression.Parent is ParenthesizedExpressionSyntax or PostfixUnaryExpressionSyntax(SyntaxKind.SuppressNullableWarningExpression))
expression = (ExpressionSyntax)expression.Parent;

if (expression.Parent is RefExpressionSyntax or
ArgumentSyntax { RefOrOutKeyword.RawKind: (int)SyntaxKind.RefKeyword })
{
refParent = expression.Parent;
return true;
}

refParent = null;
return false;
}

public static bool IsInInContext(this ExpressionSyntax expression)
=> (expression?.Parent as ArgumentSyntax)?.RefKindKeyword.Kind() == SyntaxKind.InKeyword;
Expand Down Expand Up @@ -311,12 +330,12 @@ public static bool IsWrittenTo(this ExpressionSyntax expression, SemanticModel s
if (expression.IsOnlyWrittenTo())
return true;

if (expression.IsInRefContext())
if (expression.IsInRefContext(out var refParent))
{
// most cases of `ref x` will count as a potential write of `x`. An important exception is:
// `ref readonly y = ref x`. In that case, because 'y' can't be written to, this would not
// be a write of 'x'.
if (expression is { Parent: { RawKind: (int)SyntaxKind.RefExpression, Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: RefTypeSyntax refType } } } } }
if (refParent.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type: RefTypeSyntax refType } } }
&& refType.ReadOnlyKeyword != default)
{
return false;
Expand Down