提交 ab6c64b6 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #19836 from CyrusNajmabadi/nullPropagationNullable

Properly use ?. in an expression containing a nullable type.
......@@ -360,5 +360,75 @@ public void Method<T>(Expression<Func<T, string>> functor)
}
}");
}
[WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestNullableMemberAccess()
{
await TestInRegularAndScriptAsync(
@"
using System;
class C
{
void Main(DateTime? toDate)
{
var v = [||]toDate == null ? null : toDate.Value.ToString(""yyyy/MM/ dd"");
}
}
",
@"
using System;
class C
{
void Main(DateTime? toDate)
{
var v = toDate?.ToString(""yyyy/MM/ dd"");
}
}
");
}
[WorkItem(19774, "https://github.com/dotnet/roslyn/issues/19774")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestNullableElementAccess()
{
await TestInRegularAndScriptAsync(
@"
using System;
struct S
{
public string this[int i] => """";
}
class C
{
void Main(S? s)
{
var x = [||]s == null ? null : s.Value[0];
}
}
",
@"
using System;
struct S
{
public string this[int i] => """";
}
class C
{
void Main(S? s)
{
var x = s?[0];
}
}
");
}
}
}
\ No newline at end of file
......@@ -110,10 +110,7 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
var arguments = (SeparatedSyntaxList<TArgumentSyntax>)syntaxFacts.GetArgumentsOfObjectCreationExpression(objectCreation);
var comparer = syntaxFacts.IsCaseSensitive
? StringComparer.Ordinal
: CaseInsensitiveComparison.Comparer;
var comparer = syntaxFacts.StringComparer;
var constructorsAndArgumentToAdd = ArrayBuilder<(IMethodSymbol constructor, TArgumentSyntax argument, int index)>.GetInstance();
foreach (var constructor in type.InstanceConstructors.OrderBy(m => m.Parameters.Length))
......
......@@ -121,7 +121,7 @@ public ITypeSymbol DetermineReturnType(CancellationToken cancellationToken)
var languageServiceProvider = this.Document.Project.Solution.Workspace.Services.GetLanguageServices(this.State.TypeToGenerateIn.Language);
var syntaxFacts = languageServiceProvider.GetService<ISyntaxFactsService>();
var equalityComparer = syntaxFacts.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase;
var equalityComparer = syntaxFacts.StringComparer;
var reservedParameterNames = this.DetermineParameterNames(cancellationToken)
.Select(p => p.BestNameForParameter)
.ToSet(equalityComparer);
......
......@@ -65,6 +65,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
syntaxFacts.GetPartsOfConditionalExpression(
conditionalExpression, out var condition, out var whenTrue, out var whenFalse);
var whenPartIsNullable = diagnostic.Properties.ContainsKey(UseNullPropagationConstants.WhenPartIsNullable);
editor.ReplaceNode(conditionalExpression,
(c, g) => {
syntaxFacts.GetPartsOfConditionalExpression(
......@@ -82,7 +83,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
}
var newNode = CreateConditionalAccessExpression(
syntaxFacts, g, currentWhenPartToCheck, match, c);
syntaxFacts, g, whenPartIsNullable, currentWhenPartToCheck, match, c);
newNode = newNode.WithTriviaFrom(c);
return newNode;
......@@ -93,10 +94,40 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
}
private SyntaxNode CreateConditionalAccessExpression(
ISyntaxFactsService syntaxFacts, SyntaxGenerator generator,
ISyntaxFactsService syntaxFacts, SyntaxGenerator generator, bool whenPartIsNullable,
SyntaxNode whenPart, SyntaxNode match, SyntaxNode currentConditional)
{
var memberAccess = match.Parent as TMemberAccessExpression;
if (whenPartIsNullable)
{
if (match.Parent is TMemberAccessExpression memberAccess)
{
var nameNode = syntaxFacts.GetNameOfMemberAccessExpression(memberAccess);
syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out var arity);
var comparer = syntaxFacts.StringComparer;
if (arity == 0 && comparer.Equals(name, nameof(Nullable<int>.Value)))
{
// They're calling ".Value" off of a nullable. Because we're moving to ?.
// we want to remove the .Value as well. i.e. we should generate:
//
// foo?.Bar() not foo?.Value.Bar();
return CreateConditionalAccessExpression(
syntaxFacts, generator, whenPart, match,
memberAccess.Parent, currentConditional);
}
}
}
return CreateConditionalAccessExpression(
syntaxFacts, generator, whenPart, match,
match.Parent, currentConditional);
}
private SyntaxNode CreateConditionalAccessExpression(
ISyntaxFactsService syntaxFacts, SyntaxGenerator generator,
SyntaxNode whenPart, SyntaxNode match, SyntaxNode matchParent, SyntaxNode currentConditional)
{
var memberAccess = matchParent as TMemberAccessExpression;
if (memberAccess != null)
{
return whenPart.ReplaceNode(memberAccess,
......@@ -106,7 +137,7 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
syntaxFacts.GetNameOfMemberAccessExpression(memberAccess))));
}
var elementAccess = match.Parent as TElementAccessExpression;
var elementAccess = matchParent as TElementAccessExpression;
if (elementAccess != null)
{
return whenPart.ReplaceNode(elementAccess,
......
......@@ -8,6 +8,11 @@
namespace Microsoft.CodeAnalysis.UseNullPropagation
{
internal static class UseNullPropagationConstants
{
public const string WhenPartIsNullable = nameof(WhenPartIsNullable);
}
internal abstract class AbstractUseNullPropagationDiagnosticAnalyzer<
TSyntaxKind,
TExpressionSyntax,
......@@ -147,10 +152,18 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol e
conditionPartToCheck.GetLocation(),
whenPartToCheck.GetLocation());
var properties = ImmutableDictionary<string, string>.Empty;
var whenPartIsNullable = semanticModel.GetTypeInfo(whenPartMatch).Type?.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T;
if (whenPartIsNullable)
{
properties = properties.Add(UseNullPropagationConstants.WhenPartIsNullable, "");
}
context.ReportDiagnostic(Diagnostic.Create(
this.GetDescriptorWithSeverity(option.Notification.Value),
conditionalExpression.GetLocation(),
locations));
locations,
properties));
}
internal static SyntaxNode GetWhenPartMatch(
......
......@@ -30,6 +30,8 @@ private CSharpSyntaxFactsService()
public bool IsCaseSensitive => true;
public StringComparer StringComparer { get; } = StringComparer.Ordinal;
protected override IDocumentationCommentService DocumentationCommentService
=> CSharpDocumentationCommentService.Instance;
......
......@@ -69,8 +69,6 @@ public static async Task<IEnumerable<SyntaxToken>> GetConstructorInitializerToke
}
internal static bool TextMatch(this ISyntaxFactsService syntaxFacts, string text1, string text2)
{
return syntaxFacts.IsCaseSensitive ? text1 == text2 : string.Equals(text1, text2, StringComparison.OrdinalIgnoreCase);
}
=> syntaxFacts.StringComparer.Equals(text1, text2);
}
}
}
\ No newline at end of file
......@@ -13,6 +13,7 @@ namespace Microsoft.CodeAnalysis.LanguageServices
internal interface ISyntaxFactsService : ILanguageService
{
bool IsCaseSensitive { get; }
StringComparer StringComparer { get; }
bool SupportsIndexingInitializer(ParseOptions options);
bool SupportsThrowExpression(ParseOptions options);
......
......@@ -40,6 +40,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
End Get
End Property
Public ReadOnly Property StringComparer As StringComparer Implements ISyntaxFactsService.StringComparer
Get
Return CaseInsensitiveComparison.Comparer
End Get
End Property
Protected Overrides ReadOnly Property DocumentationCommentService As IDocumentationCommentService
Get
Return VisualBasicDocumentationCommentService.Instance
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册