diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs index 6b3d737e90..b4a00e9dcc 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocations.Fixer.cs @@ -1,19 +1,24 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; +using Analyzer.Utilities.Lightup; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; using Microsoft.NetCore.Analyzers.Runtime; +using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation; namespace Microsoft.NetCore.CSharp.Analyzers.Runtime { [ExportCodeFixProvider(LanguageNames.CSharp)] - public sealed class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer + public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer : ForwardCancellationTokenToInvocationsFixer { protected override bool TryGetInvocation( SemanticModel model, @@ -46,18 +51,50 @@ protected override SyntaxNode GetConditionalOperationInvocationExpression(Syntax protected override bool TryGetExpressionAndArguments( SyntaxNode invocationNode, [NotNullWhen(returnValue: true)] out SyntaxNode? expression, - out ImmutableArray arguments) + out ImmutableArray arguments) { if (invocationNode is InvocationExpressionSyntax invocationExpression) { expression = invocationExpression.Expression; - arguments = ImmutableArray.CreateRange(invocationExpression.ArgumentList.Arguments); + arguments = invocationExpression.ArgumentList.Arguments.ToImmutableArray(); return true; } expression = null; - arguments = ImmutableArray.Empty; + arguments = ImmutableArray.Empty; return false; } + + protected override SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type) + { + var typeName = TypeNameVisitor.GetTypeSyntaxForSymbol(type.ElementType); + if (type.ElementType.IsReferenceType) + { + var additionalAnnotation = type.NullableAnnotation() switch + { + NullableAnnotation.None => NullableSyntaxAnnotationEx.Oblivious, + NullableAnnotation.Annotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated, + NullableAnnotation.NotAnnotated => NullableSyntaxAnnotationEx.AnnotatedOrNotAnnotated, + _ => null, + }; + + if (additionalAnnotation is not null) + { + typeName = typeName.WithAdditionalAnnotations(additionalAnnotation); + } + } + + return typeName; + } + + protected override IEnumerable GetExpressions(ImmutableArray newArguments) + { + return newArguments.Select(x => x.Expression); + } + + protected override SyntaxNode GetArrayCreationExpression(SyntaxGenerator generator, SyntaxNode typeSyntax, IEnumerable expressions) + { + return generator.ArrayCreationExpression(typeSyntax, expressions); + } } } diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs new file mode 100644 index 0000000000..a5d74c0759 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.cs @@ -0,0 +1,233 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities.Lightup; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Simplification; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; +using NullableAnnotation = Analyzer.Utilities.Lightup.NullableAnnotation; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + public sealed partial class CSharpForwardCancellationTokenToInvocationsFixer + { + private class TypeNameVisitor : SymbolVisitor + { + public static TypeSyntax GetTypeSyntaxForSymbol(INamespaceOrTypeSymbol symbol) + { + return symbol.Accept(new TypeNameVisitor()).WithAdditionalAnnotations(Simplifier.Annotation); + } + + public override TypeSyntax DefaultVisit(ISymbol symbol) + => throw new NotImplementedException(); + + public override TypeSyntax VisitAlias(IAliasSymbol symbol) + { + return AddInformationTo(ToIdentifierName(symbol.Name)); + } + + public override TypeSyntax VisitDynamicType(IDynamicTypeSymbol symbol) + { + return AddInformationTo(IdentifierName("dynamic")); + } + + public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol) + { + if (TryCreateNativeIntegerType(symbol, out var typeSyntax)) + return typeSyntax; + + typeSyntax = CreateSimpleTypeSyntax(symbol); + if (!(typeSyntax is SimpleNameSyntax)) + return typeSyntax; + + var simpleNameSyntax = (SimpleNameSyntax)typeSyntax; + if (symbol.ContainingType is not null) + { + if (symbol.ContainingType.TypeKind != TypeKind.Submission) + { + var containingTypeSyntax = symbol.ContainingType.Accept(this); + if (containingTypeSyntax is NameSyntax name) + { + typeSyntax = AddInformationTo( + QualifiedName(name, simpleNameSyntax)); + } + else + { + typeSyntax = AddInformationTo(simpleNameSyntax); + } + } + } + else if (symbol.ContainingNamespace is not null) + { + if (symbol.ContainingNamespace.IsGlobalNamespace) + { + if (symbol.TypeKind != TypeKind.Error) + { + typeSyntax = AddGlobalAlias(simpleNameSyntax); + } + } + else + { + var container = symbol.ContainingNamespace.Accept(this)!; + typeSyntax = AddInformationTo(QualifiedName( + (NameSyntax)container, + simpleNameSyntax)); + } + } + + if (symbol.NullableAnnotation() == NullableAnnotation.Annotated && + !symbol.IsValueType) + { + typeSyntax = AddInformationTo(NullableType(typeSyntax)); + } + + return typeSyntax; + } + + public override TypeSyntax VisitNamespace(INamespaceSymbol symbol) + { + var syntax = AddInformationTo(ToIdentifierName(symbol.Name)); + if (symbol.ContainingNamespace == null) + { + return syntax; + } + + if (symbol.ContainingNamespace.IsGlobalNamespace) + { + return AddGlobalAlias(syntax); + } + else + { + var container = symbol.ContainingNamespace.Accept(this)!; + return AddInformationTo(QualifiedName( + (NameSyntax)container, + syntax)); + } + } + + public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol) + { + TypeSyntax typeSyntax = AddInformationTo(ToIdentifierName(symbol.Name)); + if (symbol.NullableAnnotation() == NullableAnnotation.Annotated) + typeSyntax = AddInformationTo(NullableType(typeSyntax)); + + return typeSyntax; + } + + private TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol) + { + if (symbol.IsTupleType && symbol.TupleUnderlyingType != null && !symbol.Equals(symbol.TupleUnderlyingType)) + { + return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType); + } + + if (string.IsNullOrEmpty(symbol.Name) || symbol.IsAnonymousType) + { + return CreateSystemObject(); + } + + if (symbol.TypeParameters.Length == 0) + { + if (symbol.TypeKind == TypeKind.Error && symbol.Name == "var") + { + return CreateSystemObject(); + } + + return ToIdentifierName(symbol.Name); + } + + var typeArguments = symbol.IsUnboundGenericType + ? Enumerable.Repeat((TypeSyntax)OmittedTypeArgument(), symbol.TypeArguments.Length) + : symbol.TypeArguments.Select(t => GetTypeSyntaxForSymbol(t)); + + return GenericName( + ToIdentifierToken(symbol.Name), + TypeArgumentList(SeparatedList(typeArguments))); + } + + private static QualifiedNameSyntax CreateSystemObject() + { + return QualifiedName( + AliasQualifiedName( + CreateGlobalIdentifier(), + IdentifierName("System")), + IdentifierName("Object")); + } + + private static TTypeSyntax AddInformationTo(TTypeSyntax syntax) + where TTypeSyntax : TypeSyntax + { + syntax = syntax.WithLeadingTrivia(ElasticMarker).WithTrailingTrivia(ElasticMarker); + return syntax; + } + + /// + /// We always unilaterally add "global::" to all named types/namespaces. This + /// will then be trimmed off if possible by the simplifier + /// + private static TypeSyntax AddGlobalAlias(SimpleNameSyntax syntax) + { + return AddInformationTo(AliasQualifiedName(CreateGlobalIdentifier(), syntax)); + } + + private static IdentifierNameSyntax ToIdentifierName(string identifier) + => IdentifierName(ToIdentifierToken(identifier)); + + private static IdentifierNameSyntax CreateGlobalIdentifier() + => IdentifierName(Token(SyntaxKind.GlobalKeyword)); + + private static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNullWhen(true)] out TypeSyntax? syntax) + { + if (symbol.IsNativeIntegerType()) + { + syntax = IdentifierName(symbol.SpecialType == SpecialType.System_IntPtr ? "nint" : "nuint"); + return true; + } + + syntax = null; + return false; + } + + private static SyntaxToken ToIdentifierToken(string identifier) + { + var escaped = EscapeIdentifier(identifier); + + if (escaped.Length == 0 || escaped[0] != '@') + { + return Identifier(escaped); + } + + var unescaped = identifier.StartsWith("@", StringComparison.Ordinal) + ? identifier[1..] + : identifier; + + var token = Identifier( + default, SyntaxKind.None, "@" + unescaped, unescaped, default); + + if (!identifier.StartsWith("@", StringComparison.Ordinal)) + { + token = token.WithAdditionalAnnotations(Simplifier.Annotation); + } + + return token; + } + + private static string EscapeIdentifier(string identifier) + { + var nullIndex = identifier.IndexOf('\0'); + if (nullIndex >= 0) + { + identifier = identifier.Substring(0, nullIndex); + } + + var needsEscaping = SyntaxFacts.GetKeywordKind(identifier) != SyntaxKind.None; + + return needsEscaping ? "@" + identifier : identifier; + } + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs index 9afbb490d4..8b9053578a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocations.Fixer.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Analyzer.Utilities; @@ -13,7 +15,8 @@ namespace Microsoft.NetCore.Analyzers.Runtime { - public abstract class ForwardCancellationTokenToInvocationsFixer : CodeFixProvider + public abstract class ForwardCancellationTokenToInvocationsFixer : CodeFixProvider + where TArgumentSyntax : SyntaxNode { // Attempts to retrieve the invocation from the current operation. protected abstract bool TryGetInvocation( @@ -26,7 +29,7 @@ protected abstract bool TryGetInvocation( protected abstract bool TryGetExpressionAndArguments( SyntaxNode invocationNode, [NotNullWhen(returnValue: true)] out SyntaxNode? expression, - out ImmutableArray arguments); + out ImmutableArray arguments); // Verifies if the specified argument was passed with an explicit name. protected abstract bool IsArgumentNamed(IArgumentOperation argumentOperation); @@ -34,6 +37,10 @@ protected abstract bool TryGetExpressionAndArguments( // Retrieves the invocation expression for a conditional operation, which consists of the dot and the method name. protected abstract SyntaxNode GetConditionalOperationInvocationExpression(SyntaxNode invocationNode); + protected abstract SyntaxNode GetTypeSyntaxForArray(IArrayTypeSymbol type); + protected abstract IEnumerable GetExpressions(ImmutableArray newArguments); + protected abstract SyntaxNode GetArrayCreationExpression(SyntaxGenerator generator, SyntaxNode typeSyntax, IEnumerable expressions); + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(ForwardCancellationTokenToInvocationsAnalyzer.RuleId); @@ -83,14 +90,15 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) string title = MicrosoftNetCoreAnalyzersResources.ForwardCancellationTokenToInvocationsTitle; - if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out ImmutableArray newArguments)) + if (!TryGetExpressionAndArguments(invocation.Syntax, out SyntaxNode? expression, out ImmutableArray newArguments)) { return; } + var paramsArrayType = invocation.Arguments.SingleOrDefault(a => a.ArgumentKind == ArgumentKind.ParamArray)?.Value.Type as IArrayTypeSymbol; Task CreateChangedDocumentAsync(CancellationToken _) { - SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments); + SyntaxNode newRoot = TryGenerateNewDocumentRoot(doc, root, invocation, argumentName, parameterName, expression, newArguments, paramsArrayType); Document newDocument = doc.WithSyntaxRoot(newRoot); return Task.FromResult(newDocument); } @@ -103,17 +111,32 @@ Task CreateChangedDocumentAsync(CancellationToken _) context.Diagnostics); } - private static SyntaxNode TryGenerateNewDocumentRoot( + private SyntaxNode TryGenerateNewDocumentRoot( Document doc, SyntaxNode root, IInvocationOperation invocation, string invocationTokenArgumentName, string ancestorTokenParameterName, SyntaxNode expression, - ImmutableArray newArguments) + ImmutableArray currentArguments, + IArrayTypeSymbol? paramsArrayType) { SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); + ImmutableArray newArguments; + if (paramsArrayType is not null) + { + // current callsite is a params array, we need to wrap all these arguments to preserve semantics + var typeSyntax = GetTypeSyntaxForArray(paramsArrayType); + var expressions = GetExpressions(currentArguments); + newArguments = ImmutableArray.Create(GetArrayCreationExpression(generator, typeSyntax, expressions)); + } + else + { + // not a params array just pass the existing arguments along + newArguments = currentArguments.CastArray(); + } + SyntaxNode identifier = generator.IdentifierName(invocationTokenArgumentName); SyntaxNode cancellationTokenArgument; if (!string.IsNullOrEmpty(ancestorTokenParameterName)) @@ -142,4 +165,4 @@ public MyCodeAction(string title, Func> create } } } -} \ No newline at end of file +} diff --git a/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs new file mode 100644 index 0000000000..4957e192df --- /dev/null +++ b/src/NetAnalyzers/Core/NullableSyntaxAnnotationEx.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace Analyzer.Utilities.Lightup +{ + internal static class NullableSyntaxAnnotationEx + { + public static SyntaxAnnotation? Oblivious { get; } + public static SyntaxAnnotation? AnnotatedOrNotAnnotated { get; } + + static NullableSyntaxAnnotationEx() + { + var nullableSyntaxAnnotation = typeof(Workspace).Assembly.GetType("Microsoft.CodeAnalysis.CodeGeneration.NullableSyntaxAnnotation", throwOnError: false); + if (nullableSyntaxAnnotation is not null) + { + Oblivious = (SyntaxAnnotation?)nullableSyntaxAnnotation.GetField(nameof(Oblivious), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); + AnnotatedOrNotAnnotated = (SyntaxAnnotation?)nullableSyntaxAnnotation.GetField(nameof(AnnotatedOrNotAnnotated), BindingFlags.Static | BindingFlags.Public)?.GetValue(null); + } + } + } +} diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs index d1da1bf5d0..a84548263c 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/ForwardCancellationTokenToInvocationsTests.cs @@ -2479,6 +2479,47 @@ static async Task M1Async(string s, CancellationToken cancellationToken) return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + [WorkItem(4842, "https://github.com/dotnet/roslyn-analyzers/issues/4842")] + public Task CS_ParamsArray() + { + string originalCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; + +public class C{ + public ValueTask FindAsync(params object[] keyValues) => throw new NotImplementedException(); + public ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) => throw new NotImplementedException(); +} + +public class B { + async Task M(string[] args, CancellationToken token) + { + var c = new C(); + var result = await [|c.FindAsync|](5); + } +}"; + string fixedCode = @" +using System; +using System.Threading; +using System.Threading.Tasks; + +public class C{ + public ValueTask FindAsync(params object[] keyValues) => throw new NotImplementedException(); + public ValueTask FindAsync(object[] keyValues, CancellationToken cancellationToken) => throw new NotImplementedException(); +} + +public class B { + async Task M(string[] args, CancellationToken token) + { + var c = new C(); + var result = await c.FindAsync(new object[] { 5 }, cancellationToken: token); + } +}"; + return VerifyCS.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region No Diagnostic - VB @@ -4637,6 +4678,53 @@ End Module return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); } + [Fact] + [WorkItem(4842, "https://github.com/dotnet/roslyn-analyzers/issues/4842")] + public Task VB_ParamsArray() + { + string originalCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks + +Public Class C + Public Function FindAsync(ParamArray keyValues() As Object) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Public Function FindAsync(keyValues() As Object, cancellationToken As CancellationToken) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Async Function M(args As String(), cancellationToken As CancellationToken) As Task + Dim c = New C() + Dim result = Await c.[|FindAsync|](5) + End Function +End Class +"; + string fixedCode = @" +Imports System +Imports System.Threading +Imports System.Threading.Tasks + +Public Class C + Public Function FindAsync(ParamArray keyValues() As Object) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Public Function FindAsync(keyValues() As Object, cancellationToken As CancellationToken) As Task(Of Object) + Throw New NotImplementedException() + End Function + + Async Function M(args As String(), cancellationToken As CancellationToken) As Task + Dim c = New C() + Dim result = Await c.FindAsync(New Object() {5}, cancellationToken:=cancellationToken) + End Function +End Class +"; + return VerifyVB.VerifyCodeFixAsync(originalCode, fixedCode); + } + #endregion #region Helpers @@ -4697,4 +4785,4 @@ private static async Task VB16VerifyAnalyzerAsync(string originalCode) #endregion } -} \ No newline at end of file +} diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb index cc0a72b93b..2e6daa0e0d 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocations.Fixer.vb @@ -5,16 +5,19 @@ Imports System.Diagnostics.CodeAnalysis Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Operations +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.NetCore.Analyzers.Runtime Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime - Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer - - Inherits ForwardCancellationTokenToInvocationsFixer + Partial Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer + Inherits ForwardCancellationTokenToInvocationsFixer(Of ArgumentSyntax) Protected Overrides Function TryGetInvocation(model As SemanticModel, node As SyntaxNode, ct As CancellationToken, ByRef invocation As IInvocationOperation) As Boolean @@ -46,24 +49,40 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime End Function - Protected Overrides Function TryGetExpressionAndArguments(invocationNode As SyntaxNode, ByRef expression As SyntaxNode, ByRef arguments As ImmutableArray(Of SyntaxNode)) As Boolean + Protected Overrides Function TryGetExpressionAndArguments(invocationNode As SyntaxNode, ByRef expression As SyntaxNode, ByRef arguments As ImmutableArray(Of ArgumentSyntax)) As Boolean Dim invocationExpression As InvocationExpressionSyntax = TryCast(invocationNode, InvocationExpressionSyntax) If invocationExpression IsNot Nothing Then expression = invocationExpression.Expression - arguments = ImmutableArray.CreateRange(Of SyntaxNode)(invocationExpression.ArgumentList.Arguments) + arguments = invocationExpression.ArgumentList.Arguments.ToImmutableArray Return True End If expression = Nothing - arguments = ImmutableArray(Of SyntaxNode).Empty + arguments = ImmutableArray(Of ArgumentSyntax).Empty Return False End Function - End Class + Protected Overrides Function GetTypeSyntaxForArray(type As IArrayTypeSymbol) As SyntaxNode + Return TypeNameVisitor.GetTypeSyntaxForSymbol(type.ElementType) + End Function + Protected Overrides Function GetExpressions(newArguments As ImmutableArray(Of ArgumentSyntax)) As IEnumerable(Of SyntaxNode) + Return From argument In newArguments + Select argument.GetExpression() + End Function + + Protected Overrides Function GetArrayCreationExpression(generator As SyntaxGenerator, typeSyntax As SyntaxNode, expressions As IEnumerable(Of SyntaxNode)) As SyntaxNode + ' VB SyntaxGenerator will create ArgumentList nodes by default but the parse creates ArrayRankSpecifier nodes + ' We contruct the syntax manually to work around this + Dim rankSpecifiers = SyntaxFactory.List(Of ArrayRankSpecifierSyntax).Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.Token(SyntaxKind.OpenParenToken), Nothing, SyntaxFactory.Token(SyntaxKind.CloseParenToken))) + Dim initializer = SyntaxFactory.CollectionInitializer(SyntaxFactory.SeparatedList(expressions.Cast(Of ExpressionSyntax))) + Dim arrayCreationExpression = SyntaxFactory.ArrayCreationExpression(SyntaxFactory.Token(SyntaxKind.NewKeyword), Nothing, CType(typeSyntax, TypeSyntax), Nothing, rankSpecifiers, initializer) + Return arrayCreationExpression.WithAdditionalAnnotations(Formatter.Annotation, Simplifier.Annotation) + End Function + End Class End Namespace diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb new file mode 100644 index 0000000000..d26b5257bb --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicForwardCancellationTokenToInvocationsFixer.TypeNameVisitor.vb @@ -0,0 +1,309 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.Simplification +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + Partial Public NotInheritable Class BasicForwardCancellationTokenToInvocationsFixer + Private Class TypeNameVisitor + Inherits SymbolVisitor(Of TypeSyntax) + + Public Shared Function GetTypeSyntaxForSymbol(symbol As INamespaceOrTypeSymbol) As TypeSyntax + Return symbol.Accept(New TypeNameVisitor()).WithAdditionalAnnotations(Simplifier.Annotation) + End Function + + Public Overrides Function DefaultVisit(symbol As ISymbol) As TypeSyntax + Throw New NotImplementedException() + End Function + + Public Overrides Function VisitAlias(symbol As IAliasSymbol) As TypeSyntax + Return AddInformationTo(ToIdentifierName(symbol.Name)) + End Function + + Public Overrides Function VisitArrayType(symbol As IArrayTypeSymbol) As TypeSyntax + Dim underlyingNonArrayType = symbol.ElementType + While underlyingNonArrayType.Kind = SymbolKind.ArrayType + underlyingNonArrayType = DirectCast(underlyingNonArrayType, IArrayTypeSymbol).ElementType + End While + + Dim elementTypeSyntax = underlyingNonArrayType.Accept(Me) + Dim ranks = New List(Of ArrayRankSpecifierSyntax)() + Dim arrayType = symbol + While arrayType IsNot Nothing + Dim commaCount = Math.Max(0, arrayType.Rank - 1) + Dim commas = SyntaxFactory.TokenList(Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), commaCount)) + ranks.Add(SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.Token(SyntaxKind.OpenParenToken), commas, SyntaxFactory.Token(SyntaxKind.CloseParenToken))) + arrayType = TryCast(arrayType.ElementType, IArrayTypeSymbol) + End While + + Dim arrayTypeSyntax = SyntaxFactory.ArrayType(elementTypeSyntax, SyntaxFactory.List(ranks)) + Return AddInformationTo(arrayTypeSyntax) + End Function + + Public Overrides Function VisitDynamicType(symbol As IDynamicTypeSymbol) As TypeSyntax + Return AddInformationTo(SyntaxFactory.IdentifierName("dynamic")) + End Function + + Public Overrides Function VisitNamedType(symbol As INamedTypeSymbol) As TypeSyntax + Dim typeSyntax = CreateSimpleTypeSyntax(symbol) + If Not (TypeOf typeSyntax Is SimpleNameSyntax) Then + Return typeSyntax + End If + + Dim simpleNameSyntax = DirectCast(typeSyntax, SimpleNameSyntax) + If symbol.ContainingType IsNot Nothing Then + If symbol.ContainingType.TypeKind = TypeKind.Submission Then + Return typeSyntax + Else + Return AddInformationTo(SyntaxFactory.QualifiedName(DirectCast(symbol.ContainingType.Accept(Me), NameSyntax), simpleNameSyntax)) + End If + ElseIf symbol.ContainingNamespace IsNot Nothing Then + If symbol.ContainingNamespace.IsGlobalNamespace Then + If symbol.TypeKind <> TypeKind.[Error] Then + Return AddInformationTo(SyntaxFactory.QualifiedName(SyntaxFactory.GlobalName(), simpleNameSyntax)) + End If + Else + Dim container = symbol.ContainingNamespace.Accept(Me) + Return AddInformationTo(SyntaxFactory.QualifiedName(DirectCast(container, NameSyntax), simpleNameSyntax)) + End If + End If + + Return simpleNameSyntax + End Function + + Public Overrides Function VisitNamespace(symbol As INamespaceSymbol) As TypeSyntax + Dim result = AddInformationTo(ToIdentifierName(symbol.Name)) + If symbol.ContainingNamespace Is Nothing Then + Return result + End If + + If symbol.ContainingNamespace.IsGlobalNamespace Then + Return AddInformationTo(SyntaxFactory.QualifiedName(SyntaxFactory.GlobalName(), result)) + Else + Dim container = symbol.ContainingNamespace.Accept(Me) + Return AddInformationTo(SyntaxFactory.QualifiedName(DirectCast(container, NameSyntax), result)) + End If + End Function + + Public Overrides Function VisitPointerType(symbol As IPointerTypeSymbol) As TypeSyntax + Return symbol.PointedAtType.Accept(Me) + End Function + + Public Overrides Function VisitTypeParameter(symbol As ITypeParameterSymbol) As TypeSyntax + Return AddInformationTo(ToIdentifierName(symbol.Name)) + End Function + + Private Shared Function AddInformationTo(Of TTypeSyntax As TypeSyntax)(type As TTypeSyntax) As TTypeSyntax + type = type.WithLeadingTrivia(SyntaxFactory.ElasticMarker).WithTrailingTrivia(SyntaxFactory.ElasticMarker) + Return type + End Function + + Private Shared Function CreateSimpleTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax + Dim syntax = TryCreateSpecializedNamedTypeSyntax(symbol) + If syntax IsNot Nothing Then + Return syntax + End If + + If symbol.IsTupleType AndAlso symbol.TupleUnderlyingType IsNot Nothing AndAlso Not symbol.Equals(symbol.TupleUnderlyingType) Then + Return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType) + End If + + If symbol.Name = String.Empty OrElse symbol.IsAnonymousType Then + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Object")) + End If + + If symbol.OriginalDefinition.SpecialType = SpecialType.System_Nullable_T Then + Return AddInformationTo(SyntaxFactory.NullableType(symbol.TypeArguments.First().Accept(New TypeNameVisitor()))) + End If + + If symbol.TypeParameters.Length = 0 Then + Return ToIdentifierName(symbol.Name) + End If + + Return SyntaxFactory.GenericName( + ToIdentifierToken(symbol.Name), + SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(symbol.TypeArguments.[Select](Function(t) t.Accept(New TypeNameVisitor()))))) + End Function + + Private Shared Function TryCreateSpecializedNamedTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax + Select Case symbol.SpecialType + Case SpecialType.System_Object + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Object")) + Case SpecialType.System_Boolean + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Boolean")) + Case SpecialType.System_Char + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Char")) + Case SpecialType.System_SByte + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("SByte")) + Case SpecialType.System_Byte + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Byte")) + Case SpecialType.System_Int16 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Int16")) + Case SpecialType.System_UInt16 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("UInt16")) + Case SpecialType.System_Int32 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Int32")) + Case SpecialType.System_Int64 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Int64")) + Case SpecialType.System_UInt32 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("UInt32")) + Case SpecialType.System_UInt64 + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("UInt64")) + Case SpecialType.System_Decimal + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Decimal")) + Case SpecialType.System_Single + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Single")) + Case SpecialType.System_Double + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("Double")) + Case SpecialType.System_String + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("String")) + Case SpecialType.System_DateTime + Return SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"), SyntaxFactory.IdentifierName("DateTime")) + End Select + + If symbol.IsTupleType AndAlso symbol.TupleElements.Length >= 2 Then + Return CreateTupleTypeSyntax(symbol) + End If + + Return Nothing + End Function + + Private Shared Function CreateTupleTypeSyntax(symbol As INamedTypeSymbol) As TypeSyntax + Dim elements = symbol.TupleElements + + Return SyntaxFactory.TupleType(SyntaxFactory.SeparatedList( + elements.Select(Function(element) If(Not element.IsImplicitlyDeclared, + SyntaxFactory.NamedTupleElement( + SyntaxFactory.Identifier(element.Name), + SyntaxFactory.SimpleAsClause( + SyntaxFactory.Token(SyntaxKind.AsKeyword), + Nothing, + GetTypeSyntaxForSymbol(element.Type))), + DirectCast(SyntaxFactory.TypedTupleElement( + GetTypeSyntaxForSymbol(element.Type)), TupleElementSyntax))))) + End Function + + Private Shared Function ToIdentifierName(text As String) As IdentifierNameSyntax + Return SyntaxFactory.IdentifierName(ToIdentifierToken(text)).WithAdditionalAnnotations(Simplifier.Annotation) + End Function + + Private Shared Function ToIdentifierToken(text As String, Optional afterDot As Boolean = False, Optional symbol As ISymbol = Nothing, Optional withinAsyncMethod As Boolean = False) As SyntaxToken + + Dim unescaped = text + Dim wasAlreadyEscaped = False + + If text.Length > 2 AndAlso MakeHalfWidthIdentifier(text.First()) = "[" AndAlso MakeHalfWidthIdentifier(text.Last()) = "]" Then + unescaped = text.Substring(1, text.Length() - 2) + wasAlreadyEscaped = True + End If + + Dim escaped = EscapeIdentifier(text, afterDot, symbol, withinAsyncMethod) + Dim token = If(escaped.Length > 0 AndAlso escaped(0) = "["c, + SyntaxFactory.Identifier(escaped, isBracketed:=True, identifierText:=unescaped, typeCharacter:=TypeCharacter.None), + SyntaxFactory.Identifier(text)) + + If Not wasAlreadyEscaped Then + token = token.WithAdditionalAnnotations(Simplifier.Annotation) + End If + + Return token + End Function + + Private Shared Function EscapeIdentifier(text As String, Optional afterDot As Boolean = False, Optional symbol As ISymbol = Nothing, Optional withinAsyncMethod As Boolean = False) As String + Dim keywordKind = SyntaxFacts.GetKeywordKind(text) + Dim needsEscaping = keywordKind <> SyntaxKind.None + + ' REM and New must always be escaped, but there are some conditions where + ' keywords are not escaped + If needsEscaping AndAlso + keywordKind <> SyntaxKind.REMKeyword AndAlso + keywordKind <> SyntaxKind.NewKeyword Then + + needsEscaping = Not afterDot + + If needsEscaping Then + Dim typeSymbol = TryCast(symbol, ITypeSymbol) + needsEscaping = typeSymbol Is Nothing OrElse Not IsPredefinedType(typeSymbol) + End If + End If + + ' GetKeywordKind won't return SyntaxKind.AwaitKeyword (943836) + If withinAsyncMethod AndAlso text = "Await" Then + needsEscaping = True + End If + + Return If(needsEscaping, "[" & text & "]", text) + End Function + + Private Shared Function IsPredefinedType(type As ITypeSymbol) As Boolean + Select Case type.SpecialType + Case SpecialType.System_Boolean, + SpecialType.System_Byte, + SpecialType.System_SByte, + SpecialType.System_Int16, + SpecialType.System_UInt16, + SpecialType.System_Int32, + SpecialType.System_UInt32, + SpecialType.System_Int64, + SpecialType.System_UInt64, + SpecialType.System_Single, + SpecialType.System_Double, + SpecialType.System_Decimal, + SpecialType.System_DateTime, + SpecialType.System_Char, + SpecialType.System_String, + SpecialType.System_Object + Return True + Case Else + Return False + End Select + End Function + + ''' + ''' Creates a half width form Unicode character string. + ''' + ''' The text representing the original identifier. This can be in full width or half width Unicode form. + ''' A string representing the text in a half width Unicode form. + Private Shared Function MakeHalfWidthIdentifier(text As String) As String + If text Is Nothing Then + Return text + End If + + Dim characters As Char() = Nothing + For i = 0 To text.Length - 1 + Dim c = text(i) + + If IsFullWidth(c) Then + If characters Is Nothing Then + characters = New Char(text.Length - 1) {} + text.CopyTo(0, characters, 0, i) + End If + + characters(i) = MakeHalfWidth(c) + ElseIf characters IsNot Nothing Then + characters(i) = c + End If + Next + + Return If(characters Is Nothing, text, New String(characters)) + End Function + + Private Const s_fullwidth = &HFF00L - &H20L + + '// IsFullWidth - Returns if the character is full width + Private Shared Function IsFullWidth(c As Char) As Boolean + ' Do not use "AndAlso" or it will not inline. + Return c > ChrW(&HFF00US) And c < ChrW(&HFF5FUS) + End Function + + '// MakeHalfWidth - Converts a full-width character to half-width + Friend Shared Function MakeHalfWidth(c As Char) As Char + Debug.Assert(IsFullWidth(c)) + + Return Convert.ToChar(Convert.ToUInt16(c) - s_fullwidth) + End Function + End Class + End Class +End Namespace diff --git a/src/PerformanceTests/Tests/PerformanceTests.csproj b/src/PerformanceTests/Tests/PerformanceTests.csproj index 5b32efdb20..c35c019b5a 100644 --- a/src/PerformanceTests/Tests/PerformanceTests.csproj +++ b/src/PerformanceTests/Tests/PerformanceTests.csproj @@ -1,7 +1,7 @@  net6.0 - 9.0 + preview disable Exe diff --git a/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs b/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs index 847ce639aa..a8bd229613 100644 --- a/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs +++ b/src/Utilities/Compiler/Lightup/ITypeSymbolExtensions.cs @@ -13,10 +13,16 @@ private static readonly Func s_nullableAnnotati private static readonly Func s_withNullableAnnotation = LightupHelpers.CreateSymbolWithPropertyAccessor(typeof(ITypeSymbol), nameof(NullableAnnotation), fallbackResult: Lightup.NullableAnnotation.None); + private static readonly Func s_isNativeIntegerType + = LightupHelpers.CreateSymbolPropertyAccessor(typeof(ITypeSymbol), nameof(IsNativeIntegerType), fallbackResult: false); + public static NullableAnnotation NullableAnnotation(this ITypeSymbol typeSymbol) => s_nullableAnnotation(typeSymbol); public static ITypeSymbol WithNullableAnnotation(this ITypeSymbol typeSymbol, NullableAnnotation nullableAnnotation) => s_withNullableAnnotation(typeSymbol, nullableAnnotation); + + public static bool IsNativeIntegerType(this ITypeSymbol typeSymbol) + => s_isNativeIntegerType(typeSymbol); } } diff --git a/temp/perfBaseline b/temp/perfBaseline new file mode 160000 index 0000000000..f71538a093 --- /dev/null +++ b/temp/perfBaseline @@ -0,0 +1 @@ +Subproject commit f71538a0935809fdc9b6dd17046f86809a231109