From 0db8fa718756d9b7668c70d8adb1143434bb1bdc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 12:15:21 -0800 Subject: [PATCH 01/14] Extract simplifier logic into unique location. --- ...harpSimplifyTypeNamesDiagnosticAnalyzer.cs | 7 +++- .../Extensions/ExpressionSyntaxExtensions.cs | 6 +-- .../Reducers/CSharpNameReducer.cs | 8 ++-- .../Simplifiers/AbstractCSharpSimplifier.cs | 21 ++++++++++ .../Simplifiers/QualifiedCrefSimplifier.cs} | 40 ++++++++++++------- 5 files changed, 60 insertions(+), 22 deletions(-) create mode 100644 src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs rename src/Workspaces/CSharp/Portable/{Extensions/CrefSyntaxExtensions.cs => Simplification/Simplifiers/QualifiedCrefSimplifier.cs} (74%) diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs index c949e7d0b6569..9fa971e042bc2 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs @@ -6,6 +6,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Options; @@ -84,8 +85,12 @@ internal override bool CanSimplifyTypeNameExpression( SyntaxNode replacementSyntax; if (node.IsKind(SyntaxKind.QualifiedCref, out QualifiedCrefSyntax crefSyntax)) { - if (!crefSyntax.TryReduceOrSimplifyExplicitName(model, out var replacement, out issueSpan, optionSet, cancellationToken)) + if (!QualifiedCrefSimplifier.Instance.TrySimplify( + crefSyntax, model, optionSet, + out var replacement, out issueSpan, cancellationToken)) + { return false; + } replacementSyntax = replacement; } diff --git a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs index 1307956b1fba8..8872087133ee2 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs @@ -7,6 +7,7 @@ using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Simplification; +using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; @@ -1740,11 +1741,8 @@ private static bool TryReduceCrefColorColor( // QualifiedCrefSyntax var qualifiedReplacement = SyntaxFactory.QualifiedCref(replacement, qualifiedCrefParent.Member); - if (qualifiedCrefParent.TryReduceOrSimplifyQualifiedCref( - semanticModel, qualifiedReplacement, out _, out _, cancellationToken)) - { + if (QualifiedCrefSimplifier.CanSimplifyWithReplacement(qualifiedCrefParent, semanticModel, qualifiedReplacement, cancellationToken)) return true; - } } else if (name.Parent is QualifiedNameSyntax qualifiedParent && qualifiedParent.Left == name && replacement is NameSyntax replacementName) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs index 0284aef67fe71..d06971475d05d 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs @@ -3,6 +3,7 @@ using System; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Options; @@ -32,10 +33,11 @@ private static SyntaxNode SimplifyName( SyntaxNode replacementNode; TextSpan issueSpan; - if (node.Kind() == SyntaxKind.QualifiedCref) + if (node.IsKind(SyntaxKind.QualifiedCref, out QualifiedCrefSyntax crefSyntax)) { - var crefSyntax = (QualifiedCrefSyntax)node; - if (!crefSyntax.TryReduceOrSimplifyExplicitName(semanticModel, out var crefReplacement, out issueSpan, optionSet, cancellationToken)) + if (!QualifiedCrefSimplifier.Instance.TrySimplify( + crefSyntax, semanticModel, optionSet, + out var crefReplacement, out issueSpan, cancellationToken)) { return node; } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs new file mode 100644 index 0000000000000..8a0d96f97c37a --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers +{ + internal abstract class AbstractCSharpSimplifier + where TSyntax : SyntaxNode + where TSimplifiedSyntax : SyntaxNode + { + public abstract bool TrySimplify( + TSyntax crefSyntax, + SemanticModel semanticModel, + OptionSet optionSet, + out TSimplifiedSyntax replacementNode, + out TextSpan issueSpan, + CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/CSharp/Portable/Extensions/CrefSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs similarity index 74% rename from src/Workspaces/CSharp/Portable/Extensions/CrefSyntaxExtensions.cs rename to src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs index 6a6f9e5ea5956..7b8ce0dea8b75 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/CrefSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs @@ -2,23 +2,30 @@ using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.CodeAnalysis.CSharp.Extensions +namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { - using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + using static SyntaxFactory; - internal static class CrefSyntaxExtensions + internal class QualifiedCrefSimplifier : AbstractCSharpSimplifier { - public static bool TryReduceOrSimplifyExplicitName( - this QualifiedCrefSyntax crefSyntax, + public static readonly QualifiedCrefSimplifier Instance = new QualifiedCrefSimplifier(); + + private QualifiedCrefSimplifier() + { + } + + public override bool TrySimplify( + QualifiedCrefSyntax crefSyntax, SemanticModel semanticModel, + OptionSet optionSet, out CrefSyntax replacementNode, out TextSpan issueSpan, - OptionSet optionSet, CancellationToken cancellationToken) { replacementNode = null; @@ -54,7 +61,7 @@ public static bool TryReduceOrSimplifyExplicitName( } } - return TryReduceOrSimplifyQualifiedCref( + return CanSimplifyWithReplacement( crefSyntax, semanticModel, memberCref, out replacementNode, out issueSpan, cancellationToken); } @@ -66,19 +73,24 @@ private static TypeCrefSyntax CreateReplacement(QualifiedCrefSyntax crefSyntax, return TypeCref(PredefinedType(token)).WithAdditionalAnnotations(annotation); } - public static bool TryReduceOrSimplifyQualifiedCref( - this QualifiedCrefSyntax crefSyntax, SemanticModel semanticModel, + public static bool CanSimplifyWithReplacement( + QualifiedCrefSyntax crefSyntax, SemanticModel semanticModel, + CrefSyntax replacement, CancellationToken cancellationToken) + { + return CanSimplifyWithReplacement(crefSyntax, semanticModel, replacement, out _, out _, cancellationToken); + } + + private static bool CanSimplifyWithReplacement( + QualifiedCrefSyntax crefSyntax, SemanticModel semanticModel, CrefSyntax replacement, out CrefSyntax replacementNode, out TextSpan issueSpan, CancellationToken cancellationToken) { var oldSymbol = semanticModel.GetSymbolInfo(crefSyntax, cancellationToken).Symbol; if (oldSymbol != null) { - var speculativeBindingOption = SpeculativeBindingOption.BindAsExpression; - if (oldSymbol is INamespaceOrTypeSymbol) - { - speculativeBindingOption = SpeculativeBindingOption.BindAsTypeOrNamespace; - } + var speculativeBindingOption = oldSymbol is INamespaceOrTypeSymbol + ? SpeculativeBindingOption.BindAsTypeOrNamespace + : SpeculativeBindingOption.BindAsExpression; var newSymbol = semanticModel.GetSpeculativeSymbolInfo(crefSyntax.SpanStart, replacement, speculativeBindingOption).Symbol; From d5f99b509148255068bafe4bef47cefae4a625e1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 12:18:41 -0800 Subject: [PATCH 02/14] Remove unnecessary indirections in SimplifyTypeNames --- ...harpSimplifyTypeNamesDiagnosticAnalyzer.cs | 11 ---------- ...SimplifyTypeNamesDiagnosticAnalyzerBase.cs | 20 +------------------ ...asicSimplifyTypeNamesDiagnosticAnalyzer.vb | 8 -------- 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs index c949e7d0b6569..7f9627510d7fc 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs @@ -49,17 +49,6 @@ protected override void AnalyzeSemanticModel(SemanticModelAnalysisContext contex internal override bool IsCandidate(SyntaxNode node) => node != null && s_kindsOfInterest.Contains(node.Kind()); - protected sealed override bool CanSimplifyTypeNameExpressionCore( - SemanticModel model, SyntaxNode node, OptionSet optionSet, - out TextSpan issueSpan, out string diagnosticId, out bool inDeclaration, - CancellationToken cancellationToken) - { - return CanSimplifyTypeNameExpression( - model, node, optionSet, - out issueSpan, out diagnosticId, out inDeclaration, - cancellationToken); - } - internal override bool CanSimplifyTypeNameExpression( SemanticModel model, SyntaxNode node, OptionSet optionSet, out TextSpan issueSpan, out string diagnosticId, out bool inDeclaration, diff --git a/src/Features/Core/Portable/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs b/src/Features/Core/Portable/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs index 0524d9c7fe39a..f43da43c18a93 100644 --- a/src/Features/Core/Portable/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs +++ b/src/Features/Core/Portable/SimplifyTypeNames/SimplifyTypeNamesDiagnosticAnalyzerBase.cs @@ -96,29 +96,11 @@ private void AnalyzeCompilation(CompilationStartAnalysisContext context) protected abstract void AnalyzeSemanticModel(SemanticModelAnalysisContext context); - protected abstract bool CanSimplifyTypeNameExpressionCore( - SemanticModel model, SyntaxNode node, OptionSet optionSet, - out TextSpan issueSpan, out string diagnosticId, out bool inDeclaration, - CancellationToken cancellationToken); - protected abstract string GetLanguageName(); - protected bool TrySimplifyTypeNameExpression(SemanticModel model, SyntaxNode node, AnalyzerOptions analyzerOptions, out Diagnostic diagnostic, CancellationToken cancellationToken) - { - var syntaxTree = node.SyntaxTree; - var optionSet = analyzerOptions.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); - if (optionSet == null) - { - diagnostic = null; - return false; - } - - return TrySimplify(model, node, out diagnostic, optionSet, cancellationToken); - } - public bool TrySimplify(SemanticModel model, SyntaxNode node, out Diagnostic diagnostic, OptionSet optionSet, CancellationToken cancellationToken) { - if (!CanSimplifyTypeNameExpressionCore( + if (!CanSimplifyTypeNameExpression( model, node, optionSet, out var issueSpan, out var diagnosticId, out var inDeclaration, cancellationToken)) diff --git a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb index 26f75e4e4aa8f..6c62b52781166 100644 --- a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb @@ -46,14 +46,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.SimplifyTypeNames Return node IsNot Nothing AndAlso IsNodeKindInteresting(node) End Function - Protected Overrides Function CanSimplifyTypeNameExpressionCore( - model As SemanticModel, node As SyntaxNode, optionSet As OptionSet, - ByRef issueSpan As TextSpan, ByRef diagnosticId As String, ByRef inDeclaration As Boolean, - cancellationToken As CancellationToken) As Boolean - Return CanSimplifyTypeNameExpression( - model, node, optionSet, issueSpan, diagnosticId, inDeclaration, cancellationToken) - End Function - Friend Overrides Function CanSimplifyTypeNameExpression( model As SemanticModel, node As SyntaxNode, optionSet As OptionSet, ByRef issueSpan As TextSpan, ByRef diagnosticId As String, ByRef inDeclaration As Boolean, From 17caaddcdfadc276219be617385f4223c5b02938 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 13:15:39 -0800 Subject: [PATCH 03/14] Move code --- .../Extensions/ExpressionSyntaxExtensions.cs | 513 ------------ .../Simplifiers/ExpressionSyntaxSimplifier.cs | 534 +++++++++++++ .../Simplifiers/NameSyntaxSimplifier.cs | 732 ++++++++++++++++++ 3 files changed, 1266 insertions(+), 513 deletions(-) create mode 100644 src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs create mode 100644 src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs diff --git a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs index 8872087133ee2..e1147de603d5b 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs @@ -749,519 +749,6 @@ public static bool CanAccessInstanceAndStaticMembersOffOf( return false; } - public static bool TryReduceOrSimplifyExplicitName( - this ExpressionSyntax expression, - SemanticModel semanticModel, - out ExpressionSyntax replacementNode, - out TextSpan issueSpan, - OptionSet optionSet, - CancellationToken cancellationToken) - { - if (TryReduceExplicitName(expression, semanticModel, out var replacementTypeNode, out issueSpan, optionSet, cancellationToken)) - { - replacementNode = replacementTypeNode; - return true; - } - - return TrySimplify(expression, semanticModel, out replacementNode, out issueSpan); - } - - private static bool TryReduceExplicitName( - ExpressionSyntax expression, - SemanticModel semanticModel, - out TypeSyntax replacementNode, - out TextSpan issueSpan, - OptionSet optionSet, - CancellationToken cancellationToken) - { - replacementNode = null; - issueSpan = default; - - if (expression.ContainsInterleavedDirective(cancellationToken)) - return false; - - if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess)) - return TryReduceMemberAccessExpression(memberAccess, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken); - - if (expression is NameSyntax name) - return TryReduceName(name, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken); - - return false; - } - - private static bool TryReduceMemberAccessExpression( - MemberAccessExpressionSyntax memberAccess, - SemanticModel semanticModel, - out TypeSyntax replacementNode, - out TextSpan issueSpan, - OptionSet optionSet, - CancellationToken cancellationToken) - { - replacementNode = null; - issueSpan = default; - - if (memberAccess.Name == null || memberAccess.Expression == null) - return false; - - // if this node is annotated as being a SpecialType, let's use this information. - if (memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind)) - { - replacementNode = SyntaxFactory.PredefinedType( - SyntaxFactory.Token( - memberAccess.GetLeadingTrivia(), - GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())), - memberAccess.GetTrailingTrivia())); - - issueSpan = memberAccess.Span; - return true; - } - - // See https://github.com/dotnet/roslyn/issues/40974 - // - // To be very safe, we only support simplifying code that bound to a symbol without any - // sort of problems. We could potentially relax this in the future. However, we would - // need to be very careful about the implications of us offering to fixup 'broken' code - // in a manner that might end up making things worse or confusing the user. - var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, memberAccess); - if (symbol == null) - return false; - - if (memberAccess.Expression.IsKind(SyntaxKind.ThisExpression) && - !SimplificationHelpers.ShouldSimplifyThisOrMeMemberAccessExpression(semanticModel, optionSet, symbol)) - { - return false; - } - - // if this node is on the left side, we could simplify to aliases - if (!memberAccess.IsRightSideOfDot()) - { - // Check if we need to replace this syntax with an alias identifier - if (TryReplaceExpressionWithAlias( - memberAccess, semanticModel, symbol, - cancellationToken, out var aliasReplacement)) - { - // get the token text as it appears in source code to preserve e.g. unicode character escaping - var text = aliasReplacement.Name; - var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault(); - - if (syntaxRef != null) - { - var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier; - text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString(); - } - - replacementNode = SyntaxFactory.IdentifierName( - memberAccess.Name.Identifier.CopyAnnotationsTo(SyntaxFactory.Identifier( - memberAccess.GetLeadingTrivia(), - SyntaxKind.IdentifierToken, - text, - aliasReplacement.Name, - memberAccess.GetTrailingTrivia()))); - - replacementNode = memberAccess.CopyAnnotationsTo(replacementNode); - replacementNode = memberAccess.Name.CopyAnnotationsTo(replacementNode); - - issueSpan = memberAccess.Span; - - // In case the alias name is the same as the last name of the alias target, we only include - // the left part of the name in the unnecessary span to Not confuse uses. - if (memberAccess.Name.Identifier.ValueText == ((IdentifierNameSyntax)replacementNode).Identifier.ValueText) - { - issueSpan = memberAccess.Expression.Span; - } - - return true; - } - - // Check if the Expression can be replaced by Predefined Type keyword - if (PreferPredefinedTypeKeywordInMemberAccess(memberAccess, optionSet, semanticModel)) - { - if (symbol != null && symbol.IsKind(SymbolKind.NamedType)) - { - var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)symbol).SpecialType); - if (keywordKind != SyntaxKind.None) - { - replacementNode = CreatePredefinedTypeSyntax(memberAccess, keywordKind); - - replacementNode = replacementNode - .WithAdditionalAnnotations(new SyntaxAnnotation( - nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess))); - - issueSpan = memberAccess.Span; // we want to show the whole expression as unnecessary - - return true; - } - } - } - } - - // Try to eliminate cases without actually calling CanReplaceWithReducedName. For expressions of the form - // 'this.Name' or 'base.Name', no additional check here is required. - if (!memberAccess.Expression.IsKind(SyntaxKind.ThisExpression, SyntaxKind.BaseExpression)) - { - GetReplacementCandidates( - semanticModel, - memberAccess, - symbol, - out var speculativeSymbols, - out var speculativeNamespacesAndTypes); - - if (!IsReplacementCandidate(symbol, speculativeSymbols, speculativeNamespacesAndTypes)) - { - return false; - } - } - - replacementNode = memberAccess.GetNameWithTriviaMoved(); - issueSpan = memberAccess.Expression.Span; - - return CanReplaceWithReducedName( - memberAccess, replacementNode, semanticModel, symbol, cancellationToken); - } - - public static SimpleNameSyntax GetNameWithTriviaMoved(this MemberAccessExpressionSyntax memberAccess) - => memberAccess.Name - .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) - .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); - - private static void GetReplacementCandidates( - SemanticModel semanticModel, - MemberAccessExpressionSyntax memberAccess, - ISymbol actualSymbol, - out ImmutableArray speculativeSymbols, - out ImmutableArray speculativeNamespacesAndTypes) - { - var containsNamespaceOrTypeSymbol = actualSymbol is INamespaceOrTypeSymbol; - var containsOtherSymbol = !containsNamespaceOrTypeSymbol; - - speculativeSymbols = containsOtherSymbol - ? semanticModel.LookupSymbols(memberAccess.SpanStart, name: memberAccess.Name.Identifier.ValueText) - : ImmutableArray.Empty; - speculativeNamespacesAndTypes = containsNamespaceOrTypeSymbol - ? semanticModel.LookupNamespacesAndTypes(memberAccess.SpanStart, name: memberAccess.Name.Identifier.ValueText) - : ImmutableArray.Empty; - } - - /// - /// Determines if and - /// together contain a superset of the symbols in . - /// - private static bool IsReplacementCandidate(ISymbol actualSymbol, ImmutableArray speculativeSymbols, ImmutableArray speculativeNamespacesAndTypes) - { - if (speculativeSymbols.IsEmpty && speculativeNamespacesAndTypes.IsEmpty) - { - return false; - } - - if (actualSymbol is object) - { - return speculativeSymbols.Contains(actualSymbol, CandidateSymbolEqualityComparer.Instance) - || speculativeNamespacesAndTypes.Contains(actualSymbol, CandidateSymbolEqualityComparer.Instance); - } - - return true; - } - - /// - /// Compares symbols by their original definition. - /// - private sealed class CandidateSymbolEqualityComparer : IEqualityComparer - { - public static CandidateSymbolEqualityComparer Instance { get; } = new CandidateSymbolEqualityComparer(); - - private CandidateSymbolEqualityComparer() - { - } - - public bool Equals(ISymbol x, ISymbol y) - { - if (x is null || y is null) - { - return x == y; - } - - return x.OriginalDefinition.Equals(y.OriginalDefinition); - } - - public int GetHashCode(ISymbol obj) - { - return obj?.OriginalDefinition.GetHashCode() ?? 0; - } - } - - private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(MemberAccessExpressionSyntax memberAccess) - { - // We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. - // However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering - // aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines - // etc. around the user's original code. - return memberAccess.GetLeadingTrivia() - .AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia()) - .AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia()) - .AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia()) - .AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia()); - } - - private static IEnumerable WithoutElasticTrivia(this IEnumerable list) - { - return list.Where(t => !t.IsElastic()); - } - - public static bool InsideCrefReference(this ExpressionSyntax expression) - { - var crefAttribute = expression.FirstAncestorOrSelf(); - return crefAttribute != null; - } - - private static bool InsideNameOfExpression(ExpressionSyntax expression, SemanticModel semanticModel) - { - var nameOfInvocationExpr = expression.FirstAncestorOrSelf( - invocationExpr => - { - return (invocationExpr.Expression is IdentifierNameSyntax identifierName) && (identifierName.Identifier.Text == "nameof") && - semanticModel.GetConstantValue(invocationExpr).HasValue && - (semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String); - }); - - return nameOfInvocationExpr != null; - } - - private static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel) - { - return !IsInMemberAccessContext(name) && - !InsideCrefReference(name) && - !InsideNameOfExpression(name, semanticModel) && - SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language); - } - - private static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, OptionSet optionSet, SemanticModel semanticModel) - { - if (!SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, semanticModel.Language)) - return false; - - return (IsInMemberAccessContext(expression) || InsideCrefReference(expression)) && - !InsideNameOfExpression(expression, semanticModel); - } - - public static bool IsInMemberAccessContext(this ExpressionSyntax expression) => - expression?.Parent is MemberAccessExpressionSyntax; - - private static bool IsAliasReplaceableExpression(ExpressionSyntax expression) - { - var current = expression; - while (current.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax currentMember)) - { - current = currentMember.Expression; - continue; - } - - return current.IsKind(SyntaxKind.AliasQualifiedName, - SyntaxKind.IdentifierName, - SyntaxKind.GenericName, - SyntaxKind.QualifiedName); - } - - [PerformanceSensitive( - "https://github.com/dotnet/roslyn/issues/23582", - Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")] - private static bool TryReplaceExpressionWithAlias( - ExpressionSyntax node, SemanticModel semanticModel, - ISymbol symbol, CancellationToken cancellationToken, out IAliasSymbol aliasReplacement) - { - aliasReplacement = null; - - if (!IsAliasReplaceableExpression(node)) - return false; - - // Avoid the TryReplaceWithAlias algorithm if the tree has no using alias directives. Since the input node - // might be a speculative node (not fully rooted in a tree), we use the original semantic model to find the - // equivalent node in the original tree, and from there determine if the tree has any using alias - // directives. - var originalModel = semanticModel.GetOriginalSemanticModel(); - - // Perf: We are only using the syntax tree root in a fast-path syntax check. If the root is not readily - // available, it is fine to continue through the normal algorithm. - if (originalModel.SyntaxTree.TryGetRoot(out var root)) - { - if (!HasUsingAliasDirective(root)) - { - return false; - } - } - - // If the Symbol is a constructor get its containing type - if (symbol.IsConstructor()) - { - symbol = symbol.ContainingType; - } - - if (node is QualifiedNameSyntax || node is AliasQualifiedNameSyntax) - { - SyntaxAnnotation aliasAnnotationInfo = null; - - // The following condition checks if the user has used alias in the original code and - // if so the expression is replaced with the Alias - if (node is QualifiedNameSyntax qualifiedNameNode) - { - if (qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind)) - { - aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); - } - } - - if (node is AliasQualifiedNameSyntax aliasQualifiedNameNode) - { - if (aliasQualifiedNameNode.Name.Identifier.HasAnnotations(AliasAnnotation.Kind)) - { - aliasAnnotationInfo = aliasQualifiedNameNode.Name.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); - } - } - - if (aliasAnnotationInfo != null) - { - var aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo); - var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName); - - var aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace); - - if (aliasTypeInfo != null) - { - aliasReplacement = aliasTypeInfo; - return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); - } - } - } - - if (node.Kind() == SyntaxKind.IdentifierName && - semanticModel.GetAliasInfo((IdentifierNameSyntax)node, cancellationToken) != null) - { - return false; - } - - // an alias can only replace a type or namespace - if (symbol == null || - (symbol.Kind != SymbolKind.Namespace && symbol.Kind != SymbolKind.NamedType)) - { - return false; - } - - var preferAliasToQualifiedName = true; - if (node is QualifiedNameSyntax qualifiedName) - { - if (!qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) - { - var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; - if (type != null) - { - var keywordKind = GetPredefinedKeywordKind(type.SpecialType); - if (keywordKind != SyntaxKind.None) - { - preferAliasToQualifiedName = false; - } - } - } - } - - if (node is AliasQualifiedNameSyntax aliasQualifiedNameSyntax) - { - if (!aliasQualifiedNameSyntax.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) - { - var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; - if (type != null) - { - var keywordKind = GetPredefinedKeywordKind(type.SpecialType); - if (keywordKind != SyntaxKind.None) - { - preferAliasToQualifiedName = false; - } - } - } - } - - aliasReplacement = GetAliasForSymbol((INamespaceOrTypeSymbol)symbol, node.GetFirstToken(), semanticModel, cancellationToken); - if (aliasReplacement != null && preferAliasToQualifiedName) - { - return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); - } - - return false; - } - - private static bool HasUsingAliasDirective(SyntaxNode syntax) - { - SyntaxList usings; - SyntaxList members; - if (syntax.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration)) - { - usings = namespaceDeclaration.Usings; - members = namespaceDeclaration.Members; - } - else if (syntax.IsKind(SyntaxKind.CompilationUnit, out CompilationUnitSyntax compilationUnit)) - { - usings = compilationUnit.Usings; - members = compilationUnit.Members; - } - else - { - return false; - } - - foreach (var usingDirective in usings) - { - if (usingDirective.Alias != null) - { - return true; - } - } - - foreach (var member in members) - { - if (HasUsingAliasDirective(member)) - { - return true; - } - } - - return false; - } - - // We must verify that the alias actually binds back to the thing it's aliasing. - // It's possible there's another symbol with the same name as the alias that binds - // first - private static bool ValidateAliasForTarget(IAliasSymbol aliasReplacement, SemanticModel semanticModel, ExpressionSyntax node, ISymbol symbol) - { - var aliasName = aliasReplacement.Name; - - // If we're the argument of a nameof(X.Y) call, then we can't simplify to an - // alias unless the alias has the same name as us (i.e. 'Y'). - if (node.IsNameOfArgumentExpression()) - { - var nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent); - if (!nameofValueOpt.HasValue) - { - return false; - } - - if (nameofValueOpt.Value is string existingVal && - existingVal != aliasName) - { - return false; - } - } - - var boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name: aliasName); - - if (boundSymbols.Length == 1) - { - if (boundSymbols[0] is IAliasSymbol boundAlias && aliasReplacement.Target.Equals(symbol)) - { - return true; - } - } - - return false; - } - public static bool IsNameOfArgumentExpression(this ExpressionSyntax expression) { return expression.IsParentKind(SyntaxKind.Argument) && diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs new file mode 100644 index 0000000000000..fdd7004eb5ecf --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs @@ -0,0 +1,534 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers +{ + internal class ExpressionSyntaxSimplifier : AbstractCSharpSimplifier + { + public static readonly ExpressionSyntaxSimplifier Instance = new ExpressionSyntaxSimplifier(); + + private ExpressionSyntaxSimplifier() + { + } + + + + public static bool TryReduceOrSimplifyExplicitName( + this ExpressionSyntax expression, + SemanticModel semanticModel, + out ExpressionSyntax replacementNode, + out TextSpan issueSpan, + OptionSet optionSet, + CancellationToken cancellationToken) + { + if (TryReduceExplicitName(expression, semanticModel, out var replacementTypeNode, out issueSpan, optionSet, cancellationToken)) + { + replacementNode = replacementTypeNode; + return true; + } + + return TrySimplify(expression, semanticModel, out replacementNode, out issueSpan); + } + + private static bool TryReduceExplicitName( + ExpressionSyntax expression, + SemanticModel semanticModel, + out TypeSyntax replacementNode, + out TextSpan issueSpan, + OptionSet optionSet, + CancellationToken cancellationToken) + { + replacementNode = null; + issueSpan = default; + + if (expression.ContainsInterleavedDirective(cancellationToken)) + return false; + + if (expression.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax memberAccess)) + return TryReduceMemberAccessExpression(memberAccess, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken); + + if (expression is NameSyntax name) + return TryReduceName(name, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken); + + return false; + } + + private static bool TryReduceMemberAccessExpression( + MemberAccessExpressionSyntax memberAccess, + SemanticModel semanticModel, + out TypeSyntax replacementNode, + out TextSpan issueSpan, + OptionSet optionSet, + CancellationToken cancellationToken) + { + replacementNode = null; + issueSpan = default; + + if (memberAccess.Name == null || memberAccess.Expression == null) + return false; + + // if this node is annotated as being a SpecialType, let's use this information. + if (memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind)) + { + replacementNode = SyntaxFactory.PredefinedType( + SyntaxFactory.Token( + memberAccess.GetLeadingTrivia(), + GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())), + memberAccess.GetTrailingTrivia())); + + issueSpan = memberAccess.Span; + return true; + } + + // See https://github.com/dotnet/roslyn/issues/40974 + // + // To be very safe, we only support simplifying code that bound to a symbol without any + // sort of problems. We could potentially relax this in the future. However, we would + // need to be very careful about the implications of us offering to fixup 'broken' code + // in a manner that might end up making things worse or confusing the user. + var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, memberAccess); + if (symbol == null) + return false; + + if (memberAccess.Expression.IsKind(SyntaxKind.ThisExpression) && + !SimplificationHelpers.ShouldSimplifyThisOrMeMemberAccessExpression(semanticModel, optionSet, symbol)) + { + return false; + } + + // if this node is on the left side, we could simplify to aliases + if (!memberAccess.IsRightSideOfDot()) + { + // Check if we need to replace this syntax with an alias identifier + if (TryReplaceExpressionWithAlias( + memberAccess, semanticModel, symbol, + cancellationToken, out var aliasReplacement)) + { + // get the token text as it appears in source code to preserve e.g. unicode character escaping + var text = aliasReplacement.Name; + var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault(); + + if (syntaxRef != null) + { + var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier; + text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString(); + } + + replacementNode = SyntaxFactory.IdentifierName( + memberAccess.Name.Identifier.CopyAnnotationsTo(SyntaxFactory.Identifier( + memberAccess.GetLeadingTrivia(), + SyntaxKind.IdentifierToken, + text, + aliasReplacement.Name, + memberAccess.GetTrailingTrivia()))); + + replacementNode = memberAccess.CopyAnnotationsTo(replacementNode); + replacementNode = memberAccess.Name.CopyAnnotationsTo(replacementNode); + + issueSpan = memberAccess.Span; + + // In case the alias name is the same as the last name of the alias target, we only include + // the left part of the name in the unnecessary span to Not confuse uses. + if (memberAccess.Name.Identifier.ValueText == ((IdentifierNameSyntax)replacementNode).Identifier.ValueText) + { + issueSpan = memberAccess.Expression.Span; + } + + return true; + } + + // Check if the Expression can be replaced by Predefined Type keyword + if (PreferPredefinedTypeKeywordInMemberAccess(memberAccess, optionSet, semanticModel)) + { + if (symbol != null && symbol.IsKind(SymbolKind.NamedType)) + { + var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)symbol).SpecialType); + if (keywordKind != SyntaxKind.None) + { + replacementNode = CreatePredefinedTypeSyntax(memberAccess, keywordKind); + + replacementNode = replacementNode + .WithAdditionalAnnotations(new SyntaxAnnotation( + nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess))); + + issueSpan = memberAccess.Span; // we want to show the whole expression as unnecessary + + return true; + } + } + } + } + + // Try to eliminate cases without actually calling CanReplaceWithReducedName. For expressions of the form + // 'this.Name' or 'base.Name', no additional check here is required. + if (!memberAccess.Expression.IsKind(SyntaxKind.ThisExpression, SyntaxKind.BaseExpression)) + { + GetReplacementCandidates( + semanticModel, + memberAccess, + symbol, + out var speculativeSymbols, + out var speculativeNamespacesAndTypes); + + if (!IsReplacementCandidate(symbol, speculativeSymbols, speculativeNamespacesAndTypes)) + { + return false; + } + } + + replacementNode = memberAccess.GetNameWithTriviaMoved(); + issueSpan = memberAccess.Expression.Span; + + return CanReplaceWithReducedName( + memberAccess, replacementNode, semanticModel, symbol, cancellationToken); + } + + public static SimpleNameSyntax GetNameWithTriviaMoved(this MemberAccessExpressionSyntax memberAccess) + => memberAccess.Name + .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) + .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); + + private static void GetReplacementCandidates( + SemanticModel semanticModel, + MemberAccessExpressionSyntax memberAccess, + ISymbol actualSymbol, + out ImmutableArray speculativeSymbols, + out ImmutableArray speculativeNamespacesAndTypes) + { + var containsNamespaceOrTypeSymbol = actualSymbol is INamespaceOrTypeSymbol; + var containsOtherSymbol = !containsNamespaceOrTypeSymbol; + + speculativeSymbols = containsOtherSymbol + ? semanticModel.LookupSymbols(memberAccess.SpanStart, name: memberAccess.Name.Identifier.ValueText) + : ImmutableArray.Empty; + speculativeNamespacesAndTypes = containsNamespaceOrTypeSymbol + ? semanticModel.LookupNamespacesAndTypes(memberAccess.SpanStart, name: memberAccess.Name.Identifier.ValueText) + : ImmutableArray.Empty; + } + + /// + /// Determines if and + /// together contain a superset of the symbols in . + /// + private static bool IsReplacementCandidate(ISymbol actualSymbol, ImmutableArray speculativeSymbols, ImmutableArray speculativeNamespacesAndTypes) + { + if (speculativeSymbols.IsEmpty && speculativeNamespacesAndTypes.IsEmpty) + { + return false; + } + + if (actualSymbol is object) + { + return speculativeSymbols.Contains(actualSymbol, CandidateSymbolEqualityComparer.Instance) + || speculativeNamespacesAndTypes.Contains(actualSymbol, CandidateSymbolEqualityComparer.Instance); + } + + return true; + } + + /// + /// Compares symbols by their original definition. + /// + private sealed class CandidateSymbolEqualityComparer : IEqualityComparer + { + public static CandidateSymbolEqualityComparer Instance { get; } = new CandidateSymbolEqualityComparer(); + + private CandidateSymbolEqualityComparer() + { + } + + public bool Equals(ISymbol x, ISymbol y) + { + if (x is null || y is null) + { + return x == y; + } + + return x.OriginalDefinition.Equals(y.OriginalDefinition); + } + + public int GetHashCode(ISymbol obj) + { + return obj?.OriginalDefinition.GetHashCode() ?? 0; + } + } + + private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(MemberAccessExpressionSyntax memberAccess) + { + // We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. + // However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering + // aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines + // etc. around the user's original code. + return memberAccess.GetLeadingTrivia() + .AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia()) + .AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia()) + .AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia()) + .AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia()); + } + + private static IEnumerable WithoutElasticTrivia(this IEnumerable list) + { + return list.Where(t => !t.IsElastic()); + } + + public static bool InsideCrefReference(this ExpressionSyntax expression) + { + var crefAttribute = expression.FirstAncestorOrSelf(); + return crefAttribute != null; + } + + private static bool InsideNameOfExpression(ExpressionSyntax expression, SemanticModel semanticModel) + { + var nameOfInvocationExpr = expression.FirstAncestorOrSelf( + invocationExpr => + { + return (invocationExpr.Expression is IdentifierNameSyntax identifierName) && (identifierName.Identifier.Text == "nameof") && + semanticModel.GetConstantValue(invocationExpr).HasValue && + (semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String); + }); + + return nameOfInvocationExpr != null; + } + + private static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel) + { + return !IsInMemberAccessContext(name) && + !InsideCrefReference(name) && + !InsideNameOfExpression(name, semanticModel) && + SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language); + } + + private static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, OptionSet optionSet, SemanticModel semanticModel) + { + if (!SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, semanticModel.Language)) + return false; + + return (IsInMemberAccessContext(expression) || InsideCrefReference(expression)) && + !InsideNameOfExpression(expression, semanticModel); + } + + public static bool IsInMemberAccessContext(this ExpressionSyntax expression) => + expression?.Parent is MemberAccessExpressionSyntax; + + private static bool IsAliasReplaceableExpression(ExpressionSyntax expression) + { + var current = expression; + while (current.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax currentMember)) + { + current = currentMember.Expression; + continue; + } + + return current.IsKind(SyntaxKind.AliasQualifiedName, + SyntaxKind.IdentifierName, + SyntaxKind.GenericName, + SyntaxKind.QualifiedName); + } + + [PerformanceSensitive( + "https://github.com/dotnet/roslyn/issues/23582", + Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")] + private static bool TryReplaceExpressionWithAlias( + ExpressionSyntax node, SemanticModel semanticModel, + ISymbol symbol, CancellationToken cancellationToken, out IAliasSymbol aliasReplacement) + { + aliasReplacement = null; + + if (!IsAliasReplaceableExpression(node)) + return false; + + // Avoid the TryReplaceWithAlias algorithm if the tree has no using alias directives. Since the input node + // might be a speculative node (not fully rooted in a tree), we use the original semantic model to find the + // equivalent node in the original tree, and from there determine if the tree has any using alias + // directives. + var originalModel = semanticModel.GetOriginalSemanticModel(); + + // Perf: We are only using the syntax tree root in a fast-path syntax check. If the root is not readily + // available, it is fine to continue through the normal algorithm. + if (originalModel.SyntaxTree.TryGetRoot(out var root)) + { + if (!HasUsingAliasDirective(root)) + { + return false; + } + } + + // If the Symbol is a constructor get its containing type + if (symbol.IsConstructor()) + { + symbol = symbol.ContainingType; + } + + if (node is QualifiedNameSyntax || node is AliasQualifiedNameSyntax) + { + SyntaxAnnotation aliasAnnotationInfo = null; + + // The following condition checks if the user has used alias in the original code and + // if so the expression is replaced with the Alias + if (node is QualifiedNameSyntax qualifiedNameNode) + { + if (qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind)) + { + aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); + } + } + + if (node is AliasQualifiedNameSyntax aliasQualifiedNameNode) + { + if (aliasQualifiedNameNode.Name.Identifier.HasAnnotations(AliasAnnotation.Kind)) + { + aliasAnnotationInfo = aliasQualifiedNameNode.Name.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); + } + } + + if (aliasAnnotationInfo != null) + { + var aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo); + var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName); + + var aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace); + + if (aliasTypeInfo != null) + { + aliasReplacement = aliasTypeInfo; + return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); + } + } + } + + if (node.Kind() == SyntaxKind.IdentifierName && + semanticModel.GetAliasInfo((IdentifierNameSyntax)node, cancellationToken) != null) + { + return false; + } + + // an alias can only replace a type or namespace + if (symbol == null || + (symbol.Kind != SymbolKind.Namespace && symbol.Kind != SymbolKind.NamedType)) + { + return false; + } + + var preferAliasToQualifiedName = true; + if (node is QualifiedNameSyntax qualifiedName) + { + if (!qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + { + var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; + if (type != null) + { + var keywordKind = GetPredefinedKeywordKind(type.SpecialType); + if (keywordKind != SyntaxKind.None) + { + preferAliasToQualifiedName = false; + } + } + } + } + + if (node is AliasQualifiedNameSyntax aliasQualifiedNameSyntax) + { + if (!aliasQualifiedNameSyntax.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + { + var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; + if (type != null) + { + var keywordKind = GetPredefinedKeywordKind(type.SpecialType); + if (keywordKind != SyntaxKind.None) + { + preferAliasToQualifiedName = false; + } + } + } + } + + aliasReplacement = GetAliasForSymbol((INamespaceOrTypeSymbol)symbol, node.GetFirstToken(), semanticModel, cancellationToken); + if (aliasReplacement != null && preferAliasToQualifiedName) + { + return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); + } + + return false; + } + + private static bool HasUsingAliasDirective(SyntaxNode syntax) + { + SyntaxList usings; + SyntaxList members; + if (syntax.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration)) + { + usings = namespaceDeclaration.Usings; + members = namespaceDeclaration.Members; + } + else if (syntax.IsKind(SyntaxKind.CompilationUnit, out CompilationUnitSyntax compilationUnit)) + { + usings = compilationUnit.Usings; + members = compilationUnit.Members; + } + else + { + return false; + } + + foreach (var usingDirective in usings) + { + if (usingDirective.Alias != null) + { + return true; + } + } + + foreach (var member in members) + { + if (HasUsingAliasDirective(member)) + { + return true; + } + } + + return false; + } + + // We must verify that the alias actually binds back to the thing it's aliasing. + // It's possible there's another symbol with the same name as the alias that binds + // first + private static bool ValidateAliasForTarget(IAliasSymbol aliasReplacement, SemanticModel semanticModel, ExpressionSyntax node, ISymbol symbol) + { + var aliasName = aliasReplacement.Name; + + // If we're the argument of a nameof(X.Y) call, then we can't simplify to an + // alias unless the alias has the same name as us (i.e. 'Y'). + if (node.IsNameOfArgumentExpression()) + { + var nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent); + if (!nameofValueOpt.HasValue) + { + return false; + } + + if (nameofValueOpt.Value is string existingVal && + existingVal != aliasName) + { + return false; + } + } + + var boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name: aliasName); + + if (boundSymbols.Length == 1) + { + if (boundSymbols[0] is IAliasSymbol boundAlias && aliasReplacement.Target.Equals(symbol)) + { + return true; + } + } + + return false; + } + + } +} diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs new file mode 100644 index 0000000000000..be321b1161025 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs @@ -0,0 +1,732 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers +{ + internal class NameSyntaxSimplifier : AbstractCSharpSimplifier + { + public static readonly NameSyntaxSimplifier Instance = new NameSyntaxSimplifier(); + + private NameSyntaxSimplifier() + { + } + + + private static bool TryReduceName( + NameSyntax name, + SemanticModel semanticModel, + out TypeSyntax replacementNode, + out TextSpan issueSpan, + OptionSet optionSet, + CancellationToken cancellationToken) + { + replacementNode = null; + issueSpan = default; + + if (name.IsVar) + { + return false; + } + + // we should not simplify a name of a namespace declaration + if (IsPartOfNamespaceDeclarationName(name)) + { + return false; + } + + // We can simplify Qualified names and AliasQualifiedNames. Generally, if we have + // something like "A.B.C.D", we only consider the full thing something we can simplify. + // However, in the case of "A.B.C<>.D", then we'll only consider simplifying up to the + // first open name. This is because if we remove the open name, we'll often change + // meaning as "D" will bind to C.D which is different than C<>.D! + if (name is QualifiedNameSyntax qualifiedName) + { + var left = qualifiedName.Left; + if (ContainsOpenName(left)) + { + // Don't simplify A.B<>.C + return false; + } + } + + // 1. see whether binding the name binds to a symbol/type. if not, it is ambiguous and + // nothing we can do here. + var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name); + if (symbol == null) + { + return false; + } + + // treat constructor names as types + var method = symbol as IMethodSymbol; + if (method.IsConstructor()) + { + symbol = method.ContainingType; + } + + if (symbol.Kind == SymbolKind.Method && name.Kind() == SyntaxKind.GenericName) + { + var genericName = (GenericNameSyntax)name; + replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier) + .WithLeadingTrivia(genericName.GetLeadingTrivia()) + .WithTrailingTrivia(genericName.GetTrailingTrivia()); + + issueSpan = genericName.TypeArgumentList.Span; + return CanReplaceWithReducedName( + name, replacementNode, semanticModel, cancellationToken); + } + + if (!(symbol is INamespaceOrTypeSymbol)) + { + return false; + } + + if (name.HasAnnotations(SpecialTypeAnnotation.Kind)) + { + replacementNode = SyntaxFactory.PredefinedType( + SyntaxFactory.Token( + name.GetLeadingTrivia(), + GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())), + name.GetTrailingTrivia())); + + issueSpan = name.Span; + + return name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel); + } + else + { + if (!name.IsRightSideOfDotOrColonColon()) + { + if (TryReplaceExpressionWithAlias(name, semanticModel, symbol, cancellationToken, out var aliasReplacement)) + { + // get the token text as it appears in source code to preserve e.g. Unicode character escaping + var text = aliasReplacement.Name; + var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault(); + + if (syntaxRef != null) + { + var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier; + text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString(); + } + + var identifierToken = SyntaxFactory.Identifier( + name.GetLeadingTrivia(), + SyntaxKind.IdentifierToken, + text, + aliasReplacement.Name, + name.GetTrailingTrivia()); + + identifierToken = CSharpSimplificationService.TryEscapeIdentifierToken(identifierToken, name, semanticModel); + replacementNode = SyntaxFactory.IdentifierName(identifierToken); + + // Merge annotation to new syntax node + var annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind); + foreach (var annotatedNodeOrToken in annotatedNodesOrTokens) + { + if (annotatedNodeOrToken.IsToken) + { + identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken); + } + else + { + replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode); + } + } + + annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind); + foreach (var annotatedNodeOrToken in annotatedNodesOrTokens) + { + if (annotatedNodeOrToken.IsToken) + { + identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken); + } + else + { + replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode); + } + } + + replacementNode = ((SimpleNameSyntax)replacementNode).WithIdentifier(identifierToken); + issueSpan = name.Span; + + // In case the alias name is the same as the last name of the alias target, we only include + // the left part of the name in the unnecessary span to Not confuse uses. + if (name.Kind() == SyntaxKind.QualifiedName) + { + var qualifiedName3 = (QualifiedNameSyntax)name; + + if (qualifiedName3.Right.Identifier.ValueText == identifierToken.ValueText) + { + issueSpan = qualifiedName3.Left.Span; + } + } + + // first check if this would be a valid reduction + if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel)) + { + // in case this alias name ends with "Attribute", we're going to see if we can also + // remove that suffix. + if (TryReduceAttributeSuffix( + name, + identifierToken, + out var replacementNodeWithoutAttributeSuffix, + out var issueSpanWithoutAttributeSuffix)) + { + if (CanReplaceWithReducedName(name, replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken)) + { + replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix); + issueSpan = issueSpanWithoutAttributeSuffix; + } + } + + return true; + } + + return false; + } + + var nameHasNoAlias = false; + + if (name is SimpleNameSyntax simpleName) + { + if (!simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind)) + { + nameHasNoAlias = true; + } + } + + if (name is QualifiedNameSyntax qualifiedName2) + { + if (!qualifiedName2.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + { + nameHasNoAlias = true; + } + } + + if (name is AliasQualifiedNameSyntax aliasQualifiedName) + { + if (aliasQualifiedName.Name is SimpleNameSyntax && + !aliasQualifiedName.Name.Identifier.HasAnnotations(AliasAnnotation.Kind) && + !aliasQualifiedName.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + { + nameHasNoAlias = true; + } + } + + var aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken); + if (nameHasNoAlias && aliasInfo == null) + { + // Don't simplify to predefined type if name is part of a QualifiedName. + // QualifiedNames can't contain PredefinedTypeNames (although MemberAccessExpressions can). + // In other words, the left side of a QualifiedName can't be a PredefinedTypeName. + var inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, optionSet, semanticModel); + var inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, optionSet, semanticModel); + + if (!name.Parent.IsKind(SyntaxKind.QualifiedName) && (inDeclarationContext || inMemberAccessContext)) + { + // See if we can simplify this name (like System.Int32) to a built-in type (like 'int'). + // If not, we'll still fall through and see if we can convert it to Int32. + + var codeStyleOptionName = inDeclarationContext + ? nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration) + : nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); + + var type = semanticModel.GetTypeInfo(name, cancellationToken).Type; + if (type != null) + { + var keywordKind = GetPredefinedKeywordKind(type.SpecialType); + if (keywordKind != SyntaxKind.None && + CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName)) + { + return true; + } + } + else + { + var typeSymbol = semanticModel.GetSymbolInfo(name, cancellationToken).Symbol; + if (typeSymbol.IsKind(SymbolKind.NamedType)) + { + var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)typeSymbol).SpecialType); + if (keywordKind != SyntaxKind.None && + CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName)) + { + return true; + } + } + } + } + } + + // Nullable rewrite: Nullable -> int? + // Don't rewrite in the case where Nullable is part of some qualified name like Nullable.Something + if (!name.IsVar && (symbol.Kind == SymbolKind.NamedType) && !name.IsLeftSideOfQualifiedName()) + { + var type = (INamedTypeSymbol)symbol; + if (aliasInfo == null && CanSimplifyNullable(type, name, semanticModel)) + { + GenericNameSyntax genericName; + if (name.Kind() == SyntaxKind.QualifiedName) + { + genericName = (GenericNameSyntax)((QualifiedNameSyntax)name).Right; + } + else + { + genericName = (GenericNameSyntax)name; + } + + var oldType = genericName.TypeArgumentList.Arguments.First(); + if (oldType.Kind() == SyntaxKind.OmittedTypeArgument) + { + return false; + } + + replacementNode = SyntaxFactory.NullableType(oldType) + .WithLeadingTrivia(name.GetLeadingTrivia()) + .WithTrailingTrivia(name.GetTrailingTrivia()); + issueSpan = name.Span; + + // we need to simplify the whole qualified name at once, because replacing the identifier on the left in + // System.Nullable alone would be illegal. + // If this fails we want to continue to try at least to remove the System if possible. + if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel)) + { + return true; + } + } + } + } + + SyntaxToken identifier; + switch (name.Kind()) + { + case SyntaxKind.AliasQualifiedName: + var simpleName = ((AliasQualifiedNameSyntax)name).Name + .WithLeadingTrivia(name.GetLeadingTrivia()); + + simpleName = simpleName.ReplaceToken(simpleName.Identifier, + ((AliasQualifiedNameSyntax)name).Name.Identifier.CopyAnnotationsTo( + simpleName.Identifier.WithLeadingTrivia( + ((AliasQualifiedNameSyntax)name).Alias.Identifier.LeadingTrivia))); + + replacementNode = simpleName; + + issueSpan = ((AliasQualifiedNameSyntax)name).Alias.Span; + + break; + + case SyntaxKind.QualifiedName: + replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); + issueSpan = ((QualifiedNameSyntax)name).Left.Span; + + break; + + case SyntaxKind.IdentifierName: + identifier = ((IdentifierNameSyntax)name).Identifier; + + // we can try to remove the Attribute suffix if this is the attribute name + TryReduceAttributeSuffix(name, identifier, out replacementNode, out issueSpan); + break; + } + } + + if (replacementNode == null) + { + return false; + } + + // We may be looking at a name `X.Y` seeing if we can replace it with `Y`. However, in + // order to know for sure, we actually have to look slightly higher at `X.Y.Z` to see if + // it can simplify to `Y.Z`. This is because in the `Color Color` case we can only tell + // if we can reduce by looking by also looking at what comes next to see if it will + // cause the simplified name to bind to the instance or static side. + if (TryReduceCrefColorColor(name, replacementNode, semanticModel, cancellationToken)) + { + return true; + } + + return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken); + } + + private static bool TryReduceCrefColorColor( + NameSyntax name, TypeSyntax replacement, + SemanticModel semanticModel, CancellationToken cancellationToken) + { + if (!InsideCrefReference(name)) + return false; + + if (name.Parent is QualifiedCrefSyntax qualifiedCrefParent && qualifiedCrefParent.Container == name) + { + // we have and we're trying to see if we can replace + // A.B.C with C. In this case the parent of A.B.C is A.B.C.D which is a + // QualifiedCrefSyntax + + var qualifiedReplacement = SyntaxFactory.QualifiedCref(replacement, qualifiedCrefParent.Member); + if (QualifiedCrefSimplifier.CanSimplifyWithReplacement(qualifiedCrefParent, semanticModel, qualifiedReplacement, cancellationToken)) + return true; + } + else if (name.Parent is QualifiedNameSyntax qualifiedParent && qualifiedParent.Left == name && + replacement is NameSyntax replacementName) + { + // we have and we're trying to see if we can replace + // A.B with B. In this case the parent of A.B is A.B.C which is a + // QualifiedNameSyntax + + var qualifiedReplacement = SyntaxFactory.QualifiedName(replacementName, qualifiedParent.Right); + return CanReplaceWithReducedName( + qualifiedParent, qualifiedReplacement, semanticModel, cancellationToken); + } + + return false; + } + + private static bool CanSimplifyNullable(INamedTypeSymbol type, NameSyntax name, SemanticModel semanticModel) + { + if (!type.IsNullable()) + { + return false; + } + + if (type.IsUnboundGenericType) + { + // Don't simplify unbound generic type "Nullable<>". + return false; + } + + if (InsideNameOfExpression(name, semanticModel)) + { + // Nullable can't be simplified to T? in nameof expressions. + return false; + } + + if (!InsideCrefReference(name)) + { + // Nullable can always be simplified to T? outside crefs. + return true; + } + + if (name.Parent is NameMemberCrefSyntax) + return false; + + // Inside crefs, if the T in this Nullable{T} is being declared right here + // then this Nullable{T} is not a constructed generic type and we should + // not offer to simplify this to T?. + // + // For example, we should not offer the simplification in the following cases where + // T does not bind to an existing type / type parameter in the user's code. + // - + // - + // + // And we should offer the simplification in the following cases where SomeType and + // SomeMethod bind to a type and method declared elsewhere in the users code. + // - + + var argument = type.TypeArguments.SingleOrDefault(); + if (argument == null || argument.IsErrorType()) + { + return false; + } + + var argumentDecl = argument.DeclaringSyntaxReferences.FirstOrDefault(); + if (argumentDecl == null) + { + // The type argument is a type from metadata - so this is a constructed generic nullable type that can be simplified (e.g. Nullable(Of Integer)). + return true; + } + + return !name.Span.Contains(argumentDecl.Span); + } + + private static bool CanReplaceWithPredefinedTypeKeywordInContext( + NameSyntax name, + SemanticModel semanticModel, + out TypeSyntax replacementNode, + ref TextSpan issueSpan, + SyntaxKind keywordKind, + string codeStyleOptionName) + { + replacementNode = CreatePredefinedTypeSyntax(name, keywordKind); + + issueSpan = name.Span; // we want to show the whole name expression as unnecessary + + var canReduce = name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel); + + if (canReduce) + { + replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName)); + } + + return canReduce; + } + + private static TypeSyntax CreatePredefinedTypeSyntax(ExpressionSyntax expression, SyntaxKind keywordKind) + { + return SyntaxFactory.PredefinedType(SyntaxFactory.Token(expression.GetLeadingTrivia(), keywordKind, expression.GetTrailingTrivia())); + } + + private static bool TryReduceAttributeSuffix( + NameSyntax name, + SyntaxToken identifierToken, + out TypeSyntax replacementNode, + out TextSpan issueSpan) + { + issueSpan = default; + replacementNode = default; + + // we can try to remove the Attribute suffix if this is the attribute name + if (SyntaxFacts.IsAttributeName(name)) + { + if (name.Parent.Kind() == SyntaxKind.Attribute || name.IsRightSideOfDotOrColonColon()) + { + const string AttributeName = "Attribute"; + + // an attribute that should keep it (unnecessary "Attribute" suffix should be annotated with a DontSimplifyAnnotation + if (identifierToken.ValueText != AttributeName && identifierToken.ValueText.EndsWith(AttributeName, StringComparison.Ordinal) && !identifierToken.HasAnnotation(SimplificationHelpers.DontSimplifyAnnotation)) + { + // weird. the semantic model is able to bind attribute syntax like "[as()]" although it's not valid code. + // so we need another check for keywords manually. + var newAttributeName = identifierToken.ValueText.Substring(0, identifierToken.ValueText.Length - 9); + if (SyntaxFacts.GetKeywordKind(newAttributeName) != SyntaxKind.None) + { + return false; + } + + // if this attribute name in source contained Unicode escaping, we will loose it now + // because there is no easy way to determine the substring from identifier->ToString() + // which would be needed to pass to SyntaxFactory.Identifier + // The result is an unescaped Unicode character in source. + + // once we remove the Attribute suffix, we can't use an escaped identifier + var newIdentifierToken = identifierToken.CopyAnnotationsTo( + SyntaxFactory.Identifier( + identifierToken.LeadingTrivia, + newAttributeName, + identifierToken.TrailingTrivia)); + + replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken) + .WithLeadingTrivia(name.GetLeadingTrivia()); + issueSpan = new TextSpan(identifierToken.Span.End - 9, 9); + + return true; + } + } + } + + return false; + } + + /// + /// Checks if the SyntaxNode is a name of a namespace declaration. To be a namespace name, the syntax + /// must be parented by an namespace declaration and the node itself must be equal to the declaration's Name + /// property. + /// + /// + /// + private static bool IsPartOfNamespaceDeclarationName(SyntaxNode node) + { + var parent = node; + + while (parent != null) + { + switch (parent.Kind()) + { + case SyntaxKind.IdentifierName: + case SyntaxKind.QualifiedName: + node = parent; + parent = parent.Parent; + break; + + case SyntaxKind.NamespaceDeclaration: + var namespaceDeclaration = (NamespaceDeclarationSyntax)parent; + return object.Equals(namespaceDeclaration.Name, node); + + default: + return false; + } + } + + return false; + } + + private static bool TrySimplify( + ExpressionSyntax expression, + SemanticModel semanticModel, + out ExpressionSyntax replacementNode, + out TextSpan issueSpan) + { + replacementNode = null; + issueSpan = default; + + switch (expression.Kind()) + { + case SyntaxKind.SimpleMemberAccessExpression: + { + var memberAccess = (MemberAccessExpressionSyntax)expression; + if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) + { + return false; + } + + if (TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, out var newLeft, out issueSpan)) + { + // replacement node might not be in it's simplest form, so add simplify annotation to it. + replacementNode = memberAccess.Update(newLeft, memberAccess.OperatorToken, memberAccess.Name) + .WithAdditionalAnnotations(Simplifier.Annotation); + + // Ensure that replacement doesn't change semantics. + return !ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel); + } + + return false; + } + + case SyntaxKind.QualifiedName: + { + var qualifiedName = (QualifiedNameSyntax)expression; + if (TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, out var newLeft, out issueSpan)) + { + // replacement node might not be in it's simplest form, so add simplify annotation to it. + replacementNode = qualifiedName.Update((NameSyntax)newLeft, qualifiedName.DotToken, qualifiedName.Right) + .WithAdditionalAnnotations(Simplifier.Annotation); + + // Ensure that replacement doesn't change semantics. + return !ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel); + } + + return false; + } + } + + return false; + } + + private static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) + { + var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); + return speculationAnalyzer.ReplacementChangesSemantics(); + } + + // Note: The caller needs to verify that replacement doesn't change semantics of the original expression. + private static bool TrySimplifyMemberAccessOrQualifiedName( + ExpressionSyntax left, + ExpressionSyntax right, + SemanticModel semanticModel, + out ExpressionSyntax replacementNode, + out TextSpan issueSpan) + { + replacementNode = null; + issueSpan = default; + + if (left != null && right != null) + { + var leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left); + if (leftSymbol != null && (leftSymbol.Kind == SymbolKind.NamedType)) + { + var rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right); + if (rightSymbol != null && (rightSymbol.IsStatic || rightSymbol.Kind == SymbolKind.NamedType)) + { + // Static member access or nested type member access. + var containingType = rightSymbol.ContainingType; + + var enclosingSymbol = semanticModel.GetEnclosingSymbol(left.SpanStart); + var enclosingTypeParametersInsideOut = new List(); + + while (enclosingSymbol != null) + { + if (enclosingSymbol is IMethodSymbol methodSymbol) + { + if (methodSymbol.TypeArguments.Length != 0) + { + enclosingTypeParametersInsideOut.AddRange(methodSymbol.TypeArguments); + } + } + + if (enclosingSymbol is INamedTypeSymbol namedTypeSymbol) + { + if (namedTypeSymbol.TypeArguments.Length != 0) + { + enclosingTypeParametersInsideOut.AddRange(namedTypeSymbol.TypeArguments); + } + } + + enclosingSymbol = enclosingSymbol.ContainingSymbol; + } + + if (containingType != null && !containingType.Equals(leftSymbol)) + { + if (leftSymbol is INamedTypeSymbol namedType && + containingType.TypeArguments.Length != 0) + { + return false; + } + + // We have a static member access or a nested type member access using a more derived type. + // Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type. + replacementNode = containingType.GenerateTypeSyntax() + .WithLeadingTrivia(left.GetLeadingTrivia()) + .WithTrailingTrivia(left.GetTrailingTrivia()); + issueSpan = left.Span; + return true; + } + } + } + } + + return false; + } + + private static bool CanReplaceWithReducedName( + MemberAccessExpressionSyntax memberAccess, + ExpressionSyntax reducedName, + SemanticModel semanticModel, + ISymbol symbol, + CancellationToken cancellationToken) + { + if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel)) + { + return false; + } + + var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken); + if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() || + speculationAnalyzer.ReplacementChangesSemantics()) + { + return false; + } + + if (WillConflictWithExistingLocal(memberAccess, reducedName, semanticModel)) + { + return false; + } + + if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) + { + return false; + } + + if (AccessMethodWithDynamicArgumentInsideStructConstructor(memberAccess, semanticModel)) + { + return false; + } + + if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression) + { + var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken); + if (enclosingNamedType != null && + !enclosingNamedType.IsSealed && + symbol != null && + symbol.IsOverridable()) + { + return false; + } + } + + var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess); + + return !invalidTransformation1; + } + } +} From 505de28f2b0a9d78c603afaccc77acecd466b30a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 13:38:56 -0800 Subject: [PATCH 04/14] Full move --- ...harpSimplifyTypeNamesDiagnosticAnalyzer.cs | 2 +- .../Extensions/ExpressionSyntaxExtensions.cs | 1200 ----------------- .../Reducers/CSharpNameReducer.cs | 2 +- .../Simplifiers/AbstractCSharpSimplifier.cs | 432 +++++- ...xSimplifier.cs => ExpressionSimplifier.cs} | 431 +++--- ...eSyntaxSimplifier.cs => NameSimplifier.cs} | 337 ++--- .../Simplifiers/QualifiedCrefSimplifier.cs | 2 +- 7 files changed, 856 insertions(+), 1550 deletions(-) rename src/Workspaces/CSharp/Portable/Simplification/Simplifiers/{ExpressionSyntaxSimplifier.cs => ExpressionSimplifier.cs} (50%) rename src/Workspaces/CSharp/Portable/Simplification/Simplifiers/{NameSyntaxSimplifier.cs => NameSimplifier.cs} (72%) diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs index dce26abc4de66..bd88ac42d0c57 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs @@ -92,7 +92,7 @@ internal override bool CanSimplifyTypeNameExpression( var expressionToCheck = expression.Kind() == SyntaxKind.AsExpression || expression.Kind() == SyntaxKind.IsExpression ? ((BinaryExpressionSyntax)expression).Right : expression; - if (!expressionToCheck.TryReduceOrSimplifyExplicitName(model, out var replacement, out issueSpan, optionSet, cancellationToken)) + if (!ExpressionSimplifier.Instance.TrySimplify(expressionToCheck, model, optionSet, out var replacement, out issueSpan, cancellationToken)) return false; replacementSyntax = replacement; diff --git a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs index e1147de603d5b..cb3d6d1f59797 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs @@ -1,22 +1,13 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; using System.Threading; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.CSharp.Simplification; -using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; -using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Utilities; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Rename.ConflictEngine; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; -using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Extensions @@ -763,1197 +754,6 @@ public static bool IsNameOfInvocation(this InvocationExpressionSyntax invocation identifierName.Identifier.IsKindOrHasMatchingText(SyntaxKind.NameOfKeyword); } - public static IAliasSymbol GetAliasForSymbol(INamespaceOrTypeSymbol symbol, SyntaxToken token, SemanticModel semanticModel, CancellationToken cancellationToken) - { - var originalSemanticModel = semanticModel.GetOriginalSemanticModel(); - if (!originalSemanticModel.SyntaxTree.HasCompilationUnitRoot) - { - return null; - } - - var namespaceId = GetNamespaceIdForAliasSearch(semanticModel, token, cancellationToken); - if (namespaceId < 0) - { - return null; - } - - if (!AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId, symbol, out var aliasSymbol)) - { - // add cache - AliasSymbolCache.AddAliasSymbols(originalSemanticModel, namespaceId, semanticModel.LookupNamespacesAndTypes(token.SpanStart).OfType()); - - // retry - AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId, symbol, out aliasSymbol); - } - - return aliasSymbol; - } - - private static SyntaxNode GetStartNodeForNamespaceId(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) - { - if (!semanticModel.IsSpeculativeSemanticModel) - { - return token.Parent; - } - - var originalSemanticMode = semanticModel.GetOriginalSemanticModel(); - token = originalSemanticMode.SyntaxTree.GetRoot(cancellationToken).FindToken(semanticModel.OriginalPositionForSpeculation); - - return token.Parent; - } - - private static int GetNamespaceIdForAliasSearch(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) - { - var startNode = GetStartNodeForNamespaceId(semanticModel, token, cancellationToken); - if (!startNode.SyntaxTree.HasCompilationUnitRoot) - { - return -1; - } - - // NOTE: If we're currently in a block of usings, then we want to collect the - // aliases that are higher up than this block. Using aliases declared in a block of - // usings are not usable from within that same block. - var usingDirective = startNode.GetAncestorOrThis(); - if (usingDirective != null) - { - startNode = usingDirective.Parent.Parent; - if (startNode == null) - { - return -1; - } - } - - // check whether I am under a namespace - var @namespace = startNode.GetAncestorOrThis(); - if (@namespace != null) - { - // since we have node inside of the root, root should be already there - // search for namespace id should be quite cheap since normally there should be - // only a few namespace defined in a source file if it is not 1. that is why it is - // not cached. - var startIndex = 1; - return GetNamespaceId(startNode.SyntaxTree.GetRoot(cancellationToken), @namespace, ref startIndex); - } - - // no namespace, under compilation unit directly - return 0; - } - - private static int GetNamespaceId(SyntaxNode container, NamespaceDeclarationSyntax target, ref int index) - { - if (container is CompilationUnitSyntax compilation) - { - return GetNamespaceId(compilation.Members, target, ref index); - } - - if (container is NamespaceDeclarationSyntax @namespace) - { - return GetNamespaceId(@namespace.Members, target, ref index); - } - - return Contract.FailWithReturn("shouldn't reach here"); - } - - private static int GetNamespaceId(SyntaxList members, NamespaceDeclarationSyntax target, ref int index) - { - foreach (var member in members) - { - if (!(member is NamespaceDeclarationSyntax childNamespace)) - { - continue; - } - - if (childNamespace == target) - { - return index; - } - - index++; - var result = GetNamespaceId(childNamespace, target, ref index); - if (result > 0) - { - return result; - } - } - - return -1; - } - - private static bool TryReduceName( - NameSyntax name, - SemanticModel semanticModel, - out TypeSyntax replacementNode, - out TextSpan issueSpan, - OptionSet optionSet, - CancellationToken cancellationToken) - { - replacementNode = null; - issueSpan = default; - - if (name.IsVar) - { - return false; - } - - // we should not simplify a name of a namespace declaration - if (IsPartOfNamespaceDeclarationName(name)) - { - return false; - } - - // We can simplify Qualified names and AliasQualifiedNames. Generally, if we have - // something like "A.B.C.D", we only consider the full thing something we can simplify. - // However, in the case of "A.B.C<>.D", then we'll only consider simplifying up to the - // first open name. This is because if we remove the open name, we'll often change - // meaning as "D" will bind to C.D which is different than C<>.D! - if (name is QualifiedNameSyntax qualifiedName) - { - var left = qualifiedName.Left; - if (ContainsOpenName(left)) - { - // Don't simplify A.B<>.C - return false; - } - } - - // 1. see whether binding the name binds to a symbol/type. if not, it is ambiguous and - // nothing we can do here. - var symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name); - if (symbol == null) - { - return false; - } - - // treat constructor names as types - var method = symbol as IMethodSymbol; - if (method.IsConstructor()) - { - symbol = method.ContainingType; - } - - if (symbol.Kind == SymbolKind.Method && name.Kind() == SyntaxKind.GenericName) - { - var genericName = (GenericNameSyntax)name; - replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier) - .WithLeadingTrivia(genericName.GetLeadingTrivia()) - .WithTrailingTrivia(genericName.GetTrailingTrivia()); - - issueSpan = genericName.TypeArgumentList.Span; - return CanReplaceWithReducedName( - name, replacementNode, semanticModel, cancellationToken); - } - - if (!(symbol is INamespaceOrTypeSymbol)) - { - return false; - } - - if (name.HasAnnotations(SpecialTypeAnnotation.Kind)) - { - replacementNode = SyntaxFactory.PredefinedType( - SyntaxFactory.Token( - name.GetLeadingTrivia(), - GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())), - name.GetTrailingTrivia())); - - issueSpan = name.Span; - - return name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel); - } - else - { - if (!name.IsRightSideOfDotOrColonColon()) - { - if (TryReplaceExpressionWithAlias(name, semanticModel, symbol, cancellationToken, out var aliasReplacement)) - { - // get the token text as it appears in source code to preserve e.g. Unicode character escaping - var text = aliasReplacement.Name; - var syntaxRef = aliasReplacement.DeclaringSyntaxReferences.FirstOrDefault(); - - if (syntaxRef != null) - { - var declIdentifier = ((UsingDirectiveSyntax)syntaxRef.GetSyntax(cancellationToken)).Alias.Name.Identifier; - text = declIdentifier.IsVerbatimIdentifier() ? declIdentifier.ToString().Substring(1) : declIdentifier.ToString(); - } - - var identifierToken = SyntaxFactory.Identifier( - name.GetLeadingTrivia(), - SyntaxKind.IdentifierToken, - text, - aliasReplacement.Name, - name.GetTrailingTrivia()); - - identifierToken = CSharpSimplificationService.TryEscapeIdentifierToken(identifierToken, name, semanticModel); - replacementNode = SyntaxFactory.IdentifierName(identifierToken); - - // Merge annotation to new syntax node - var annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind); - foreach (var annotatedNodeOrToken in annotatedNodesOrTokens) - { - if (annotatedNodeOrToken.IsToken) - { - identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken); - } - else - { - replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode); - } - } - - annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind); - foreach (var annotatedNodeOrToken in annotatedNodesOrTokens) - { - if (annotatedNodeOrToken.IsToken) - { - identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken); - } - else - { - replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode); - } - } - - replacementNode = ((SimpleNameSyntax)replacementNode).WithIdentifier(identifierToken); - issueSpan = name.Span; - - // In case the alias name is the same as the last name of the alias target, we only include - // the left part of the name in the unnecessary span to Not confuse uses. - if (name.Kind() == SyntaxKind.QualifiedName) - { - var qualifiedName3 = (QualifiedNameSyntax)name; - - if (qualifiedName3.Right.Identifier.ValueText == identifierToken.ValueText) - { - issueSpan = qualifiedName3.Left.Span; - } - } - - // first check if this would be a valid reduction - if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel)) - { - // in case this alias name ends with "Attribute", we're going to see if we can also - // remove that suffix. - if (TryReduceAttributeSuffix( - name, - identifierToken, - out var replacementNodeWithoutAttributeSuffix, - out var issueSpanWithoutAttributeSuffix)) - { - if (CanReplaceWithReducedName(name, replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken)) - { - replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix); - issueSpan = issueSpanWithoutAttributeSuffix; - } - } - - return true; - } - - return false; - } - - var nameHasNoAlias = false; - - if (name is SimpleNameSyntax simpleName) - { - if (!simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind)) - { - nameHasNoAlias = true; - } - } - - if (name is QualifiedNameSyntax qualifiedName2) - { - if (!qualifiedName2.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) - { - nameHasNoAlias = true; - } - } - - if (name is AliasQualifiedNameSyntax aliasQualifiedName) - { - if (aliasQualifiedName.Name is SimpleNameSyntax && - !aliasQualifiedName.Name.Identifier.HasAnnotations(AliasAnnotation.Kind) && - !aliasQualifiedName.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) - { - nameHasNoAlias = true; - } - } - - var aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken); - if (nameHasNoAlias && aliasInfo == null) - { - // Don't simplify to predefined type if name is part of a QualifiedName. - // QualifiedNames can't contain PredefinedTypeNames (although MemberAccessExpressions can). - // In other words, the left side of a QualifiedName can't be a PredefinedTypeName. - var inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, optionSet, semanticModel); - var inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, optionSet, semanticModel); - - if (!name.Parent.IsKind(SyntaxKind.QualifiedName) && (inDeclarationContext || inMemberAccessContext)) - { - // See if we can simplify this name (like System.Int32) to a built-in type (like 'int'). - // If not, we'll still fall through and see if we can convert it to Int32. - - var codeStyleOptionName = inDeclarationContext - ? nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration) - : nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess); - - var type = semanticModel.GetTypeInfo(name, cancellationToken).Type; - if (type != null) - { - var keywordKind = GetPredefinedKeywordKind(type.SpecialType); - if (keywordKind != SyntaxKind.None && - CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName)) - { - return true; - } - } - else - { - var typeSymbol = semanticModel.GetSymbolInfo(name, cancellationToken).Symbol; - if (typeSymbol.IsKind(SymbolKind.NamedType)) - { - var keywordKind = GetPredefinedKeywordKind(((INamedTypeSymbol)typeSymbol).SpecialType); - if (keywordKind != SyntaxKind.None && - CanReplaceWithPredefinedTypeKeywordInContext(name, semanticModel, out replacementNode, ref issueSpan, keywordKind, codeStyleOptionName)) - { - return true; - } - } - } - } - } - - // Nullable rewrite: Nullable -> int? - // Don't rewrite in the case where Nullable is part of some qualified name like Nullable.Something - if (!name.IsVar && (symbol.Kind == SymbolKind.NamedType) && !name.IsLeftSideOfQualifiedName()) - { - var type = (INamedTypeSymbol)symbol; - if (aliasInfo == null && CanSimplifyNullable(type, name, semanticModel)) - { - GenericNameSyntax genericName; - if (name.Kind() == SyntaxKind.QualifiedName) - { - genericName = (GenericNameSyntax)((QualifiedNameSyntax)name).Right; - } - else - { - genericName = (GenericNameSyntax)name; - } - - var oldType = genericName.TypeArgumentList.Arguments.First(); - if (oldType.Kind() == SyntaxKind.OmittedTypeArgument) - { - return false; - } - - replacementNode = SyntaxFactory.NullableType(oldType) - .WithLeadingTrivia(name.GetLeadingTrivia()) - .WithTrailingTrivia(name.GetTrailingTrivia()); - issueSpan = name.Span; - - // we need to simplify the whole qualified name at once, because replacing the identifier on the left in - // System.Nullable alone would be illegal. - // If this fails we want to continue to try at least to remove the System if possible. - if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel)) - { - return true; - } - } - } - } - - SyntaxToken identifier; - switch (name.Kind()) - { - case SyntaxKind.AliasQualifiedName: - var simpleName = ((AliasQualifiedNameSyntax)name).Name - .WithLeadingTrivia(name.GetLeadingTrivia()); - - simpleName = simpleName.ReplaceToken(simpleName.Identifier, - ((AliasQualifiedNameSyntax)name).Name.Identifier.CopyAnnotationsTo( - simpleName.Identifier.WithLeadingTrivia( - ((AliasQualifiedNameSyntax)name).Alias.Identifier.LeadingTrivia))); - - replacementNode = simpleName; - - issueSpan = ((AliasQualifiedNameSyntax)name).Alias.Span; - - break; - - case SyntaxKind.QualifiedName: - replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); - issueSpan = ((QualifiedNameSyntax)name).Left.Span; - - break; - - case SyntaxKind.IdentifierName: - identifier = ((IdentifierNameSyntax)name).Identifier; - - // we can try to remove the Attribute suffix if this is the attribute name - TryReduceAttributeSuffix(name, identifier, out replacementNode, out issueSpan); - break; - } - } - - if (replacementNode == null) - { - return false; - } - - // We may be looking at a name `X.Y` seeing if we can replace it with `Y`. However, in - // order to know for sure, we actually have to look slightly higher at `X.Y.Z` to see if - // it can simplify to `Y.Z`. This is because in the `Color Color` case we can only tell - // if we can reduce by looking by also looking at what comes next to see if it will - // cause the simplified name to bind to the instance or static side. - if (TryReduceCrefColorColor(name, replacementNode, semanticModel, cancellationToken)) - { - return true; - } - - return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken); - } - - private static bool TryReduceCrefColorColor( - NameSyntax name, TypeSyntax replacement, - SemanticModel semanticModel, CancellationToken cancellationToken) - { - if (!InsideCrefReference(name)) - return false; - - if (name.Parent is QualifiedCrefSyntax qualifiedCrefParent && qualifiedCrefParent.Container == name) - { - // we have and we're trying to see if we can replace - // A.B.C with C. In this case the parent of A.B.C is A.B.C.D which is a - // QualifiedCrefSyntax - - var qualifiedReplacement = SyntaxFactory.QualifiedCref(replacement, qualifiedCrefParent.Member); - if (QualifiedCrefSimplifier.CanSimplifyWithReplacement(qualifiedCrefParent, semanticModel, qualifiedReplacement, cancellationToken)) - return true; - } - else if (name.Parent is QualifiedNameSyntax qualifiedParent && qualifiedParent.Left == name && - replacement is NameSyntax replacementName) - { - // we have and we're trying to see if we can replace - // A.B with B. In this case the parent of A.B is A.B.C which is a - // QualifiedNameSyntax - - var qualifiedReplacement = SyntaxFactory.QualifiedName(replacementName, qualifiedParent.Right); - return CanReplaceWithReducedName( - qualifiedParent, qualifiedReplacement, semanticModel, cancellationToken); - } - - return false; - } - - private static bool CanSimplifyNullable(INamedTypeSymbol type, NameSyntax name, SemanticModel semanticModel) - { - if (!type.IsNullable()) - { - return false; - } - - if (type.IsUnboundGenericType) - { - // Don't simplify unbound generic type "Nullable<>". - return false; - } - - if (InsideNameOfExpression(name, semanticModel)) - { - // Nullable can't be simplified to T? in nameof expressions. - return false; - } - - if (!InsideCrefReference(name)) - { - // Nullable can always be simplified to T? outside crefs. - return true; - } - - if (name.Parent is NameMemberCrefSyntax) - return false; - - // Inside crefs, if the T in this Nullable{T} is being declared right here - // then this Nullable{T} is not a constructed generic type and we should - // not offer to simplify this to T?. - // - // For example, we should not offer the simplification in the following cases where - // T does not bind to an existing type / type parameter in the user's code. - // - - // - - // - // And we should offer the simplification in the following cases where SomeType and - // SomeMethod bind to a type and method declared elsewhere in the users code. - // - - - var argument = type.TypeArguments.SingleOrDefault(); - if (argument == null || argument.IsErrorType()) - { - return false; - } - - var argumentDecl = argument.DeclaringSyntaxReferences.FirstOrDefault(); - if (argumentDecl == null) - { - // The type argument is a type from metadata - so this is a constructed generic nullable type that can be simplified (e.g. Nullable(Of Integer)). - return true; - } - - return !name.Span.Contains(argumentDecl.Span); - } - - private static bool CanReplaceWithPredefinedTypeKeywordInContext( - NameSyntax name, - SemanticModel semanticModel, - out TypeSyntax replacementNode, - ref TextSpan issueSpan, - SyntaxKind keywordKind, - string codeStyleOptionName) - { - replacementNode = CreatePredefinedTypeSyntax(name, keywordKind); - - issueSpan = name.Span; // we want to show the whole name expression as unnecessary - - var canReduce = name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel); - - if (canReduce) - { - replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName)); - } - - return canReduce; - } - - private static TypeSyntax CreatePredefinedTypeSyntax(ExpressionSyntax expression, SyntaxKind keywordKind) - { - return SyntaxFactory.PredefinedType(SyntaxFactory.Token(expression.GetLeadingTrivia(), keywordKind, expression.GetTrailingTrivia())); - } - - private static bool TryReduceAttributeSuffix( - NameSyntax name, - SyntaxToken identifierToken, - out TypeSyntax replacementNode, - out TextSpan issueSpan) - { - issueSpan = default; - replacementNode = default; - - // we can try to remove the Attribute suffix if this is the attribute name - if (SyntaxFacts.IsAttributeName(name)) - { - if (name.Parent.Kind() == SyntaxKind.Attribute || name.IsRightSideOfDotOrColonColon()) - { - const string AttributeName = "Attribute"; - - // an attribute that should keep it (unnecessary "Attribute" suffix should be annotated with a DontSimplifyAnnotation - if (identifierToken.ValueText != AttributeName && identifierToken.ValueText.EndsWith(AttributeName, StringComparison.Ordinal) && !identifierToken.HasAnnotation(SimplificationHelpers.DontSimplifyAnnotation)) - { - // weird. the semantic model is able to bind attribute syntax like "[as()]" although it's not valid code. - // so we need another check for keywords manually. - var newAttributeName = identifierToken.ValueText.Substring(0, identifierToken.ValueText.Length - 9); - if (SyntaxFacts.GetKeywordKind(newAttributeName) != SyntaxKind.None) - { - return false; - } - - // if this attribute name in source contained Unicode escaping, we will loose it now - // because there is no easy way to determine the substring from identifier->ToString() - // which would be needed to pass to SyntaxFactory.Identifier - // The result is an unescaped Unicode character in source. - - // once we remove the Attribute suffix, we can't use an escaped identifier - var newIdentifierToken = identifierToken.CopyAnnotationsTo( - SyntaxFactory.Identifier( - identifierToken.LeadingTrivia, - newAttributeName, - identifierToken.TrailingTrivia)); - - replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken) - .WithLeadingTrivia(name.GetLeadingTrivia()); - issueSpan = new TextSpan(identifierToken.Span.End - 9, 9); - - return true; - } - } - } - - return false; - } - - /// - /// Checks if the SyntaxNode is a name of a namespace declaration. To be a namespace name, the syntax - /// must be parented by an namespace declaration and the node itself must be equal to the declaration's Name - /// property. - /// - /// - /// - private static bool IsPartOfNamespaceDeclarationName(SyntaxNode node) - { - var parent = node; - - while (parent != null) - { - switch (parent.Kind()) - { - case SyntaxKind.IdentifierName: - case SyntaxKind.QualifiedName: - node = parent; - parent = parent.Parent; - break; - - case SyntaxKind.NamespaceDeclaration: - var namespaceDeclaration = (NamespaceDeclarationSyntax)parent; - return object.Equals(namespaceDeclaration.Name, node); - - default: - return false; - } - } - - return false; - } - - private static bool TrySimplify( - ExpressionSyntax expression, - SemanticModel semanticModel, - out ExpressionSyntax replacementNode, - out TextSpan issueSpan) - { - replacementNode = null; - issueSpan = default; - - switch (expression.Kind()) - { - case SyntaxKind.SimpleMemberAccessExpression: - { - var memberAccess = (MemberAccessExpressionSyntax)expression; - if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) - { - return false; - } - - if (TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, out var newLeft, out issueSpan)) - { - // replacement node might not be in it's simplest form, so add simplify annotation to it. - replacementNode = memberAccess.Update(newLeft, memberAccess.OperatorToken, memberAccess.Name) - .WithAdditionalAnnotations(Simplifier.Annotation); - - // Ensure that replacement doesn't change semantics. - return !ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel); - } - - return false; - } - - case SyntaxKind.QualifiedName: - { - var qualifiedName = (QualifiedNameSyntax)expression; - if (TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, out var newLeft, out issueSpan)) - { - // replacement node might not be in it's simplest form, so add simplify annotation to it. - replacementNode = qualifiedName.Update((NameSyntax)newLeft, qualifiedName.DotToken, qualifiedName.Right) - .WithAdditionalAnnotations(Simplifier.Annotation); - - // Ensure that replacement doesn't change semantics. - return !ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel); - } - - return false; - } - } - - return false; - } - - private static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) - { - var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); - return speculationAnalyzer.ReplacementChangesSemantics(); - } - - // Note: The caller needs to verify that replacement doesn't change semantics of the original expression. - private static bool TrySimplifyMemberAccessOrQualifiedName( - ExpressionSyntax left, - ExpressionSyntax right, - SemanticModel semanticModel, - out ExpressionSyntax replacementNode, - out TextSpan issueSpan) - { - replacementNode = null; - issueSpan = default; - - if (left != null && right != null) - { - var leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left); - if (leftSymbol != null && (leftSymbol.Kind == SymbolKind.NamedType)) - { - var rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right); - if (rightSymbol != null && (rightSymbol.IsStatic || rightSymbol.Kind == SymbolKind.NamedType)) - { - // Static member access or nested type member access. - var containingType = rightSymbol.ContainingType; - - var enclosingSymbol = semanticModel.GetEnclosingSymbol(left.SpanStart); - var enclosingTypeParametersInsideOut = new List(); - - while (enclosingSymbol != null) - { - if (enclosingSymbol is IMethodSymbol methodSymbol) - { - if (methodSymbol.TypeArguments.Length != 0) - { - enclosingTypeParametersInsideOut.AddRange(methodSymbol.TypeArguments); - } - } - - if (enclosingSymbol is INamedTypeSymbol namedTypeSymbol) - { - if (namedTypeSymbol.TypeArguments.Length != 0) - { - enclosingTypeParametersInsideOut.AddRange(namedTypeSymbol.TypeArguments); - } - } - - enclosingSymbol = enclosingSymbol.ContainingSymbol; - } - - if (containingType != null && !containingType.Equals(leftSymbol)) - { - if (leftSymbol is INamedTypeSymbol namedType && - containingType.TypeArguments.Length != 0) - { - return false; - } - - // We have a static member access or a nested type member access using a more derived type. - // Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type. - replacementNode = containingType.GenerateTypeSyntax() - .WithLeadingTrivia(left.GetLeadingTrivia()) - .WithTrailingTrivia(left.GetTrailingTrivia()); - issueSpan = left.Span; - return true; - } - } - } - } - - return false; - } - - private static bool CanReplaceWithReducedName( - MemberAccessExpressionSyntax memberAccess, - ExpressionSyntax reducedName, - SemanticModel semanticModel, - ISymbol symbol, - CancellationToken cancellationToken) - { - if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel)) - { - return false; - } - - var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken); - if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() || - speculationAnalyzer.ReplacementChangesSemantics()) - { - return false; - } - - if (WillConflictWithExistingLocal(memberAccess, reducedName, semanticModel)) - { - return false; - } - - if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) - { - return false; - } - - if (AccessMethodWithDynamicArgumentInsideStructConstructor(memberAccess, semanticModel)) - { - return false; - } - - if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression) - { - var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken); - if (enclosingNamedType != null && - !enclosingNamedType.IsSealed && - symbol != null && - symbol.IsOverridable()) - { - return false; - } - } - - var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess); - - return !invalidTransformation1; - } - - private static bool ParserWouldTreatExpressionAsCast(ExpressionSyntax reducedNode, MemberAccessExpressionSyntax originalNode) - { - SyntaxNode parent = originalNode; - while (parent != null) - { - if (parent.IsParentKind(SyntaxKind.SimpleMemberAccessExpression)) - { - parent = parent.Parent; - continue; - } - - if (!parent.IsParentKind(SyntaxKind.ParenthesizedExpression)) - { - return false; - } - - break; - } - - var newExpression = parent.ReplaceNode(originalNode, reducedNode); - - // detect cast ambiguities according to C# spec #7.7.6 - if (IsNameOrMemberAccessButNoExpression(newExpression)) - { - var nextToken = parent.Parent.GetLastToken().GetNextToken(); - - return nextToken.Kind() == SyntaxKind.OpenParenToken || - nextToken.Kind() == SyntaxKind.TildeToken || - nextToken.Kind() == SyntaxKind.ExclamationToken || - (SyntaxFacts.IsKeywordKind(nextToken.Kind()) && !(nextToken.Kind() == SyntaxKind.AsKeyword || nextToken.Kind() == SyntaxKind.IsKeyword)); - } - - return false; - } - - private static bool IsNameOrMemberAccessButNoExpression(SyntaxNode node) - { - if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression)) - { - var memberAccess = (MemberAccessExpressionSyntax)node; - - return memberAccess.Expression.IsKind(SyntaxKind.IdentifierName) || - IsNameOrMemberAccessButNoExpression(memberAccess.Expression); - } - - return node.IsKind(SyntaxKind.IdentifierName); - } - - /// - /// Tells if the Member access is the starting part of a Dynamic Invocation - /// - /// - /// - /// Return true, if the member access is the starting point of a Dynamic Invocation - private static bool IsMemberAccessADynamicInvocation(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) - { - var ancestorInvocation = memberAccess.FirstAncestorOrSelf(); - - if (ancestorInvocation != null && ancestorInvocation.SpanStart == memberAccess.SpanStart) - { - var typeInfo = semanticModel.GetTypeInfo(ancestorInvocation); - if (typeInfo.Type != null && - typeInfo.Type.Kind == SymbolKind.DynamicType) - { - return true; - } - } - - return false; - } - - /* - * Name Reduction, to implicitly mean "this", is possible only after the initialization of all member variables but - * since the check for initialization of all member variable is a lot of work for this simplification we don't simplify - * even if all the member variables are initialized - */ - private static bool AccessMethodWithDynamicArgumentInsideStructConstructor(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) - { - var constructor = memberAccess.Ancestors().OfType().SingleOrDefault(); - - if (constructor == null || constructor.Parent.Kind() != SyntaxKind.StructDeclaration) - { - return false; - } - - return semanticModel.GetSymbolInfo(memberAccess.Name).CandidateReason == CandidateReason.LateBound; - } - - private static bool CanReplaceWithReducedName(NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken) - { - var speculationAnalyzer = new SpeculationAnalyzer(name, reducedName, semanticModel, cancellationToken); - if (speculationAnalyzer.ReplacementChangesSemantics()) - { - return false; - } - - return CanReplaceWithReducedNameInContext(name, reducedName, semanticModel); - } - - private static bool CanReplaceWithReducedNameInContext( - this NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel) - { - // Check for certain things that would prevent us from reducing this name in this context. - // For example, you can simplify "using a = System.Int32" to "using a = int" as it's simply - // not allowed in the C# grammar. - - if (IsNonNameSyntaxInUsingDirective(name, reducedName) || - WillConflictWithExistingLocal(name, reducedName, semanticModel) || - IsAmbiguousCast(name, reducedName) || - IsNullableTypeInPointerExpression(reducedName) || - IsNotNullableReplaceable(name, reducedName) || - IsNonReducableQualifiedNameInUsingDirective(semanticModel, name, reducedName)) - { - return false; - } - - return true; - } - - private static bool IsNonReducableQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name, TypeSyntax reducedName) - { - // Whereas most of the time we do not want to reduce namespace names, We will - // make an exception for namespaces with the global:: alias. - return IsQualifiedNameInUsingDirective(model, name) && - !IsGlobalAliasQualifiedName(name); - } - - private static bool IsQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name) - { - while (name.IsLeftSideOfQualifiedName()) - { - name = (NameSyntax)name.Parent; - } - - if (name.IsParentKind(SyntaxKind.UsingDirective) && - ((UsingDirectiveSyntax)name.Parent).Alias == null) - { - // We're a qualified name in a using. We don't want to reduce this name as people like - // fully qualified names in usings so they can properly tell what the name is resolving - // to. - // However, if this name is actually referencing the special Script class, then we do - // want to allow that to be reduced. - - return !IsInScriptClass(model, name); - } - - return false; - } - - private static bool IsGlobalAliasQualifiedName(NameSyntax name) - { - // Checks whether the `global::` alias is applied to the name - return name is AliasQualifiedNameSyntax aliasName && - aliasName.Alias.Identifier.IsKind(SyntaxKind.GlobalKeyword); - } - - private static bool IsInScriptClass(SemanticModel model, NameSyntax name) - { - var symbol = model.GetSymbolInfo(name).Symbol as INamedTypeSymbol; - while (symbol != null) - { - if (symbol.IsScriptClass) - { - return true; - } - - symbol = symbol.ContainingType; - } - - return false; - } - - private static bool IsNotNullableReplaceable(NameSyntax name, TypeSyntax reducedName) - { - var isNotNullableReplaceable = false; - var isLeftSideOfDot = name.IsLeftSideOfDot(); - var isRightSideOfDot = name.IsRightSideOfDot(); - - if (reducedName.Kind() == SyntaxKind.NullableType) - { - if (((NullableTypeSyntax)reducedName).ElementType.Kind() == SyntaxKind.OmittedTypeArgument) - { - isNotNullableReplaceable = true; - } - else - { - isNotNullableReplaceable = name.IsLeftSideOfDot() || name.IsRightSideOfDot(); - } - } - - return isNotNullableReplaceable; - } - - private static bool IsThisOrTypeOrNamespace(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) - { - if (memberAccess.Expression.Kind() == SyntaxKind.ThisExpression) - { - var previousToken = memberAccess.Expression.GetFirstToken().GetPreviousToken(); - - var symbol = semanticModel.GetSymbolInfo(memberAccess.Name).Symbol; - - if (previousToken.Kind() == SyntaxKind.OpenParenToken && - previousToken.Parent.IsKind(SyntaxKind.ParenthesizedExpression) && - !previousToken.Parent.IsParentKind(SyntaxKind.ParenthesizedExpression) && - ((ParenthesizedExpressionSyntax)previousToken.Parent).Expression.Kind() == SyntaxKind.SimpleMemberAccessExpression && - symbol != null && symbol.Kind == SymbolKind.Method) - { - return false; - } - - return true; - } - - var expressionInfo = semanticModel.GetSymbolInfo(memberAccess.Expression); - if (SimplificationHelpers.IsValidSymbolInfo(expressionInfo.Symbol)) - { - if (expressionInfo.Symbol is INamespaceOrTypeSymbol) - { - return true; - } - - if (expressionInfo.Symbol.IsThisParameter()) - { - return true; - } - } - - return false; - } - - private static bool ContainsOpenName(NameSyntax name) - { - if (name is QualifiedNameSyntax qualifiedName) - { - return ContainsOpenName(qualifiedName.Left) || ContainsOpenName(qualifiedName.Right); - } - else if (name is GenericNameSyntax genericName) - { - return genericName.IsUnboundGenericName; - } - else - { - return false; - } - } - - private static bool IsNullableTypeInPointerExpression(ExpressionSyntax simplifiedNode) - { - // Note: nullable type syntax is not allowed in pointer type syntax - if (simplifiedNode.Kind() == SyntaxKind.NullableType && - simplifiedNode.DescendantNodes().Any(n => n is PointerTypeSyntax)) - { - return true; - } - - return false; - } - - private static bool IsNonNameSyntaxInUsingDirective(ExpressionSyntax expression, ExpressionSyntax simplifiedNode) - { - return - expression.IsParentKind(SyntaxKind.UsingDirective) && - !(simplifiedNode is NameSyntax); - } - - private static bool WillConflictWithExistingLocal( - ExpressionSyntax expression, ExpressionSyntax simplifiedNode, SemanticModel semanticModel) - { - if (simplifiedNode is IdentifierNameSyntax identifierName && - !SyntaxFacts.IsInNamespaceOrTypeContext(expression)) - { - var symbols = semanticModel.LookupSymbols(expression.SpanStart, name: identifierName.Identifier.ValueText); - return symbols.Any(s => s is ILocalSymbol); - } - - return false; - } - - private static bool IsAmbiguousCast(ExpressionSyntax expression, ExpressionSyntax simplifiedNode) - { - // Can't simplify a type name in a cast expression if it would then cause the cast to be - // parsed differently. For example: (Goo::Bar)+1 is a cast. But if that simplifies to - // (Bar)+1 then that's an arithmetic expression. - if (expression.IsParentKind(SyntaxKind.CastExpression)) - { - var castExpression = (CastExpressionSyntax)expression.Parent; - if (castExpression.Type == expression) - { - var newCastExpression = castExpression.ReplaceNode(castExpression.Type, simplifiedNode); - var reparsedCastExpression = SyntaxFactory.ParseExpression(newCastExpression.ToString()); - - if (!reparsedCastExpression.IsKind(SyntaxKind.CastExpression)) - { - return true; - } - } - } - - return false; - } - - private static SyntaxNode FindImmediatelyEnclosingLocalVariableDeclarationSpace(SyntaxNode syntax) - { - for (var declSpace = syntax; declSpace != null; declSpace = declSpace.Parent) - { - switch (declSpace.Kind()) - { - // These are declaration-space-defining syntaxes, by the spec: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.IndexerDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.ConstructorDeclaration: - case SyntaxKind.Block: - case SyntaxKind.ParenthesizedLambdaExpression: - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.AnonymousMethodExpression: - case SyntaxKind.SwitchStatement: - case SyntaxKind.ForEachKeyword: - case SyntaxKind.ForStatement: - case SyntaxKind.UsingStatement: - - // SPEC VIOLATION: We also want to stop walking out if, say, we are in a field - // initializer. Technically according to the wording of the spec it should be - // legal to use a simple name inconsistently inside a field initializer because - // it does not define a local variable declaration space. In practice of course - // we want to check for that. (As the native compiler does as well.) - - case SyntaxKind.FieldDeclaration: - return declSpace; - } - } - - return null; - } - - /// - /// Returns the predefined keyword kind for a given . - /// - /// The of this type. - /// The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type. - public static SyntaxKind GetPredefinedKeywordKind(SpecialType specialType) - => specialType switch - { - SpecialType.System_Boolean => SyntaxKind.BoolKeyword, - SpecialType.System_Byte => SyntaxKind.ByteKeyword, - SpecialType.System_SByte => SyntaxKind.SByteKeyword, - SpecialType.System_Int32 => SyntaxKind.IntKeyword, - SpecialType.System_UInt32 => SyntaxKind.UIntKeyword, - SpecialType.System_Int16 => SyntaxKind.ShortKeyword, - SpecialType.System_UInt16 => SyntaxKind.UShortKeyword, - SpecialType.System_Int64 => SyntaxKind.LongKeyword, - SpecialType.System_UInt64 => SyntaxKind.ULongKeyword, - SpecialType.System_Single => SyntaxKind.FloatKeyword, - SpecialType.System_Double => SyntaxKind.DoubleKeyword, - SpecialType.System_Decimal => SyntaxKind.DecimalKeyword, - SpecialType.System_String => SyntaxKind.StringKeyword, - SpecialType.System_Char => SyntaxKind.CharKeyword, - SpecialType.System_Object => SyntaxKind.ObjectKeyword, - SpecialType.System_Void => SyntaxKind.VoidKeyword, - _ => SyntaxKind.None, - }; - public static SimpleNameSyntax GetRightmostName(this ExpressionSyntax node) { if (node is MemberAccessExpressionSyntax memberAccess && memberAccess.Name != null) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs index d06971475d05d..3981e329090e0 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Reducers/CSharpNameReducer.cs @@ -47,7 +47,7 @@ private static SyntaxNode SimplifyName( else { var expressionSyntax = (ExpressionSyntax)node; - if (!expressionSyntax.TryReduceOrSimplifyExplicitName(semanticModel, out var expressionReplacement, out issueSpan, optionSet, cancellationToken)) + if (!ExpressionSimplifier.Instance.TrySimplify(expressionSyntax, semanticModel, optionSet, out var expressionReplacement, out issueSpan, cancellationToken)) { return node; } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs index 8a0d96f97c37a..0067f281628d0 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs @@ -1,8 +1,17 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { @@ -11,11 +20,432 @@ internal abstract class AbstractCSharpSimplifier where TSimplifiedSyntax : SyntaxNode { public abstract bool TrySimplify( - TSyntax crefSyntax, + TSyntax syntax, SemanticModel semanticModel, OptionSet optionSet, out TSimplifiedSyntax replacementNode, out TextSpan issueSpan, CancellationToken cancellationToken); + + /// + /// Returns the predefined keyword kind for a given . + /// + /// The of this type. + /// The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type. + public static SyntaxKind GetPredefinedKeywordKind(SpecialType specialType) + => specialType switch + { + SpecialType.System_Boolean => SyntaxKind.BoolKeyword, + SpecialType.System_Byte => SyntaxKind.ByteKeyword, + SpecialType.System_SByte => SyntaxKind.SByteKeyword, + SpecialType.System_Int32 => SyntaxKind.IntKeyword, + SpecialType.System_UInt32 => SyntaxKind.UIntKeyword, + SpecialType.System_Int16 => SyntaxKind.ShortKeyword, + SpecialType.System_UInt16 => SyntaxKind.UShortKeyword, + SpecialType.System_Int64 => SyntaxKind.LongKeyword, + SpecialType.System_UInt64 => SyntaxKind.ULongKeyword, + SpecialType.System_Single => SyntaxKind.FloatKeyword, + SpecialType.System_Double => SyntaxKind.DoubleKeyword, + SpecialType.System_Decimal => SyntaxKind.DecimalKeyword, + SpecialType.System_String => SyntaxKind.StringKeyword, + SpecialType.System_Char => SyntaxKind.CharKeyword, + SpecialType.System_Object => SyntaxKind.ObjectKeyword, + SpecialType.System_Void => SyntaxKind.VoidKeyword, + _ => SyntaxKind.None, + }; + + protected static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) + { + var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); + return speculationAnalyzer.ReplacementChangesSemantics(); + } + + protected static bool InsideCrefReference(ExpressionSyntax expression) + { + var crefAttribute = expression.FirstAncestorOrSelf(); + return crefAttribute != null; + } + + [PerformanceSensitive( + "https://github.com/dotnet/roslyn/issues/23582", + Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")] + protected static bool TryReplaceExpressionWithAlias( + ExpressionSyntax node, SemanticModel semanticModel, + ISymbol symbol, CancellationToken cancellationToken, out IAliasSymbol aliasReplacement) + { + aliasReplacement = null; + + if (!IsAliasReplaceableExpression(node)) + return false; + + // Avoid the TryReplaceWithAlias algorithm if the tree has no using alias directives. Since the input node + // might be a speculative node (not fully rooted in a tree), we use the original semantic model to find the + // equivalent node in the original tree, and from there determine if the tree has any using alias + // directives. + var originalModel = semanticModel.GetOriginalSemanticModel(); + + // Perf: We are only using the syntax tree root in a fast-path syntax check. If the root is not readily + // available, it is fine to continue through the normal algorithm. + if (originalModel.SyntaxTree.TryGetRoot(out var root)) + { + if (!HasUsingAliasDirective(root)) + { + return false; + } + } + + // If the Symbol is a constructor get its containing type + if (symbol.IsConstructor()) + { + symbol = symbol.ContainingType; + } + + if (node is QualifiedNameSyntax || node is AliasQualifiedNameSyntax) + { + SyntaxAnnotation aliasAnnotationInfo = null; + + // The following condition checks if the user has used alias in the original code and + // if so the expression is replaced with the Alias + if (node is QualifiedNameSyntax qualifiedNameNode) + { + if (qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind)) + { + aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); + } + } + + if (node is AliasQualifiedNameSyntax aliasQualifiedNameNode) + { + if (aliasQualifiedNameNode.Name.Identifier.HasAnnotations(AliasAnnotation.Kind)) + { + aliasAnnotationInfo = aliasQualifiedNameNode.Name.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); + } + } + + if (aliasAnnotationInfo != null) + { + var aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo); + var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName); + + var aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace); + + if (aliasTypeInfo != null) + { + aliasReplacement = aliasTypeInfo; + return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); + } + } + } + + if (node.Kind() == SyntaxKind.IdentifierName && + semanticModel.GetAliasInfo((IdentifierNameSyntax)node, cancellationToken) != null) + { + return false; + } + + // an alias can only replace a type or namespace + if (symbol == null || + (symbol.Kind != SymbolKind.Namespace && symbol.Kind != SymbolKind.NamedType)) + { + return false; + } + + var preferAliasToQualifiedName = true; + if (node is QualifiedNameSyntax qualifiedName) + { + if (!qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + { + var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; + if (type != null) + { + var keywordKind = GetPredefinedKeywordKind(type.SpecialType); + if (keywordKind != SyntaxKind.None) + { + preferAliasToQualifiedName = false; + } + } + } + } + + if (node is AliasQualifiedNameSyntax aliasQualifiedNameSyntax) + { + if (!aliasQualifiedNameSyntax.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + { + var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; + if (type != null) + { + var keywordKind = GetPredefinedKeywordKind(type.SpecialType); + if (keywordKind != SyntaxKind.None) + { + preferAliasToQualifiedName = false; + } + } + } + } + + aliasReplacement = GetAliasForSymbol((INamespaceOrTypeSymbol)symbol, node.GetFirstToken(), semanticModel, cancellationToken); + if (aliasReplacement != null && preferAliasToQualifiedName) + { + return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); + } + + return false; + } + + protected static bool IsAliasReplaceableExpression(ExpressionSyntax expression) + { + var current = expression; + while (current.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax currentMember)) + { + current = currentMember.Expression; + continue; + } + + return current.IsKind(SyntaxKind.AliasQualifiedName, + SyntaxKind.IdentifierName, + SyntaxKind.GenericName, + SyntaxKind.QualifiedName); + } + + protected static bool HasUsingAliasDirective(SyntaxNode syntax) + { + SyntaxList usings; + SyntaxList members; + if (syntax.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration)) + { + usings = namespaceDeclaration.Usings; + members = namespaceDeclaration.Members; + } + else if (syntax.IsKind(SyntaxKind.CompilationUnit, out CompilationUnitSyntax compilationUnit)) + { + usings = compilationUnit.Usings; + members = compilationUnit.Members; + } + else + { + return false; + } + + foreach (var usingDirective in usings) + { + if (usingDirective.Alias != null) + { + return true; + } + } + + foreach (var member in members) + { + if (HasUsingAliasDirective(member)) + { + return true; + } + } + + return false; + } + + // We must verify that the alias actually binds back to the thing it's aliasing. + // It's possible there's another symbol with the same name as the alias that binds + // first + private static bool ValidateAliasForTarget(IAliasSymbol aliasReplacement, SemanticModel semanticModel, ExpressionSyntax node, ISymbol symbol) + { + var aliasName = aliasReplacement.Name; + + // If we're the argument of a nameof(X.Y) call, then we can't simplify to an + // alias unless the alias has the same name as us (i.e. 'Y'). + if (node.IsNameOfArgumentExpression()) + { + var nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent); + if (!nameofValueOpt.HasValue) + { + return false; + } + + if (nameofValueOpt.Value is string existingVal && + existingVal != aliasName) + { + return false; + } + } + + var boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name: aliasName); + + if (boundSymbols.Length == 1) + { + if (boundSymbols[0] is IAliasSymbol boundAlias && aliasReplacement.Target.Equals(symbol)) + { + return true; + } + } + + return false; + } + + private static IAliasSymbol GetAliasForSymbol(INamespaceOrTypeSymbol symbol, SyntaxToken token, SemanticModel semanticModel, CancellationToken cancellationToken) + { + var originalSemanticModel = semanticModel.GetOriginalSemanticModel(); + if (!originalSemanticModel.SyntaxTree.HasCompilationUnitRoot) + { + return null; + } + + var namespaceId = GetNamespaceIdForAliasSearch(semanticModel, token, cancellationToken); + if (namespaceId < 0) + { + return null; + } + + if (!AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId, symbol, out var aliasSymbol)) + { + // add cache + AliasSymbolCache.AddAliasSymbols(originalSemanticModel, namespaceId, semanticModel.LookupNamespacesAndTypes(token.SpanStart).OfType()); + + // retry + AliasSymbolCache.TryGetAliasSymbol(originalSemanticModel, namespaceId, symbol, out aliasSymbol); + } + + return aliasSymbol; + } + + private static int GetNamespaceIdForAliasSearch(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) + { + var startNode = GetStartNodeForNamespaceId(semanticModel, token, cancellationToken); + if (!startNode.SyntaxTree.HasCompilationUnitRoot) + { + return -1; + } + + // NOTE: If we're currently in a block of usings, then we want to collect the + // aliases that are higher up than this block. Using aliases declared in a block of + // usings are not usable from within that same block. + var usingDirective = startNode.GetAncestorOrThis(); + if (usingDirective != null) + { + startNode = usingDirective.Parent.Parent; + if (startNode == null) + { + return -1; + } + } + + // check whether I am under a namespace + var @namespace = startNode.GetAncestorOrThis(); + if (@namespace != null) + { + // since we have node inside of the root, root should be already there + // search for namespace id should be quite cheap since normally there should be + // only a few namespace defined in a source file if it is not 1. that is why it is + // not cached. + var startIndex = 1; + return GetNamespaceId(startNode.SyntaxTree.GetRoot(cancellationToken), @namespace, ref startIndex); + } + + // no namespace, under compilation unit directly + return 0; + } + + private static SyntaxNode GetStartNodeForNamespaceId(SemanticModel semanticModel, SyntaxToken token, CancellationToken cancellationToken) + { + if (!semanticModel.IsSpeculativeSemanticModel) + { + return token.Parent; + } + + var originalSemanticMode = semanticModel.GetOriginalSemanticModel(); + token = originalSemanticMode.SyntaxTree.GetRoot(cancellationToken).FindToken(semanticModel.OriginalPositionForSpeculation); + + return token.Parent; + } + + private static int GetNamespaceId(SyntaxNode container, NamespaceDeclarationSyntax target, ref int index) + { + if (container is CompilationUnitSyntax compilation) + { + return GetNamespaceId(compilation.Members, target, ref index); + } + + if (container is NamespaceDeclarationSyntax @namespace) + { + return GetNamespaceId(@namespace.Members, target, ref index); + } + + return Contract.FailWithReturn("shouldn't reach here"); + } + + private static int GetNamespaceId(SyntaxList members, NamespaceDeclarationSyntax target, ref int index) + { + foreach (var member in members) + { + if (!(member is NamespaceDeclarationSyntax childNamespace)) + { + continue; + } + + if (childNamespace == target) + { + return index; + } + + index++; + var result = GetNamespaceId(childNamespace, target, ref index); + if (result > 0) + { + return result; + } + } + + return -1; + } + + protected static TypeSyntax CreatePredefinedTypeSyntax(ExpressionSyntax expression, SyntaxKind keywordKind) + => SyntaxFactory.PredefinedType(SyntaxFactory.Token(expression.GetLeadingTrivia(), keywordKind, expression.GetTrailingTrivia())); + + protected static bool InsideNameOfExpression(ExpressionSyntax expression, SemanticModel semanticModel) + { + var nameOfInvocationExpr = expression.FirstAncestorOrSelf( + invocationExpr => + { + return (invocationExpr.Expression is IdentifierNameSyntax identifierName) && (identifierName.Identifier.Text == "nameof") && + semanticModel.GetConstantValue(invocationExpr).HasValue && + (semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String); + }); + + return nameOfInvocationExpr != null; + } + + protected static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel) + { + return !IsInMemberAccessContext(name) && + !InsideCrefReference(name) && + !InsideNameOfExpression(name, semanticModel) && + SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language); + } + + protected static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, OptionSet optionSet, SemanticModel semanticModel) + { + if (!SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, semanticModel.Language)) + return false; + + return (IsInMemberAccessContext(expression) || InsideCrefReference(expression)) && + !InsideNameOfExpression(expression, semanticModel); + } + + public static bool IsInMemberAccessContext(ExpressionSyntax expression) => + expression?.Parent is MemberAccessExpressionSyntax; + + protected static bool WillConflictWithExistingLocal( + ExpressionSyntax expression, ExpressionSyntax simplifiedNode, SemanticModel semanticModel) + { + if (simplifiedNode is IdentifierNameSyntax identifierName && + !SyntaxFacts.IsInNamespaceOrTypeContext(expression)) + { + var symbols = semanticModel.LookupSymbols(expression.SpanStart, name: identifierName.Identifier.ValueText); + return symbols.Any(s => s is ILocalSymbol); + } + + return false; + } + } } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs similarity index 50% rename from src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs rename to src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs index fdd7004eb5ecf..b1d834240e4e1 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSyntaxSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs @@ -2,27 +2,38 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { - internal class ExpressionSyntaxSimplifier : AbstractCSharpSimplifier + using static SyntaxFactory; + + internal class ExpressionSimplifier : AbstractCSharpSimplifier { - public static readonly ExpressionSyntaxSimplifier Instance = new ExpressionSyntaxSimplifier(); + public static readonly ExpressionSimplifier Instance = new ExpressionSimplifier(); - private ExpressionSyntaxSimplifier() + private ExpressionSimplifier() { } - - - public static bool TryReduceOrSimplifyExplicitName( - this ExpressionSyntax expression, + public override bool TrySimplify( + ExpressionSyntax expression, SemanticModel semanticModel, + OptionSet optionSet, out ExpressionSyntax replacementNode, out TextSpan issueSpan, - OptionSet optionSet, CancellationToken cancellationToken) { if (TryReduceExplicitName(expression, semanticModel, out var replacementTypeNode, out issueSpan, optionSet, cancellationToken)) @@ -52,7 +63,7 @@ private static bool TryReduceExplicitName( return TryReduceMemberAccessExpression(memberAccess, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken); if (expression is NameSyntax name) - return TryReduceName(name, semanticModel, out replacementNode, out issueSpan, optionSet, cancellationToken); + return NameSimplifier.Instance.TrySimplify(name, semanticModel, optionSet, out replacementNode, out issueSpan, cancellationToken); return false; } @@ -152,7 +163,7 @@ private static bool TryReduceMemberAccessExpression( replacementNode = CreatePredefinedTypeSyntax(memberAccess, keywordKind); replacementNode = replacementNode - .WithAdditionalAnnotations(new SyntaxAnnotation( + .WithAdditionalAnnotations(new SyntaxAnnotation( nameof(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess))); issueSpan = memberAccess.Span; // we want to show the whole expression as unnecessary @@ -180,17 +191,17 @@ private static bool TryReduceMemberAccessExpression( } } - replacementNode = memberAccess.GetNameWithTriviaMoved(); + replacementNode = GetNameWithTriviaMoved(memberAccess); issueSpan = memberAccess.Expression.Span; return CanReplaceWithReducedName( memberAccess, replacementNode, semanticModel, symbol, cancellationToken); } - public static SimpleNameSyntax GetNameWithTriviaMoved(this MemberAccessExpressionSyntax memberAccess) + public static SimpleNameSyntax GetNameWithTriviaMoved(MemberAccessExpressionSyntax memberAccess) => memberAccess.Name - .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) - .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); + .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) + .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); private static void GetReplacementCandidates( SemanticModel semanticModel, @@ -263,228 +274,260 @@ private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(Member // However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering // aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines // etc. around the user's original code. - return memberAccess.GetLeadingTrivia() - .AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia()) - .AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia()) - .AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia()) - .AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia()); + return TriviaList(WithoutElasticTrivia( + memberAccess.GetLeadingTrivia() + .AddRange(memberAccess.Expression.GetTrailingTrivia()) + .AddRange(memberAccess.OperatorToken.LeadingTrivia) + .AddRange(memberAccess.OperatorToken.TrailingTrivia) + .AddRange(memberAccess.Name.GetLeadingTrivia()))); } - private static IEnumerable WithoutElasticTrivia(this IEnumerable list) + private static IEnumerable WithoutElasticTrivia(IEnumerable list) { return list.Where(t => !t.IsElastic()); } - public static bool InsideCrefReference(this ExpressionSyntax expression) + private static bool TrySimplify( + ExpressionSyntax expression, + SemanticModel semanticModel, + out ExpressionSyntax replacementNode, + out TextSpan issueSpan) { - var crefAttribute = expression.FirstAncestorOrSelf(); - return crefAttribute != null; - } + replacementNode = null; + issueSpan = default; - private static bool InsideNameOfExpression(ExpressionSyntax expression, SemanticModel semanticModel) - { - var nameOfInvocationExpr = expression.FirstAncestorOrSelf( - invocationExpr => - { - return (invocationExpr.Expression is IdentifierNameSyntax identifierName) && (identifierName.Identifier.Text == "nameof") && - semanticModel.GetConstantValue(invocationExpr).HasValue && - (semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String); - }); + switch (expression.Kind()) + { + case SyntaxKind.SimpleMemberAccessExpression: + { + var memberAccess = (MemberAccessExpressionSyntax)expression; + if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) + { + return false; + } - return nameOfInvocationExpr != null; - } + if (TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, out var newLeft, out issueSpan)) + { + // replacement node might not be in it's simplest form, so add simplify annotation to it. + replacementNode = memberAccess.Update(newLeft, memberAccess.OperatorToken, memberAccess.Name) + .WithAdditionalAnnotations(Simplifier.Annotation); - private static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel) - { - return !IsInMemberAccessContext(name) && - !InsideCrefReference(name) && - !InsideNameOfExpression(name, semanticModel) && - SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language); - } + // Ensure that replacement doesn't change semantics. + return !ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel); + } - private static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, OptionSet optionSet, SemanticModel semanticModel) - { - if (!SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, semanticModel.Language)) - return false; + return false; + } - return (IsInMemberAccessContext(expression) || InsideCrefReference(expression)) && - !InsideNameOfExpression(expression, semanticModel); - } + case SyntaxKind.QualifiedName: + { + var qualifiedName = (QualifiedNameSyntax)expression; + if (TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, out var newLeft, out issueSpan)) + { + // replacement node might not be in it's simplest form, so add simplify annotation to it. + replacementNode = qualifiedName.Update((NameSyntax)newLeft, qualifiedName.DotToken, qualifiedName.Right) + .WithAdditionalAnnotations(Simplifier.Annotation); - public static bool IsInMemberAccessContext(this ExpressionSyntax expression) => - expression?.Parent is MemberAccessExpressionSyntax; + // Ensure that replacement doesn't change semantics. + return !ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel); + } - private static bool IsAliasReplaceableExpression(ExpressionSyntax expression) - { - var current = expression; - while (current.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax currentMember)) - { - current = currentMember.Expression; - continue; + return false; + } } - return current.IsKind(SyntaxKind.AliasQualifiedName, - SyntaxKind.IdentifierName, - SyntaxKind.GenericName, - SyntaxKind.QualifiedName); + return false; } - [PerformanceSensitive( - "https://github.com/dotnet/roslyn/issues/23582", - Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")] - private static bool TryReplaceExpressionWithAlias( - ExpressionSyntax node, SemanticModel semanticModel, - ISymbol symbol, CancellationToken cancellationToken, out IAliasSymbol aliasReplacement) + private static bool CanReplaceWithReducedName( + MemberAccessExpressionSyntax memberAccess, + ExpressionSyntax reducedName, + SemanticModel semanticModel, + ISymbol symbol, + CancellationToken cancellationToken) { - aliasReplacement = null; - - if (!IsAliasReplaceableExpression(node)) + if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel)) + { return false; + } - // Avoid the TryReplaceWithAlias algorithm if the tree has no using alias directives. Since the input node - // might be a speculative node (not fully rooted in a tree), we use the original semantic model to find the - // equivalent node in the original tree, and from there determine if the tree has any using alias - // directives. - var originalModel = semanticModel.GetOriginalSemanticModel(); + var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken); + if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() || + speculationAnalyzer.ReplacementChangesSemantics()) + { + return false; + } - // Perf: We are only using the syntax tree root in a fast-path syntax check. If the root is not readily - // available, it is fine to continue through the normal algorithm. - if (originalModel.SyntaxTree.TryGetRoot(out var root)) + if (WillConflictWithExistingLocal(memberAccess, reducedName, semanticModel)) { - if (!HasUsingAliasDirective(root)) - { - return false; - } + return false; } - // If the Symbol is a constructor get its containing type - if (symbol.IsConstructor()) + if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) { - symbol = symbol.ContainingType; + return false; } - if (node is QualifiedNameSyntax || node is AliasQualifiedNameSyntax) + if (AccessMethodWithDynamicArgumentInsideStructConstructor(memberAccess, semanticModel)) { - SyntaxAnnotation aliasAnnotationInfo = null; + return false; + } - // The following condition checks if the user has used alias in the original code and - // if so the expression is replaced with the Alias - if (node is QualifiedNameSyntax qualifiedNameNode) + if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression) + { + var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken); + if (enclosingNamedType != null && + !enclosingNamedType.IsSealed && + symbol != null && + symbol.IsOverridable()) { - if (qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind)) - { - aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); - } + return false; } + } - if (node is AliasQualifiedNameSyntax aliasQualifiedNameNode) - { - if (aliasQualifiedNameNode.Name.Identifier.HasAnnotations(AliasAnnotation.Kind)) - { - aliasAnnotationInfo = aliasQualifiedNameNode.Name.Identifier.GetAnnotations(AliasAnnotation.Kind).Single(); - } - } + var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess); - if (aliasAnnotationInfo != null) - { - var aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo); - var aliasIdentifier = SyntaxFactory.IdentifierName(aliasName); + return !invalidTransformation1; + } - var aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace); + /// + /// Tells if the Member access is the starting part of a Dynamic Invocation + /// + /// + /// + /// Return true, if the member access is the starting point of a Dynamic Invocation + private static bool IsMemberAccessADynamicInvocation(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) + { + var ancestorInvocation = memberAccess.FirstAncestorOrSelf(); - if (aliasTypeInfo != null) - { - aliasReplacement = aliasTypeInfo; - return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); - } + if (ancestorInvocation != null && ancestorInvocation.SpanStart == memberAccess.SpanStart) + { + var typeInfo = semanticModel.GetTypeInfo(ancestorInvocation); + if (typeInfo.Type != null && + typeInfo.Type.Kind == SymbolKind.DynamicType) + { + return true; } } - if (node.Kind() == SyntaxKind.IdentifierName && - semanticModel.GetAliasInfo((IdentifierNameSyntax)node, cancellationToken) != null) - { - return false; - } + return false; + } + + /* + * Name Reduction, to implicitly mean "this", is possible only after the initialization of all member variables but + * since the check for initialization of all member variable is a lot of work for this simplification we don't simplify + * even if all the member variables are initialized + */ + private static bool AccessMethodWithDynamicArgumentInsideStructConstructor(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) + { + var constructor = memberAccess.Ancestors().OfType().SingleOrDefault(); - // an alias can only replace a type or namespace - if (symbol == null || - (symbol.Kind != SymbolKind.Namespace && symbol.Kind != SymbolKind.NamedType)) + if (constructor == null || constructor.Parent.Kind() != SyntaxKind.StructDeclaration) { return false; } - var preferAliasToQualifiedName = true; - if (node is QualifiedNameSyntax qualifiedName) + return semanticModel.GetSymbolInfo(memberAccess.Name).CandidateReason == CandidateReason.LateBound; + } + + // Note: The caller needs to verify that replacement doesn't change semantics of the original expression. + private static bool TrySimplifyMemberAccessOrQualifiedName( + ExpressionSyntax left, + ExpressionSyntax right, + SemanticModel semanticModel, + out ExpressionSyntax replacementNode, + out TextSpan issueSpan) + { + replacementNode = null; + issueSpan = default; + + if (left != null && right != null) { - if (!qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation)) + var leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left); + if (leftSymbol != null && (leftSymbol.Kind == SymbolKind.NamedType)) { - var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; - if (type != null) + var rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right); + if (rightSymbol != null && (rightSymbol.IsStatic || rightSymbol.Kind == SymbolKind.NamedType)) { - var keywordKind = GetPredefinedKeywordKind(type.SpecialType); - if (keywordKind != SyntaxKind.None) + // Static member access or nested type member access. + var containingType = rightSymbol.ContainingType; + + var enclosingSymbol = semanticModel.GetEnclosingSymbol(left.SpanStart); + var enclosingTypeParametersInsideOut = new List(); + + while (enclosingSymbol != null) { - preferAliasToQualifiedName = false; + if (enclosingSymbol is IMethodSymbol methodSymbol) + { + if (methodSymbol.TypeArguments.Length != 0) + { + enclosingTypeParametersInsideOut.AddRange(methodSymbol.TypeArguments); + } + } + + if (enclosingSymbol is INamedTypeSymbol namedTypeSymbol) + { + if (namedTypeSymbol.TypeArguments.Length != 0) + { + enclosingTypeParametersInsideOut.AddRange(namedTypeSymbol.TypeArguments); + } + } + + enclosingSymbol = enclosingSymbol.ContainingSymbol; } - } - } - } - if (node is AliasQualifiedNameSyntax aliasQualifiedNameSyntax) - { - if (!aliasQualifiedNameSyntax.Name.HasAnnotation(Simplifier.SpecialTypeAnnotation)) - { - var type = semanticModel.GetTypeInfo(node, cancellationToken).Type; - if (type != null) - { - var keywordKind = GetPredefinedKeywordKind(type.SpecialType); - if (keywordKind != SyntaxKind.None) + if (containingType != null && !containingType.Equals(leftSymbol)) { - preferAliasToQualifiedName = false; + if (leftSymbol is INamedTypeSymbol namedType && + containingType.TypeArguments.Length != 0) + { + return false; + } + + // We have a static member access or a nested type member access using a more derived type. + // Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type. + replacementNode = containingType.GenerateTypeSyntax() + .WithLeadingTrivia(left.GetLeadingTrivia()) + .WithTrailingTrivia(left.GetTrailingTrivia()); + issueSpan = left.Span; + return true; } } } } - aliasReplacement = GetAliasForSymbol((INamespaceOrTypeSymbol)symbol, node.GetFirstToken(), semanticModel, cancellationToken); - if (aliasReplacement != null && preferAliasToQualifiedName) - { - return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol); - } - return false; } - private static bool HasUsingAliasDirective(SyntaxNode syntax) + private static bool IsThisOrTypeOrNamespace(MemberAccessExpressionSyntax memberAccess, SemanticModel semanticModel) { - SyntaxList usings; - SyntaxList members; - if (syntax.IsKind(SyntaxKind.NamespaceDeclaration, out NamespaceDeclarationSyntax namespaceDeclaration)) - { - usings = namespaceDeclaration.Usings; - members = namespaceDeclaration.Members; - } - else if (syntax.IsKind(SyntaxKind.CompilationUnit, out CompilationUnitSyntax compilationUnit)) - { - usings = compilationUnit.Usings; - members = compilationUnit.Members; - } - else + if (memberAccess.Expression.Kind() == SyntaxKind.ThisExpression) { - return false; + var previousToken = memberAccess.Expression.GetFirstToken().GetPreviousToken(); + + var symbol = semanticModel.GetSymbolInfo(memberAccess.Name).Symbol; + + if (previousToken.Kind() == SyntaxKind.OpenParenToken && + previousToken.Parent.IsKind(SyntaxKind.ParenthesizedExpression) && + !previousToken.Parent.IsParentKind(SyntaxKind.ParenthesizedExpression) && + ((ParenthesizedExpressionSyntax)previousToken.Parent).Expression.Kind() == SyntaxKind.SimpleMemberAccessExpression && + symbol != null && symbol.Kind == SymbolKind.Method) + { + return false; + } + + return true; } - foreach (var usingDirective in usings) + var expressionInfo = semanticModel.GetSymbolInfo(memberAccess.Expression); + if (SimplificationHelpers.IsValidSymbolInfo(expressionInfo.Symbol)) { - if (usingDirective.Alias != null) + if (expressionInfo.Symbol is INamespaceOrTypeSymbol) { return true; } - } - foreach (var member in members) - { - if (HasUsingAliasDirective(member)) + if (expressionInfo.Symbol.IsThisParameter()) { return true; } @@ -493,42 +536,52 @@ private static bool HasUsingAliasDirective(SyntaxNode syntax) return false; } - // We must verify that the alias actually binds back to the thing it's aliasing. - // It's possible there's another symbol with the same name as the alias that binds - // first - private static bool ValidateAliasForTarget(IAliasSymbol aliasReplacement, SemanticModel semanticModel, ExpressionSyntax node, ISymbol symbol) + private static bool ParserWouldTreatExpressionAsCast(ExpressionSyntax reducedNode, MemberAccessExpressionSyntax originalNode) { - var aliasName = aliasReplacement.Name; - - // If we're the argument of a nameof(X.Y) call, then we can't simplify to an - // alias unless the alias has the same name as us (i.e. 'Y'). - if (node.IsNameOfArgumentExpression()) + SyntaxNode parent = originalNode; + while (parent != null) { - var nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent); - if (!nameofValueOpt.HasValue) + if (parent.IsParentKind(SyntaxKind.SimpleMemberAccessExpression)) { - return false; + parent = parent.Parent; + continue; } - if (nameofValueOpt.Value is string existingVal && - existingVal != aliasName) + if (!parent.IsParentKind(SyntaxKind.ParenthesizedExpression)) { return false; } + + break; } - var boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name: aliasName); + var newExpression = parent.ReplaceNode(originalNode, reducedNode); - if (boundSymbols.Length == 1) + // detect cast ambiguities according to C# spec #7.7.6 + if (IsNameOrMemberAccessButNoExpression(newExpression)) { - if (boundSymbols[0] is IAliasSymbol boundAlias && aliasReplacement.Target.Equals(symbol)) - { - return true; - } + var nextToken = parent.Parent.GetLastToken().GetNextToken(); + + return nextToken.Kind() == SyntaxKind.OpenParenToken || + nextToken.Kind() == SyntaxKind.TildeToken || + nextToken.Kind() == SyntaxKind.ExclamationToken || + (SyntaxFacts.IsKeywordKind(nextToken.Kind()) && !(nextToken.Kind() == SyntaxKind.AsKeyword || nextToken.Kind() == SyntaxKind.IsKeyword)); } return false; } + private static bool IsNameOrMemberAccessButNoExpression(SyntaxNode node) + { + if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + { + var memberAccess = (MemberAccessExpressionSyntax)node; + + return memberAccess.Expression.IsKind(SyntaxKind.IdentifierName) || + IsNameOrMemberAccessButNoExpression(memberAccess.Expression); + } + + return node.IsKind(SyntaxKind.IdentifierName); + } } } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs similarity index 72% rename from src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs rename to src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs index be321b1161025..8fc7033fc6db0 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSyntaxSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs @@ -1,27 +1,36 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Text; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Utilities; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { - internal class NameSyntaxSimplifier : AbstractCSharpSimplifier + internal class NameSimplifier : AbstractCSharpSimplifier { - public static readonly NameSyntaxSimplifier Instance = new NameSyntaxSimplifier(); + public static readonly NameSimplifier Instance = new NameSimplifier(); - private NameSyntaxSimplifier() + private NameSimplifier() { } - - private static bool TryReduceName( + public override bool TrySimplify( NameSyntax name, SemanticModel semanticModel, + OptionSet optionSet, out TypeSyntax replacementNode, out TextSpan issueSpan, - OptionSet optionSet, CancellationToken cancellationToken) { replacementNode = null; @@ -72,8 +81,8 @@ private static bool TryReduceName( { var genericName = (GenericNameSyntax)name; replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier) - .WithLeadingTrivia(genericName.GetLeadingTrivia()) - .WithTrailingTrivia(genericName.GetTrailingTrivia()); + .WithLeadingTrivia(genericName.GetLeadingTrivia()) + .WithTrailingTrivia(genericName.GetTrailingTrivia()); issueSpan = genericName.TypeArgumentList.Span; return CanReplaceWithReducedName( @@ -95,7 +104,7 @@ private static bool TryReduceName( issueSpan = name.Span; - return name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel); + return CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel); } else { @@ -166,7 +175,7 @@ private static bool TryReduceName( } // first check if this would be a valid reduction - if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel)) + if (CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel)) { // in case this alias name ends with "Attribute", we're going to see if we can also // remove that suffix. @@ -285,14 +294,14 @@ private static bool TryReduceName( } replacementNode = SyntaxFactory.NullableType(oldType) - .WithLeadingTrivia(name.GetLeadingTrivia()) - .WithTrailingTrivia(name.GetTrailingTrivia()); + .WithLeadingTrivia(name.GetLeadingTrivia()) + .WithTrailingTrivia(name.GetTrailingTrivia()); issueSpan = name.Span; // we need to simplify the whole qualified name at once, because replacing the identifier on the left in // System.Nullable alone would be illegal. // If this fails we want to continue to try at least to remove the System if possible. - if (name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel)) + if (CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel)) { return true; } @@ -305,9 +314,9 @@ private static bool TryReduceName( { case SyntaxKind.AliasQualifiedName: var simpleName = ((AliasQualifiedNameSyntax)name).Name - .WithLeadingTrivia(name.GetLeadingTrivia()); + .WithLeadingTrivia(name.GetLeadingTrivia()); - simpleName = simpleName.ReplaceToken(simpleName.Identifier, + simpleName = simpleName.ReplaceToken(simpleName.Identifier, ((AliasQualifiedNameSyntax)name).Name.Identifier.CopyAnnotationsTo( simpleName.Identifier.WithLeadingTrivia( ((AliasQualifiedNameSyntax)name).Alias.Identifier.LeadingTrivia))); @@ -319,7 +328,7 @@ private static bool TryReduceName( break; case SyntaxKind.QualifiedName: - replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); + replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); issueSpan = ((QualifiedNameSyntax)name).Left.Span; break; @@ -452,21 +461,16 @@ private static bool CanReplaceWithPredefinedTypeKeywordInContext( issueSpan = name.Span; // we want to show the whole name expression as unnecessary - var canReduce = name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel); + var canReduce = CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel); if (canReduce) { - replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName)); + replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName)); } return canReduce; } - private static TypeSyntax CreatePredefinedTypeSyntax(ExpressionSyntax expression, SyntaxKind keywordKind) - { - return SyntaxFactory.PredefinedType(SyntaxFactory.Token(expression.GetLeadingTrivia(), keywordKind, expression.GetTrailingTrivia())); - } - private static bool TryReduceAttributeSuffix( NameSyntax name, SyntaxToken identifierToken, @@ -507,7 +511,7 @@ private static bool TryReduceAttributeSuffix( identifierToken.TrailingTrivia)); replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken) - .WithLeadingTrivia(name.GetLeadingTrivia()); + .WithLeadingTrivia(name.GetLeadingTrivia()); issueSpan = new TextSpan(identifierToken.Span.End - 9, 9); return true; @@ -551,126 +555,109 @@ private static bool IsPartOfNamespaceDeclarationName(SyntaxNode node) return false; } - private static bool TrySimplify( - ExpressionSyntax expression, - SemanticModel semanticModel, - out ExpressionSyntax replacementNode, - out TextSpan issueSpan) + public static bool CanReplaceWithReducedNameInContext( + NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel) { - replacementNode = null; - issueSpan = default; + // Check for certain things that would prevent us from reducing this name in this context. + // For example, you can simplify "using a = System.Int32" to "using a = int" as it's simply + // not allowed in the C# grammar. + + if (IsNonNameSyntaxInUsingDirective(name, reducedName) || + WillConflictWithExistingLocal(name, reducedName, semanticModel) || + IsAmbiguousCast(name, reducedName) || + IsNullableTypeInPointerExpression(reducedName) || + IsNotNullableReplaceable(name, reducedName) || + IsNonReducableQualifiedNameInUsingDirective(semanticModel, name, reducedName)) + { + return false; + } + + return true; + } - switch (expression.Kind()) + private static bool ContainsOpenName(NameSyntax name) + { + if (name is QualifiedNameSyntax qualifiedName) { - case SyntaxKind.SimpleMemberAccessExpression: - { - var memberAccess = (MemberAccessExpressionSyntax)expression; - if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) - { - return false; - } + return ContainsOpenName(qualifiedName.Left) || ContainsOpenName(qualifiedName.Right); + } + else if (name is GenericNameSyntax genericName) + { + return genericName.IsUnboundGenericName; + } + else + { + return false; + } + } - if (TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, out var newLeft, out issueSpan)) - { - // replacement node might not be in it's simplest form, so add simplify annotation to it. - replacementNode = memberAccess.Update(newLeft, memberAccess.OperatorToken, memberAccess.Name) - .WithAdditionalAnnotations(Simplifier.Annotation); + private static bool CanReplaceWithReducedName(NameSyntax name, TypeSyntax reducedName, SemanticModel semanticModel, CancellationToken cancellationToken) + { + var speculationAnalyzer = new SpeculationAnalyzer(name, reducedName, semanticModel, cancellationToken); + if (speculationAnalyzer.ReplacementChangesSemantics()) + { + return false; + } - // Ensure that replacement doesn't change semantics. - return !ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel); - } + return NameSimplifier.CanReplaceWithReducedNameInContext(name, reducedName, semanticModel); + } - return false; - } + private static bool IsNotNullableReplaceable(NameSyntax name, TypeSyntax reducedName) + { + var isNotNullableReplaceable = false; + var isLeftSideOfDot = name.IsLeftSideOfDot(); + var isRightSideOfDot = name.IsRightSideOfDot(); - case SyntaxKind.QualifiedName: - { - var qualifiedName = (QualifiedNameSyntax)expression; - if (TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, out var newLeft, out issueSpan)) - { - // replacement node might not be in it's simplest form, so add simplify annotation to it. - replacementNode = qualifiedName.Update((NameSyntax)newLeft, qualifiedName.DotToken, qualifiedName.Right) - .WithAdditionalAnnotations(Simplifier.Annotation); + if (reducedName.Kind() == SyntaxKind.NullableType) + { + if (((NullableTypeSyntax)reducedName).ElementType.Kind() == SyntaxKind.OmittedTypeArgument) + { + isNotNullableReplaceable = true; + } + else + { + isNotNullableReplaceable = name.IsLeftSideOfDot() || name.IsRightSideOfDot(); + } + } - // Ensure that replacement doesn't change semantics. - return !ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel); - } + return isNotNullableReplaceable; + } - return false; - } + private static bool IsNullableTypeInPointerExpression(ExpressionSyntax simplifiedNode) + { + // Note: nullable type syntax is not allowed in pointer type syntax + if (simplifiedNode.Kind() == SyntaxKind.NullableType && + simplifiedNode.DescendantNodes().Any(n => n is PointerTypeSyntax)) + { + return true; } return false; } - private static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) + private static bool IsNonNameSyntaxInUsingDirective(ExpressionSyntax expression, ExpressionSyntax simplifiedNode) { - var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); - return speculationAnalyzer.ReplacementChangesSemantics(); + return + expression.IsParentKind(SyntaxKind.UsingDirective) && + !(simplifiedNode is NameSyntax); } - // Note: The caller needs to verify that replacement doesn't change semantics of the original expression. - private static bool TrySimplifyMemberAccessOrQualifiedName( - ExpressionSyntax left, - ExpressionSyntax right, - SemanticModel semanticModel, - out ExpressionSyntax replacementNode, - out TextSpan issueSpan) + private static bool IsAmbiguousCast(ExpressionSyntax expression, ExpressionSyntax simplifiedNode) { - replacementNode = null; - issueSpan = default; - - if (left != null && right != null) + // Can't simplify a type name in a cast expression if it would then cause the cast to be + // parsed differently. For example: (Goo::Bar)+1 is a cast. But if that simplifies to + // (Bar)+1 then that's an arithmetic expression. + if (expression.IsParentKind(SyntaxKind.CastExpression)) { - var leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left); - if (leftSymbol != null && (leftSymbol.Kind == SymbolKind.NamedType)) + var castExpression = (CastExpressionSyntax)expression.Parent; + if (castExpression.Type == expression) { - var rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right); - if (rightSymbol != null && (rightSymbol.IsStatic || rightSymbol.Kind == SymbolKind.NamedType)) - { - // Static member access or nested type member access. - var containingType = rightSymbol.ContainingType; - - var enclosingSymbol = semanticModel.GetEnclosingSymbol(left.SpanStart); - var enclosingTypeParametersInsideOut = new List(); - - while (enclosingSymbol != null) - { - if (enclosingSymbol is IMethodSymbol methodSymbol) - { - if (methodSymbol.TypeArguments.Length != 0) - { - enclosingTypeParametersInsideOut.AddRange(methodSymbol.TypeArguments); - } - } - - if (enclosingSymbol is INamedTypeSymbol namedTypeSymbol) - { - if (namedTypeSymbol.TypeArguments.Length != 0) - { - enclosingTypeParametersInsideOut.AddRange(namedTypeSymbol.TypeArguments); - } - } - - enclosingSymbol = enclosingSymbol.ContainingSymbol; - } - - if (containingType != null && !containingType.Equals(leftSymbol)) - { - if (leftSymbol is INamedTypeSymbol namedType && - containingType.TypeArguments.Length != 0) - { - return false; - } + var newCastExpression = castExpression.ReplaceNode(castExpression.Type, simplifiedNode); + var reparsedCastExpression = SyntaxFactory.ParseExpression(newCastExpression.ToString()); - // We have a static member access or a nested type member access using a more derived type. - // Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type. - replacementNode = containingType.GenerateTypeSyntax() - .WithLeadingTrivia(left.GetLeadingTrivia()) - .WithTrailingTrivia(left.GetTrailingTrivia()); - issueSpan = left.Span; - return true; - } + if (!reparsedCastExpression.IsKind(SyntaxKind.CastExpression)) + { + return true; } } } @@ -678,55 +665,91 @@ private static bool TrySimplifyMemberAccessOrQualifiedName( return false; } - private static bool CanReplaceWithReducedName( - MemberAccessExpressionSyntax memberAccess, - ExpressionSyntax reducedName, - SemanticModel semanticModel, - ISymbol symbol, - CancellationToken cancellationToken) + private static SyntaxNode FindImmediatelyEnclosingLocalVariableDeclarationSpace(SyntaxNode syntax) { - if (!IsThisOrTypeOrNamespace(memberAccess, semanticModel)) + for (var declSpace = syntax; declSpace != null; declSpace = declSpace.Parent) { - return false; + switch (declSpace.Kind()) + { + // These are declaration-space-defining syntaxes, by the spec: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.IndexerDeclaration: + case SyntaxKind.OperatorDeclaration: + case SyntaxKind.ConstructorDeclaration: + case SyntaxKind.Block: + case SyntaxKind.ParenthesizedLambdaExpression: + case SyntaxKind.SimpleLambdaExpression: + case SyntaxKind.AnonymousMethodExpression: + case SyntaxKind.SwitchStatement: + case SyntaxKind.ForEachKeyword: + case SyntaxKind.ForStatement: + case SyntaxKind.UsingStatement: + + // SPEC VIOLATION: We also want to stop walking out if, say, we are in a field + // initializer. Technically according to the wording of the spec it should be + // legal to use a simple name inconsistently inside a field initializer because + // it does not define a local variable declaration space. In practice of course + // we want to check for that. (As the native compiler does as well.) + + case SyntaxKind.FieldDeclaration: + return declSpace; + } } - var speculationAnalyzer = new SpeculationAnalyzer(memberAccess, reducedName, semanticModel, cancellationToken); - if (!speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() || - speculationAnalyzer.ReplacementChangesSemantics()) - { - return false; - } + return null; + } - if (WillConflictWithExistingLocal(memberAccess, reducedName, semanticModel)) - { - return false; - } + private static bool IsNonReducableQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name, TypeSyntax reducedName) + { + // Whereas most of the time we do not want to reduce namespace names, We will + // make an exception for namespaces with the global:: alias. + return IsQualifiedNameInUsingDirective(model, name) && + !IsGlobalAliasQualifiedName(name); + } - if (IsMemberAccessADynamicInvocation(memberAccess, semanticModel)) + private static bool IsQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name) + { + while (name.IsLeftSideOfQualifiedName()) { - return false; + name = (NameSyntax)name.Parent; } - if (AccessMethodWithDynamicArgumentInsideStructConstructor(memberAccess, semanticModel)) + if (name.IsParentKind(SyntaxKind.UsingDirective) && + ((UsingDirectiveSyntax)name.Parent).Alias == null) { - return false; + // We're a qualified name in a using. We don't want to reduce this name as people like + // fully qualified names in usings so they can properly tell what the name is resolving + // to. + // However, if this name is actually referencing the special Script class, then we do + // want to allow that to be reduced. + + return !IsInScriptClass(model, name); } - if (memberAccess.Expression.Kind() == SyntaxKind.BaseExpression) + return false; + } + + private static bool IsGlobalAliasQualifiedName(NameSyntax name) + { + // Checks whether the `global::` alias is applied to the name + return name is AliasQualifiedNameSyntax aliasName && + aliasName.Alias.Identifier.IsKind(SyntaxKind.GlobalKeyword); + } + + private static bool IsInScriptClass(SemanticModel model, NameSyntax name) + { + var symbol = model.GetSymbolInfo(name).Symbol as INamedTypeSymbol; + while (symbol != null) { - var enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken); - if (enclosingNamedType != null && - !enclosingNamedType.IsSealed && - symbol != null && - symbol.IsOverridable()) + if (symbol.IsScriptClass) { - return false; + return true; } - } - var invalidTransformation1 = ParserWouldTreatExpressionAsCast(reducedName, memberAccess); + symbol = symbol.ContainingType; + } - return !invalidTransformation1; + return false; } } } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs index 7b8ce0dea8b75..777ec6dd0cb5d 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/QualifiedCrefSimplifier.cs @@ -46,7 +46,7 @@ public override bool TrySimplify( // 1. Check for Predefined Types if (symbol is INamedTypeSymbol namedSymbol) { - var keywordKind = ExpressionSyntaxExtensions.GetPredefinedKeywordKind(namedSymbol.SpecialType); + var keywordKind = GetPredefinedKeywordKind(namedSymbol.SpecialType); if (keywordKind != SyntaxKind.None) { From 89ee178721092e4470db0521e7d58df8169a26ad Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 13:49:35 -0800 Subject: [PATCH 05/14] Cleanup --- ...rpPreferFrameworkTypeDiagnosticAnalyzer.cs | 2 +- ...SharpSimplifyThisOrMeDiagnosticAnalyzer.cs | 3 +- .../Extensions/ExpressionSyntaxExtensions.cs | 6 +++ .../MemberAccessExpressionSyntaxExtensions.cs | 36 +++++++++++++++ .../Simplifiers/AbstractCSharpSimplifier.cs | 32 ++----------- .../Simplifiers/ExpressionSimplifier.cs | 34 +++----------- .../Simplifiers/NameSimplifier.cs | 46 ++++--------------- 7 files changed, 66 insertions(+), 93 deletions(-) create mode 100644 src/Workspaces/CSharp/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.cs diff --git a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpPreferFrameworkTypeDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpPreferFrameworkTypeDiagnosticAnalyzer.cs index 85bcb9bd4041f..8d74fdeb7c034 100644 --- a/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpPreferFrameworkTypeDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/Diagnostics/Analyzers/CSharpPreferFrameworkTypeDiagnosticAnalyzer.cs @@ -22,7 +22,7 @@ protected override bool IsPredefinedTypeReplaceableWithFrameworkType(PredefinedT => node.Keyword.Kind() != SyntaxKind.VoidKeyword; protected override bool IsInMemberAccessOrCrefReferenceContext(ExpressionSyntax node) - => node.IsInMemberAccessContext() || node.InsideCrefReference(); + => node.IsDirectChildOfMemberAccessExpression() || node.InsideCrefReference(); protected override string GetLanguageName() => LanguageNames.CSharp; diff --git a/src/Features/CSharp/Portable/SimplifyThisOrMe/CSharpSimplifyThisOrMeDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/SimplifyThisOrMe/CSharpSimplifyThisOrMeDiagnosticAnalyzer.cs index 190b899b7f9d8..57ee937aed6ce 100644 --- a/src/Features/CSharp/Portable/SimplifyThisOrMe/CSharpSimplifyThisOrMeDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/SimplifyThisOrMe/CSharpSimplifyThisOrMeDiagnosticAnalyzer.cs @@ -3,6 +3,7 @@ using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServices; @@ -35,7 +36,7 @@ protected override bool CanSimplifyTypeNameExpression( SemanticModel model, MemberAccessExpressionSyntax node, OptionSet optionSet, out TextSpan issueSpan, CancellationToken cancellationToken) { - return node.TryReduceOrSimplifyExplicitName(model, out _, out issueSpan, optionSet, cancellationToken); + return ExpressionSimplifier.Instance.TrySimplify(node, model, optionSet, out _, out issueSpan, cancellationToken); } } } diff --git a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs index cb3d6d1f59797..71609c4cf85eb 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/ExpressionSyntaxExtensions.cs @@ -985,5 +985,11 @@ private static StatementSyntax ConvertToStatement(ExpressionSyntax expression, S .WithSemicolonToken(semicolonToken); } } + + public static bool IsDirectChildOfMemberAccessExpression(this ExpressionSyntax expression) => + expression?.Parent is MemberAccessExpressionSyntax; + + public static bool InsideCrefReference(this ExpressionSyntax expression) + => expression.FirstAncestorOrSelf() != null; } } diff --git a/src/Workspaces/CSharp/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.cs new file mode 100644 index 0000000000000..191b542d3afe2 --- /dev/null +++ b/src/Workspaces/CSharp/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.Extensions +{ + internal static class MemberAccessExpressionSyntaxExtensions + { + public static SimpleNameSyntax GetNameWithTriviaMoved(this MemberAccessExpressionSyntax memberAccess) + => memberAccess.Name + .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) + .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); + + private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(MemberAccessExpressionSyntax memberAccess) + { + // We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. + // However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering + // aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines + // etc. around the user's original code. + return SyntaxFactory.TriviaList(WithoutElasticTrivia( + memberAccess.GetLeadingTrivia() + .AddRange(memberAccess.Expression.GetTrailingTrivia()) + .AddRange(memberAccess.OperatorToken.LeadingTrivia) + .AddRange(memberAccess.OperatorToken.TrailingTrivia) + .AddRange(memberAccess.Name.GetLeadingTrivia()))); + } + + private static IEnumerable WithoutElasticTrivia(IEnumerable list) + { + return list.Where(t => !t.IsElastic()); + } + } +} diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs index 0067f281628d0..26ff1784346b0 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs @@ -32,7 +32,7 @@ public abstract bool TrySimplify( /// /// The of this type. /// The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type. - public static SyntaxKind GetPredefinedKeywordKind(SpecialType specialType) + protected static SyntaxKind GetPredefinedKeywordKind(SpecialType specialType) => specialType switch { SpecialType.System_Boolean => SyntaxKind.BoolKeyword, @@ -54,18 +54,6 @@ public static SyntaxKind GetPredefinedKeywordKind(SpecialType specialType) _ => SyntaxKind.None, }; - protected static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) - { - var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); - return speculationAnalyzer.ReplacementChangesSemantics(); - } - - protected static bool InsideCrefReference(ExpressionSyntax expression) - { - var crefAttribute = expression.FirstAncestorOrSelf(); - return crefAttribute != null; - } - [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Most trees do not have using alias directives, so avoid the expensive " + nameof(CSharpExtensions.GetSymbolInfo) + " call for this case.")] @@ -192,7 +180,7 @@ protected static bool TryReplaceExpressionWithAlias( return false; } - protected static bool IsAliasReplaceableExpression(ExpressionSyntax expression) + private static bool IsAliasReplaceableExpression(ExpressionSyntax expression) { var current = expression; while (current.IsKind(SyntaxKind.SimpleMemberAccessExpression, out MemberAccessExpressionSyntax currentMember)) @@ -207,7 +195,7 @@ protected static bool IsAliasReplaceableExpression(ExpressionSyntax expression) SyntaxKind.QualifiedName); } - protected static bool HasUsingAliasDirective(SyntaxNode syntax) + private static bool HasUsingAliasDirective(SyntaxNode syntax) { SyntaxList usings; SyntaxList members; @@ -414,26 +402,15 @@ protected static bool InsideNameOfExpression(ExpressionSyntax expression, Semant return nameOfInvocationExpr != null; } - protected static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel) - { - return !IsInMemberAccessContext(name) && - !InsideCrefReference(name) && - !InsideNameOfExpression(name, semanticModel) && - SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language); - } - protected static bool PreferPredefinedTypeKeywordInMemberAccess(ExpressionSyntax expression, OptionSet optionSet, SemanticModel semanticModel) { if (!SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, semanticModel.Language)) return false; - return (IsInMemberAccessContext(expression) || InsideCrefReference(expression)) && + return (expression.IsDirectChildOfMemberAccessExpression() || expression.InsideCrefReference()) && !InsideNameOfExpression(expression, semanticModel); } - public static bool IsInMemberAccessContext(ExpressionSyntax expression) => - expression?.Parent is MemberAccessExpressionSyntax; - protected static bool WillConflictWithExistingLocal( ExpressionSyntax expression, ExpressionSyntax simplifiedNode, SemanticModel semanticModel) { @@ -446,6 +423,5 @@ protected static bool WillConflictWithExistingLocal( return false; } - } } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs index b1d834240e4e1..b4f46b24bea7b 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs @@ -18,8 +18,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { - using static SyntaxFactory; - internal class ExpressionSimplifier : AbstractCSharpSimplifier { public static readonly ExpressionSimplifier Instance = new ExpressionSimplifier(); @@ -191,18 +189,13 @@ private static bool TryReduceMemberAccessExpression( } } - replacementNode = GetNameWithTriviaMoved(memberAccess); + replacementNode = memberAccess.GetNameWithTriviaMoved(); issueSpan = memberAccess.Expression.Span; return CanReplaceWithReducedName( memberAccess, replacementNode, semanticModel, symbol, cancellationToken); } - public static SimpleNameSyntax GetNameWithTriviaMoved(MemberAccessExpressionSyntax memberAccess) - => memberAccess.Name - .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) - .WithTrailingTrivia(memberAccess.GetTrailingTrivia()); - private static void GetReplacementCandidates( SemanticModel semanticModel, MemberAccessExpressionSyntax memberAccess, @@ -268,25 +261,6 @@ public int GetHashCode(ISymbol obj) } } - private static SyntaxTriviaList GetLeadingTriviaForSimplifiedMemberAccess(MemberAccessExpressionSyntax memberAccess) - { - // We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. - // However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering - // aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander and use that as a cue to introduce unnecessary blank lines - // etc. around the user's original code. - return TriviaList(WithoutElasticTrivia( - memberAccess.GetLeadingTrivia() - .AddRange(memberAccess.Expression.GetTrailingTrivia()) - .AddRange(memberAccess.OperatorToken.LeadingTrivia) - .AddRange(memberAccess.OperatorToken.TrailingTrivia) - .AddRange(memberAccess.Name.GetLeadingTrivia()))); - } - - private static IEnumerable WithoutElasticTrivia(IEnumerable list) - { - return list.Where(t => !t.IsElastic()); - } - private static bool TrySimplify( ExpressionSyntax expression, SemanticModel semanticModel, @@ -583,5 +557,11 @@ private static bool IsNameOrMemberAccessButNoExpression(SyntaxNode node) return node.IsKind(SyntaxKind.IdentifierName); } + + protected static bool ReplacementChangesSemantics(ExpressionSyntax originalExpression, ExpressionSyntax replacedExpression, SemanticModel semanticModel) + { + var speculationAnalyzer = new SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None); + return speculationAnalyzer.ReplacementChangesSemantics(); + } } } diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs index 8fc7033fc6db0..3d5123d3521ab 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs @@ -364,7 +364,7 @@ private static bool TryReduceCrefColorColor( NameSyntax name, TypeSyntax replacement, SemanticModel semanticModel, CancellationToken cancellationToken) { - if (!InsideCrefReference(name)) + if (!name.InsideCrefReference()) return false; if (name.Parent is QualifiedCrefSyntax qualifiedCrefParent && qualifiedCrefParent.Container == name) @@ -411,7 +411,7 @@ private static bool CanSimplifyNullable(INamedTypeSymbol type, NameSyntax name, return false; } - if (!InsideCrefReference(name)) + if (!name.InsideCrefReference()) { // Nullable can always be simplified to T? outside crefs. return true; @@ -665,40 +665,6 @@ private static bool IsAmbiguousCast(ExpressionSyntax expression, ExpressionSynta return false; } - private static SyntaxNode FindImmediatelyEnclosingLocalVariableDeclarationSpace(SyntaxNode syntax) - { - for (var declSpace = syntax; declSpace != null; declSpace = declSpace.Parent) - { - switch (declSpace.Kind()) - { - // These are declaration-space-defining syntaxes, by the spec: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.IndexerDeclaration: - case SyntaxKind.OperatorDeclaration: - case SyntaxKind.ConstructorDeclaration: - case SyntaxKind.Block: - case SyntaxKind.ParenthesizedLambdaExpression: - case SyntaxKind.SimpleLambdaExpression: - case SyntaxKind.AnonymousMethodExpression: - case SyntaxKind.SwitchStatement: - case SyntaxKind.ForEachKeyword: - case SyntaxKind.ForStatement: - case SyntaxKind.UsingStatement: - - // SPEC VIOLATION: We also want to stop walking out if, say, we are in a field - // initializer. Technically according to the wording of the spec it should be - // legal to use a simple name inconsistently inside a field initializer because - // it does not define a local variable declaration space. In practice of course - // we want to check for that. (As the native compiler does as well.) - - case SyntaxKind.FieldDeclaration: - return declSpace; - } - } - - return null; - } - private static bool IsNonReducableQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name, TypeSyntax reducedName) { // Whereas most of the time we do not want to reduce namespace names, We will @@ -751,5 +717,13 @@ private static bool IsInScriptClass(SemanticModel model, NameSyntax name) return false; } + + private static bool PreferPredefinedTypeKeywordInDeclarations(NameSyntax name, OptionSet optionSet, SemanticModel semanticModel) + { + return !name.IsDirectChildOfMemberAccessExpression() && + !name.InsideCrefReference() && + !InsideNameOfExpression(name, semanticModel) && + SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, semanticModel.Language); + } } } From d3fcd21347c027b899194532cb7399ddf8849c88 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 13:53:59 -0800 Subject: [PATCH 06/14] Comment --- .../Simplification/Simplifiers/AbstractCSharpSimplifier.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs index 26ff1784346b0..fe86ced49b32b 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs @@ -15,6 +15,9 @@ namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { + /// + /// Contains helpers used by several simplifier subclasses. + /// internal abstract class AbstractCSharpSimplifier where TSyntax : SyntaxNode where TSimplifiedSyntax : SyntaxNode From 80c5b072e085e48320c3ab850dd31577028733bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 14:10:31 -0800 Subject: [PATCH 07/14] Break out MemberAccessExpression extnesions --- .../Simplifiers/AbstractCSharpSimplifier.cs | 10 ++---- .../Simplifiers/AbstractSimplifier.cs | 21 ++++++++++++ .../Extensions/ExpressionSyntaxExtensions.vb | 33 ------------------- .../MemberAccessExpressionSyntaxExtensions.vb | 32 ++++++++++++++++++ .../AbstractVisualBasicSimplifier.vb | 10 ++++++ .../Simplifiers/ExpressionSimplifier.vb | 21 ++++++++++++ 6 files changed, 86 insertions(+), 41 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Simplification/Simplifiers/AbstractSimplifier.cs create mode 100644 src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb create mode 100644 src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs index fe86ced49b32b..9243d4b601f04 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Simplification.Simplifiers; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -19,17 +20,10 @@ namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers /// Contains helpers used by several simplifier subclasses. /// internal abstract class AbstractCSharpSimplifier + : AbstractSimplifier where TSyntax : SyntaxNode where TSimplifiedSyntax : SyntaxNode { - public abstract bool TrySimplify( - TSyntax syntax, - SemanticModel semanticModel, - OptionSet optionSet, - out TSimplifiedSyntax replacementNode, - out TextSpan issueSpan, - CancellationToken cancellationToken); - /// /// Returns the predefined keyword kind for a given . /// diff --git a/src/Workspaces/Core/Portable/Simplification/Simplifiers/AbstractSimplifier.cs b/src/Workspaces/Core/Portable/Simplification/Simplifiers/AbstractSimplifier.cs new file mode 100644 index 0000000000000..37155171e7a7c --- /dev/null +++ b/src/Workspaces/Core/Portable/Simplification/Simplifiers/AbstractSimplifier.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Threading; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Simplification.Simplifiers +{ + internal abstract class AbstractSimplifier + where TSyntax : SyntaxNode + where TSimplifiedSyntax : SyntaxNode + { + public abstract bool TrySimplify( + TSyntax syntax, + SemanticModel semanticModel, + OptionSet optionSet, + out TSimplifiedSyntax replacementNode, + out TextSpan issueSpan, + CancellationToken cancellationToken); + } +} diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb index 99ee8721d44a9..679492af7146c 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb @@ -60,7 +60,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions result) End Function - Public Function IsAliasReplaceableExpression(expression As ExpressionSyntax) As Boolean If expression.Kind = SyntaxKind.IdentifierName OrElse @@ -1061,38 +1060,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return False End Function - - Public Function GetNameWithTriviaMoved(memberAccess As MemberAccessExpressionSyntax, - semanticModel As SemanticModel) As SimpleNameSyntax - Dim replacementNode = memberAccess.Name - replacementNode = DirectCast(replacementNode, SimpleNameSyntax) _ - .WithIdentifier(VisualBasicSimplificationService.TryEscapeIdentifierToken( - memberAccess.Name.Identifier, - semanticModel)) _ - .WithLeadingTrivia(memberAccess.GetLeadingTriviaForSimplifiedMemberAccess()) _ - .WithTrailingTrivia(memberAccess.GetTrailingTrivia()) - - Return replacementNode - End Function - - - Private Function GetLeadingTriviaForSimplifiedMemberAccess(memberAccess As MemberAccessExpressionSyntax) As SyntaxTriviaList - ' We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. - ' However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering - ' aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander And use that as a cue to introduce unnecessary blank lines - ' etc. around the user's original code. - Return memberAccess.GetLeadingTrivia(). - AddRange(memberAccess.Expression.GetTrailingTrivia().WithoutElasticTrivia()). - AddRange(memberAccess.OperatorToken.LeadingTrivia.WithoutElasticTrivia()). - AddRange(memberAccess.OperatorToken.TrailingTrivia.WithoutElasticTrivia()). - AddRange(memberAccess.Name.GetLeadingTrivia().WithoutElasticTrivia()) - End Function - - - Private Function WithoutElasticTrivia(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) - Return list.Where(Function(t) Not t.IsElastic()) - End Function - Public Function InsideCrefReference(expression As ExpressionSyntax) As Boolean Dim crefAttribute = expression.FirstAncestorOrSelf(Of XmlCrefAttributeSyntax)() diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.vb index 112531c056564..b376f1fa5099c 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/MemberAccessExpressionSyntaxExtensions.vb @@ -8,6 +8,7 @@ Imports System.Text Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -123,5 +124,36 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return Nothing End Function + + + Public Function GetNameWithTriviaMoved(memberAccess As MemberAccessExpressionSyntax, + semanticModel As SemanticModel) As SimpleNameSyntax + Dim replacementNode = memberAccess.Name + replacementNode = DirectCast(replacementNode, SimpleNameSyntax) _ + .WithIdentifier(VisualBasicSimplificationService.TryEscapeIdentifierToken( + memberAccess.Name.Identifier, + semanticModel)) _ + .WithLeadingTrivia(GetLeadingTriviaForSimplifiedMemberAccess(memberAccess)) _ + .WithTrailingTrivia(memberAccess.GetTrailingTrivia()) + + Return replacementNode + End Function + + Private Function GetLeadingTriviaForSimplifiedMemberAccess(memberAccess As MemberAccessExpressionSyntax) As SyntaxTriviaList + ' We want to include any user-typed trivia that may be present between the 'Expression', 'OperatorToken' and 'Identifier' of the MemberAccessExpression. + ' However, we don't want to include any elastic trivia that may have been introduced by the expander in these locations. This is to avoid triggering + ' aggressive formatting. Otherwise, formatter will see this elastic trivia added by the expander And use that as a cue to introduce unnecessary blank lines + ' etc. around the user's original code. + Return SyntaxFactory.TriviaList(WithoutElasticTrivia( + memberAccess.GetLeadingTrivia(). + AddRange(memberAccess.Expression.GetTrailingTrivia()). + AddRange(memberAccess.OperatorToken.LeadingTrivia). + AddRange(memberAccess.OperatorToken.TrailingTrivia). + AddRange(memberAccess.Name.GetLeadingTrivia()))) + End Function + + Private Function WithoutElasticTrivia(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia) + Return list.Where(Function(t) Not t.IsElastic()) + End Function End Module End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb new file mode 100644 index 0000000000000..30d21a41c4656 --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb @@ -0,0 +1,10 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis.Simplification.Simplifiers + +Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers + Friend MustInherit Class AbstractVisualBasicSimplifier(Of TSyntax As SyntaxNode, TSimplifiedSyntax As SyntaxNode) + Inherits AbstractSimplifier(Of TSyntax, TSimplifiedSyntax) + + End Class +End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb new file mode 100644 index 0000000000000..ef1bf4240887b --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb @@ -0,0 +1,21 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Threading +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers + Friend Class ExpressionSimplifier + Inherits AbstractVisualBasicSimplifier(Of ExpressionSyntax, ExpressionSyntax) + + Public Overrides Function TrySimplify(syntax As ExpressionSyntax, + semanticModel As SemanticModel, + optionSet As OptionSet, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + cancellationToken As CancellationToken) As Boolean + Throw New NotImplementedException() + End Function + End Class +End Namespace From b830137a9fd760dfc60fc3acb79bf5f496cd7bc7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 14:33:08 -0800 Subject: [PATCH 08/14] Extract VB simplification code --- ...asicSimplifyTypeNamesDiagnosticAnalyzer.vb | 3 +- ...BasicSimplifyThisOrMeDiagnosticAnalyzer.vb | 3 +- .../Extensions/ExpressionSyntaxExtensions.vb | 1067 ----------------- .../Reducers/VisualBasicNameReducer.vb | 9 +- .../VisualBasicVariableDeclaratorReducer.vb | 130 +- .../AbstractVisualBasicSimplifier.vb | 185 +++ .../Simplifiers/ExpressionSimplifier.vb | 328 ++++- .../Simplifiers/NameSimplifier.vb | 464 +++++++ 8 files changed, 1107 insertions(+), 1082 deletions(-) create mode 100644 src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb diff --git a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb index 6c62b52781166..ad18063402746 100644 --- a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicSimplifyTypeNamesDiagnosticAnalyzer.vb @@ -7,6 +7,7 @@ Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.SimplifyTypeNames Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.SimplifyTypeNames @@ -66,7 +67,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.SimplifyTypeNames End If Dim replacementSyntax As ExpressionSyntax = Nothing - If Not expression.TryReduceOrSimplifyExplicitName(model, replacementSyntax, issueSpan, optionSet, cancellationToken) Then + If Not ExpressionSimplifier.Instance.TrySimplify(expression, model, optionSet, replacementSyntax, issueSpan, cancellationToken) Then Return False End If diff --git a/src/Features/VisualBasic/Portable/SimplifyThisOrMe/VisualBasicSimplifyThisOrMeDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/SimplifyThisOrMe/VisualBasicSimplifyThisOrMeDiagnosticAnalyzer.vb index bc8f790b3b721..8bcdd448e6ebe 100644 --- a/src/Features/VisualBasic/Portable/SimplifyThisOrMe/VisualBasicSimplifyThisOrMeDiagnosticAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/SimplifyThisOrMe/VisualBasicSimplifyThisOrMeDiagnosticAnalyzer.vb @@ -7,6 +7,7 @@ Imports Microsoft.CodeAnalysis.LanguageServices Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.SimplifyThisOrMe Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyThisOrMe @@ -36,7 +37,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyThisOrMe cancellationToken As CancellationToken) As Boolean Dim replacementSyntax As ExpressionSyntax = Nothing - Return memberAccess.TryReduceOrSimplifyExplicitName(model, replacementSyntax, issueSpan, optionSet, cancellationToken) + Return ExpressionSimplifier.Instance.TrySimplify(memberAccess, model, optionSet, replacementSyntax, issueSpan, cancellationToken) End Function End Class End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb index 679492af7146c..5c559464b70e9 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb @@ -784,322 +784,17 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return semanticModel.Compilation.CreateArrayTypeSymbol(type, rank) End Function - - Public Function TryReduceVariableDeclaratorWithoutType( - variableDeclarator As VariableDeclaratorSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As SyntaxNode, - ByRef issueSpan As TextSpan, - optionSet As OptionSet, - cancellationToken As CancellationToken - ) As Boolean - - replacementNode = Nothing - issueSpan = Nothing - - ' Failfast Conditions - If variableDeclarator.AsClause Is Nothing OrElse - Not variableDeclarator.Parent.IsKind( - SyntaxKind.LocalDeclarationStatement, - SyntaxKind.UsingStatement, - SyntaxKind.ForStatement, - SyntaxKind.ForEachStatement, - SyntaxKind.FieldDeclaration) Then - Return False - End If - - If variableDeclarator.Names.Count <> 1 Then - Return False - End If - - Dim parent = variableDeclarator.Parent - Dim modifiedIdentifier = variableDeclarator.Names.Single() - - Dim simpleAsClause = TryCast(variableDeclarator.AsClause, SimpleAsClauseSyntax) - If simpleAsClause Is Nothing Then - Return False - End If - - If (parent.IsKind(SyntaxKind.LocalDeclarationStatement, SyntaxKind.UsingStatement, SyntaxKind.FieldDeclaration) AndAlso - variableDeclarator.Initializer IsNot Nothing) Then - - ' Type Check - - Dim declaredSymbolType As ITypeSymbol = Nothing - If Not HasValidDeclaredTypeSymbol(modifiedIdentifier, semanticModel, declaredSymbolType) Then - Return False - End If - - Dim initializerType As ITypeSymbol = Nothing - - If declaredSymbolType.IsArrayType() AndAlso variableDeclarator.Initializer.Value.Kind() = SyntaxKind.CollectionInitializer Then - ' Get type of the array literal in context without the target type - initializerType = semanticModel.GetSpeculativeTypeInfo(variableDeclarator.Initializer.Value.SpanStart, variableDeclarator.Initializer.Value, SpeculativeBindingOption.BindAsExpression).ConvertedType - Else - initializerType = semanticModel.GetTypeInfo(variableDeclarator.Initializer.Value).Type - End If - - If Not declaredSymbolType.Equals(initializerType) Then - Return False - End If - - Dim newModifiedIdentifier = SyntaxFactory.ModifiedIdentifier(modifiedIdentifier.Identifier) ' LeadingTrivia is copied here - replacementNode = SyntaxFactory.VariableDeclarator(SyntaxFactory.SingletonSeparatedList(newModifiedIdentifier.WithTrailingTrivia(variableDeclarator.AsClause.GetTrailingTrivia())), - asClause:=Nothing, - initializer:=variableDeclarator.Initializer) 'TrailingTrivia is copied here - issueSpan = variableDeclarator.Span - Return True - End If - - If (parent.IsKind(SyntaxKind.ForEachStatement, SyntaxKind.ForStatement)) Then - ' Type Check for ForStatement - If parent.IsKind(SyntaxKind.ForStatement) Then - Dim declaredSymbolType As ITypeSymbol = Nothing - If Not HasValidDeclaredTypeSymbol(modifiedIdentifier, semanticModel, declaredSymbolType) Then - Return False - End If - - Dim valueType = semanticModel.GetTypeInfo(DirectCast(parent, ForStatementSyntax).ToValue).Type - - If Not valueType.Equals(declaredSymbolType) Then - Return False - End If - End If - - If parent.IsKind(SyntaxKind.ForEachStatement) Then - Dim forEachStatementInfo = semanticModel.GetForEachStatementInfo(DirectCast(parent, ForEachStatementSyntax)) - If Not forEachStatementInfo.ElementConversion.IsIdentity Then - Return False - End If - End If - - Dim newIdentifierName = SyntaxFactory.IdentifierName(modifiedIdentifier.Identifier) ' Leading Trivia is copied here - replacementNode = newIdentifierName.WithTrailingTrivia(variableDeclarator.AsClause.GetTrailingTrivia()) ' Trailing Trivia is copied here - issueSpan = variableDeclarator.Span - Return True - End If - - Return False - End Function - - Private Function HasValidDeclaredTypeSymbol( - modifiedIdentifier As ModifiedIdentifierSyntax, - semanticModel As SemanticModel, - ByRef typeSymbol As ITypeSymbol) As Boolean - - Dim declaredSymbol = semanticModel.GetDeclaredSymbol(modifiedIdentifier) - If declaredSymbol Is Nothing OrElse - (Not TypeOf declaredSymbol Is ILocalSymbol AndAlso Not TypeOf declaredSymbol Is IFieldSymbol) Then - Return False - End If - - Dim localSymbol = TryCast(declaredSymbol, ILocalSymbol) - If localSymbol IsNot Nothing AndAlso TypeOf localSymbol IsNot IErrorTypeSymbol AndAlso TypeOf localSymbol.Type IsNot IErrorTypeSymbol Then - typeSymbol = localSymbol.Type - Return True - End If - - Dim fieldSymbol = TryCast(declaredSymbol, IFieldSymbol) - If fieldSymbol IsNot Nothing AndAlso TypeOf fieldSymbol IsNot IErrorTypeSymbol AndAlso TypeOf fieldSymbol.Type IsNot IErrorTypeSymbol Then - typeSymbol = fieldSymbol.Type - Return True - End If - - Return False - End Function - - - Public Function TryReduceOrSimplifyExplicitName( - expression As ExpressionSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - optionSet As OptionSet, - cancellationToken As CancellationToken - ) As Boolean - If expression.TryReduceExplicitName(semanticModel, replacementNode, issueSpan, optionSet, cancellationToken) Then - Return True - End If - - Return expression.TrySimplify(semanticModel, replacementNode, issueSpan) - End Function - - - Public Function TryReduceExplicitName( - expression As ExpressionSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - optionSet As OptionSet, - cancellationToken As CancellationToken - ) As Boolean - replacementNode = Nothing - issueSpan = Nothing - - If expression.Kind = SyntaxKind.SimpleMemberAccessExpression Then - Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax) - Return memberAccess.TryReduce(semanticModel, - replacementNode, - issueSpan, - optionSet, - cancellationToken) - ElseIf TypeOf (expression) Is NameSyntax Then - Dim name = DirectCast(expression, NameSyntax) - Return name.TryReduce(semanticModel, - replacementNode, - issueSpan, - optionSet, - cancellationToken) - End If - - Return False - End Function - - - Private Function TryReduce( - memberAccess As MemberAccessExpressionSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - optionSet As OptionSet, - cancellationToken As CancellationToken - ) As Boolean - If memberAccess.Expression Is Nothing OrElse memberAccess.Name Is Nothing Then - Return False - End If - - If memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind) Then - replacementNode = SyntaxFactory.PredefinedType( - SyntaxFactory.Token( - GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())))) _ - .WithLeadingTrivia(memberAccess.GetLeadingTrivia()) - - issueSpan = memberAccess.Span - Return True - End If - - ' See https//github.com/dotnet/roslyn/issues/40974 - ' - ' To be very safe, we only support simplifying code that bound to a symbol without any - ' sort of problems. We could potentially relax this in the future. However, we would - ' need to be very careful about the implications of us offering to fixup 'broken' code - ' in a manner that might end up making things worse Or confusing the user. - Dim symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, memberAccess) - If symbol Is Nothing Then - Return False - End If - - If memberAccess.Expression.IsKind(SyntaxKind.MeExpression) AndAlso - Not SimplificationHelpers.ShouldSimplifyThisOrMeMemberAccessExpression(semanticModel, optionSet, symbol) Then - Return False - End If - - If Not memberAccess.IsRightSideOfDot() Then - Dim aliasReplacement As IAliasSymbol = Nothing - - If memberAccess.TryReplaceWithAlias(semanticModel, aliasReplacement) Then - Dim identifierToken = SyntaxFactory.Identifier( - memberAccess.GetLeadingTrivia(), - aliasReplacement.Name, - memberAccess.GetTrailingTrivia()) - - identifierToken = VisualBasicSimplificationService.TryEscapeIdentifierToken( - identifierToken, - semanticModel) - replacementNode = SyntaxFactory.IdentifierName(identifierToken) - - issueSpan = memberAccess.Span - - ' In case the alias name is the same as the last name of the alias target, we only include - ' the left part of the name in the unnecessary span to Not confuse uses. - If memberAccess.Name.Identifier.ValueText = identifierToken.ValueText Then - issueSpan = memberAccess.Expression.Span - End If - - Return True - End If - - If PreferPredefinedTypeKeywordInMemberAccess(memberAccess, optionSet) Then - If (symbol IsNot Nothing AndAlso symbol.IsKind(SymbolKind.NamedType)) Then - Dim keywordKind = GetPredefinedKeywordKind(DirectCast(symbol, INamedTypeSymbol).SpecialType) - If keywordKind <> SyntaxKind.None Then - replacementNode = SyntaxFactory.PredefinedType( - SyntaxFactory.Token( - memberAccess.GetLeadingTrivia(), - keywordKind, - memberAccess.GetTrailingTrivia())) - - replacementNode = replacementNode.WithAdditionalAnnotations( - New SyntaxAnnotation(NameOf(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess))) - - issueSpan = memberAccess.Span - Return True - End If - End If - End If - End If - - ' a module name was inserted by the name expansion, so removing this should be tried first. - If memberAccess.HasAnnotation(SimplificationHelpers.SimplifyModuleNameAnnotation) Then - If TryOmitModuleName(memberAccess, semanticModel, symbol, replacementNode, issueSpan, cancellationToken) Then - Return True - End If - End If - - replacementNode = memberAccess.GetNameWithTriviaMoved(semanticModel) - issueSpan = memberAccess.Expression.Span - - If memberAccess.CanReplaceWithReducedName(replacementNode, semanticModel, symbol, cancellationToken) Then - Return True - End If - - If TryOmitModuleName(memberAccess, semanticModel, symbol, replacementNode, issueSpan, cancellationToken) Then - Return True - End If - - Return False - End Function - Public Function InsideCrefReference(expression As ExpressionSyntax) As Boolean Dim crefAttribute = expression.FirstAncestorOrSelf(Of XmlCrefAttributeSyntax)() Return crefAttribute IsNot Nothing End Function - Private Function InsideNameOfExpression(expr As ExpressionSyntax) As Boolean - Dim nameOfExpression = expr.FirstAncestorOrSelf(Of NameOfExpressionSyntax)() - Return nameOfExpression IsNot Nothing - End Function - - Private Function PreferPredefinedTypeKeywordInMemberAccess(expression As ExpressionSyntax, optionSet As OptionSet) As Boolean - Return (IsInMemberAccessContext(expression) OrElse IsInCrefReferenceForPredefinedTypeInMemberAccessContext(expression)) AndAlso - (Not InsideNameOfExpression(expression)) AndAlso - SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, LanguageNames.VisualBasic) - End Function - - ''' - ''' Note: This helper exists solely to work around Bug 1012713. Once it is fixed, this helper must be - ''' deleted in favor of . - ''' Context: Bug 1012713 makes it so that the compiler doesn't support PredefinedType.Member inside crefs - ''' (i.e. System.Int32.MaxValue is supported but Integer.MaxValue isn't). Until this bug is fixed, we don't - ''' support simplifying types names Like System.Int32.MaxValue to Integer.MaxValue. - ''' - Private Function IsInCrefReferenceForPredefinedTypeInMemberAccessContext(expression As ExpressionSyntax) As Boolean - Return (InsideCrefReference(expression) AndAlso Not expression.IsLeftSideOfQualifiedName) - End Function - Public Function IsInMemberAccessContext(expression As ExpressionSyntax) As Boolean Return TypeOf expression?.Parent Is MemberAccessExpressionSyntax End Function - Private Function PreferPredefinedTypeKeywordInDeclarations(name As NameSyntax, optionSet As OptionSet) As Boolean - Return (Not IsInMemberAccessContext(name)) AndAlso - (Not InsideCrefReference(name)) AndAlso - (Not InsideNameOfExpression(name)) AndAlso - SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, LanguageNames.VisualBasic) - End Function - Public Function GetRightmostName(node As ExpressionSyntax) As NameSyntax Dim memberAccess = TryCast(node, MemberAccessExpressionSyntax) @@ -1120,773 +815,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions Return Nothing End Function - Private Function TryOmitModuleName(memberAccess As MemberAccessExpressionSyntax, - semanticModel As SemanticModel, - symbol As ISymbol, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - cancellationToken As CancellationToken) As Boolean - If memberAccess.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) Then - Dim symbolForMemberAccess = semanticModel.GetSymbolInfo(DirectCast(memberAccess.Parent, MemberAccessExpressionSyntax)).Symbol - If symbolForMemberAccess.IsModuleMember Then - replacementNode = memberAccess.Expression.WithLeadingTrivia(memberAccess.GetLeadingTrivia()) - issueSpan = memberAccess.Name.Span - - Dim parent = DirectCast(memberAccess.Parent, MemberAccessExpressionSyntax) - Dim parentReplacement = parent.ReplaceNode(parent.Expression, replacementNode) - - If parent.CanReplaceWithReducedName(parentReplacement, semanticModel, symbol, cancellationToken) Then - Return True - End If - End If - End If - - Return False - End Function - - - Private Function CanReplaceWithReducedName( - memberAccess As MemberAccessExpressionSyntax, - reducedNode As ExpressionSyntax, - semanticModel As SemanticModel, - symbol As ISymbol, - cancellationToken As CancellationToken - ) As Boolean - If Not IsMeOrNamedTypeOrNamespace(memberAccess.Expression, semanticModel) Then - Return False - End If - - ' See if we can simplify a member access expression of the form E.M or E.M() to M or M() - Dim speculationAnalyzer = New SpeculationAnalyzer(memberAccess, reducedNode, semanticModel, cancellationToken) - If Not speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() OrElse - speculationAnalyzer.ReplacementChangesSemantics() Then - Return False - End If - - If memberAccess.Expression.IsKind(SyntaxKind.MyBaseExpression) Then - Dim enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken) - If enclosingNamedType IsNot Nothing AndAlso - Not enclosingNamedType.IsSealed AndAlso - symbol IsNot Nothing AndAlso - symbol.IsOverridable() Then - Return False - End If - End If - - Return True - End Function - - - Private Function TryReduce( - name As NameSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - optionSet As OptionSet, - cancellationToken As CancellationToken - ) As Boolean - - ' do not simplify names of a namespace declaration - If IsPartOfNamespaceDeclarationName(name) Then - Return False - End If - - ' see whether binding the name binds to a symbol/type. if not, it is ambiguous and - ' nothing we can do here. - Dim symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name) - If SimplificationHelpers.IsValidSymbolInfo(symbol) Then - If symbol.Kind = SymbolKind.Method AndAlso symbol.IsConstructor() Then - symbol = symbol.ContainingType - End If - - If symbol.Kind = SymbolKind.Method AndAlso name.Kind = SyntaxKind.GenericName Then - Dim genericName = DirectCast(name, GenericNameSyntax) - replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier).WithLeadingTrivia(genericName.GetLeadingTrivia()).WithTrailingTrivia(genericName.GetTrailingTrivia()) - - issueSpan = genericName.TypeArgumentList.Span - Return name.CanReplaceWithReducedName(replacementNode, semanticModel, cancellationToken) - End If - - If Not TypeOf symbol Is INamespaceOrTypeSymbol Then - Return False - End If - Else - Return False - End If - - If name.HasAnnotations(SpecialTypeAnnotation.Kind) Then - replacementNode = SyntaxFactory.PredefinedType( - SyntaxFactory.Token(name.GetLeadingTrivia(), - GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())), - name.GetTrailingTrivia())) - - issueSpan = name.Span - - Return name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel, cancellationToken) - Else - - If Not name.IsRightSideOfDot() Then - - Dim aliasReplacement As IAliasSymbol = Nothing - If name.TryReplaceWithAlias(semanticModel, aliasReplacement) Then - Dim identifierToken = SyntaxFactory.Identifier( - name.GetLeadingTrivia(), - aliasReplacement.Name, - name.GetTrailingTrivia()) - - identifierToken = VisualBasicSimplificationService.TryEscapeIdentifierToken( - identifierToken, - semanticModel) - - replacementNode = SyntaxFactory.IdentifierName(identifierToken) - - Dim annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind) - For Each annotatedNodeOrToken In annotatedNodesOrTokens - If annotatedNodeOrToken.IsToken Then - identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken) - Else - replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode) - End If - Next - - annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind) - For Each annotatedNodeOrToken In annotatedNodesOrTokens - If annotatedNodeOrToken.IsToken Then - identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken) - Else - replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode) - End If - Next - - replacementNode = DirectCast(replacementNode, SimpleNameSyntax).WithIdentifier(identifierToken) - issueSpan = name.Span - - ' In case the alias name is the same as the last name of the alias target, we only include - ' the left part of the name in the unnecessary span to Not confuse uses. - If name.Kind = SyntaxKind.QualifiedName Then - Dim qualifiedName As QualifiedNameSyntax = DirectCast(name, QualifiedNameSyntax) - - If qualifiedName.Right.Identifier.ValueText = identifierToken.ValueText Then - issueSpan = qualifiedName.Left.Span - End If - End If - - If name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel, cancellationToken) Then - - ' check if the alias name ends with an Attribute suffix that can be omitted. - Dim replacementNodeWithoutAttributeSuffix As ExpressionSyntax = Nothing - Dim issueSpanWithoutAttributeSuffix As TextSpan = Nothing - If TryReduceAttributeSuffix(name, identifierToken, semanticModel, aliasReplacement IsNot Nothing, replacementNodeWithoutAttributeSuffix, issueSpanWithoutAttributeSuffix, cancellationToken) Then - If name.CanReplaceWithReducedName(replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken) Then - replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix) - issueSpan = issueSpanWithoutAttributeSuffix - End If - End If - - Return True - End If - - Return False - End If - - Dim nameHasNoAlias = False - - If TypeOf name Is SimpleNameSyntax Then - Dim simpleName = DirectCast(name, SimpleNameSyntax) - If Not simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind) Then - nameHasNoAlias = True - End If - End If - - If TypeOf name Is QualifiedNameSyntax Then - Dim qualifiedNameSyntax = DirectCast(name, QualifiedNameSyntax) - If Not qualifiedNameSyntax.Right.Identifier.HasAnnotations(AliasAnnotation.Kind) Then - nameHasNoAlias = True - End If - End If - - Dim aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken) - - ' Don't simplify to predefined type if name is part of a QualifiedName. - ' QualifiedNames can't contain PredefinedTypeNames (although MemberAccessExpressions can). - ' In other words, the left side of a QualifiedName can't be a PredefinedTypeName. - If nameHasNoAlias AndAlso aliasInfo Is Nothing AndAlso Not name.Parent.IsKind(SyntaxKind.QualifiedName) Then - Dim type = semanticModel.GetTypeInfo(name).Type - If type IsNot Nothing Then - Dim keywordKind = GetPredefinedKeywordKind(type.SpecialType) - If keywordKind <> SyntaxKind.None Then - ' But do simplify to predefined type if not simplifying results in just the addition of escaping - ' brackets. E.g., even if specified otherwise, prefer `String` to `[String]`. - Dim token = SyntaxFactory.Token( - name.GetLeadingTrivia(), - keywordKind, - name.GetTrailingTrivia()) - Dim valueText = TryCast(name, IdentifierNameSyntax)?.Identifier.ValueText - Dim inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, optionSet) - Dim inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, optionSet) - If token.Text = valueText OrElse (inDeclarationContext OrElse inMemberAccessContext) Then - - Dim codeStyleOptionName As String = Nothing - If inDeclarationContext Then - codeStyleOptionName = NameOf(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration) - ElseIf inMemberAccessContext Then - codeStyleOptionName = NameOf(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess) - End If - - replacementNode = SyntaxFactory.PredefinedType(token) - issueSpan = name.Span - - Dim canReplace = name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel, cancellationToken) - If canReplace Then - replacementNode = replacementNode.WithAdditionalAnnotations(New SyntaxAnnotation(codeStyleOptionName)) - End If - - Return canReplace - End If - End If - End If - End If - - ' Nullable rewrite: Nullable(Of Integer) -> Integer? - ' Don't rewrite in the case where Nullable(Of Integer) is part of some qualified name like Nullable(Of Integer).Something - If (symbol.Kind = SymbolKind.NamedType) AndAlso (Not name.IsLeftSideOfQualifiedName) Then - Dim type = DirectCast(symbol, INamedTypeSymbol) - If aliasInfo Is Nothing AndAlso CanSimplifyNullable(type, name) Then - Dim genericName As GenericNameSyntax - If name.Kind = SyntaxKind.QualifiedName Then - genericName = DirectCast(DirectCast(name, QualifiedNameSyntax).Right, GenericNameSyntax) - Else - genericName = DirectCast(name, GenericNameSyntax) - End If - - Dim oldType = genericName.TypeArgumentList.Arguments.First() - replacementNode = SyntaxFactory.NullableType(oldType).WithLeadingTrivia(name.GetLeadingTrivia()) - - issueSpan = name.Span - - If name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel, cancellationToken) Then - Return True - End If - End If - End If - End If - - Select Case name.Kind - Case SyntaxKind.QualifiedName - ' a module name was inserted by the name expansion, so removing this should be tried first. - Dim qualifiedName = DirectCast(name, QualifiedNameSyntax) - If qualifiedName.HasAnnotation(SimplificationHelpers.SimplifyModuleNameAnnotation) Then - If TryOmitModuleName(qualifiedName, semanticModel, replacementNode, issueSpan, cancellationToken) Then - Return True - End If - End If - - replacementNode = qualifiedName.Right.WithLeadingTrivia(name.GetLeadingTrivia()) - replacementNode = DirectCast(replacementNode, SimpleNameSyntax) _ - .WithIdentifier(VisualBasicSimplificationService.TryEscapeIdentifierToken( - DirectCast(replacementNode, SimpleNameSyntax).Identifier, - semanticModel)) - issueSpan = qualifiedName.Left.Span - - - If name.CanReplaceWithReducedName(replacementNode, semanticModel, cancellationToken) Then - Return True - End If - - If TryOmitModuleName(qualifiedName, semanticModel, replacementNode, issueSpan, cancellationToken) Then - Return True - End If - - Case SyntaxKind.IdentifierName - Dim identifier = DirectCast(name, IdentifierNameSyntax).Identifier - TryReduceAttributeSuffix(name, identifier, semanticModel, False, replacementNode, issueSpan, cancellationToken) - End Select - End If - - If replacementNode Is Nothing Then - Return False - End If - - Return name.CanReplaceWithReducedName(replacementNode, semanticModel, cancellationToken) - End Function - - Private Function CanSimplifyNullable(type As INamedTypeSymbol, name As NameSyntax) As Boolean - If Not type.IsNullable Then - Return False - End If - - If type.IsUnboundGenericType Then - ' Don't simplify unbound generic type "Nullable(Of )". - Return False - End If - - If InsideNameOfExpression(name) Then - ' Nullable(Of T) can't be simplified to T? in nameof expressions. - Return False - End If - - If Not InsideCrefReference(name) Then - ' Nullable(Of T) can always be simplified to T? outside crefs. - Return True - End If - - ' Inside crefs, if the T in this Nullable(Of T) is being declared right here - ' then this Nullable(Of T) is not a constructed generic type and we should - ' not offer to simplify this to T?. - ' - ' For example, we should not offer the simplification in the following cases where - ' T does not bind to an existing type / type parameter in the user's code. - ' - - ' - - ' - ' And we should offer the simplification in the following cases where SomeType and - ' SomeMethod bind to a type and method declared elsewhere in the users code. - ' - - - If name.IsKind(SyntaxKind.GenericName) Then - If (name.IsParentKind(SyntaxKind.CrefReference)) OrElse ' cref="Nullable(Of T)" - (name.IsParentKind(SyntaxKind.QualifiedName) AndAlso name.Parent?.IsParentKind(SyntaxKind.CrefReference)) OrElse ' cref="System.Nullable(Of T)" - (name.IsParentKind(SyntaxKind.QualifiedName) AndAlso (name.Parent?.IsParentKind(SyntaxKind.QualifiedName)).GetValueOrDefault() AndAlso name.Parent.Parent?.IsParentKind(SyntaxKind.CrefReference)) Then ' cref="System.Nullable(Of T).Value" - ' Unfortunately, unlike in corresponding C# case, we need syntax based checking to detect these cases because of bugs in the VB SemanticModel. - ' See https://github.com/dotnet/roslyn/issues/2196, https://github.com/dotnet/roslyn/issues/2197 - Return False - End If - End If - - Dim argument = type.TypeArguments.SingleOrDefault() - If argument Is Nothing OrElse argument.IsErrorType() Then - Return False - End If - - Dim argumentDecl = argument.DeclaringSyntaxReferences.FirstOrDefault() - If argumentDecl Is Nothing Then - ' The type argument is a type from metadata - so this is a constructed generic nullable type that can be simplified (e.g. Nullable(Of Integer)). - Return True - End If - - Return Not name.Span.Contains(argumentDecl.Span) - End Function - - Private Function TryReduceAttributeSuffix( - name As NameSyntax, - identifierToken As SyntaxToken, - semanticModel As SemanticModel, - isIdentifierNameFromAlias As Boolean, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan, - cancellationToken As CancellationToken - ) As Boolean - If SyntaxFacts.IsAttributeName(name) AndAlso Not isIdentifierNameFromAlias Then - - ' When the replacement is an Alias we don't want the "Attribute" Suffix to be removed because this will result in symbol change - Dim aliasSymbol = semanticModel.GetAliasInfo(name, cancellationToken) - If aliasSymbol IsNot Nothing AndAlso - String.Compare(aliasSymbol.Name, identifierToken.ValueText, StringComparison.OrdinalIgnoreCase) = 0 Then - Return False - End If - - If name.Parent.Kind = SyntaxKind.Attribute OrElse name.IsRightSideOfDot() Then - Dim newIdentifierText = String.Empty - - ' an attribute that should keep it (unnecessary "Attribute" suffix should be annotated with a DontSimplifyAnnotation - If identifierToken.HasAnnotation(SimplificationHelpers.DontSimplifyAnnotation) Then - newIdentifierText = identifierToken.ValueText + "Attribute" - ElseIf identifierToken.ValueText.TryReduceAttributeSuffix(newIdentifierText) Then - issueSpan = New TextSpan(name.Span.End - 9, 9) - Else - Return False - End If - - ' escape it (VB allows escaping even for abbreviated identifiers, C# does not!) - Dim newIdentifierToken = identifierToken.CopyAnnotationsTo( - SyntaxFactory.Identifier( - identifierToken.LeadingTrivia, - newIdentifierText, - identifierToken.TrailingTrivia)) - newIdentifierToken = VisualBasicSimplificationService.TryEscapeIdentifierToken(newIdentifierToken, semanticModel) - replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken).WithLeadingTrivia(name.GetLeadingTrivia()) - Return True - End If - End If - - Return False - End Function - - ''' - ''' Checks if the SyntaxNode is a name of a namespace declaration. To be a namespace name, the syntax - ''' must be parented by an namespace declaration and the node itself must be equal to the declaration's Name - ''' property. - ''' - Private Function IsPartOfNamespaceDeclarationName(node As SyntaxNode) As Boolean - - Dim nextNode As SyntaxNode = node - - Do While nextNode IsNot Nothing - - Select Case nextNode.Kind - - Case SyntaxKind.IdentifierName, SyntaxKind.QualifiedName - node = nextNode - nextNode = nextNode.Parent - - Case SyntaxKind.NamespaceStatement - Dim namespaceStatement = DirectCast(nextNode, NamespaceStatementSyntax) - Return namespaceStatement.Name Is node - - Case Else - Return False - - End Select - - Loop - - Return False - End Function - - Private Function TryOmitModuleName(name As QualifiedNameSyntax, semanticModel As SemanticModel, ByRef replacementNode As ExpressionSyntax, ByRef issueSpan As TextSpan, cancellationToken As CancellationToken) As Boolean - If name.IsParentKind(SyntaxKind.QualifiedName) Then - Dim symbolForName = semanticModel.GetSymbolInfo(DirectCast(name.Parent, QualifiedNameSyntax)).Symbol - - ' in case this QN is used in a "New NSName.ModuleName.MemberName()" expression - ' the returned symbol is a constructor. Then we need to get the containing type. - If symbolForName.IsConstructor Then - symbolForName = symbolForName.ContainingType - End If - - If symbolForName.IsModuleMember Then - - replacementNode = name.Left.WithLeadingTrivia(name.GetLeadingTrivia()) - issueSpan = name.Right.Span - - Dim parent = DirectCast(name.Parent, QualifiedNameSyntax) - Dim parentReplacement = parent.ReplaceNode(parent.Left, replacementNode) - - If parent.CanReplaceWithReducedName(parentReplacement, semanticModel, cancellationToken) Then - Return True - End If - End If - End If - - Return False - End Function - - - Private Function TrySimplify( - expression As ExpressionSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan - ) As Boolean - replacementNode = Nothing - issueSpan = Nothing - - Select Case expression.Kind - Case SyntaxKind.SimpleMemberAccessExpression - If True Then - Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax) - Dim newLeft As ExpressionSyntax = Nothing - If TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, newLeft, issueSpan) Then - ' replacement node might not be in it's simplest form, so add simplify annotation to it. - replacementNode = memberAccess.Update(memberAccess.Kind, newLeft, memberAccess.OperatorToken, memberAccess.Name).WithAdditionalAnnotations(Simplifier.Annotation) - - ' Ensure that replacement doesn't change semantics. - Return Not ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel) - End If - - Return False - End If - - Case SyntaxKind.QualifiedName - If True Then - Dim qualifiedName = DirectCast(expression, QualifiedNameSyntax) - Dim newLeft As ExpressionSyntax = Nothing - If TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, newLeft, issueSpan) Then - If Not TypeOf newLeft Is NameSyntax Then - Contract.Fail("QualifiedName Left = " + qualifiedName.Left.ToString() + " and QualifiedName Right = " + qualifiedName.Right.ToString() + " . Left is tried to be replaced with the PredefinedType " + replacementNode.ToString()) - End If - - ' replacement node might not be in it's simplest form, so add simplify annotation to it. - replacementNode = qualifiedName.Update(DirectCast(newLeft, NameSyntax), qualifiedName.DotToken, qualifiedName.Right).WithAdditionalAnnotations(Simplifier.Annotation) - - ' Ensure that replacement doesn't change semantics. - Return Not ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel) - End If - - Return False - End If - End Select - - Return False - End Function - - Private Function ReplacementChangesSemantics(originalExpression As ExpressionSyntax, replacedExpression As ExpressionSyntax, semanticModel As SemanticModel) As Boolean - Dim speculationAnalyzer = New SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None) - Return speculationAnalyzer.ReplacementChangesSemantics() - End Function - - ' Note: The caller needs to verify that replacement doesn't change semantics of the original expression. - Private Function TrySimplifyMemberAccessOrQualifiedName( - left As ExpressionSyntax, - right As ExpressionSyntax, - semanticModel As SemanticModel, - ByRef replacementNode As ExpressionSyntax, - ByRef issueSpan As TextSpan - ) As Boolean - replacementNode = Nothing - issueSpan = Nothing - - If left IsNot Nothing AndAlso right IsNot Nothing Then - Dim leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left) - If leftSymbol IsNot Nothing AndAlso leftSymbol.Kind = SymbolKind.NamedType Then - Dim rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right) - If rightSymbol IsNot Nothing AndAlso (rightSymbol.IsStatic OrElse rightSymbol.Kind = SymbolKind.NamedType) Then - ' Static member access or nested type member access. - Dim containingType As INamedTypeSymbol = rightSymbol.ContainingType - Dim isInCref = left.Ancestors(ascendOutOfTrivia:=True).OfType(Of CrefReferenceSyntax)().Any() - - ' Crefs in VB , irrespective of the expression are parsed as QualifiedName (no MemberAccessExpression) - ' Hence the Left can never be a PredefinedType (or anything other than NameSyntax) - If isInCref AndAlso TypeOf rightSymbol Is IMethodSymbol AndAlso Not containingType.SpecialType = SpecialType.None Then - Return False - End If - - If containingType IsNot Nothing AndAlso Not containingType.Equals(leftSymbol) Then - - Dim namedType = TryCast(leftSymbol, INamedTypeSymbol) - If namedType IsNot Nothing AndAlso - containingType.TypeArguments.Length <> 0 Then - Return False - End If - - ' We have a static member access or a nested type member access using a more derived type. - ' Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type. - replacementNode = containingType.GenerateTypeSyntax().WithLeadingTrivia(left.GetLeadingTrivia()).WithTrailingTrivia(left.GetTrailingTrivia()).WithAdditionalAnnotations(Simplifier.Annotation) - issueSpan = left.Span - Return True - End If - End If - End If - End If - - Return False - End Function - - - Private Function TryReplaceWithAlias( - node As ExpressionSyntax, - semanticModel As SemanticModel, - ByRef aliasReplacement As IAliasSymbol) As Boolean - aliasReplacement = Nothing - - If Not node.IsAliasReplaceableExpression() Then - Return False - End If - - Dim symbol = semanticModel.GetSymbolInfo(node).Symbol - - If (symbol.IsConstructor()) Then - symbol = symbol.ContainingType - End If - - ' The following condition checks if the user has used alias in the original code and - ' if so the expression is replaced with the Alias - If TypeOf node Is QualifiedNameSyntax Then - Dim qualifiedNameNode = DirectCast(node, QualifiedNameSyntax) - If qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind) Then - Dim aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single() - - Dim aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo) - Dim aliasIdentifier = SyntaxFactory.IdentifierName(aliasName) - - Dim aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace) - - If Not aliasTypeInfo Is Nothing Then - aliasReplacement = aliasTypeInfo - Return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol) - End If - End If - End If - - If node.Kind = SyntaxKind.IdentifierName AndAlso semanticModel.GetAliasInfo(DirectCast(node, IdentifierNameSyntax)) IsNot Nothing Then - Return False - End If - - ' an alias can only replace a type Or namespace - If symbol Is Nothing OrElse - (symbol.Kind <> SymbolKind.Namespace AndAlso symbol.Kind <> SymbolKind.NamedType) Then - - Return False - End If - - If symbol Is Nothing OrElse Not TypeOf (symbol) Is INamespaceOrTypeSymbol Then - Return False - End If - - Dim preferAliasToQualifiedName = True - - If TypeOf node Is QualifiedNameSyntax Then - Dim qualifiedName = DirectCast(node, QualifiedNameSyntax) - If Not qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation) Then - Dim type = semanticModel.GetTypeInfo(node).Type - If Not type Is Nothing Then - Dim keywordKind = GetPredefinedKeywordKind(type.SpecialType) - If keywordKind <> SyntaxKind.None Then - preferAliasToQualifiedName = False - End If - End If - End If - End If - - aliasReplacement = DirectCast(symbol, INamespaceOrTypeSymbol).GetAliasForSymbol(node, semanticModel) - If aliasReplacement IsNot Nothing And preferAliasToQualifiedName Then - Return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol) - End If - - Return False - End Function - - ' We must verify that the alias actually binds back to the thing it's aliasing. - ' It's possible there is another symbol with the same name as the alias that binds first - Private Function ValidateAliasForTarget(aliasReplacement As IAliasSymbol, semanticModel As SemanticModel, node As ExpressionSyntax, symbol As ISymbol) As Boolean - Dim aliasName = aliasReplacement.Name - - ' If we're the argument of a NameOf(X.Y) call, then we can't simplify to an - ' alias unless the alias has the same name as us (i.e. 'Y'). - If node.IsNameOfArgumentExpression() Then - Dim nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent) - If Not nameofValueOpt.HasValue Then - Return False - End If - - Dim existingValue = TryCast(nameofValueOpt.Value, String) - If existingValue Is Nothing OrElse existingValue <> aliasName Then - Return False - End If - End If - - Dim boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name:=aliasName) - If boundSymbols.Length = 1 Then - Dim boundAlias = TryCast(boundSymbols(0), IAliasSymbol) - If boundAlias IsNot Nothing And aliasReplacement.Target.Equals(symbol) Then - If symbol.IsAttribute Then - boundSymbols = semanticModel.LookupNamespacesAndTypes(node.Span.Start, name:=aliasName + "Attribute") - Return boundSymbols.IsEmpty - End If - - Return True - End If - End If - Return False - End Function - Public Function IsNameOfArgumentExpression(expression As ExpressionSyntax) As Boolean Return expression.IsParentKind(SyntaxKind.NameOfExpression) End Function - - Private Function CanReplaceWithReducedName( - name As NameSyntax, - replacementNode As ExpressionSyntax, - semanticModel As SemanticModel, - cancellationToken As CancellationToken - ) As Boolean - Dim speculationAnalyzer = New SpeculationAnalyzer(name, replacementNode, semanticModel, cancellationToken) - If speculationAnalyzer.ReplacementChangesSemantics() Then - Return False - End If - - Return name.CanReplaceWithReducedNameInContext(replacementNode, semanticModel, cancellationToken) - End Function - - - Private Function CanReplaceWithReducedNameInContext(name As NameSyntax, replacementNode As ExpressionSyntax, semanticModel As SemanticModel, cancellationToken As CancellationToken) As Boolean - - ' Special case. if this new minimal name parses out to a predefined type, then we - ' have to make sure that we're not in a using alias. That's the one place where the - ' language doesn't allow predefined types. You have to use the fully qualified name - ' instead. - Dim invalidTransformation1 = IsNonNameSyntaxInImportsDirective(name, replacementNode) - Dim invalidTransformation2 = IsReservedNameInAttribute(name, replacementNode) - - Dim invalidTransformation3 = IsNullableTypeSyntaxLeftOfDotInMemberAccess(name, replacementNode) - - Return Not (invalidTransformation1 OrElse invalidTransformation2 OrElse invalidTransformation3) - End Function - - Private Function IsMeOrNamedTypeOrNamespace(expression As ExpressionSyntax, semanticModel As SemanticModel) As Boolean - If expression.Kind = SyntaxKind.MeExpression Then - Return True - End If - - Dim expressionInfo = semanticModel.GetSymbolInfo(expression) - If SimplificationHelpers.IsValidSymbolInfo(expressionInfo.Symbol) Then - If TypeOf expressionInfo.Symbol Is INamespaceOrTypeSymbol Then - Return True - End If - - If expressionInfo.Symbol.IsThisParameter() Then - Return True - End If - End If - - Return False - End Function - - ''' - ''' Returns the predefined keyword kind for a given special type. - ''' - ''' The specialtype of this type. - ''' The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type. - Private Function GetPredefinedKeywordKind(type As SpecialType) As SyntaxKind - Select Case type - Case SpecialType.System_Boolean - Return SyntaxKind.BooleanKeyword - Case SpecialType.System_Byte - Return SyntaxKind.ByteKeyword - Case SpecialType.System_SByte - Return SyntaxKind.SByteKeyword - Case SpecialType.System_Int32 - Return SyntaxKind.IntegerKeyword - Case SpecialType.System_UInt32 - Return SyntaxKind.UIntegerKeyword - Case SpecialType.System_Int16 - Return SyntaxKind.ShortKeyword - Case SpecialType.System_UInt16 - Return SyntaxKind.UShortKeyword - Case SpecialType.System_Int64 - Return SyntaxKind.LongKeyword - Case SpecialType.System_UInt64 - Return SyntaxKind.ULongKeyword - Case SpecialType.System_Single - Return SyntaxKind.SingleKeyword - Case SpecialType.System_Double - Return SyntaxKind.DoubleKeyword - Case SpecialType.System_Decimal - Return SyntaxKind.DecimalKeyword - Case SpecialType.System_String - Return SyntaxKind.StringKeyword - Case SpecialType.System_Char - Return SyntaxKind.CharKeyword - Case SpecialType.System_Object - Return SyntaxKind.ObjectKeyword - Case SpecialType.System_DateTime - Return SyntaxKind.DateKeyword - Case Else - Return SyntaxKind.None - End Select - End Function - - Private Function IsNullableTypeSyntaxLeftOfDotInMemberAccess(expression As ExpressionSyntax, simplifiedNode As ExpressionSyntax) As Boolean - Return expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso - simplifiedNode.Kind = SyntaxKind.NullableType - End Function - - Private Function IsNonNameSyntaxInImportsDirective(expression As ExpressionSyntax, simplifiedNode As ExpressionSyntax) As Boolean - Return TypeOf expression.Parent Is ImportsClauseSyntax AndAlso - Not TypeOf simplifiedNode Is NameSyntax - End Function - Public Function IsReservedNameInAttribute(originalName As NameSyntax, simplifiedNode As ExpressionSyntax) As Boolean Dim attribute = originalName.GetAncestorOrThis(Of AttributeSyntax)() If attribute Is Nothing Then diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb index 49523473e85dd..d6111e6268f3e 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicNameReducer.vb @@ -7,6 +7,7 @@ Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification @@ -31,11 +32,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Dim replacementNode As ExpressionSyntax = Nothing Dim issueSpan As TextSpan - If Not node.TryReduceOrSimplifyExplicitName(semanticModel, - replacementNode, - issueSpan, - optionSet, - cancellationToken) Then + If Not ExpressionSimplifier.Instance.TrySimplify( + node, semanticModel, optionSet, + replacementNode, issueSpan, cancellationToken) Then Return node End If diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb index 9d6a1fbb1e4cc..a78ed88fe5027 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb @@ -1,5 +1,6 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Runtime.InteropServices Imports System.Threading Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Options @@ -30,17 +31,134 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Dim replacementNode As SyntaxNode = Nothing Dim issueSpan As TextSpan - If Not node.TryReduceVariableDeclaratorWithoutType( - semanticModel, - replacementNode, - issueSpan, - optionSet, - cancellationToken) Then + If Not TryReduceVariableDeclaratorWithoutType( + node, semanticModel, replacementNode, issueSpan) Then Return node End If replacementNode = node.CopyAnnotationsTo(replacementNode).WithAdditionalAnnotations(Formatter.Annotation) Return replacementNode.WithoutAnnotations(Simplifier.Annotation) End Function + + Private Shared Function TryReduceVariableDeclaratorWithoutType( + variableDeclarator As VariableDeclaratorSyntax, + semanticModel As SemanticModel, + ByRef replacementNode As SyntaxNode, + ByRef issueSpan As TextSpan) As Boolean + + replacementNode = Nothing + issueSpan = Nothing + + ' Failfast Conditions + If variableDeclarator.AsClause Is Nothing OrElse + Not variableDeclarator.Parent.IsKind( + SyntaxKind.LocalDeclarationStatement, + SyntaxKind.UsingStatement, + SyntaxKind.ForStatement, + SyntaxKind.ForEachStatement, + SyntaxKind.FieldDeclaration) Then + Return False + End If + + If variableDeclarator.Names.Count <> 1 Then + Return False + End If + + Dim parent = variableDeclarator.Parent + Dim modifiedIdentifier = variableDeclarator.Names.Single() + + Dim simpleAsClause = TryCast(variableDeclarator.AsClause, SimpleAsClauseSyntax) + If simpleAsClause Is Nothing Then + Return False + End If + + If (parent.IsKind(SyntaxKind.LocalDeclarationStatement, SyntaxKind.UsingStatement, SyntaxKind.FieldDeclaration) AndAlso + variableDeclarator.Initializer IsNot Nothing) Then + + ' Type Check + + Dim declaredSymbolType As ITypeSymbol = Nothing + If Not HasValidDeclaredTypeSymbol(modifiedIdentifier, semanticModel, declaredSymbolType) Then + Return False + End If + + Dim initializerType As ITypeSymbol = Nothing + + If declaredSymbolType.IsArrayType() AndAlso variableDeclarator.Initializer.Value.Kind() = SyntaxKind.CollectionInitializer Then + ' Get type of the array literal in context without the target type + initializerType = semanticModel.GetSpeculativeTypeInfo(variableDeclarator.Initializer.Value.SpanStart, variableDeclarator.Initializer.Value, SpeculativeBindingOption.BindAsExpression).ConvertedType + Else + initializerType = semanticModel.GetTypeInfo(variableDeclarator.Initializer.Value).Type + End If + + If Not declaredSymbolType.Equals(initializerType) Then + Return False + End If + + Dim newModifiedIdentifier = SyntaxFactory.ModifiedIdentifier(modifiedIdentifier.Identifier) ' LeadingTrivia is copied here + replacementNode = SyntaxFactory.VariableDeclarator(SyntaxFactory.SingletonSeparatedList(newModifiedIdentifier.WithTrailingTrivia(variableDeclarator.AsClause.GetTrailingTrivia())), + asClause:=Nothing, + initializer:=variableDeclarator.Initializer) 'TrailingTrivia is copied here + issueSpan = variableDeclarator.Span + Return True + End If + + If (parent.IsKind(SyntaxKind.ForEachStatement, SyntaxKind.ForStatement)) Then + ' Type Check for ForStatement + If parent.IsKind(SyntaxKind.ForStatement) Then + Dim declaredSymbolType As ITypeSymbol = Nothing + If Not HasValidDeclaredTypeSymbol(modifiedIdentifier, semanticModel, declaredSymbolType) Then + Return False + End If + + Dim valueType = semanticModel.GetTypeInfo(DirectCast(parent, ForStatementSyntax).ToValue).Type + + If Not valueType.Equals(declaredSymbolType) Then + Return False + End If + End If + + If parent.IsKind(SyntaxKind.ForEachStatement) Then + Dim forEachStatementInfo = semanticModel.GetForEachStatementInfo(DirectCast(parent, ForEachStatementSyntax)) + If Not forEachStatementInfo.ElementConversion.IsIdentity Then + Return False + End If + End If + + Dim newIdentifierName = SyntaxFactory.IdentifierName(modifiedIdentifier.Identifier) ' Leading Trivia is copied here + replacementNode = newIdentifierName.WithTrailingTrivia(variableDeclarator.AsClause.GetTrailingTrivia()) ' Trailing Trivia is copied here + issueSpan = variableDeclarator.Span + Return True + End If + + Return False + End Function + + Private Shared Function HasValidDeclaredTypeSymbol( + modifiedIdentifier As ModifiedIdentifierSyntax, + semanticModel As SemanticModel, + ByRef typeSymbol As ITypeSymbol) As Boolean + + Dim declaredSymbol = semanticModel.GetDeclaredSymbol(modifiedIdentifier) + If declaredSymbol Is Nothing OrElse + (Not TypeOf declaredSymbol Is ILocalSymbol AndAlso Not TypeOf declaredSymbol Is IFieldSymbol) Then + Return False + End If + + Dim localSymbol = TryCast(declaredSymbol, ILocalSymbol) + If localSymbol IsNot Nothing AndAlso TypeOf localSymbol IsNot IErrorTypeSymbol AndAlso TypeOf localSymbol.Type IsNot IErrorTypeSymbol Then + typeSymbol = localSymbol.Type + Return True + End If + + Dim fieldSymbol = TryCast(declaredSymbol, IFieldSymbol) + If fieldSymbol IsNot Nothing AndAlso TypeOf fieldSymbol IsNot IErrorTypeSymbol AndAlso TypeOf fieldSymbol.Type IsNot IErrorTypeSymbol Then + typeSymbol = fieldSymbol.Type + Return True + End If + + Return False + End Function + End Class End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb index 30d21a41c4656..51059adf21cde 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb @@ -1,10 +1,195 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Runtime.InteropServices +Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Simplification.Simplifiers +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports System.Runtime.CompilerServices +Imports System.Threading +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeStyle +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.CodeAnalysis.Rename.ConflictEngine +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Friend MustInherit Class AbstractVisualBasicSimplifier(Of TSyntax As SyntaxNode, TSimplifiedSyntax As SyntaxNode) Inherits AbstractSimplifier(Of TSyntax, TSimplifiedSyntax) + ''' + ''' Returns the predefined keyword kind for a given special type. + ''' + ''' The specialtype of this type. + ''' The keyword kind for a given special type, or SyntaxKind.None if the type name is not a predefined type. + Protected Shared Function GetPredefinedKeywordKind(type As SpecialType) As SyntaxKind + Select Case type + Case SpecialType.System_Boolean + Return SyntaxKind.BooleanKeyword + Case SpecialType.System_Byte + Return SyntaxKind.ByteKeyword + Case SpecialType.System_SByte + Return SyntaxKind.SByteKeyword + Case SpecialType.System_Int32 + Return SyntaxKind.IntegerKeyword + Case SpecialType.System_UInt32 + Return SyntaxKind.UIntegerKeyword + Case SpecialType.System_Int16 + Return SyntaxKind.ShortKeyword + Case SpecialType.System_UInt16 + Return SyntaxKind.UShortKeyword + Case SpecialType.System_Int64 + Return SyntaxKind.LongKeyword + Case SpecialType.System_UInt64 + Return SyntaxKind.ULongKeyword + Case SpecialType.System_Single + Return SyntaxKind.SingleKeyword + Case SpecialType.System_Double + Return SyntaxKind.DoubleKeyword + Case SpecialType.System_Decimal + Return SyntaxKind.DecimalKeyword + Case SpecialType.System_String + Return SyntaxKind.StringKeyword + Case SpecialType.System_Char + Return SyntaxKind.CharKeyword + Case SpecialType.System_Object + Return SyntaxKind.ObjectKeyword + Case SpecialType.System_DateTime + Return SyntaxKind.DateKeyword + Case Else + Return SyntaxKind.None + End Select + End Function + + Protected Shared Function TryReplaceWithAlias( + node As ExpressionSyntax, + semanticModel As SemanticModel, + ByRef aliasReplacement As IAliasSymbol) As Boolean + aliasReplacement = Nothing + + If Not node.IsAliasReplaceableExpression() Then + Return False + End If + + Dim symbol = semanticModel.GetSymbolInfo(node).Symbol + + If (symbol.IsConstructor()) Then + symbol = symbol.ContainingType + End If + + ' The following condition checks if the user has used alias in the original code and + ' if so the expression is replaced with the Alias + If TypeOf node Is QualifiedNameSyntax Then + Dim qualifiedNameNode = DirectCast(node, QualifiedNameSyntax) + If qualifiedNameNode.Right.Identifier.HasAnnotations(AliasAnnotation.Kind) Then + Dim aliasAnnotationInfo = qualifiedNameNode.Right.Identifier.GetAnnotations(AliasAnnotation.Kind).Single() + + Dim aliasName = AliasAnnotation.GetAliasName(aliasAnnotationInfo) + Dim aliasIdentifier = SyntaxFactory.IdentifierName(aliasName) + + Dim aliasTypeInfo = semanticModel.GetSpeculativeAliasInfo(node.SpanStart, aliasIdentifier, SpeculativeBindingOption.BindAsTypeOrNamespace) + + If Not aliasTypeInfo Is Nothing Then + aliasReplacement = aliasTypeInfo + Return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol) + End If + End If + End If + + If node.Kind = SyntaxKind.IdentifierName AndAlso semanticModel.GetAliasInfo(DirectCast(node, IdentifierNameSyntax)) IsNot Nothing Then + Return False + End If + + ' an alias can only replace a type Or namespace + If symbol Is Nothing OrElse + (symbol.Kind <> SymbolKind.Namespace AndAlso symbol.Kind <> SymbolKind.NamedType) Then + + Return False + End If + + If symbol Is Nothing OrElse Not TypeOf (symbol) Is INamespaceOrTypeSymbol Then + Return False + End If + + Dim preferAliasToQualifiedName = True + + If TypeOf node Is QualifiedNameSyntax Then + Dim qualifiedName = DirectCast(node, QualifiedNameSyntax) + If Not qualifiedName.Right.HasAnnotation(Simplifier.SpecialTypeAnnotation) Then + Dim type = semanticModel.GetTypeInfo(node).Type + If Not type Is Nothing Then + Dim keywordKind = GetPredefinedKeywordKind(type.SpecialType) + If keywordKind <> SyntaxKind.None Then + preferAliasToQualifiedName = False + End If + End If + End If + End If + + aliasReplacement = DirectCast(symbol, INamespaceOrTypeSymbol).GetAliasForSymbol(node, semanticModel) + If aliasReplacement IsNot Nothing And preferAliasToQualifiedName Then + Return ValidateAliasForTarget(aliasReplacement, semanticModel, node, symbol) + End If + + Return False + End Function + + ' We must verify that the alias actually binds back to the thing it's aliasing. + ' It's possible there is another symbol with the same name as the alias that binds first + Private Shared Function ValidateAliasForTarget(aliasReplacement As IAliasSymbol, semanticModel As SemanticModel, node As ExpressionSyntax, symbol As ISymbol) As Boolean + Dim aliasName = aliasReplacement.Name + + ' If we're the argument of a NameOf(X.Y) call, then we can't simplify to an + ' alias unless the alias has the same name as us (i.e. 'Y'). + If node.IsNameOfArgumentExpression() Then + Dim nameofValueOpt = semanticModel.GetConstantValue(node.Parent.Parent.Parent) + If Not nameofValueOpt.HasValue Then + Return False + End If + + Dim existingValue = TryCast(nameofValueOpt.Value, String) + If existingValue Is Nothing OrElse existingValue <> aliasName Then + Return False + End If + End If + + Dim boundSymbols = semanticModel.LookupNamespacesAndTypes(node.SpanStart, name:=aliasName) + If boundSymbols.Length = 1 Then + Dim boundAlias = TryCast(boundSymbols(0), IAliasSymbol) + If boundAlias IsNot Nothing And aliasReplacement.Target.Equals(symbol) Then + If symbol.IsAttribute Then + boundSymbols = semanticModel.LookupNamespacesAndTypes(node.Span.Start, name:=aliasName + "Attribute") + Return boundSymbols.IsEmpty + End If + + Return True + End If + End If + Return False + End Function + + Protected Shared Function PreferPredefinedTypeKeywordInMemberAccess(expression As ExpressionSyntax, optionSet As OptionSet) As Boolean + Return (IsInMemberAccessContext(expression) OrElse IsInCrefReferenceForPredefinedTypeInMemberAccessContext(expression)) AndAlso + (Not InsideNameOfExpression(expression)) AndAlso + SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, LanguageNames.VisualBasic) + End Function + + Protected Shared Function InsideNameOfExpression(expr As ExpressionSyntax) As Boolean + Dim nameOfExpression = expr.FirstAncestorOrSelf(Of NameOfExpressionSyntax)() + Return nameOfExpression IsNot Nothing + End Function + + ''' + ''' Note: This helper exists solely to work around Bug 1012713. Once it is fixed, this helper must be + ''' deleted in favor of . + ''' Context: Bug 1012713 makes it so that the compiler doesn't support PredefinedType.Member inside crefs + ''' (i.e. System.Int32.MaxValue is supported but Integer.MaxValue isn't). Until this bug is fixed, we don't + ''' support simplifying types names Like System.Int32.MaxValue to Integer.MaxValue. + ''' + Private Shared Function IsInCrefReferenceForPredefinedTypeInMemberAccessContext(expression As ExpressionSyntax) As Boolean + Return (InsideCrefReference(expression) AndAlso Not expression.IsLeftSideOfQualifiedName) + End Function End Class End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb index ef1bf4240887b..2c51a9cd9b161 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb @@ -1,21 +1,345 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +Imports System.Runtime.CompilerServices +Imports System.Runtime.InteropServices Imports System.Threading +Imports Microsoft.CodeAnalysis.CodeStyle Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Rename.ConflictEngine +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Friend Class ExpressionSimplifier Inherits AbstractVisualBasicSimplifier(Of ExpressionSyntax, ExpressionSyntax) - Public Overrides Function TrySimplify(syntax As ExpressionSyntax, + Public Shared ReadOnly Instance As New ExpressionSimplifier() + + Private Sub New() + End Sub + + Public Overrides Function TrySimplify(expression As ExpressionSyntax, semanticModel As SemanticModel, optionSet As OptionSet, ByRef replacementNode As ExpressionSyntax, ByRef issueSpan As TextSpan, cancellationToken As CancellationToken) As Boolean - Throw New NotImplementedException() + If TryReduceExplicitName(expression, semanticModel, replacementNode, issueSpan, optionSet, cancellationToken) Then + Return True + End If + + Return TrySimplify(expression, semanticModel, replacementNode, issueSpan) + End Function + + Private Function TryReduceExplicitName( + expression As ExpressionSyntax, + semanticModel As SemanticModel, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + optionSet As OptionSet, + cancellationToken As CancellationToken + ) As Boolean + replacementNode = Nothing + issueSpan = Nothing + + If expression.Kind = SyntaxKind.SimpleMemberAccessExpression Then + Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax) + Return TryReduce(memberAccess, semanticModel, + replacementNode, + issueSpan, + optionSet, + cancellationToken) + ElseIf TypeOf expression Is NameSyntax Then + Dim name = DirectCast(expression, NameSyntax) + Return NameSimplifier.Instance.TrySimplify( + name, semanticModel, optionSet, + replacementNode, issueSpan, cancellationToken) + End If + + Return False + End Function + + Private Function TryReduce( + memberAccess As MemberAccessExpressionSyntax, + semanticModel As SemanticModel, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + optionSet As OptionSet, + cancellationToken As CancellationToken + ) As Boolean + If memberAccess.Expression Is Nothing OrElse memberAccess.Name Is Nothing Then + Return False + End If + + If memberAccess.HasAnnotations(SpecialTypeAnnotation.Kind) Then + replacementNode = SyntaxFactory.PredefinedType( + SyntaxFactory.Token( + GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(memberAccess.GetAnnotations(SpecialTypeAnnotation.Kind).First())))) _ + .WithLeadingTrivia(memberAccess.GetLeadingTrivia()) + + issueSpan = memberAccess.Span + Return True + End If + + ' See https//github.com/dotnet/roslyn/issues/40974 + ' + ' To be very safe, we only support simplifying code that bound to a symbol without any + ' sort of problems. We could potentially relax this in the future. However, we would + ' need to be very careful about the implications of us offering to fixup 'broken' code + ' in a manner that might end up making things worse Or confusing the user. + Dim symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, memberAccess) + If symbol Is Nothing Then + Return False + End If + + If memberAccess.Expression.IsKind(SyntaxKind.MeExpression) AndAlso + Not SimplificationHelpers.ShouldSimplifyThisOrMeMemberAccessExpression(semanticModel, optionSet, symbol) Then + Return False + End If + + If Not memberAccess.IsRightSideOfDot() Then + Dim aliasReplacement As IAliasSymbol = Nothing + + If TryReplaceWithAlias(memberAccess, semanticModel, aliasReplacement) Then + Dim identifierToken = SyntaxFactory.Identifier( + memberAccess.GetLeadingTrivia(), + aliasReplacement.Name, + memberAccess.GetTrailingTrivia()) + + identifierToken = VisualBasicSimplificationService.TryEscapeIdentifierToken( + identifierToken, + semanticModel) + replacementNode = SyntaxFactory.IdentifierName(identifierToken) + + issueSpan = memberAccess.Span + + ' In case the alias name is the same as the last name of the alias target, we only include + ' the left part of the name in the unnecessary span to Not confuse uses. + If memberAccess.Name.Identifier.ValueText = identifierToken.ValueText Then + issueSpan = memberAccess.Expression.Span + End If + + Return True + End If + + If PreferPredefinedTypeKeywordInMemberAccess(memberAccess, optionSet) Then + If (symbol IsNot Nothing AndAlso symbol.IsKind(SymbolKind.NamedType)) Then + Dim keywordKind = GetPredefinedKeywordKind(DirectCast(symbol, INamedTypeSymbol).SpecialType) + If keywordKind <> SyntaxKind.None Then + replacementNode = SyntaxFactory.PredefinedType( + SyntaxFactory.Token( + memberAccess.GetLeadingTrivia(), + keywordKind, + memberAccess.GetTrailingTrivia())) + + replacementNode = replacementNode.WithAdditionalAnnotations( + New SyntaxAnnotation(NameOf(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess))) + + issueSpan = memberAccess.Span + Return True + End If + End If + End If + End If + + ' a module name was inserted by the name expansion, so removing this should be tried first. + If memberAccess.HasAnnotation(SimplificationHelpers.SimplifyModuleNameAnnotation) Then + If TryOmitModuleName(memberAccess, semanticModel, symbol, replacementNode, issueSpan, cancellationToken) Then + Return True + End If + End If + + replacementNode = memberAccess.GetNameWithTriviaMoved(semanticModel) + issueSpan = memberAccess.Expression.Span + + If CanReplaceWithReducedName(memberAccess, replacementNode, semanticModel, symbol, cancellationToken) Then + Return True + End If + + If TryOmitModuleName(memberAccess, semanticModel, symbol, replacementNode, issueSpan, cancellationToken) Then + Return True + End If + + Return False + End Function + + Private Overloads Function TrySimplify( + expression As ExpressionSyntax, + semanticModel As SemanticModel, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan + ) As Boolean + replacementNode = Nothing + issueSpan = Nothing + + Select Case expression.Kind + Case SyntaxKind.SimpleMemberAccessExpression + If True Then + Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax) + Dim newLeft As ExpressionSyntax = Nothing + If TrySimplifyMemberAccessOrQualifiedName(memberAccess.Expression, memberAccess.Name, semanticModel, newLeft, issueSpan) Then + ' replacement node might not be in it's simplest form, so add simplify annotation to it. + replacementNode = memberAccess.Update(memberAccess.Kind, newLeft, memberAccess.OperatorToken, memberAccess.Name).WithAdditionalAnnotations(Simplifier.Annotation) + + ' Ensure that replacement doesn't change semantics. + Return Not ReplacementChangesSemantics(memberAccess, replacementNode, semanticModel) + End If + + Return False + End If + + Case SyntaxKind.QualifiedName + If True Then + Dim qualifiedName = DirectCast(expression, QualifiedNameSyntax) + Dim newLeft As ExpressionSyntax = Nothing + If TrySimplifyMemberAccessOrQualifiedName(qualifiedName.Left, qualifiedName.Right, semanticModel, newLeft, issueSpan) Then + If Not TypeOf newLeft Is NameSyntax Then + Contract.Fail("QualifiedName Left = " + qualifiedName.Left.ToString() + " and QualifiedName Right = " + qualifiedName.Right.ToString() + " . Left is tried to be replaced with the PredefinedType " + replacementNode.ToString()) + End If + + ' replacement node might not be in it's simplest form, so add simplify annotation to it. + replacementNode = qualifiedName.Update(DirectCast(newLeft, NameSyntax), qualifiedName.DotToken, qualifiedName.Right).WithAdditionalAnnotations(Simplifier.Annotation) + + ' Ensure that replacement doesn't change semantics. + Return Not ReplacementChangesSemantics(qualifiedName, replacementNode, semanticModel) + End If + + Return False + End If + End Select + + Return False + End Function + + Private Function ReplacementChangesSemantics(originalExpression As ExpressionSyntax, replacedExpression As ExpressionSyntax, semanticModel As SemanticModel) As Boolean + Dim speculationAnalyzer = New SpeculationAnalyzer(originalExpression, replacedExpression, semanticModel, CancellationToken.None) + Return speculationAnalyzer.ReplacementChangesSemantics() + End Function + + ' Note: The caller needs to verify that replacement doesn't change semantics of the original expression. + Private Function TrySimplifyMemberAccessOrQualifiedName( + left As ExpressionSyntax, + right As ExpressionSyntax, + semanticModel As SemanticModel, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan + ) As Boolean + replacementNode = Nothing + issueSpan = Nothing + + If left IsNot Nothing AndAlso right IsNot Nothing Then + Dim leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left) + If leftSymbol IsNot Nothing AndAlso leftSymbol.Kind = SymbolKind.NamedType Then + Dim rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right) + If rightSymbol IsNot Nothing AndAlso (rightSymbol.IsStatic OrElse rightSymbol.Kind = SymbolKind.NamedType) Then + ' Static member access or nested type member access. + Dim containingType As INamedTypeSymbol = rightSymbol.ContainingType + Dim isInCref = left.Ancestors(ascendOutOfTrivia:=True).OfType(Of CrefReferenceSyntax)().Any() + + ' Crefs in VB , irrespective of the expression are parsed as QualifiedName (no MemberAccessExpression) + ' Hence the Left can never be a PredefinedType (or anything other than NameSyntax) + If isInCref AndAlso TypeOf rightSymbol Is IMethodSymbol AndAlso Not containingType.SpecialType = SpecialType.None Then + Return False + End If + + If containingType IsNot Nothing AndAlso Not containingType.Equals(leftSymbol) Then + + Dim namedType = TryCast(leftSymbol, INamedTypeSymbol) + If namedType IsNot Nothing AndAlso + containingType.TypeArguments.Length <> 0 Then + Return False + End If + + ' We have a static member access or a nested type member access using a more derived type. + ' Simplify syntax so as to use accessed member's most immediate containing type instead of the derived type. + replacementNode = containingType.GenerateTypeSyntax().WithLeadingTrivia(left.GetLeadingTrivia()).WithTrailingTrivia(left.GetTrailingTrivia()).WithAdditionalAnnotations(Simplifier.Annotation) + issueSpan = left.Span + Return True + End If + End If + End If + End If + + Return False + End Function + + Private Function TryOmitModuleName(memberAccess As MemberAccessExpressionSyntax, + semanticModel As SemanticModel, + symbol As ISymbol, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + cancellationToken As CancellationToken) As Boolean + If memberAccess.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) Then + Dim symbolForMemberAccess = semanticModel.GetSymbolInfo(DirectCast(memberAccess.Parent, MemberAccessExpressionSyntax)).Symbol + If symbolForMemberAccess.IsModuleMember Then + replacementNode = memberAccess.Expression.WithLeadingTrivia(memberAccess.GetLeadingTrivia()) + issueSpan = memberAccess.Name.Span + + Dim parent = DirectCast(memberAccess.Parent, MemberAccessExpressionSyntax) + Dim parentReplacement = parent.ReplaceNode(parent.Expression, replacementNode) + + If CanReplaceWithReducedName(parent, parentReplacement, semanticModel, symbol, cancellationToken) Then + Return True + End If + End If + End If + + Return False + End Function + + Private Shared Function CanReplaceWithReducedName( + memberAccess As MemberAccessExpressionSyntax, + reducedNode As ExpressionSyntax, + semanticModel As SemanticModel, + symbol As ISymbol, + cancellationToken As CancellationToken + ) As Boolean + If Not IsMeOrNamedTypeOrNamespace(memberAccess.Expression, semanticModel) Then + Return False + End If + + ' See if we can simplify a member access expression of the form E.M or E.M() to M or M() + Dim speculationAnalyzer = New SpeculationAnalyzer(memberAccess, reducedNode, semanticModel, cancellationToken) + If Not speculationAnalyzer.SymbolsForOriginalAndReplacedNodesAreCompatible() OrElse + speculationAnalyzer.ReplacementChangesSemantics() Then + Return False + End If + + If memberAccess.Expression.IsKind(SyntaxKind.MyBaseExpression) Then + Dim enclosingNamedType = semanticModel.GetEnclosingNamedType(memberAccess.SpanStart, cancellationToken) + If enclosingNamedType IsNot Nothing AndAlso + Not enclosingNamedType.IsSealed AndAlso + symbol IsNot Nothing AndAlso + symbol.IsOverridable() Then + Return False + End If + End If + + Return True + End Function + + Private Shared Function IsMeOrNamedTypeOrNamespace(expression As ExpressionSyntax, semanticModel As SemanticModel) As Boolean + If expression.Kind = SyntaxKind.MeExpression Then + Return True + End If + + Dim expressionInfo = semanticModel.GetSymbolInfo(expression) + If SimplificationHelpers.IsValidSymbolInfo(expressionInfo.Symbol) Then + If TypeOf expressionInfo.Symbol Is INamespaceOrTypeSymbol Then + Return True + End If + + If expressionInfo.Symbol.IsThisParameter() Then + Return True + End If + End If + + Return False End Function End Class End Namespace diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb new file mode 100644 index 0000000000000..7e06088e2d5d2 --- /dev/null +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb @@ -0,0 +1,464 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Runtime.InteropServices +Imports System.Threading +Imports Microsoft.CodeAnalysis.Options +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.Text +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports System.Runtime.CompilerServices +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeStyle +Imports Microsoft.CodeAnalysis.Rename.ConflictEngine +Imports Microsoft.CodeAnalysis.VisualBasic.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Utilities + +Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers + Friend Class NameSimplifier + Inherits AbstractVisualBasicSimplifier(Of NameSyntax, ExpressionSyntax) + + Public Shared ReadOnly Instance As New NameSimplifier() + + Private Sub New() + End Sub + + Public Overrides Function TrySimplify( + name As NameSyntax, + semanticModel As SemanticModel, + optionSet As OptionSet, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + cancellationToken As CancellationToken) As Boolean + + ' do not simplify names of a namespace declaration + If IsPartOfNamespaceDeclarationName(name) Then + Return False + End If + + ' see whether binding the name binds to a symbol/type. if not, it is ambiguous and + ' nothing we can do here. + Dim symbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, name) + If SimplificationHelpers.IsValidSymbolInfo(symbol) Then + If symbol.Kind = SymbolKind.Method AndAlso symbol.IsConstructor() Then + symbol = symbol.ContainingType + End If + + If symbol.Kind = SymbolKind.Method AndAlso name.Kind = SyntaxKind.GenericName Then + Dim genericName = DirectCast(name, GenericNameSyntax) + replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier).WithLeadingTrivia(genericName.GetLeadingTrivia()).WithTrailingTrivia(genericName.GetTrailingTrivia()) + + issueSpan = genericName.TypeArgumentList.Span + Return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken) + End If + + If Not TypeOf symbol Is INamespaceOrTypeSymbol Then + Return False + End If + Else + Return False + End If + + If name.HasAnnotations(SpecialTypeAnnotation.Kind) Then + replacementNode = SyntaxFactory.PredefinedType( + SyntaxFactory.Token(name.GetLeadingTrivia(), + GetPredefinedKeywordKind(SpecialTypeAnnotation.GetSpecialType(name.GetAnnotations(SpecialTypeAnnotation.Kind).First())), + name.GetTrailingTrivia())) + + issueSpan = name.Span + + Return CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) + Else + + If Not name.IsRightSideOfDot() Then + + Dim aliasReplacement As IAliasSymbol = Nothing + If TryReplaceWithAlias(name, semanticModel, aliasReplacement) Then + Dim identifierToken = SyntaxFactory.Identifier( + name.GetLeadingTrivia(), + aliasReplacement.Name, + name.GetTrailingTrivia()) + + identifierToken = VisualBasicSimplificationService.TryEscapeIdentifierToken( + identifierToken, + semanticModel) + + replacementNode = SyntaxFactory.IdentifierName(identifierToken) + + Dim annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(RenameAnnotation.Kind) + For Each annotatedNodeOrToken In annotatedNodesOrTokens + If annotatedNodeOrToken.IsToken Then + identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken) + Else + replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode) + End If + Next + + annotatedNodesOrTokens = name.GetAnnotatedNodesAndTokens(AliasAnnotation.Kind) + For Each annotatedNodeOrToken In annotatedNodesOrTokens + If annotatedNodeOrToken.IsToken Then + identifierToken = annotatedNodeOrToken.AsToken().CopyAnnotationsTo(identifierToken) + Else + replacementNode = annotatedNodeOrToken.AsNode().CopyAnnotationsTo(replacementNode) + End If + Next + + replacementNode = DirectCast(replacementNode, SimpleNameSyntax).WithIdentifier(identifierToken) + issueSpan = name.Span + + ' In case the alias name is the same as the last name of the alias target, we only include + ' the left part of the name in the unnecessary span to Not confuse uses. + If name.Kind = SyntaxKind.QualifiedName Then + Dim qualifiedName As QualifiedNameSyntax = DirectCast(name, QualifiedNameSyntax) + + If qualifiedName.Right.Identifier.ValueText = identifierToken.ValueText Then + issueSpan = qualifiedName.Left.Span + End If + End If + + If CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) Then + + ' check if the alias name ends with an Attribute suffix that can be omitted. + Dim replacementNodeWithoutAttributeSuffix As ExpressionSyntax = Nothing + Dim issueSpanWithoutAttributeSuffix As TextSpan = Nothing + If TryReduceAttributeSuffix(name, identifierToken, semanticModel, aliasReplacement IsNot Nothing, replacementNodeWithoutAttributeSuffix, issueSpanWithoutAttributeSuffix, cancellationToken) Then + If CanReplaceWithReducedName(name, replacementNodeWithoutAttributeSuffix, semanticModel, cancellationToken) Then + replacementNode = replacementNode.CopyAnnotationsTo(replacementNodeWithoutAttributeSuffix) + issueSpan = issueSpanWithoutAttributeSuffix + End If + End If + + Return True + End If + + Return False + End If + + Dim nameHasNoAlias = False + + If TypeOf name Is SimpleNameSyntax Then + Dim simpleName = DirectCast(name, SimpleNameSyntax) + If Not simpleName.Identifier.HasAnnotations(AliasAnnotation.Kind) Then + nameHasNoAlias = True + End If + End If + + If TypeOf name Is QualifiedNameSyntax Then + Dim qualifiedNameSyntax = DirectCast(name, QualifiedNameSyntax) + If Not qualifiedNameSyntax.Right.Identifier.HasAnnotations(AliasAnnotation.Kind) Then + nameHasNoAlias = True + End If + End If + + Dim aliasInfo = semanticModel.GetAliasInfo(name, cancellationToken) + + ' Don't simplify to predefined type if name is part of a QualifiedName. + ' QualifiedNames can't contain PredefinedTypeNames (although MemberAccessExpressions can). + ' In other words, the left side of a QualifiedName can't be a PredefinedTypeName. + If nameHasNoAlias AndAlso aliasInfo Is Nothing AndAlso Not name.Parent.IsKind(SyntaxKind.QualifiedName) Then + Dim type = semanticModel.GetTypeInfo(name).Type + If type IsNot Nothing Then + Dim keywordKind = GetPredefinedKeywordKind(type.SpecialType) + If keywordKind <> SyntaxKind.None Then + ' But do simplify to predefined type if not simplifying results in just the addition of escaping + ' brackets. E.g., even if specified otherwise, prefer `String` to `[String]`. + Dim token = SyntaxFactory.Token( + name.GetLeadingTrivia(), + keywordKind, + name.GetTrailingTrivia()) + Dim valueText = TryCast(name, IdentifierNameSyntax)?.Identifier.ValueText + Dim inDeclarationContext = PreferPredefinedTypeKeywordInDeclarations(name, optionSet) + Dim inMemberAccessContext = PreferPredefinedTypeKeywordInMemberAccess(name, optionSet) + If token.Text = valueText OrElse (inDeclarationContext OrElse inMemberAccessContext) Then + + Dim codeStyleOptionName As String = Nothing + If inDeclarationContext Then + codeStyleOptionName = NameOf(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration) + ElseIf inMemberAccessContext Then + codeStyleOptionName = NameOf(CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess) + End If + + replacementNode = SyntaxFactory.PredefinedType(token) + issueSpan = name.Span + + Dim canReplace = CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) + If canReplace Then + replacementNode = replacementNode.WithAdditionalAnnotations(New SyntaxAnnotation(codeStyleOptionName)) + End If + + Return canReplace + End If + End If + End If + End If + + ' Nullable rewrite: Nullable(Of Integer) -> Integer? + ' Don't rewrite in the case where Nullable(Of Integer) is part of some qualified name like Nullable(Of Integer).Something + If (symbol.Kind = SymbolKind.NamedType) AndAlso (Not name.IsLeftSideOfQualifiedName) Then + Dim type = DirectCast(symbol, INamedTypeSymbol) + If aliasInfo Is Nothing AndAlso CanSimplifyNullable(type, name) Then + Dim genericName As GenericNameSyntax + If name.Kind = SyntaxKind.QualifiedName Then + genericName = DirectCast(DirectCast(name, QualifiedNameSyntax).Right, GenericNameSyntax) + Else + genericName = DirectCast(name, GenericNameSyntax) + End If + + Dim oldType = genericName.TypeArgumentList.Arguments.First() + replacementNode = SyntaxFactory.NullableType(oldType).WithLeadingTrivia(name.GetLeadingTrivia()) + + issueSpan = name.Span + + If CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) Then + Return True + End If + End If + End If + End If + + Select Case name.Kind + Case SyntaxKind.QualifiedName + ' a module name was inserted by the name expansion, so removing this should be tried first. + Dim qualifiedName = DirectCast(name, QualifiedNameSyntax) + If qualifiedName.HasAnnotation(SimplificationHelpers.SimplifyModuleNameAnnotation) Then + If TryOmitModuleName(qualifiedName, semanticModel, replacementNode, issueSpan, cancellationToken) Then + Return True + End If + End If + + replacementNode = qualifiedName.Right.WithLeadingTrivia(name.GetLeadingTrivia()) + replacementNode = DirectCast(replacementNode, SimpleNameSyntax) _ + .WithIdentifier(VisualBasicSimplificationService.TryEscapeIdentifierToken( + DirectCast(replacementNode, SimpleNameSyntax).Identifier, + semanticModel)) + issueSpan = qualifiedName.Left.Span + + + If CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken) Then + Return True + End If + + If TryOmitModuleName(qualifiedName, semanticModel, replacementNode, issueSpan, cancellationToken) Then + Return True + End If + + Case SyntaxKind.IdentifierName + Dim identifier = DirectCast(name, IdentifierNameSyntax).Identifier + TryReduceAttributeSuffix(name, identifier, semanticModel, False, replacementNode, issueSpan, cancellationToken) + End Select + End If + + If replacementNode Is Nothing Then + Return False + End If + + Return CanReplaceWithReducedName(name, replacementNode, semanticModel, cancellationToken) + End Function + + ''' + ''' Checks if the SyntaxNode is a name of a namespace declaration. To be a namespace name, the syntax + ''' must be parented by an namespace declaration and the node itself must be equal to the declaration's Name + ''' property. + ''' + Private Function IsPartOfNamespaceDeclarationName(node As SyntaxNode) As Boolean + + Dim nextNode As SyntaxNode = node + + Do While nextNode IsNot Nothing + + Select Case nextNode.Kind + + Case SyntaxKind.IdentifierName, SyntaxKind.QualifiedName + node = nextNode + nextNode = nextNode.Parent + + Case SyntaxKind.NamespaceStatement + Dim namespaceStatement = DirectCast(nextNode, NamespaceStatementSyntax) + Return namespaceStatement.Name Is node + + Case Else + Return False + + End Select + + Loop + + Return False + End Function + + Private Function CanReplaceWithReducedName( + name As NameSyntax, + replacementNode As ExpressionSyntax, + semanticModel As SemanticModel, + cancellationToken As CancellationToken + ) As Boolean + Dim speculationAnalyzer = New SpeculationAnalyzer(name, replacementNode, semanticModel, cancellationToken) + If speculationAnalyzer.ReplacementChangesSemantics() Then + Return False + End If + + Return CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) + End Function + + Private Function CanReplaceWithReducedNameInContext(name As NameSyntax, replacementNode As ExpressionSyntax, semanticModel As SemanticModel, cancellationToken As CancellationToken) As Boolean + + ' Special case. if this new minimal name parses out to a predefined type, then we + ' have to make sure that we're not in a using alias. That's the one place where the + ' language doesn't allow predefined types. You have to use the fully qualified name + ' instead. + Dim invalidTransformation1 = IsNonNameSyntaxInImportsDirective(name, replacementNode) + Dim invalidTransformation2 = IsReservedNameInAttribute(name, replacementNode) + + Dim invalidTransformation3 = IsNullableTypeSyntaxLeftOfDotInMemberAccess(name, replacementNode) + + Return Not (invalidTransformation1 OrElse invalidTransformation2 OrElse invalidTransformation3) + End Function + + Private Function IsNonNameSyntaxInImportsDirective(expression As ExpressionSyntax, simplifiedNode As ExpressionSyntax) As Boolean + Return TypeOf expression.Parent Is ImportsClauseSyntax AndAlso + Not TypeOf simplifiedNode Is NameSyntax + End Function + + Private Function IsNullableTypeSyntaxLeftOfDotInMemberAccess(expression As ExpressionSyntax, simplifiedNode As ExpressionSyntax) As Boolean + Return expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso + simplifiedNode.Kind = SyntaxKind.NullableType + End Function + + Private Function TryOmitModuleName(name As QualifiedNameSyntax, semanticModel As SemanticModel, ByRef replacementNode As ExpressionSyntax, ByRef issueSpan As TextSpan, cancellationToken As CancellationToken) As Boolean + If name.IsParentKind(SyntaxKind.QualifiedName) Then + Dim symbolForName = semanticModel.GetSymbolInfo(DirectCast(name.Parent, QualifiedNameSyntax)).Symbol + + ' in case this QN is used in a "New NSName.ModuleName.MemberName()" expression + ' the returned symbol is a constructor. Then we need to get the containing type. + If symbolForName.IsConstructor Then + symbolForName = symbolForName.ContainingType + End If + + If symbolForName.IsModuleMember Then + + replacementNode = name.Left.WithLeadingTrivia(name.GetLeadingTrivia()) + issueSpan = name.Right.Span + + Dim parent = DirectCast(name.Parent, QualifiedNameSyntax) + Dim parentReplacement = parent.ReplaceNode(parent.Left, replacementNode) + + If CanReplaceWithReducedName(parent, parentReplacement, semanticModel, cancellationToken) Then + Return True + End If + End If + End If + + Return False + End Function + + Private Shared Function TryReduceAttributeSuffix( + name As NameSyntax, + identifierToken As SyntaxToken, + semanticModel As SemanticModel, + isIdentifierNameFromAlias As Boolean, + ByRef replacementNode As ExpressionSyntax, + ByRef issueSpan As TextSpan, + cancellationToken As CancellationToken + ) As Boolean + If SyntaxFacts.IsAttributeName(name) AndAlso Not isIdentifierNameFromAlias Then + + ' When the replacement is an Alias we don't want the "Attribute" Suffix to be removed because this will result in symbol change + Dim aliasSymbol = semanticModel.GetAliasInfo(name, cancellationToken) + If aliasSymbol IsNot Nothing AndAlso + String.Compare(aliasSymbol.Name, identifierToken.ValueText, StringComparison.OrdinalIgnoreCase) = 0 Then + Return False + End If + + If name.Parent.Kind = SyntaxKind.Attribute OrElse name.IsRightSideOfDot() Then + Dim newIdentifierText = String.Empty + + ' an attribute that should keep it (unnecessary "Attribute" suffix should be annotated with a DontSimplifyAnnotation + If identifierToken.HasAnnotation(SimplificationHelpers.DontSimplifyAnnotation) Then + newIdentifierText = identifierToken.ValueText + "Attribute" + ElseIf identifierToken.ValueText.TryReduceAttributeSuffix(newIdentifierText) Then + issueSpan = New TextSpan(name.Span.End - 9, 9) + Else + Return False + End If + + ' escape it (VB allows escaping even for abbreviated identifiers, C# does not!) + Dim newIdentifierToken = identifierToken.CopyAnnotationsTo( + SyntaxFactory.Identifier( + identifierToken.LeadingTrivia, + newIdentifierText, + identifierToken.TrailingTrivia)) + newIdentifierToken = VisualBasicSimplificationService.TryEscapeIdentifierToken(newIdentifierToken, semanticModel) + replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken).WithLeadingTrivia(name.GetLeadingTrivia()) + Return True + End If + End If + + Return False + End Function + + Private Function PreferPredefinedTypeKeywordInDeclarations(name As NameSyntax, optionSet As OptionSet) As Boolean + Return (Not IsInMemberAccessContext(name)) AndAlso + (Not InsideCrefReference(name)) AndAlso + (Not InsideNameOfExpression(name)) AndAlso + SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, LanguageNames.VisualBasic) + End Function + + Private Shared Function CanSimplifyNullable(type As INamedTypeSymbol, name As NameSyntax) As Boolean + If Not type.IsNullable Then + Return False + End If + + If type.IsUnboundGenericType Then + ' Don't simplify unbound generic type "Nullable(Of )". + Return False + End If + + If InsideNameOfExpression(name) Then + ' Nullable(Of T) can't be simplified to T? in nameof expressions. + Return False + End If + + If Not InsideCrefReference(name) Then + ' Nullable(Of T) can always be simplified to T? outside crefs. + Return True + End If + + ' Inside crefs, if the T in this Nullable(Of T) is being declared right here + ' then this Nullable(Of T) is not a constructed generic type and we should + ' not offer to simplify this to T?. + ' + ' For example, we should not offer the simplification in the following cases where + ' T does not bind to an existing type / type parameter in the user's code. + ' - + ' - + ' + ' And we should offer the simplification in the following cases where SomeType and + ' SomeMethod bind to a type and method declared elsewhere in the users code. + ' - + + If name.IsKind(SyntaxKind.GenericName) Then + If (name.IsParentKind(SyntaxKind.CrefReference)) OrElse ' cref="Nullable(Of T)" + (name.IsParentKind(SyntaxKind.QualifiedName) AndAlso name.Parent?.IsParentKind(SyntaxKind.CrefReference)) OrElse ' cref="System.Nullable(Of T)" + (name.IsParentKind(SyntaxKind.QualifiedName) AndAlso (name.Parent?.IsParentKind(SyntaxKind.QualifiedName)).GetValueOrDefault() AndAlso name.Parent.Parent?.IsParentKind(SyntaxKind.CrefReference)) Then ' cref="System.Nullable(Of T).Value" + ' Unfortunately, unlike in corresponding C# case, we need syntax based checking to detect these cases because of bugs in the VB SemanticModel. + ' See https://github.com/dotnet/roslyn/issues/2196, https://github.com/dotnet/roslyn/issues/2197 + Return False + End If + End If + + Dim argument = type.TypeArguments.SingleOrDefault() + If argument Is Nothing OrElse argument.IsErrorType() Then + Return False + End If + + Dim argumentDecl = argument.DeclaringSyntaxReferences.FirstOrDefault() + If argumentDecl Is Nothing Then + ' The type argument is a type from metadata - so this is a constructed generic nullable type that can be simplified (e.g. Nullable(Of Integer)). + Return True + End If + + Return Not name.Span.Contains(argumentDecl.Span) + End Function + + End Class +End Namespace From d35c4738021495c8f7e849a9c0a065ba6d176015 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 14:34:01 -0800 Subject: [PATCH 09/14] Remove unused imports --- .../Extensions/ExpressionSyntaxExtensions.vb | 5 ----- .../Simplifiers/AbstractVisualBasicSimplifier.vb | 12 ++---------- .../Simplifiers/ExpressionSimplifier.vb | 6 +----- .../Simplification/Simplifiers/NameSimplifier.vb | 10 ++++------ 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb index 5c559464b70e9..8d0928423eb46 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb @@ -4,13 +4,8 @@ Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices Imports System.Threading Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeStyle -Imports Microsoft.CodeAnalysis.Options -Imports Microsoft.CodeAnalysis.Rename.ConflictEngine Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Simplification -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Utilities diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb index 51059adf21cde..0844f98f41119 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb @@ -1,19 +1,11 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. Imports System.Runtime.InteropServices +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Simplification.Simplifiers Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports System.Runtime.CompilerServices -Imports System.Threading -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeStyle -Imports Microsoft.CodeAnalysis.Options -Imports Microsoft.CodeAnalysis.Rename.ConflictEngine -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Simplification -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols -Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Friend MustInherit Class AbstractVisualBasicSimplifier(Of TSyntax As SyntaxNode, TSimplifiedSyntax As SyntaxNode) diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb index 2c51a9cd9b161..aa743a61f334d 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/ExpressionSimplifier.vb @@ -1,17 +1,13 @@ ' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -Imports System.Runtime.CompilerServices Imports System.Runtime.InteropServices Imports System.Threading +Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeStyle Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Simplification Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.Rename.ConflictEngine -Imports Microsoft.CodeAnalysis.VisualBasic.Simplification -Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb index 7e06088e2d5d2..4ff15ff76a191 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb @@ -2,16 +2,14 @@ Imports System.Runtime.InteropServices Imports System.Threading -Imports Microsoft.CodeAnalysis.Options -Imports Microsoft.CodeAnalysis.Simplification -Imports Microsoft.CodeAnalysis.Text -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax -Imports System.Runtime.CompilerServices Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeStyle +Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Rename.ConflictEngine -Imports Microsoft.CodeAnalysis.VisualBasic.Simplification +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Symbols +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.CodeAnalysis.VisualBasic.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers From d021fbbb304b378e2afb69c483b9a60579eb8cba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 14:35:11 -0800 Subject: [PATCH 10/14] Move helper --- .../Extensions/ExpressionSyntaxExtensions.vb | 16 ---------------- .../AbstractVisualBasicSimplifier.vb | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb index 8d0928423eb46..e8c5524d90dd8 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb @@ -55,22 +55,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions result) End Function - - Public Function IsAliasReplaceableExpression(expression As ExpressionSyntax) As Boolean - If expression.Kind = SyntaxKind.IdentifierName OrElse - expression.Kind = SyntaxKind.QualifiedName Then - Return True - End If - - If expression.Kind = SyntaxKind.SimpleMemberAccessExpression Then - Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax) - - Return memberAccess.Expression IsNot Nothing AndAlso memberAccess.Expression.IsAliasReplaceableExpression() - End If - - Return False - End Function - Public Function IsMemberAccessExpressionName(expression As ExpressionSyntax) As Boolean Return expression.IsParentKind(SyntaxKind.SimpleMemberAccessExpression) AndAlso diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb index 0844f98f41119..07c2e2f84607a 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb @@ -61,7 +61,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers ByRef aliasReplacement As IAliasSymbol) As Boolean aliasReplacement = Nothing - If Not node.IsAliasReplaceableExpression() Then + If Not IsAliasReplaceableExpression(node) Then Return False End If @@ -128,6 +128,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Return False End Function + Private Shared Function IsAliasReplaceableExpression(expression As ExpressionSyntax) As Boolean + If expression.Kind = SyntaxKind.IdentifierName OrElse + expression.Kind = SyntaxKind.QualifiedName Then + Return True + End If + + If expression.Kind = SyntaxKind.SimpleMemberAccessExpression Then + Dim memberAccess = DirectCast(expression, MemberAccessExpressionSyntax) + + Return memberAccess.Expression IsNot Nothing AndAlso IsAliasReplaceableExpression(memberAccess.Expression) + End If + + Return False + End Function + ' We must verify that the alias actually binds back to the thing it's aliasing. ' It's possible there is another symbol with the same name as the alias that binds first Private Shared Function ValidateAliasForTarget(aliasReplacement As IAliasSymbol, semanticModel As SemanticModel, node As ExpressionSyntax, symbol As ISymbol) As Boolean From 2379649c0da1da58c2d17c0fd5b0accfc2686a56 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 14:39:02 -0800 Subject: [PATCH 11/14] Match name --- .../VisualBasicPreferFrameworkTypeDiagnosticAnalyzer.vb | 2 +- .../Portable/Extensions/ExpressionSyntaxExtensions.vb | 2 +- .../Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb | 2 +- .../Portable/Simplification/Simplifiers/NameSimplifier.vb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicPreferFrameworkTypeDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicPreferFrameworkTypeDiagnosticAnalyzer.vb index 9c04b7e7d15ee..44b0b3e238148 100644 --- a/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicPreferFrameworkTypeDiagnosticAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/Diagnostics/Analyzers/VisualBasicPreferFrameworkTypeDiagnosticAnalyzer.vb @@ -14,7 +14,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Diagnostics.Analyzers ImmutableArray.Create(SyntaxKind.PredefinedType) Protected Overrides Function IsInMemberAccessOrCrefReferenceContext(node As ExpressionSyntax) As Boolean - Return node.IsInMemberAccessContext() OrElse node.InsideCrefReference() + Return node.IsDirectChildOfMemberAccessExpression() OrElse node.InsideCrefReference() End Function Protected Overrides Function IsPredefinedTypeReplaceableWithFrameworkType(node As PredefinedTypeSyntax) As Boolean diff --git a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb index e8c5524d90dd8..9c9c44e28a526 100644 --- a/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb +++ b/src/Workspaces/VisualBasic/Portable/Extensions/ExpressionSyntaxExtensions.vb @@ -770,7 +770,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions End Function - Public Function IsInMemberAccessContext(expression As ExpressionSyntax) As Boolean + Public Function IsDirectChildOfMemberAccessExpression(expression As ExpressionSyntax) As Boolean Return TypeOf expression?.Parent Is MemberAccessExpressionSyntax End Function diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb index 07c2e2f84607a..e2cb9deeb1bbf 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/AbstractVisualBasicSimplifier.vb @@ -178,7 +178,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers End Function Protected Shared Function PreferPredefinedTypeKeywordInMemberAccess(expression As ExpressionSyntax, optionSet As OptionSet) As Boolean - Return (IsInMemberAccessContext(expression) OrElse IsInCrefReferenceForPredefinedTypeInMemberAccessContext(expression)) AndAlso + Return (IsDirectChildOfMemberAccessExpression(expression) OrElse IsInCrefReferenceForPredefinedTypeInMemberAccessContext(expression)) AndAlso (Not InsideNameOfExpression(expression)) AndAlso SimplificationHelpers.PreferPredefinedTypeKeywordInMemberAccess(optionSet, LanguageNames.VisualBasic) End Function diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb index 4ff15ff76a191..af929a0a86dc1 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb @@ -395,7 +395,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers End Function Private Function PreferPredefinedTypeKeywordInDeclarations(name As NameSyntax, optionSet As OptionSet) As Boolean - Return (Not IsInMemberAccessContext(name)) AndAlso + Return (Not IsDirectChildOfMemberAccessExpression(name)) AndAlso (Not InsideCrefReference(name)) AndAlso (Not InsideNameOfExpression(name)) AndAlso SimplificationHelpers.PreferPredefinedTypeKeywordInDeclarations(optionSet, LanguageNames.VisualBasic) From 3fe6a72ecbacb873477e569f281759717d3d8de6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 14:46:46 -0800 Subject: [PATCH 12/14] Simplify --- .../Simplifiers/NameSimplifier.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs index 3d5123d3521ab..abb6bf95fc516 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs @@ -81,8 +81,8 @@ public override bool TrySimplify( { var genericName = (GenericNameSyntax)name; replacementNode = SyntaxFactory.IdentifierName(genericName.Identifier) - .WithLeadingTrivia(genericName.GetLeadingTrivia()) - .WithTrailingTrivia(genericName.GetTrailingTrivia()); + .WithLeadingTrivia(genericName.GetLeadingTrivia()) + .WithTrailingTrivia(genericName.GetTrailingTrivia()); issueSpan = genericName.TypeArgumentList.Span; return CanReplaceWithReducedName( @@ -294,8 +294,8 @@ public override bool TrySimplify( } replacementNode = SyntaxFactory.NullableType(oldType) - .WithLeadingTrivia(name.GetLeadingTrivia()) - .WithTrailingTrivia(name.GetTrailingTrivia()); + .WithLeadingTrivia(name.GetLeadingTrivia()) + .WithTrailingTrivia(name.GetTrailingTrivia()); issueSpan = name.Span; // we need to simplify the whole qualified name at once, because replacing the identifier on the left in @@ -314,9 +314,9 @@ public override bool TrySimplify( { case SyntaxKind.AliasQualifiedName: var simpleName = ((AliasQualifiedNameSyntax)name).Name - .WithLeadingTrivia(name.GetLeadingTrivia()); + .WithLeadingTrivia(name.GetLeadingTrivia()); - simpleName = simpleName.ReplaceToken(simpleName.Identifier, + simpleName = simpleName.ReplaceToken(simpleName.Identifier, ((AliasQualifiedNameSyntax)name).Name.Identifier.CopyAnnotationsTo( simpleName.Identifier.WithLeadingTrivia( ((AliasQualifiedNameSyntax)name).Alias.Identifier.LeadingTrivia))); @@ -328,7 +328,7 @@ public override bool TrySimplify( break; case SyntaxKind.QualifiedName: - replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); + replacementNode = ((QualifiedNameSyntax)name).Right.WithLeadingTrivia(name.GetLeadingTrivia()); issueSpan = ((QualifiedNameSyntax)name).Left.Span; break; @@ -465,7 +465,7 @@ private static bool CanReplaceWithPredefinedTypeKeywordInContext( if (canReduce) { - replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName)); + replacementNode = replacementNode.WithAdditionalAnnotations(new SyntaxAnnotation(codeStyleOptionName)); } return canReduce; @@ -511,7 +511,7 @@ private static bool TryReduceAttributeSuffix( identifierToken.TrailingTrivia)); replacementNode = SyntaxFactory.IdentifierName(newIdentifierToken) - .WithLeadingTrivia(name.GetLeadingTrivia()); + .WithLeadingTrivia(name.GetLeadingTrivia()); issueSpan = new TextSpan(identifierToken.Span.End - 9, 9); return true; From a3d791de505107a4de06263e2deda72ce4f54075 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 18 Jan 2020 17:14:40 -0800 Subject: [PATCH 13/14] merge --- .../Simplifiers/AbstractCSharpSimplifier.cs | 5 ++-- .../Simplifiers/ExpressionSimplifier.cs | 2 +- .../Simplifiers/NameSimplifier.cs | 26 +++++++------------ .../VisualBasicVariableDeclaratorReducer.vb | 2 +- .../Simplifiers/NameSimplifier.vb | 12 ++++----- 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs index 9243d4b601f04..ba1c8c6028d5b 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/AbstractCSharpSimplifier.cs @@ -391,9 +391,10 @@ protected static bool InsideNameOfExpression(ExpressionSyntax expression, Semant var nameOfInvocationExpr = expression.FirstAncestorOrSelf( invocationExpr => { - return (invocationExpr.Expression is IdentifierNameSyntax identifierName) && (identifierName.Identifier.Text == "nameof") && + return invocationExpr.Expression is IdentifierNameSyntax identifierName && + identifierName.Identifier.Text == "nameof" && semanticModel.GetConstantValue(invocationExpr).HasValue && - (semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String); + semanticModel.GetTypeInfo(invocationExpr).Type.SpecialType == SpecialType.System_String; }); return nameOfInvocationExpr != null; diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs index b4f46b24bea7b..507043bced2ac 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/ExpressionSimplifier.cs @@ -418,7 +418,7 @@ private static bool TrySimplifyMemberAccessOrQualifiedName( if (left != null && right != null) { var leftSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, left); - if (leftSymbol != null && (leftSymbol.Kind == SymbolKind.NamedType)) + if (leftSymbol != null && leftSymbol.Kind == SymbolKind.NamedType) { var rightSymbol = SimplificationHelpers.GetOriginalSymbolInfo(semanticModel, right); if (rightSymbol != null && (rightSymbol.IsStatic || rightSymbol.Kind == SymbolKind.NamedType)) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs index abb6bf95fc516..e8e1e07f94ef6 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs @@ -272,7 +272,7 @@ public override bool TrySimplify( // Nullable rewrite: Nullable -> int? // Don't rewrite in the case where Nullable is part of some qualified name like Nullable.Something - if (!name.IsVar && (symbol.Kind == SymbolKind.NamedType) && !name.IsLeftSideOfQualifiedName()) + if (!name.IsVar && symbol.Kind == SymbolKind.NamedType && !name.IsLeftSideOfQualifiedName()) { var type = (INamedTypeSymbol)symbol; if (aliasInfo == null && CanSimplifyNullable(type, name, semanticModel)) @@ -567,7 +567,7 @@ public static bool CanReplaceWithReducedNameInContext( IsAmbiguousCast(name, reducedName) || IsNullableTypeInPointerExpression(reducedName) || IsNotNullableReplaceable(name, reducedName) || - IsNonReducableQualifiedNameInUsingDirective(semanticModel, name, reducedName)) + IsNonReducableQualifiedNameInUsingDirective(semanticModel, name)) { return false; } @@ -604,23 +604,15 @@ private static bool CanReplaceWithReducedName(NameSyntax name, TypeSyntax reduce private static bool IsNotNullableReplaceable(NameSyntax name, TypeSyntax reducedName) { - var isNotNullableReplaceable = false; - var isLeftSideOfDot = name.IsLeftSideOfDot(); - var isRightSideOfDot = name.IsRightSideOfDot(); - - if (reducedName.Kind() == SyntaxKind.NullableType) + if (reducedName.IsKind(SyntaxKind.NullableType, out NullableTypeSyntax nullableType)) { - if (((NullableTypeSyntax)reducedName).ElementType.Kind() == SyntaxKind.OmittedTypeArgument) - { - isNotNullableReplaceable = true; - } - else - { - isNotNullableReplaceable = name.IsLeftSideOfDot() || name.IsRightSideOfDot(); - } + if (nullableType.ElementType.Kind() == SyntaxKind.OmittedTypeArgument) + return true; + + return name.IsLeftSideOfDot() || name.IsRightSideOfDot(); } - return isNotNullableReplaceable; + return false; } private static bool IsNullableTypeInPointerExpression(ExpressionSyntax simplifiedNode) @@ -665,7 +657,7 @@ private static bool IsAmbiguousCast(ExpressionSyntax expression, ExpressionSynta return false; } - private static bool IsNonReducableQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name, TypeSyntax reducedName) + private static bool IsNonReducableQualifiedNameInUsingDirective(SemanticModel model, NameSyntax name) { // Whereas most of the time we do not want to reduce namespace names, We will // make an exception for namespaces with the global:: alias. diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb index a78ed88fe5027..5b294be8cc65b 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Reducers/VisualBasicVariableDeclaratorReducer.vb @@ -82,7 +82,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification Return False End If - Dim initializerType As ITypeSymbol = Nothing + Dim initializerType As ITypeSymbol If declaredSymbolType.IsArrayType() AndAlso variableDeclarator.Initializer.Value.Kind() = SyntaxKind.CollectionInitializer Then ' Get type of the array literal in context without the target type diff --git a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb index af929a0a86dc1..d4ca3b008b497 100644 --- a/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb +++ b/src/Workspaces/VisualBasic/Portable/Simplification/Simplifiers/NameSimplifier.vb @@ -65,7 +65,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers issueSpan = name.Span - Return CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) + Return CanReplaceWithReducedNameInContext(name, replacementNode) Else If Not name.IsRightSideOfDot() Then @@ -114,7 +114,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers End If End If - If CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) Then + If CanReplaceWithReducedNameInContext(name, replacementNode) Then ' check if the alias name ends with an Attribute suffix that can be omitted. Dim replacementNodeWithoutAttributeSuffix As ExpressionSyntax = Nothing @@ -179,7 +179,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers replacementNode = SyntaxFactory.PredefinedType(token) issueSpan = name.Span - Dim canReplace = CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) + Dim canReplace = CanReplaceWithReducedNameInContext(name, replacementNode) If canReplace Then replacementNode = replacementNode.WithAdditionalAnnotations(New SyntaxAnnotation(codeStyleOptionName)) End If @@ -207,7 +207,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers issueSpan = name.Span - If CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) Then + If CanReplaceWithReducedNameInContext(name, replacementNode) Then Return True End If End If @@ -295,10 +295,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Simplification.Simplifiers Return False End If - Return CanReplaceWithReducedNameInContext(name, replacementNode, semanticModel, cancellationToken) + Return CanReplaceWithReducedNameInContext(name, replacementNode) End Function - Private Function CanReplaceWithReducedNameInContext(name As NameSyntax, replacementNode As ExpressionSyntax, semanticModel As SemanticModel, cancellationToken As CancellationToken) As Boolean + Private Function CanReplaceWithReducedNameInContext(name As NameSyntax, replacementNode As ExpressionSyntax) As Boolean ' Special case. if this new minimal name parses out to a predefined type, then we ' have to make sure that we're not in a using alias. That's the one place where the From 6f342c99abe28797055b2ad2fc68980acd972894 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 22 Jan 2020 17:23:51 -0800 Subject: [PATCH 14/14] Fixup --- .../Portable/Simplification/Simplifiers/NameSimplifier.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs index e8e1e07f94ef6..41b54e95ce3aa 100644 --- a/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs +++ b/src/Workspaces/CSharp/Portable/Simplification/Simplifiers/NameSimplifier.cs @@ -17,6 +17,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers { + using Microsoft.CodeAnalysis.Rename.ConflictEngine; + internal class NameSimplifier : AbstractCSharpSimplifier { public static readonly NameSimplifier Instance = new NameSimplifier();