Skip to content

Commit 9b8a545

Browse files
jroesseljosefpihrt
andauthored
Fixed DeclareAsNullableCodeFixProvider for casts in variable declarations (#1393)
Co-authored-by: Josef Pihrt <josef@pihrt.net>
1 parent 2af15db commit 9b8a545

3 files changed

Lines changed: 83 additions & 1 deletion

File tree

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919

2020
- Fix analyzer [RCS0049](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0049) ([PR](https://github.com/dotnet/roslynator/pull/1386))
2121
- Fix analyzer [RCS1159](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1159) ([PR](https://github.com/dotnet/roslynator/pull/1390))
22+
- Fix code fix for [CS8600](https://josefpihrt.github.io/docs/roslynator/fixes/CS8600) changing the wrong type when casts or `var` are involved ([PR](https://github.com/dotnet/roslynator/pull/1393) by @jroessel)
2223

2324
## [4.10.0] - 2024-01-24
2425

src/CodeFixes/CSharp/CodeFixes/DeclareAsNullableCodeFixProvider.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System.Collections.Immutable;
44
using System.Composition;
5+
using System.Linq;
56
using System.Threading.Tasks;
67
using Microsoft.CodeAnalysis;
78
using Microsoft.CodeAnalysis.CodeActions;
@@ -30,7 +31,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
3031
if (!IsEnabled(diagnostic.Id, CodeFixIdentifiers.AddNullableAnnotation, context.Document, root.SyntaxTree))
3132
return;
3233

33-
if (!TryFindFirstAncestorOrSelf(root, context.Span, out SyntaxNode node, predicate: f => f.IsKind(SyntaxKind.EqualsValueClause, SyntaxKind.DeclarationExpression, SyntaxKind.SimpleAssignmentExpression)))
34+
if (!TryFindFirstAncestorOrSelf(root, context.Span, out SyntaxNode node, predicate: f => f.IsKind(SyntaxKind.EqualsValueClause, SyntaxKind.DeclarationExpression, SyntaxKind.SimpleAssignmentExpression, SyntaxKind.CastExpression)))
3435
return;
3536

3637
if (node is EqualsValueClauseSyntax equalsValueClause)
@@ -67,6 +68,10 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
6768
}
6869
}
6970
}
71+
else if (node is CastExpressionSyntax castExpression)
72+
{
73+
TryRegisterCodeFixForCast(context, diagnostic, castExpression.Type);
74+
}
7075
}
7176

7277
private static void TryRegisterCodeFix(CodeFixContext context, Diagnostic diagnostic, TypeSyntax type)
@@ -85,4 +90,46 @@ private static void TryRegisterCodeFix(CodeFixContext context, Diagnostic diagno
8590

8691
context.RegisterCodeFix(codeAction, diagnostic);
8792
}
93+
94+
private static void TryRegisterCodeFixForCast(CodeFixContext context, Diagnostic diagnostic, TypeSyntax type)
95+
{
96+
if (type.IsKind(SyntaxKind.NullableType))
97+
return;
98+
99+
CodeAction codeAction = CodeAction.Create(
100+
"Declare as nullable",
101+
async ct =>
102+
{
103+
NullableTypeSyntax newType = SyntaxFactory.NullableType(type.WithoutTrivia()).WithTriviaFrom(type);
104+
105+
// This could be in a variable declaration whose type we also may have to change
106+
if (type.Parent?.Parent is EqualsValueClauseSyntax
107+
{
108+
Parent: VariableDeclaratorSyntax
109+
{
110+
Parent: VariableDeclarationSyntax
111+
{
112+
Variables.Count: 1,
113+
Type: { IsVar: false } declarationType
114+
} variableDeclaration
115+
}
116+
}
117+
&& !declarationType.IsKind(SyntaxKind.NullableType))
118+
{
119+
NullableTypeSyntax newDeclarationType = SyntaxFactory.NullableType(declarationType.WithoutTrivia()).WithTriviaFrom(declarationType);
120+
VariableDeclarationSyntax newVariableDeclaration = variableDeclaration
121+
.ReplaceNode(type, newType)
122+
.WithType(newDeclarationType);
123+
124+
return await context.Document.ReplaceNodeAsync(variableDeclaration, newVariableDeclaration, ct).ConfigureAwait(false);
125+
}
126+
else
127+
{
128+
return await context.Document.ReplaceNodeAsync(type, newType, ct).ConfigureAwait(false);
129+
}
130+
},
131+
GetEquivalenceKey(diagnostic));
132+
133+
context.RegisterCodeFix(codeAction, diagnostic);
134+
}
88135
}

src/Tests/CodeFixes.Tests/CS8600ConvertingNullLiteralOrPossibleNullValueToNonNullableTypeTests.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,40 @@ void M()
4242
", equivalenceKey: EquivalenceKey.Create(DiagnosticId));
4343
}
4444

45+
[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.CS8600_ConvertingNullLiteralOrPossibleNullValueToNonNullableType)]
46+
public async Task Test_LocalDeclarationWithCast()
47+
{
48+
await VerifyFixAsync(@"
49+
using System;
50+
#nullable enable
51+
52+
public class C
53+
{
54+
private object? Get() => null;
55+
56+
void M()
57+
{
58+
var s = (string) Get();
59+
string s2 = (string) Get();
60+
}
61+
}
62+
", @"
63+
using System;
64+
#nullable enable
65+
66+
public class C
67+
{
68+
private object? Get() => null;
69+
70+
void M()
71+
{
72+
var s = (string?) Get();
73+
string? s2 = (string?) Get();
74+
}
75+
}
76+
", equivalenceKey: EquivalenceKey.Create(DiagnosticId));
77+
}
78+
4579
[Fact, Trait(Traits.CodeFix, CompilerDiagnosticIdentifiers.CS8600_ConvertingNullLiteralOrPossibleNullValueToNonNullableType)]
4680
public async Task Test_DeclarationExpression()
4781
{

0 commit comments

Comments
 (0)