From 08c87bf227635d20f57aa8ef7d54b385d07994b0 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Tue, 1 Dec 2020 20:44:25 -0500 Subject: [PATCH 001/220] VB only allows integral-typed literals as alignment components --- ...SimplifyInterpolationDiagnosticAnalyzer.cs | 2 ++ ...arpSimplifyInterpolationCodeFixProvider.cs | 2 ++ ...SimplifyInterpolationDiagnosticAnalyzer.cs | 6 ++-- .../SimplifyInterpolation/Helpers.cs | 14 ++++++---- ...actSimplifyInterpolationCodeFixProvider.cs | 6 ++-- ...SimplifyInterpolationDiagnosticAnalyzer.vb | 2 ++ ...sicSimplifyInterpolationCodeFixProvider.vb | 2 ++ .../SimplifyInterpolationTests.vb | 28 ++++++------------- 8 files changed, 32 insertions(+), 30 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs index 6484ab2763622..5593887667fad 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs @@ -18,6 +18,8 @@ namespace Microsoft.CodeAnalysis.CSharp.SimplifyInterpolation internal class CSharpSimplifyInterpolationDiagnosticAnalyzer : AbstractSimplifyInterpolationDiagnosticAnalyzer< InterpolationSyntax, ExpressionSyntax, ConditionalExpressionSyntax, ParenthesizedExpressionSyntax> { + protected override bool PermitNonLiteralAlignmentComponents => true; + protected override IVirtualCharService GetVirtualCharService() => CSharpVirtualCharService.Instance; diff --git a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs index bcf1edb3b4caa..6019eae72f793 100644 --- a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs @@ -25,6 +25,8 @@ public CSharpSimplifyInterpolationCodeFixProvider() { } + protected override bool PermitNonLiteralAlignmentComponents => true; + protected override InterpolationSyntax WithExpression(InterpolationSyntax interpolation, ExpressionSyntax expression) => interpolation.WithExpression(expression); diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs index 0f7cbe60530e3..1847e882fbc68 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs @@ -32,6 +32,8 @@ protected AbstractSimplifyInterpolationDiagnosticAnalyzer() { } + protected abstract bool PermitNonLiteralAlignmentComponents { get; } + protected abstract IVirtualCharService GetVirtualCharService(); protected abstract ISyntaxFacts GetSyntaxFacts(); @@ -63,8 +65,8 @@ private void AnalyzeInterpolation(OperationAnalysisContext context) } Helpers.UnwrapInterpolation( - GetVirtualCharService(), GetSyntaxFacts(), interpolation, out _, out var alignment, out _, - out var formatString, out var unnecessaryLocations); + GetVirtualCharService(), GetSyntaxFacts(), PermitNonLiteralAlignmentComponents, interpolation, out _, + out var alignment, out _, out var formatString, out var unnecessaryLocations); if (alignment == null && formatString == null) { diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index 7a81b6da5b00b..4d0938e055a30 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -29,9 +29,9 @@ private static SyntaxNode GetPreservedInterpolationExpressionSyntax( - IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IInterpolationOperation interpolation, - out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, out bool negate, - out string? formatString, out ImmutableArray unnecessaryLocations) + IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, bool permitNonLiteralAlignmentComponents, + IInterpolationOperation interpolation, out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, + out bool negate, out string? formatString, out ImmutableArray unnecessaryLocations) where TInterpolationSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode where TConditionalExpressionSyntax : TExpressionSyntax @@ -47,7 +47,7 @@ public static void UnwrapInterpolation( - expression, out expression, out alignment, out negate, unnecessarySpans); + permitNonLiteralAlignmentComponents, expression, out expression, out alignment, out negate, unnecessarySpans); } if (interpolation.FormatString == null) @@ -141,7 +141,7 @@ private static TextSpan GetSpanWithinLiteralQuotes(IVirtualCharService virtualCh } private static void UnwrapAlignmentPadding( - IOperation expression, out IOperation unwrapped, + bool permitNonLiteralAlignmentComponents, IOperation expression, out IOperation unwrapped, out TExpressionSyntax? alignment, out bool negate, List unnecessarySpans) where TExpressionSyntax : SyntaxNode where TConditionalExpressionSyntax : TExpressionSyntax @@ -160,7 +160,9 @@ private static void UnwrapAlignmentPadding CodeFixCategory.CodeStyle; + protected abstract bool PermitNonLiteralAlignmentComponents { get; } + protected abstract TInterpolationSyntax WithExpression(TInterpolationSyntax interpolation, TExpressionSyntax expression); protected abstract TInterpolationSyntax WithAlignmentClause(TInterpolationSyntax interpolation, TInterpolationAlignmentClause alignmentClause); protected abstract TInterpolationSyntax WithFormatClause(TInterpolationSyntax interpolation, TInterpolationFormatClause? formatClause); @@ -68,8 +70,8 @@ protected override async Task FixAllAsync( { Helpers.UnwrapInterpolation( document.GetRequiredLanguageService(), - document.GetRequiredLanguageService(), interpolation, out var unwrapped, - out var alignment, out var negate, out var formatString, out _); + document.GetRequiredLanguageService(), PermitNonLiteralAlignmentComponents, + interpolation, out var unwrapped, out var alignment, out var negate, out var formatString, out _); if (unwrapped == null) continue; diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb index b88ee1f3db3ae..3e3ec07f05786 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb @@ -19,6 +19,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation TernaryConditionalExpressionSyntax, ParenthesizedExpressionSyntax) + Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + Protected Overrides Function GetVirtualCharService() As IVirtualCharService Return VisualBasicVirtualCharService.Instance End Function diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb index ebbdd5712afe9..0765ea4848990 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb @@ -21,6 +21,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Public Sub New() End Sub + Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + Protected Overrides Function WithExpression(interpolation As InterpolationSyntax, expression As ExpressionSyntax) As InterpolationSyntax Return interpolation.WithExpression(expression) End Function diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb index 5743d36ea01b9..e7af02d00ec12 100644 --- a/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/SimplifyInterpolation/SimplifyInterpolationTests.vb @@ -148,19 +148,13 @@ Class C End Class") End Function - - Public Async Function PadLeftWithComplexConstantExpression() As Task - Await TestInRegularAndScriptAsync(" -Class C - Sub M(someValue As String) - Const someConstant As Integer = 1 - Dim v = $""prefix {someValue{|Unnecessary:[||].PadLeft(|}CByte(3.3) + someConstant{|Unnecessary:)|}} suffix"" - End Sub -End Class", " + + Public Async Function PadLeftWithNonLiteralConstantExpression() As Task + Await TestMissingInRegularAndScriptAsync(" Class C Sub M(someValue As String) Const someConstant As Integer = 1 - Dim v = $""prefix {someValue,CByte(3.3) + someConstant} suffix"" + Dim v = $""prefix {someValue[||].PadLeft(someConstant)} suffix"" End Sub End Class") End Function @@ -215,19 +209,13 @@ Class C End Class") End Function - - Public Async Function PadRightWithComplexConstantExpressionRequiringParentheses() As Task - Await TestInRegularAndScriptAsync(" -Class C - Sub M(someValue As String) - Const someConstant As Integer = 1 - Dim v = $""prefix {someValue{|Unnecessary:[||].PadRight(|}CByte(3.3) + someConstant{|Unnecessary:)|}} suffix"" - End Sub -End Class", " + + Public Async Function PadRightWithNonLiteralConstantExpression() As Task + Await TestMissingInRegularAndScriptAsync(" Class C Sub M(someValue As String) Const someConstant As Integer = 1 - Dim v = $""prefix {someValue,-(CByte(3.3) + someConstant)} suffix"" + Dim v = $""prefix {someValue[||].PadRight(someConstant)} suffix"" End Sub End Class") End Function From 5feb7305d879597fc202009680acf6c39939376b Mon Sep 17 00:00:00 2001 From: jnm2 Date: Tue, 1 Dec 2020 21:35:40 -0500 Subject: [PATCH 002/220] Revert "Use extra generic type parameters and apply C#-specific knowledge to all langs instead of using inheritance" This reverts commit 4365779fe54547a53ae7fd10945ac750c63ad09d. --- .../Analyzers/CSharpAnalyzers.projitems | 1 + .../SimplifyInterpolation/CSharpHelpers.cs | 23 ++++++++ ...SimplifyInterpolationDiagnosticAnalyzer.cs | 7 ++- ...arpSimplifyInterpolationCodeFixProvider.cs | 6 +- .../Core/Analyzers/Analyzers.projitems | 2 +- .../{Helpers.cs => AbstractHelpers.cs} | 58 +++++++------------ ...SimplifyInterpolationDiagnosticAnalyzer.cs | 16 ++--- ...actSimplifyInterpolationCodeFixProvider.cs | 14 ++--- .../VisualBasicHelpers.vb | 13 +++++ ...SimplifyInterpolationDiagnosticAnalyzer.vb | 10 ++-- .../Analyzers/VisualBasicAnalyzers.projitems | 1 + ...sicSimplifyInterpolationCodeFixProvider.vb | 7 ++- 12 files changed, 87 insertions(+), 71 deletions(-) create mode 100644 src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs rename src/Analyzers/Core/Analyzers/SimplifyInterpolation/{Helpers.cs => AbstractHelpers.cs} (67%) create mode 100644 src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index 3ac1b308bbb27..d99869203931f 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -47,6 +47,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs new file mode 100644 index 0000000000000..257b612b63c30 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.SimplifyInterpolation; + +namespace Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation +{ + internal sealed class CSharpHelpers : AbstractHelpers + { + protected override bool PermitNonLiteralAlignmentComponents => true; + + protected override SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation) + { + return base.GetPreservedInterpolationExpressionSyntax(operation) switch + { + ConditionalExpressionSyntax { Parent: ParenthesizedExpressionSyntax parent } => parent, + var syntax => syntax, + }; + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs index 5593887667fad..38a42680d1dc2 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs @@ -4,6 +4,7 @@ #nullable disable +using Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation; using Microsoft.CodeAnalysis.CSharp.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.CSharp.LanguageServices; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -16,14 +17,14 @@ namespace Microsoft.CodeAnalysis.CSharp.SimplifyInterpolation { [DiagnosticAnalyzer(LanguageNames.CSharp)] internal class CSharpSimplifyInterpolationDiagnosticAnalyzer : AbstractSimplifyInterpolationDiagnosticAnalyzer< - InterpolationSyntax, ExpressionSyntax, ConditionalExpressionSyntax, ParenthesizedExpressionSyntax> + InterpolationSyntax, ExpressionSyntax> { - protected override bool PermitNonLiteralAlignmentComponents => true; - protected override IVirtualCharService GetVirtualCharService() => CSharpVirtualCharService.Instance; protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; + + protected override AbstractHelpers GetHelpers() => new CSharpHelpers(); } } diff --git a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs index 6019eae72f793..5d9ab72d7b788 100644 --- a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.SimplifyInterpolation; @@ -16,8 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp.SimplifyInterpolation [ExportCodeFixProvider(LanguageNames.CSharp), Shared] internal class CSharpSimplifyInterpolationCodeFixProvider : AbstractSimplifyInterpolationCodeFixProvider< InterpolationSyntax, ExpressionSyntax, InterpolationAlignmentClauseSyntax, - InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax, - ConditionalExpressionSyntax, ParenthesizedExpressionSyntax> + InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax> { [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] @@ -25,7 +25,7 @@ public CSharpSimplifyInterpolationCodeFixProvider() { } - protected override bool PermitNonLiteralAlignmentComponents => true; + protected override AbstractHelpers GetHelpers() => new CSharpHelpers(); protected override InterpolationSyntax WithExpression(InterpolationSyntax interpolation, ExpressionSyntax expression) => interpolation.WithExpression(expression); diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems index 34335203d43b7..f621d705565b4 100644 --- a/src/Analyzers/Core/Analyzers/Analyzers.projitems +++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems @@ -58,7 +58,7 @@ - + diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractHelpers.cs similarity index 67% rename from src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs rename to src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractHelpers.cs index 4d0938e055a30..63bd46a11651f 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractHelpers.cs @@ -14,28 +14,21 @@ namespace Microsoft.CodeAnalysis.SimplifyInterpolation { - internal static class Helpers + internal abstract class AbstractHelpers { - private static SyntaxNode GetPreservedInterpolationExpressionSyntax( - IOperation operation) - where TConditionalExpressionSyntax : SyntaxNode - where TParenthesizedExpressionSyntax : SyntaxNode + protected abstract bool PermitNonLiteralAlignmentComponents { get; } + + protected virtual SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation) { - return operation.Syntax switch - { - TConditionalExpressionSyntax { Parent: TParenthesizedExpressionSyntax parent } => parent, - var syntax => syntax, - }; + return operation.Syntax; } - public static void UnwrapInterpolation( - IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, bool permitNonLiteralAlignmentComponents, - IInterpolationOperation interpolation, out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, - out bool negate, out string? formatString, out ImmutableArray unnecessaryLocations) - where TInterpolationSyntax : SyntaxNode - where TExpressionSyntax : SyntaxNode - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax + public void UnwrapInterpolation( + IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IInterpolationOperation interpolation, + out TExpressionSyntax? unwrapped, out TExpressionSyntax? alignment, out bool negate, + out string? formatString, out ImmutableArray unnecessaryLocations) + where TInterpolationSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode { alignment = null; negate = false; @@ -46,17 +39,15 @@ public static void UnwrapInterpolation( - permitNonLiteralAlignmentComponents, expression, out expression, out alignment, out negate, unnecessarySpans); + UnwrapAlignmentPadding(expression, out expression, out alignment, out negate, unnecessarySpans); } if (interpolation.FormatString == null) { - UnwrapFormatString( - virtualCharService, syntaxFacts, expression, out expression, out formatString, unnecessarySpans); + UnwrapFormatString(virtualCharService, syntaxFacts, expression, out expression, out formatString, unnecessarySpans); } - unwrapped = GetPreservedInterpolationExpressionSyntax(expression) as TExpressionSyntax; + unwrapped = GetPreservedInterpolationExpressionSyntax(expression) as TExpressionSyntax; unnecessaryLocations = unnecessarySpans.OrderBy(t => t.Start) @@ -81,11 +72,9 @@ private static IOperation Unwrap(IOperation expression) } } - private static void UnwrapFormatString( + private void UnwrapFormatString( IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IOperation expression, out IOperation unwrapped, out string? formatString, List unnecessarySpans) - where TConditionalExpressionSyntax : SyntaxNode - where TParenthesizedExpressionSyntax : SyntaxNode { if (expression is IInvocationOperation { TargetMethod: { Name: nameof(ToString) } } invocation && HasNonImplicitInstance(invocation) && @@ -100,9 +89,8 @@ private static void UnwrapFormatString(unwrapped); unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan) + .Subtract(GetPreservedInterpolationExpressionSyntax(invocation.Instance).FullSpan) .Subtract(GetSpanWithinLiteralQuotes(virtualCharService, literal.Syntax.GetFirstToken()))); return; } @@ -121,9 +109,8 @@ private static void UnwrapFormatString(unwrapped); unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan)); + .Subtract(GetPreservedInterpolationExpressionSyntax(invocation.Instance).FullSpan)); return; } } @@ -140,12 +127,10 @@ private static TextSpan GetSpanWithinLiteralQuotes(IVirtualCharService virtualCh : TextSpan.FromBounds(sequence.First().Span.Start, sequence.Last().Span.End); } - private static void UnwrapAlignmentPadding( - bool permitNonLiteralAlignmentComponents, IOperation expression, out IOperation unwrapped, + private void UnwrapAlignmentPadding( + IOperation expression, out IOperation unwrapped, out TExpressionSyntax? alignment, out bool negate, List unnecessarySpans) where TExpressionSyntax : SyntaxNode - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax { if (expression is IInvocationOperation invocation && HasNonImplicitInstance(invocation)) @@ -160,7 +145,7 @@ private static void UnwrapAlignmentPadding(unwrapped); unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan) + .Subtract(GetPreservedInterpolationExpressionSyntax(invocation.Instance).FullSpan) .Subtract(alignmentSyntax.FullSpan)); return; } diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs index 1847e882fbc68..9d251c80a6eed 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs @@ -15,13 +15,9 @@ namespace Microsoft.CodeAnalysis.SimplifyInterpolation { internal abstract class AbstractSimplifyInterpolationDiagnosticAnalyzer< TInterpolationSyntax, - TExpressionSyntax, - TConditionalExpressionSyntax, - TParenthesizedExpressionSyntax> : AbstractBuiltInCodeStyleDiagnosticAnalyzer + TExpressionSyntax> : AbstractBuiltInCodeStyleDiagnosticAnalyzer where TInterpolationSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax { protected AbstractSimplifyInterpolationDiagnosticAnalyzer() : base(IDEDiagnosticIds.SimplifyInterpolationId, @@ -32,12 +28,12 @@ protected AbstractSimplifyInterpolationDiagnosticAnalyzer() { } - protected abstract bool PermitNonLiteralAlignmentComponents { get; } - protected abstract IVirtualCharService GetVirtualCharService(); protected abstract ISyntaxFacts GetSyntaxFacts(); + protected abstract AbstractHelpers GetHelpers(); + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; @@ -64,9 +60,9 @@ private void AnalyzeInterpolation(OperationAnalysisContext context) return; } - Helpers.UnwrapInterpolation( - GetVirtualCharService(), GetSyntaxFacts(), PermitNonLiteralAlignmentComponents, interpolation, out _, - out var alignment, out _, out var formatString, out var unnecessaryLocations); + GetHelpers().UnwrapInterpolation( + GetVirtualCharService(), GetSyntaxFacts(), interpolation, out _, out var alignment, out _, + out var formatString, out var unnecessaryLocations); if (alignment == null && formatString == null) { diff --git a/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs index 9b5008631baae..0b25fd4f1ae54 100644 --- a/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs @@ -23,23 +23,19 @@ internal abstract class AbstractSimplifyInterpolationCodeFixProvider< TExpressionSyntax, TInterpolationAlignmentClause, TInterpolationFormatClause, - TInterpolatedStringExpressionSyntax, - TConditionalExpressionSyntax, - TParenthesizedExpressionSyntax> : SyntaxEditorBasedCodeFixProvider + TInterpolatedStringExpressionSyntax> : SyntaxEditorBasedCodeFixProvider where TInterpolationSyntax : SyntaxNode where TExpressionSyntax : SyntaxNode where TInterpolationAlignmentClause : SyntaxNode where TInterpolationFormatClause : SyntaxNode where TInterpolatedStringExpressionSyntax : TExpressionSyntax - where TConditionalExpressionSyntax : TExpressionSyntax - where TParenthesizedExpressionSyntax : TExpressionSyntax { public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(IDEDiagnosticIds.SimplifyInterpolationId); internal override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle; - protected abstract bool PermitNonLiteralAlignmentComponents { get; } + protected abstract AbstractHelpers GetHelpers(); protected abstract TInterpolationSyntax WithExpression(TInterpolationSyntax interpolation, TExpressionSyntax expression); protected abstract TInterpolationSyntax WithAlignmentClause(TInterpolationSyntax interpolation, TInterpolationAlignmentClause alignmentClause); @@ -61,6 +57,8 @@ protected override async Task FixAllAsync( var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var generator = editor.Generator; var generatorInternal = document.GetRequiredLanguageService(); + var helpers = GetHelpers(); + foreach (var diagnostic in diagnostics) { var loc = diagnostic.AdditionalLocations[0]; @@ -68,9 +66,9 @@ protected override async Task FixAllAsync( if (interpolation?.Syntax is TInterpolationSyntax interpolationSyntax && interpolationSyntax.Parent is TInterpolatedStringExpressionSyntax interpolatedString) { - Helpers.UnwrapInterpolation( + helpers.UnwrapInterpolation( document.GetRequiredLanguageService(), - document.GetRequiredLanguageService(), PermitNonLiteralAlignmentComponents, + document.GetRequiredLanguageService(), interpolation, out var unwrapped, out var alignment, out var negate, out var formatString, out _); if (unwrapped == null) diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb new file mode 100644 index 0000000000000..c4cae5a2a133d --- /dev/null +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb @@ -0,0 +1,13 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.SimplifyInterpolation + +Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation + Friend NotInheritable Class VisualBasicHelpers + Inherits AbstractHelpers + + Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb index 3e3ec07f05786..e3d116f8046ca 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb @@ -13,13 +13,11 @@ Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Friend Class VisualBasicSimplifyInterpolationDiagnosticAnalyzer - Inherits AbstractSimplifyInterpolationDiagnosticAnalyzer(Of - InterpolationSyntax, - ExpressionSyntax, - TernaryConditionalExpressionSyntax, - ParenthesizedExpressionSyntax) + Inherits AbstractSimplifyInterpolationDiagnosticAnalyzer(Of InterpolationSyntax, ExpressionSyntax) - Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + Protected Overrides Function GetHelpers() As AbstractHelpers + Return New VisualBasicHelpers + End Function Protected Overrides Function GetVirtualCharService() As IVirtualCharService Return VisualBasicVirtualCharService.Instance diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems index 9693cc3ac3c85..2aa4ce4cefc44 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems @@ -34,6 +34,7 @@ + diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb index 0765ea4848990..ebaee8aa1c7ae 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb @@ -13,15 +13,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Friend Class VisualBasicSimplifyInterpolationCodeFixProvider Inherits AbstractSimplifyInterpolationCodeFixProvider(Of InterpolationSyntax, ExpressionSyntax, InterpolationAlignmentClauseSyntax, - InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax, - TernaryConditionalExpressionSyntax, ParenthesizedExpressionSyntax) + InterpolationFormatClauseSyntax, InterpolatedStringExpressionSyntax) Public Sub New() End Sub - Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + Protected Overrides Function GetHelpers() As AbstractHelpers + Return New VisualBasicHelpers + End Function Protected Overrides Function WithExpression(interpolation As InterpolationSyntax, expression As ExpressionSyntax) As InterpolationSyntax Return interpolation.WithExpression(expression) From bf18a122d046afadf8d6ba35847f3f304fb55030 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 4 Dec 2020 22:41:59 -0500 Subject: [PATCH 003/220] Make Helpers classes easier to navigate to --- src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems | 2 +- .../CSharpSimplifyInterpolationDiagnosticAnalyzer.cs | 2 +- ...CSharpHelpers.cs => CSharpSimplifyInterpolationHelpers.cs} | 2 +- .../CSharpSimplifyInterpolationCodeFixProvider.cs | 2 +- src/Analyzers/Core/Analyzers/Analyzers.projitems | 2 +- .../AbstractSimplifyInterpolationDiagnosticAnalyzer.cs | 2 +- ...ractHelpers.cs => AbstractSimplifyInterpolationHelpers.cs} | 2 +- .../AbstractSimplifyInterpolationCodeFixProvider.cs | 2 +- .../VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb | 4 ++-- ...cHelpers.vb => VisualBasicSimplifyInterpolationHelpers.vb} | 4 ++-- .../VisualBasic/Analyzers/VisualBasicAnalyzers.projitems | 2 +- .../VisualBasicSimplifyInterpolationCodeFixProvider.vb | 4 ++-- 12 files changed, 15 insertions(+), 15 deletions(-) rename src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/{CSharpHelpers.cs => CSharpSimplifyInterpolationHelpers.cs} (89%) rename src/Analyzers/Core/Analyzers/SimplifyInterpolation/{AbstractHelpers.cs => AbstractSimplifyInterpolationHelpers.cs} (99%) rename src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/{VisualBasicHelpers.vb => VisualBasicSimplifyInterpolationHelpers.vb} (78%) diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index d99869203931f..80c99a19ccf00 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -47,7 +47,7 @@ - + diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs index 38a42680d1dc2..ab036bbb2f88f 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs @@ -25,6 +25,6 @@ protected override IVirtualCharService GetVirtualCharService() protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; - protected override AbstractHelpers GetHelpers() => new CSharpHelpers(); + protected override AbstractSimplifyInterpolationHelpers GetHelpers() => new CSharpSimplifyInterpolationHelpers(); } } diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs similarity index 89% rename from src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs rename to src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs index 257b612b63c30..c0b8eab0a23e5 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpHelpers.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs @@ -7,7 +7,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation { - internal sealed class CSharpHelpers : AbstractHelpers + internal sealed class CSharpSimplifyInterpolationHelpers : AbstractSimplifyInterpolationHelpers { protected override bool PermitNonLiteralAlignmentComponents => true; diff --git a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs index 5d9ab72d7b788..6c934fcca9bf9 100644 --- a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs @@ -25,7 +25,7 @@ public CSharpSimplifyInterpolationCodeFixProvider() { } - protected override AbstractHelpers GetHelpers() => new CSharpHelpers(); + protected override AbstractSimplifyInterpolationHelpers GetHelpers() => new CSharpSimplifyInterpolationHelpers(); protected override InterpolationSyntax WithExpression(InterpolationSyntax interpolation, ExpressionSyntax expression) => interpolation.WithExpression(expression); diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems index f621d705565b4..9165878c0a085 100644 --- a/src/Analyzers/Core/Analyzers/Analyzers.projitems +++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems @@ -58,7 +58,7 @@ - + diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs index 9d251c80a6eed..30d0837d1942c 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationDiagnosticAnalyzer.cs @@ -32,7 +32,7 @@ protected AbstractSimplifyInterpolationDiagnosticAnalyzer() protected abstract ISyntaxFacts GetSyntaxFacts(); - protected abstract AbstractHelpers GetHelpers(); + protected abstract AbstractSimplifyInterpolationHelpers GetHelpers(); public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractHelpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs similarity index 99% rename from src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractHelpers.cs rename to src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs index 63bd46a11651f..f303f1538e711 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractHelpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs @@ -14,7 +14,7 @@ namespace Microsoft.CodeAnalysis.SimplifyInterpolation { - internal abstract class AbstractHelpers + internal abstract class AbstractSimplifyInterpolationHelpers { protected abstract bool PermitNonLiteralAlignmentComponents { get; } diff --git a/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs index 0b25fd4f1ae54..47d4aa9417fa8 100644 --- a/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/SimplifyInterpolation/AbstractSimplifyInterpolationCodeFixProvider.cs @@ -35,7 +35,7 @@ internal abstract class AbstractSimplifyInterpolationCodeFixProvider< internal override CodeFixCategory CodeFixCategory => CodeFixCategory.CodeStyle; - protected abstract AbstractHelpers GetHelpers(); + protected abstract AbstractSimplifyInterpolationHelpers GetHelpers(); protected abstract TInterpolationSyntax WithExpression(TInterpolationSyntax interpolation, TExpressionSyntax expression); protected abstract TInterpolationSyntax WithAlignmentClause(TInterpolationSyntax interpolation, TInterpolationAlignmentClause alignmentClause); diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb index e3d116f8046ca..20aa6e094f8c2 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb @@ -15,8 +15,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Friend Class VisualBasicSimplifyInterpolationDiagnosticAnalyzer Inherits AbstractSimplifyInterpolationDiagnosticAnalyzer(Of InterpolationSyntax, ExpressionSyntax) - Protected Overrides Function GetHelpers() As AbstractHelpers - Return New VisualBasicHelpers + Protected Overrides Function GetHelpers() As AbstractSimplifyInterpolationHelpers + Return New VisualBasicSimplifyInterpolationHelpers End Function Protected Overrides Function GetVirtualCharService() As IVirtualCharService diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb similarity index 78% rename from src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb rename to src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb index c4cae5a2a133d..a77ec5a43c532 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicHelpers.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb @@ -5,8 +5,8 @@ Imports Microsoft.CodeAnalysis.SimplifyInterpolation Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation - Friend NotInheritable Class VisualBasicHelpers - Inherits AbstractHelpers + Friend NotInheritable Class VisualBasicSimplifyInterpolationHelpers + Inherits AbstractSimplifyInterpolationHelpers Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False End Class diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems index 2aa4ce4cefc44..3a5fe2841176e 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems @@ -34,7 +34,7 @@ - + diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb index ebaee8aa1c7ae..9870c8cb00102 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb @@ -20,8 +20,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Public Sub New() End Sub - Protected Overrides Function GetHelpers() As AbstractHelpers - Return New VisualBasicHelpers + Protected Overrides Function GetHelpers() As AbstractSimplifyInterpolationHelpers + Return New VisualBasicSimplifyInterpolationHelpers End Function Protected Overrides Function WithExpression(interpolation As InterpolationSyntax, expression As ExpressionSyntax) As InterpolationSyntax From 6ac79ca383010f1dd435be9b80c1a1265649605a Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 4 Dec 2020 22:45:05 -0500 Subject: [PATCH 004/220] Helpers have no instance state --- .../CSharpSimplifyInterpolationDiagnosticAnalyzer.cs | 2 +- .../CSharpSimplifyInterpolationHelpers.cs | 4 ++++ .../CSharpSimplifyInterpolationCodeFixProvider.cs | 2 +- .../VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb | 2 +- .../VisualBasicSimplifyInterpolationHelpers.vb | 5 +++++ .../VisualBasicSimplifyInterpolationCodeFixProvider.vb | 2 +- 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs index ab036bbb2f88f..8a220b1aa1e4e 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationDiagnosticAnalyzer.cs @@ -25,6 +25,6 @@ protected override IVirtualCharService GetVirtualCharService() protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; - protected override AbstractSimplifyInterpolationHelpers GetHelpers() => new CSharpSimplifyInterpolationHelpers(); + protected override AbstractSimplifyInterpolationHelpers GetHelpers() => CSharpSimplifyInterpolationHelpers.Instance; } } diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs index c0b8eab0a23e5..b1434cd533920 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs @@ -9,6 +9,10 @@ namespace Microsoft.CodeAnalysis.CSharp.Analyzers.SimplifyInterpolation { internal sealed class CSharpSimplifyInterpolationHelpers : AbstractSimplifyInterpolationHelpers { + public static CSharpSimplifyInterpolationHelpers Instance { get; } = new(); + + private CSharpSimplifyInterpolationHelpers() { } + protected override bool PermitNonLiteralAlignmentComponents => true; protected override SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation) diff --git a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs index 6c934fcca9bf9..54eb36e93eb8b 100644 --- a/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/SimplifyInterpolation/CSharpSimplifyInterpolationCodeFixProvider.cs @@ -25,7 +25,7 @@ public CSharpSimplifyInterpolationCodeFixProvider() { } - protected override AbstractSimplifyInterpolationHelpers GetHelpers() => new CSharpSimplifyInterpolationHelpers(); + protected override AbstractSimplifyInterpolationHelpers GetHelpers() => CSharpSimplifyInterpolationHelpers.Instance; protected override InterpolationSyntax WithExpression(InterpolationSyntax interpolation, ExpressionSyntax expression) => interpolation.WithExpression(expression); diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb index 20aa6e094f8c2..5699175fc243c 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationDiagnosticAnalyzer.vb @@ -16,7 +16,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Inherits AbstractSimplifyInterpolationDiagnosticAnalyzer(Of InterpolationSyntax, ExpressionSyntax) Protected Overrides Function GetHelpers() As AbstractSimplifyInterpolationHelpers - Return New VisualBasicSimplifyInterpolationHelpers + Return VisualBasicSimplifyInterpolationHelpers.Instance End Function Protected Overrides Function GetVirtualCharService() As IVirtualCharService diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb index a77ec5a43c532..bae833520494f 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb @@ -8,6 +8,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation Friend NotInheritable Class VisualBasicSimplifyInterpolationHelpers Inherits AbstractSimplifyInterpolationHelpers + Public Shared ReadOnly Property Instance As New VisualBasicSimplifyInterpolationHelpers + + Private Sub New() + End Sub + Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False End Class End Namespace diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb index 9870c8cb00102..814e083ad2b00 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyInterpolation/VisualBasicSimplifyInterpolationCodeFixProvider.vb @@ -21,7 +21,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation End Sub Protected Overrides Function GetHelpers() As AbstractSimplifyInterpolationHelpers - Return New VisualBasicSimplifyInterpolationHelpers + Return VisualBasicSimplifyInterpolationHelpers.Instance End Function Protected Overrides Function WithExpression(interpolation As InterpolationSyntax, expression As ExpressionSyntax) As InterpolationSyntax From d806eeffb6f8216c2a447bb28e6dc2cd0033c105 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 4 Dec 2020 22:47:13 -0500 Subject: [PATCH 005/220] Make all helpers abstract --- .../CSharpSimplifyInterpolationHelpers.cs | 2 +- .../AbstractSimplifyInterpolationHelpers.cs | 5 +---- .../VisualBasicSimplifyInterpolationHelpers.vb | 4 ++++ 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs index b1434cd533920..11f479e03f0b4 100644 --- a/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs +++ b/src/Analyzers/CSharp/Analyzers/SimplifyInterpolation/CSharpSimplifyInterpolationHelpers.cs @@ -17,7 +17,7 @@ private CSharpSimplifyInterpolationHelpers() { } protected override SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation) { - return base.GetPreservedInterpolationExpressionSyntax(operation) switch + return operation.Syntax switch { ConditionalExpressionSyntax { Parent: ParenthesizedExpressionSyntax parent } => parent, var syntax => syntax, diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs index f303f1538e711..946177c27f0e0 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs @@ -18,10 +18,7 @@ internal abstract class AbstractSimplifyInterpolationHelpers { protected abstract bool PermitNonLiteralAlignmentComponents { get; } - protected virtual SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation) - { - return operation.Syntax; - } + protected abstract SyntaxNode GetPreservedInterpolationExpressionSyntax(IOperation operation); public void UnwrapInterpolation( IVirtualCharService virtualCharService, ISyntaxFacts syntaxFacts, IInterpolationOperation interpolation, diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb index bae833520494f..e78561490f34d 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyInterpolation/VisualBasicSimplifyInterpolationHelpers.vb @@ -14,5 +14,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyInterpolation End Sub Protected Overrides ReadOnly Property PermitNonLiteralAlignmentComponents As Boolean = False + + Protected Overrides Function GetPreservedInterpolationExpressionSyntax(operation As IOperation) As SyntaxNode + Return operation.Syntax + End Function End Class End Namespace From 018c7aa862753c71be6ffe8d1d7afb3cd9260574 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 4 Dec 2020 22:50:22 -0500 Subject: [PATCH 006/220] Reduce amount of nesting --- .../AbstractSimplifyInterpolationHelpers.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs index 946177c27f0e0..363697ab7db25 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/AbstractSimplifyInterpolationHelpers.cs @@ -142,9 +142,10 @@ private void UnwrapAlignmentPadding( IsSpaceChar(invocation.Arguments[1])) { var alignmentOp = invocation.Arguments[0].Value; - if (alignmentOp != null && (PermitNonLiteralAlignmentComponents - ? alignmentOp.ConstantValue.HasValue - : alignmentOp.Kind == OperationKind.Literal)) + + if (PermitNonLiteralAlignmentComponents + ? alignmentOp is { ConstantValue: { HasValue: true } } + : alignmentOp is { Kind: OperationKind.Literal }) { var alignmentSyntax = alignmentOp.Syntax; From e623ea0bf390ca651abf739964556a2d1e9c94e4 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 19 Jan 2021 17:04:28 +0200 Subject: [PATCH 007/220] Fix extra space in .editorconfig --- .editorconfig | 2 +- .../LanguageClient/AlwaysActivateInProcLanguageClient.cs | 4 +--- .../LanguageClient/RazorInProcLanguageClient.cs | 3 +-- .../MainDialog/PullMemberUpDialogViewModel.cs | 2 -- .../Workspace/SourceGeneratedFileManager.cs | 9 +-------- .../Workspace/VisualStudioSymbolNavigationService.cs | 2 -- .../SourceGeneratedFileItems/SourceGeneratedFileItem.cs | 2 +- .../SourceGeneratedFileItemSource.cs | 8 ++++---- .../IntegrationTests/CSharp/CSharpSourceGenerators.cs | 4 ++-- .../Client/CloudEnvironmentSupportsFeatureService.cs | 2 +- .../Handler/Completion/CompletionResolveHandler.cs | 2 +- .../Formatting/AbstractFormatDocumentHandlerBase.cs | 2 ++ .../Handler/Formatting/FormatDocumentOnTypeHandler.cs | 2 +- .../Handler/OnAutoInsert/OnAutoInsertHandler.cs | 2 -- 14 files changed, 16 insertions(+), 30 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6d966e98ae243..f542d89185300 100644 --- a/.editorconfig +++ b/.editorconfig @@ -237,7 +237,7 @@ csharp_preserve_single_line_statements = true # warning RS0005: Do not use generic CodeAction.Create to create CodeAction dotnet_diagnostic.RS0005.severity = none -[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures, VisualStudio}/**/*.{cs,vb}] +[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}] # IDE0011: Add braces csharp_prefer_braces = when_multiline:warning diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs index e44e421647fb8..54ea049a4d4d6 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs @@ -27,8 +27,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageClient [Export(typeof(AlwaysActivateInProcLanguageClient))] internal class AlwaysActivateInProcLanguageClient : AbstractInProcLanguageClient { - private readonly IGlobalOptionService _globalOptionService; - [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, true)] public AlwaysActivateInProcLanguageClient( @@ -39,7 +37,7 @@ public AlwaysActivateInProcLanguageClient( ILspWorkspaceRegistrationService lspWorkspaceRegistrationService) : base(languageServerProtocol, workspace, diagnosticService: null, listenerProvider, lspWorkspaceRegistrationService, diagnosticsClientName: null) { - _globalOptionService = globalOptionService; + _ = globalOptionService; } public override string Name diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs index 9969cd275534a..5e5b2ae2c299b 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/RazorInProcLanguageClient.cs @@ -32,7 +32,6 @@ internal class RazorInProcLanguageClient : AbstractInProcLanguageClient { public const string ClientName = "RazorCSharp"; - private readonly IGlobalOptionService _globalOptionService; private readonly DefaultCapabilitiesProvider _defaultCapabilitiesProvider; /// @@ -52,7 +51,7 @@ public RazorInProcLanguageClient( DefaultCapabilitiesProvider defaultCapabilitiesProvider) : base(languageServerProtocol, workspace, diagnosticService, listenerProvider, lspWorkspaceRegistrationService, ClientName) { - _globalOptionService = globalOptionService; + _ = globalOptionService; _defaultCapabilitiesProvider = defaultCapabilitiesProvider; } diff --git a/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs b/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs index ffae9c56d55b9..215ee5efafbea 100644 --- a/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/PullMemberUp/MainDialog/PullMemberUpDialogViewModel.cs @@ -27,7 +27,6 @@ internal class PullMemberUpDialogViewModel : AbstractNotifyPropertyChanged private bool? _selectAllCheckBoxState; private readonly IWaitIndicator _waitIndicator; private readonly ImmutableDictionary>> _symbolToDependentsMap; - private readonly ImmutableDictionary _symbolToMemberViewMap; private bool _okButtonEnabled; public PullMemberUpDialogViewModel( @@ -38,7 +37,6 @@ public PullMemberUpDialogViewModel( { _waitIndicator = waitIndicator; _symbolToDependentsMap = dependentsMap; - _symbolToMemberViewMap = members.ToImmutableDictionary(memberViewModel => memberViewModel.Symbol); MemberSelectionViewModel = new MemberSelectionViewModel( _waitIndicator, diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index 6b58ec6b54291..c9fe7179685ac 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -44,8 +44,6 @@ internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventLis private readonly ITextDocumentFactoryService _textDocumentFactoryService; private readonly VisualStudioDocumentNavigationService _visualStudioDocumentNavigationService; - private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; - /// /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. /// @@ -104,11 +102,6 @@ public SourceGeneratedFileManager( // The IVsRunningDocumentTable is a free-threaded VS service that allows fetching of the service and advising events // to be done without implicitly marshalling to the UI thread. _runningDocumentTable = _serviceProvider.GetService(); - _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker( - threadingContext, - editorAdaptersFactoryService, - _runningDocumentTable, - this); } public void NavigateToSourceGeneratedFile(SourceGeneratedDocument document, TextSpan sourceSpan) @@ -198,7 +191,7 @@ void IRunningDocumentTableEventListener.OnOpenDocument(string moniker, ITextBuff if (TryGetGeneratedFileInformation(moniker, out var documentId, out var generatorType, out var generatedSourceHintName)) { // Attach to the text buffer if we haven't already - if (!_openFiles.TryGetValue(moniker, out OpenSourceGeneratedFile openFile)) + if (!_openFiles.TryGetValue(moniker, out var openFile)) { openFile = new OpenSourceGeneratedFile(this, textBuffer, _visualStudioWorkspace, documentId, generatorType, _threadingContext); _openFiles.Add(moniker, openFile); diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 70a19235aba7d..37da4c8e58b43 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -38,7 +38,6 @@ internal partial class VisualStudioSymbolNavigationService : ForegroundThreadAff private readonly IServiceProvider _serviceProvider; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; private readonly IMetadataAsSourceFileService _metadataAsSourceFileService; - private readonly SourceGeneratedFileManager _sourceGeneratedFileManager; public VisualStudioSymbolNavigationService( SVsServiceProvider serviceProvider, @@ -50,7 +49,6 @@ public VisualStudioSymbolNavigationService( var componentModel = IServiceProviderExtensions.GetService(_serviceProvider); _editorAdaptersFactory = componentModel.GetService(); _metadataAsSourceFileService = componentModel.GetService(); - _sourceGeneratedFileManager = componentModel.GetService(); } public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options, CancellationToken cancellationToken) diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs index 41747ccfc74cc..701e483b3ca1b 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItem.cs @@ -50,7 +50,7 @@ private sealed class InvocationControllerImpl : IInvocationController public bool Invoke(IEnumerable items, InputSource inputSource, bool preview) { - bool didNavigate = false; + var didNavigate = false; foreach (var item in items.OfType()) { diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs index a002ab577a59b..5b64883281fe2 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/SourceGeneratedFileItems/SourceGeneratedFileItemSource.cs @@ -98,7 +98,7 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel } } - for (int i = 0; i < _items.Count; i++) + for (var i = 0; i < _items.Count; i++) { // If this item that we already have is still a generated document, we'll remove it from our list; the list when we're // done is going to have the new items remaining. If it no longer exists, remove it from list. @@ -121,12 +121,12 @@ private async Task UpdateSourceGeneratedFileItemsAsync(Solution solution, Cancel foreach (var document in sourceGeneratedDocumentsForGeneratorById.Values) { // Binary search to figure out where to insert - int low = 0; - int high = _items.Count; + var low = 0; + var high = _items.Count; while (low < high) { - int mid = (low + high) / 2; + var mid = (low + high) / 2; if (StringComparer.OrdinalIgnoreCase.Compare(document.HintName, ((SourceGeneratedFileItem)_items[mid]).HintName) < 0) { diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs index 34687a2339c51..218dfa1e609f6 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSourceGenerators.cs @@ -70,7 +70,7 @@ public static void Main() VisualStudio.Editor.PlaceCaret(HelloWorldGenerator.GeneratedEnglishClassName); VisualStudio.Editor.SendKeys(Shift(VirtualKey.F12)); - string programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; + var programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; var results = VisualStudio.FindReferencesWindow.GetContents(programReferencesCaption).OrderBy(r => r.Line).ToArray(); Assert.Collection( @@ -113,7 +113,7 @@ public static void Main() VisualStudio.Editor.PlaceCaret(HelloWorldGenerator.GeneratedEnglishClassName); VisualStudio.Editor.SendKeys(Shift(VirtualKey.F12)); - string programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; + var programReferencesCaption = $"'{HelloWorldGenerator.GeneratedEnglishClassName}' references"; var results = VisualStudio.FindReferencesWindow.GetContents(programReferencesCaption); var referenceInGeneratedFile = results.Single(r => r.Code.Contains("")); VisualStudio.FindReferencesWindow.NavigateTo(programReferencesCaption, referenceInGeneratedFile, isPreview: isPreview); diff --git a/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs b/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs index 4888888e9b424..0e9319c1aeede 100644 --- a/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs +++ b/src/VisualStudio/LiveShare/Impl/Client/CloudEnvironmentSupportsFeatureService.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.LanguageServices.LiveShare.Client { [ExportWorkspaceService(typeof(ITextBufferSupportsFeatureService), WorkspaceKind.CloudEnvironmentClientWorkspace), Shared] - class CloudEnvironmentSupportsFeatureService : ITextBufferSupportsFeatureService + internal class CloudEnvironmentSupportsFeatureService : ITextBufferSupportsFeatureService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs index c07bbefdf0114..655e4a730778c 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Completion/CompletionResolveHandler.cs @@ -55,7 +55,7 @@ public CompletionResolveHandler() return completionItem; } - int offset = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(data.Position), cancellationToken).ConfigureAwait(false); + var offset = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(data.Position), cancellationToken).ConfigureAwait(false); var completionService = document.Project.LanguageServices.GetRequiredService(); var symbol = await completionService.GetSymbolAsync(new XamlCompletionContext(document, offset), completionItem.Label, cancellationToken: cancellationToken).ConfigureAwait(false); if (symbol == null) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs index 1452f904e84f8..4fdfbbe51047d 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -19,7 +19,9 @@ internal abstract class AbstractFormatDocumentHandlerBase HandleRequestAsync(RequestType request, RequestContext context, CancellationToken cancellationToken); +#pragma warning disable IDE0060 // Remove unused parameter 'documentIdentifier' - Unsure whether it can break any code relying on IVTs. protected async Task GetTextEditsAsync(LSP.TextDocumentIdentifier documentIdentifier, LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) +#pragma warning restore IDE0060 { using var _ = ArrayBuilder.GetInstance(out var edits); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs index 6405e8b9750f6..1a5eee1fa21f6 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -45,7 +45,7 @@ public async Task HandleRequestAsync(DocumentOnTypeFormattingParams { var position = await document.GetPositionFromLinePositionAsync(ProtocolConversions.PositionToLinePosition(request.Position), cancellationToken).ConfigureAwait(false); var options = new XamlFormattingOptions { InsertSpaces = request.Options.InsertSpaces, TabSize = request.Options.TabSize, OtherOptions = request.Options.OtherOptions }; - IList? textChanges = await formattingService.GetFormattingChangesAsync(document, options, request.Character[0], position, cancellationToken).ConfigureAwait(false); + var textChanges = await formattingService.GetFormattingChangesAsync(document, options, request.Character[0], position, cancellationToken).ConfigureAwait(false); if (textChanges != null) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs index 994211052c511..3596eb26eebfa 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/OnAutoInsert/OnAutoInsertHandler.cs @@ -31,8 +31,6 @@ public OnAutoInsertHandler() public async Task HandleRequestAsync(DocumentOnAutoInsertParams request, RequestContext context, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var response); - var document = context.Document; if (document == null) { From cd7a5d3554d19421311f967c23d45539a904c365 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 20 Jan 2021 18:58:38 +0200 Subject: [PATCH 008/220] Address feedback --- .../LanguageClient/AlwaysActivateInProcLanguageClient.cs | 2 -- .../Implementation/Workspace/SourceGeneratedFileManager.cs | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs index 54ea049a4d4d6..4a7c418ac3d3f 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AlwaysActivateInProcLanguageClient.cs @@ -30,14 +30,12 @@ internal class AlwaysActivateInProcLanguageClient : AbstractInProcLanguageClient [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, true)] public AlwaysActivateInProcLanguageClient( - IGlobalOptionService globalOptionService, LanguageServerProtocol languageServerProtocol, VisualStudioWorkspace workspace, IAsynchronousOperationListenerProvider listenerProvider, ILspWorkspaceRegistrationService lspWorkspaceRegistrationService) : base(languageServerProtocol, workspace, diagnosticService: null, listenerProvider, lspWorkspaceRegistrationService, diagnosticsClientName: null) { - _ = globalOptionService; } public override string Name diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index c9fe7179685ac..3c519ed88bdc2 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -102,6 +102,11 @@ public SourceGeneratedFileManager( // The IVsRunningDocumentTable is a free-threaded VS service that allows fetching of the service and advising events // to be done without implicitly marshalling to the UI thread. _runningDocumentTable = _serviceProvider.GetService(); + _ = new RunningDocumentTableEventTracker( + threadingContext, + editorAdaptersFactoryService, + _runningDocumentTable, + this); } public void NavigateToSourceGeneratedFile(SourceGeneratedDocument document, TextSpan sourceSpan) From a0944470c3d690ab61da8c25cecb2fc27c4f5284 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 7 Mar 2021 07:29:30 +0000 Subject: [PATCH 009/220] Handle trivia correctly --- .../OverloadBase/OverloadBaseTests.vb | 78 ++++++++++++------- ...oadBaseCodeFixProvider.AddKeywordAction.vb | 7 +- .../OverloadBaseCodeFixProvider.vb | 4 +- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb index 7cbe87b9e32c1..bcb75e8d2494a 100644 --- a/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/OverloadBase/OverloadBaseTests.vb @@ -2,28 +2,20 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports Microsoft.CodeAnalysis -Imports Microsoft.CodeAnalysis.CodeFixes -Imports Microsoft.CodeAnalysis.Diagnostics -Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics -Imports Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier(Of + Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, + Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase.OverloadBaseCodeFixProvider) Namespace NS Public Class OverloadBaseTests - Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest - - Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) - Return (Nothing, New OverloadBaseCodeFixProvider()) - End Function - Public Async Function TestAddOverloadsToProperty() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Property Current As Application End Class Class App : Inherits Application - [|Shared Property Current As App|] + Shared Property {|BC40003:Current|} As App End Class", "Class Application Shared Property Current As Application @@ -35,16 +27,16 @@ End Class") Public Async Function TestAddOverloadsToFunction() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Function Test() As Integer Return 1 End Function End Class Class App : Inherits Application - [|Shared Function Test() As Integer + Shared Function {|BC40003:Test|}() As Integer Return 2 - End Function|] + End Function End Class", "Class Application Shared Function Test() As Integer @@ -60,14 +52,14 @@ End Class") Public Async Function TestAddOverloadsToSub() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Sub Test() End Sub End Class Class App : Inherits Application - [|Shared Sub Test() - End Sub|] + Shared Sub {|BC40003:Test|}() + End Sub End Class", "Class Application Shared Sub Test() @@ -79,15 +71,47 @@ Class App : Inherits Application End Class") End Function + + + Public Async Function TestAddOverloadsToSub_HandlingTrivia() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Class Base + Sub M() + + End Sub +End Class + +Class Derived + Inherits Base + ' Trivia + Sub {|BC40003:M|}() + End Sub ' Trivia2 +End Class +", " +Class Base + Sub M() + + End Sub +End Class + +Class Derived + Inherits Base + ' Trivia + Overloads Sub M() + End Sub ' Trivia2 +End Class +") + End Function + Public Async Function TestAddShadowsToProperty() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Sub Current() End Sub End Class Class App : Inherits Application - [|Shared Property Current As App|] + Shared Property {|BC40004:Current|} As App End Class", "Class Application Shared Sub Current() @@ -100,14 +124,14 @@ End Class") Public Async Function TestAddShadowsToFunction() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Property Test As Integer End Class Class App : Inherits Application - [|Shared Function Test() As Integer + Shared Function {|BC40004:Test|}() As Integer Return 2 - End Function|] + End Function End Class", "Class Application Shared Property Test As Integer @@ -121,13 +145,13 @@ End Class") Public Async Function TestAddShadowsToSub() As Task - Await TestInRegularAndScriptAsync( + Await VerifyVB.VerifyCodeFixAsync( "Class Application Shared Property Test As Integer End Class Class App : Inherits Application - [|Shared Sub Test() - End Sub|] + Shared Sub {|BC40004:Test|}() + End Sub End Class", "Class Application Shared Property Test As Integer diff --git a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb index 97de0a1874c30..d7003d3d4c51f 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb @@ -6,6 +6,7 @@ Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis.CodeActions Imports Microsoft.CodeAnalysis.CodeCleanup +Imports Microsoft.CodeAnalysis.Editing Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.VisualBasic.Syntax @@ -49,10 +50,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase Private Async Function GetNewNodeAsync(document As Document, node As SyntaxNode, cancellationToken As CancellationToken) As Task(Of SyntaxNode) Dim newNode As SyntaxNode = Nothing + Dim trivia As SyntaxTriviaList = node.GetLeadingTrivia() + node = node.WithoutLeadingTrivia() Dim propertyStatement = TryCast(node, PropertyStatementSyntax) If propertyStatement IsNot Nothing Then - newNode = propertyStatement.AddModifiers(SyntaxFactory.Token(_modifier)) + newNode = propertyStatement.WithoutLeadingTrivia().AddModifiers(SyntaxFactory.Token(_modifier)) End If Dim methodStatement = TryCast(node, MethodStatementSyntax) @@ -61,7 +64,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase End If 'Make sure we preserve any trivia from the original node - newNode = newNode.WithTriviaFrom(node) + newNode = newNode.WithLeadingTrivia(trivia) 'We need to perform a cleanup on the node because AddModifiers doesn't adhere to the VB modifier ordering rules Dim cleanupService = document.GetLanguageService(Of ICodeCleanerService) diff --git a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb index 577fda16c59cc..09fcf69cb6a26 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.vb @@ -4,8 +4,8 @@ Imports System.Collections.Immutable Imports System.Composition -Imports System.Diagnostics.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase @@ -17,7 +17,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase Friend Const BC40004 As String = "BC40004" ' '{0} '{1}' overloads an overloadable member declared in the base class '{2}'. If you want to shadow the base method, this method must be declared 'Shadows'. - + Public Sub New() End Sub From 060361e01ad4a62e69f1b67731a69865cb9ad994 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 7 Mar 2021 13:16:10 +0200 Subject: [PATCH 010/220] Apply suggestions from code review --- .../OverloadBaseCodeFixProvider.AddKeywordAction.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb index d7003d3d4c51f..f8dfca13b6c34 100644 --- a/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb +++ b/src/Features/VisualBasic/Portable/CodeFixes/OverloadBase/OverloadBaseCodeFixProvider.AddKeywordAction.vb @@ -55,7 +55,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.OverloadBase Dim propertyStatement = TryCast(node, PropertyStatementSyntax) If propertyStatement IsNot Nothing Then - newNode = propertyStatement.WithoutLeadingTrivia().AddModifiers(SyntaxFactory.Token(_modifier)) + newNode = propertyStatement.AddModifiers(SyntaxFactory.Token(_modifier)) End If Dim methodStatement = TryCast(node, MethodStatementSyntax) From f92f8f5707b45ebd9c69df208e2e4225e5ba74a1 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 8 Mar 2021 21:42:33 +0200 Subject: [PATCH 011/220] Fix IDE0052 and IDE0007 --- .../UnusedReferences/RemoveUnusedReferencesCommandHandler.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs index 5791ef40449ad..5c9d28e434b09 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs @@ -34,7 +34,6 @@ internal sealed class RemoveUnusedReferencesCommandHandler private readonly Lazy _lazyReferenceCleanupService; private readonly RemoveUnusedReferencesDialogProvider _unusedReferenceDialogProvider; private readonly VisualStudioWorkspace _workspace; - private readonly IVsHierarchyItemManager _vsHierarchyItemManager; private readonly IUIThreadOperationExecutor _threadOperationExecutor; private IServiceProvider? _serviceProvider; @@ -47,7 +46,6 @@ public RemoveUnusedReferencesCommandHandler( VisualStudioWorkspace workspace) { _unusedReferenceDialogProvider = unusedReferenceDialogProvider; - _vsHierarchyItemManager = vsHierarchyItemManager; _threadOperationExecutor = threadOperationExecutor; _workspace = workspace; @@ -185,7 +183,7 @@ private void OnRemoveUnusedReferencesForSelectedProject(object sender, EventArgs private ImmutableArray GetUnusedReferencesForProject(Solution solution, string projectFilePath, string projectAssetsFile, CancellationToken cancellationToken) { - ImmutableArray unusedReferences = ThreadHelper.JoinableTaskFactory.Run(async () => + var unusedReferences = ThreadHelper.JoinableTaskFactory.Run(async () => { var projectReferences = await _lazyReferenceCleanupService.Value.GetProjectReferencesAsync(projectFilePath, cancellationToken).ConfigureAwait(true); var references = ProjectAssetsReader.ReadReferences(projectReferences, projectAssetsFile); From 41623003d8bef98a053584d05c54ae052feabcaf Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 9 Mar 2021 15:12:27 +0000 Subject: [PATCH 012/220] Offer property attribute target in record positional param --- .../PropertyKeywordRecommenderTests.cs | 31 +++++++++++++++++-- .../ContextQuery/CSharpSyntaxContext.cs | 11 ++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs index ccfc69f72a73a..142710493faac 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/PropertyKeywordRecommenderTests.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading.Tasks; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations @@ -190,5 +189,33 @@ await VerifyAbsenceAsync( @"enum E { [$$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter1() + { + await VerifyKeywordAsync("public record R([$$] string M);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter2() + { + await VerifyKeywordAsync("public record R([$$ SomeAttribute] string M);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter3() + { + await VerifyKeywordAsync("public record R([$$ string M);"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + [WorkItem(51756, "https://github.com/dotnet/roslyn/issues/51756")] + public async Task TestInRecordPositionalParameter4() + { + await VerifyKeywordAsync("public record R([$$"); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index c2e776b00ed15..4d6ec1a637669 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -321,11 +321,14 @@ public bool IsMemberAttributeContext(ISet validTypeDeclarations, Can var token = this.TargetToken; if (token.Kind() == SyntaxKind.OpenBracketToken && - token.Parent.IsKind(SyntaxKind.AttributeList) && - this.SyntaxTree.IsMemberDeclarationContext( - token.SpanStart, contextOpt: null, validModifiers: null, validTypeDeclarations: validTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + token.Parent.IsKind(SyntaxKind.AttributeList)) { - return true; + var isRecordParameter = token.Parent.IsParentKind(SyntaxKind.Parameter) && + token.Parent.Parent.IsParentKind(SyntaxKind.ParameterList) && + token.Parent.Parent.Parent.IsParentKind(SyntaxKind.RecordDeclaration); + + return isRecordParameter || SyntaxTree.IsMemberDeclarationContext( + token.SpanStart, contextOpt: null, validModifiers: null, validTypeDeclarations: validTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken); } return false; From a31622475b86171b64388be8035a1a4474ce5641 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 15 Mar 2021 18:11:03 +0000 Subject: [PATCH 013/220] Address feedback --- .../Extensions/ContextQuery/CSharpSyntaxContext.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index 4d6ec1a637669..4597a52cb5d76 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -323,12 +323,18 @@ public bool IsMemberAttributeContext(ISet validTypeDeclarations, Can if (token.Kind() == SyntaxKind.OpenBracketToken && token.Parent.IsKind(SyntaxKind.AttributeList)) { - var isRecordParameter = token.Parent.IsParentKind(SyntaxKind.Parameter) && + if (token.Parent.IsParentKind(SyntaxKind.Parameter) && token.Parent.Parent.IsParentKind(SyntaxKind.ParameterList) && - token.Parent.Parent.Parent.IsParentKind(SyntaxKind.RecordDeclaration); + token.Parent.Parent.Parent.IsParentKind(SyntaxKind.RecordDeclaration)) + { + return true; + } - return isRecordParameter || SyntaxTree.IsMemberDeclarationContext( - token.SpanStart, contextOpt: null, validModifiers: null, validTypeDeclarations: validTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken); + if (SyntaxTree.IsMemberDeclarationContext( + token.SpanStart, contextOpt: null, validModifiers: null, validTypeDeclarations: validTypeDeclarations, canBePartial: false, cancellationToken: cancellationToken)) + { + return true; + } } return false; From 659c2280957bf92fbf3bf1423ea7201caeae0d34 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 16 Mar 2021 21:09:46 +0000 Subject: [PATCH 014/220] Remove unused parameter --- .../Handler/Formatting/AbstractFormatDocumentHandlerBase.cs | 4 +--- .../Handler/Formatting/FormatDocumentHandler.cs | 2 +- .../Handler/Formatting/FormatDocumentRangeHandler.cs | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs index 0abb934cf91b8..ca1f02968f887 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/AbstractFormatDocumentHandlerBase.cs @@ -19,9 +19,7 @@ internal abstract class AbstractFormatDocumentHandlerBase false; public override bool RequiresLSPSolution => true; -#pragma warning disable IDE0060 // Remove unused parameter 'documentIdentifier' - Unsure whether it can break any code relying on IVTs. - protected async Task GetTextEditsAsync(LSP.TextDocumentIdentifier documentIdentifier, LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) -#pragma warning restore IDE0060 + protected async Task GetTextEditsAsync(LSP.FormattingOptions formattingOptions, RequestContext context, CancellationToken cancellationToken, LSP.Range? range = null) { using var _ = ArrayBuilder.GetInstance(out var edits); diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs index 8ccb5ab8986c2..41008fd3fbba0 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentHandler.cs @@ -28,6 +28,6 @@ public FormatDocumentHandler() public override LSP.TextDocumentIdentifier? GetTextDocumentIdentifier(LSP.DocumentFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync(LSP.DocumentFormattingParams request, RequestContext context, CancellationToken cancellationToken) - => GetTextEditsAsync(request.TextDocument, request.Options, context, cancellationToken); + => GetTextEditsAsync(request.Options, context, cancellationToken); } } diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs index 5317b5cb61017..504c4b5665a36 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Formatting/FormatDocumentRangeHandler.cs @@ -28,6 +28,6 @@ public FormatDocumentRangeHandler() public override TextDocumentIdentifier? GetTextDocumentIdentifier(DocumentRangeFormattingParams request) => request.TextDocument; public override Task HandleRequestAsync(DocumentRangeFormattingParams request, RequestContext context, CancellationToken cancellationToken) - => GetTextEditsAsync(request.TextDocument, request.Options, context, cancellationToken, range: request.Range); + => GetTextEditsAsync(request.Options, context, cancellationToken, range: request.Range); } } From 9234c2b1b82129740f191f541a478c2e8c4764fb Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 12 Mar 2021 08:49:48 +0200 Subject: [PATCH 015/220] Add tests for #49648 and #50734 --- .../Tests/NamingStyles/NamingStylesTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs b/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs index ed4edba46ac14..2ba3d5c096c96 100644 --- a/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs +++ b/src/Analyzers/CSharp/Tests/NamingStyles/NamingStylesTests.cs @@ -1382,5 +1382,32 @@ class C : I int [|global::I.X|] => 0; }", new TestParameters(options: s_options.PropertyNamesArePascalCase)); } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + [WorkItem(50734, "https://github.com/dotnet/roslyn/issues/50734")] + public async Task TestAsyncEntryPoint() + { + await TestMissingInRegularAndScriptAsync(@" +using System.Threading.Tasks; + +class C +{ + static async Task [|Main|]() + { + await Task.Delay(0); + } +}", new TestParameters(options: s_options.AsyncFunctionNamesEndWithAsync)); + } + + [Fact, Trait(Traits.Feature, Traits.Features.NamingStyle)] + [WorkItem(49648, "https://github.com/dotnet/roslyn/issues/49648")] + public async Task TestAsyncEntryPoint_TopLevel() + { + await TestMissingInRegularAndScriptAsync(@" +using System.Threading.Tasks; + +[|await Task.Delay(0);|] +", new TestParameters(options: s_options.AsyncFunctionNamesEndWithAsync)); + } } } From a76e0f4f5d40a5863fc49c4a0cf873c070a1058b Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 17 Mar 2021 10:03:52 +0000 Subject: [PATCH 016/220] Ignore static Main methods in NamingStyle analyzer --- .../NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs b/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs index dec3fe4ef8f1b..5a65e4795b844 100644 --- a/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs +++ b/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs @@ -108,7 +108,9 @@ void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) ConcurrentDictionary> idToCachedResult, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(symbol.Name)) + if (string.IsNullOrEmpty(symbol.Name) || + // Heuristic for recognizing entry points. + symbol.IsStatic && symbol.Name == WellKnownMemberNames.EntryPointMethodName) { return null; } From 21f339a05dd9746836cc8bc7e87ab9c81c2d6dce Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 17 Mar 2021 19:16:18 +0200 Subject: [PATCH 017/220] Update CPSProjectFactory.cs --- .../Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs index 5857e66907340..69abf0a3ee753 100644 --- a/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs +++ b/src/VisualStudio/Core/Impl/ProjectSystem/CPS/CPSProjectFactory.cs @@ -70,8 +70,10 @@ public async Task CreateProjectContextAsync( var visualStudioProject = await _projectFactory.CreateAndAddToWorkspaceAsync( projectUniqueName, languageName, creationInfo, cancellationToken).ConfigureAwait(true); +#pragma warning disable IDE0059 // Unnecessary assignment of a value // At this point we've mutated the workspace. So we're no longer cancellable. cancellationToken = CancellationToken.None; +#pragma warning restore IDE0059 // Unnecessary assignment of a value if (languageName == LanguageNames.FSharp) { From 7775b1697be03f47ad98d5eb0059c5a36b9ae15b Mon Sep 17 00:00:00 2001 From: David Barbet Date: Fri, 19 Mar 2021 14:34:05 -0700 Subject: [PATCH 018/220] Remove some dead loggers --- ...lassificationTaggerProvider.TagComputer.cs | 10 +- src/VisualStudio/Core/Def/RoslynPackage.cs | 4 - .../LinkedFileDiffMergingLogger.cs | 119 ------------------ .../LinkedFileDiffMergingSession.cs | 12 -- .../Portable/Log/RequestLatencyTracker.cs | 28 ----- .../Core/Portable/Log/SyntacticLspLogger.cs | 58 --------- .../Versions/PersistedVersionStampLogger.cs | 70 ----------- .../Remote/Core/ServiceHubRemoteHostClient.cs | 8 -- .../Compiler/Core/Log/FunctionId.cs | 22 ++-- 9 files changed, 14 insertions(+), 317 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs delete mode 100644 src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs delete mode 100644 src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs delete mode 100644 src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index c50ebf1186e89..e4912cbac818f 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -203,13 +203,9 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document document, Cancella return; } - var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); - using (latencyTracker) - { - // preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready. - // F#/typescript and other languages that doesn't support syntax tree will return null here. - _ = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - } + // preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready. + // F#/typescript and other languages that doesn't support syntax tree will return null here. + _ = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); lock (_gate) { diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs index 7261ce5c45d59..dff2ac368b1d3 100644 --- a/src/VisualStudio/Core/Def/RoslynPackage.cs +++ b/src/VisualStudio/Core/Def/RoslynPackage.cs @@ -21,7 +21,6 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Telemetry; -using Microsoft.CodeAnalysis.Versions; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.LanguageServices.ColorSchemes; using Microsoft.VisualStudio.LanguageServices.Experimentation; @@ -250,13 +249,10 @@ protected override void Dispose(bool disposing) private void ReportSessionWideTelemetry() { - PersistedVersionStampLogger.ReportTelemetry(); - LinkedFileDiffMergingLogger.ReportTelemetry(); SolutionLogger.ReportTelemetry(); AsyncCompletionLogger.ReportTelemetry(); CompletionProvidersLogger.ReportTelemetry(); ChangeSignatureLogger.ReportTelemetry(); - SyntacticLspLogger.ReportTelemetry(); } private void DisposeVisualStudioServices() diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs deleted file mode 100644 index c1b2bd5f005e8..0000000000000 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingLogger.cs +++ /dev/null @@ -1,119 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using Microsoft.CodeAnalysis.Internal.Log; -using static Microsoft.CodeAnalysis.LinkedFileDiffMergingSession; - -namespace Microsoft.CodeAnalysis -{ - internal class LinkedFileDiffMergingLogger - { - private static readonly LogAggregator LogAggregator = new(); - - internal enum MergeInfo - { - SessionsWithLinkedFiles, - LinkedFileGroupsProcessed, - IdenticalDiffs, - IsolatedDiffs, - OverlappingDistinctDiffs, - OverlappingDistinctDiffsWithSameSpan, - OverlappingDistinctDiffsWithSameSpanAndSubstringRelation, - InsertedMergeConflictComments, - InsertedMergeConflictCommentsAtAdjustedLocation - } - - internal static void LogSession(LinkedFileDiffMergingSessionInfo sessionInfo) - { - if (sessionInfo.LinkedFileGroups.Count > 1) - { - LogNewSessionWithLinkedFiles(); - LogNumberOfLinkedFileGroupsProcessed(sessionInfo.LinkedFileGroups.Count); - - foreach (var groupInfo in sessionInfo.LinkedFileGroups) - { - LogNumberOfIdenticalDiffs(groupInfo.IdenticalDiffs); - LogNumberOfIsolatedDiffs(groupInfo.IsolatedDiffs); - LogNumberOfOverlappingDistinctDiffs(groupInfo.OverlappingDistinctDiffs); - LogNumberOfOverlappingDistinctDiffsWithSameSpan(groupInfo.OverlappingDistinctDiffsWithSameSpan); - LogNumberOfOverlappingDistinctDiffsWithSameSpanAndSubstringRelation(groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation); - LogNumberOfInsertedMergeConflictComments(groupInfo.InsertedMergeConflictComments); - LogNumberOfInsertedMergeConflictCommentsAtAdjustedLocation(groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation); - - if (groupInfo.InsertedMergeConflictComments > 0 || - groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation > 0) - { - Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup, SessionLogMessage.Create(groupInfo)); - } - } - } - } - - private static void LogNewSessionWithLinkedFiles() => - Log((int)MergeInfo.SessionsWithLinkedFiles, 1); - - private static void LogNumberOfLinkedFileGroupsProcessed(int linkedFileGroupsProcessed) => - Log((int)MergeInfo.LinkedFileGroupsProcessed, linkedFileGroupsProcessed); - - private static void LogNumberOfIdenticalDiffs(int identicalDiffs) => - Log((int)MergeInfo.IdenticalDiffs, identicalDiffs); - - private static void LogNumberOfIsolatedDiffs(int isolatedDiffs) => - Log((int)MergeInfo.IsolatedDiffs, isolatedDiffs); - - private static void LogNumberOfOverlappingDistinctDiffs(int overlappingDistinctDiffs) => - Log((int)MergeInfo.OverlappingDistinctDiffs, overlappingDistinctDiffs); - - private static void LogNumberOfOverlappingDistinctDiffsWithSameSpan(int overlappingDistinctDiffsWithSameSpan) => - Log((int)MergeInfo.OverlappingDistinctDiffsWithSameSpan, overlappingDistinctDiffsWithSameSpan); - - private static void LogNumberOfOverlappingDistinctDiffsWithSameSpanAndSubstringRelation(int overlappingDistinctDiffsWithSameSpanAndSubstringRelation) => - Log((int)MergeInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation, overlappingDistinctDiffsWithSameSpanAndSubstringRelation); - - private static void LogNumberOfInsertedMergeConflictComments(int insertedMergeConflictComments) => - Log((int)MergeInfo.InsertedMergeConflictComments, insertedMergeConflictComments); - - private static void LogNumberOfInsertedMergeConflictCommentsAtAdjustedLocation(int insertedMergeConflictCommentsAtAdjustedLocation) => - Log((int)MergeInfo.InsertedMergeConflictCommentsAtAdjustedLocation, insertedMergeConflictCommentsAtAdjustedLocation); - - private static void Log(int mergeInfo, int count) - => LogAggregator.IncreaseCountBy(mergeInfo, count); - - internal static void ReportTelemetry() - { - Logger.Log(FunctionId.Workspace_Solution_LinkedFileDiffMergingSession, KeyValueLogMessage.Create(m => - { - foreach (var kv in LogAggregator) - { - var mergeInfo = ((MergeInfo)kv.Key).ToString("f"); - m[mergeInfo] = kv.Value.GetCount(); - } - })); - } - - private static class SessionLogMessage - { - private const string LinkedDocuments = nameof(LinkedDocuments); - private const string DocumentsWithChanges = nameof(DocumentsWithChanges); - - public static KeyValueLogMessage Create(LinkedFileGroupSessionInfo groupInfo) - { - return KeyValueLogMessage.Create(m => - { - m[LinkedDocuments] = groupInfo.LinkedDocuments; - m[DocumentsWithChanges] = groupInfo.DocumentsWithChanges; - m[MergeInfo.IdenticalDiffs.ToString("f")] = groupInfo.IdenticalDiffs; - m[MergeInfo.IsolatedDiffs.ToString("f")] = groupInfo.IsolatedDiffs; - m[MergeInfo.OverlappingDistinctDiffs.ToString("f")] = groupInfo.OverlappingDistinctDiffs; - m[MergeInfo.OverlappingDistinctDiffsWithSameSpan.ToString("f")] = groupInfo.OverlappingDistinctDiffsWithSameSpan; - m[MergeInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation.ToString("f")] = groupInfo.OverlappingDistinctDiffsWithSameSpanAndSubstringRelation; - m[MergeInfo.InsertedMergeConflictComments.ToString("f")] = groupInfo.InsertedMergeConflictComments; - m[MergeInfo.InsertedMergeConflictCommentsAtAdjustedLocation.ToString("f")] = groupInfo.InsertedMergeConflictCommentsAtAdjustedLocation; - }); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index cee39de1155a8..72b1594c49394 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -74,8 +74,6 @@ internal async Task MergeDiffsAsync(IMergeConflict } } - LogLinkedFileDiffMergingSessionInfo(sessionInfo); - return new LinkedFileMergeSessionResult(updatedSolution, linkedFileMergeResults); } @@ -319,16 +317,6 @@ private static IEnumerable NormalizeChanges(IEnumerable return normalizedChanges; } - private void LogLinkedFileDiffMergingSessionInfo(LinkedFileDiffMergingSessionInfo sessionInfo) - { - if (!_logSessionInfo) - { - return; - } - - LinkedFileDiffMergingLogger.LogSession(sessionInfo); - } - internal class LinkedFileDiffMergingSessionInfo { public readonly List LinkedFileGroups = new(); diff --git a/src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs b/src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs deleted file mode 100644 index 69d21fd4b407c..0000000000000 --- a/src/Workspaces/Core/Portable/Log/RequestLatencyTracker.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; - -namespace Microsoft.CodeAnalysis.Internal.Log -{ - internal sealed class RequestLatencyTracker : IDisposable - { - private readonly int _tick; - private readonly SyntacticLspLogger.RequestType _requestType; - - public RequestLatencyTracker(SyntacticLspLogger.RequestType requestType) - { - _tick = Environment.TickCount; - _requestType = requestType; - } - - public void Dispose() - { - var delta = Environment.TickCount - _tick; - SyntacticLspLogger.LogRequestLatency(_requestType, delta); - } - } -} diff --git a/src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs b/src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs deleted file mode 100644 index c73a839a3781c..0000000000000 --- a/src/Workspaces/Core/Portable/Log/SyntacticLspLogger.cs +++ /dev/null @@ -1,58 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -namespace Microsoft.CodeAnalysis.Internal.Log -{ - internal sealed class SyntacticLspLogger - { - private static readonly HistogramLogAggregator s_histogramLogAggregator = new(bucketSize: 100, maxBucketValue: 5000); - - internal enum RequestType - { - LexicalClassifications, - SyntacticClassifications, - SyntacticTagger, - } - - internal static void LogRequestLatency(RequestType requestType, decimal latency) - => s_histogramLogAggregator.IncreaseCount(requestType, latency); - - internal static void ReportTelemetry() - { - - foreach (var kv in s_histogramLogAggregator) - { - Report((RequestType)kv.Key, kv.Value); - } - - static void Report(RequestType requestType, HistogramLogAggregator.HistogramCounter counter) - { - FunctionId functionId; - switch (requestType) - { - case RequestType.LexicalClassifications: - functionId = FunctionId.Liveshare_LexicalClassifications; - break; - case RequestType.SyntacticClassifications: - functionId = FunctionId.Liveshare_SyntacticClassifications; - break; - case RequestType.SyntacticTagger: - functionId = FunctionId.Liveshare_SyntacticTagger; - break; - default: - return; - } - - Logger.Log(functionId, KeyValueLogMessage.Create(m => - { - m[$"{requestType.ToString()}.BucketSize"] = counter.BucketSize; - m[$"{requestType.ToString()}.MaxBucketValue"] = counter.MaxBucketValue; - m[$"{requestType.ToString()}.Buckets"] = counter.GetBucketsAsString(); - })); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs b/src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs deleted file mode 100644 index 4b17ad7fa5684..0000000000000 --- a/src/Workspaces/Core/Portable/Versions/PersistedVersionStampLogger.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Internal.Log; - -namespace Microsoft.CodeAnalysis.Versions -{ - internal static class PersistedVersionStampLogger - { - // we have 6 different versions to track various changes - private const string Text = nameof(Text); - private const string SyntaxTree = nameof(SyntaxTree); - private const string Project = nameof(Project); - private const string DependentProject = nameof(DependentProject); - - private static readonly LogAggregator s_logAggregator = new(); - - public static void LogPersistedTextVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(Text); - } - - public static void LogPersistedSyntaxTreeVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(SyntaxTree); - } - - public static void LogPersistedProjectVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(Project); - } - - public static void LogPersistedDependentProjectVersionUsage(bool succeeded) - { - if (!succeeded) - { - return; - } - - s_logAggregator.IncreaseCount(DependentProject); - } - - public static void ReportTelemetry() - { - Logger.Log(FunctionId.PersistedSemanticVersion_Info, KeyValueLogMessage.Create(m => - { - m[Text] = s_logAggregator.GetCount(Text); - m[SyntaxTree] = s_logAggregator.GetCount(SyntaxTree); - m[Project] = s_logAggregator.GetCount(Project); - m[DependentProject] = s_logAggregator.GetCount(DependentProject); - })); - } - } -} diff --git a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs index 45b33544e51e7..6eb9c2f441675 100644 --- a/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs +++ b/src/Workspaces/Remote/Core/ServiceHubRemoteHostClient.cs @@ -88,14 +88,6 @@ public static async Task CreateAsync( { using (Logger.LogBlock(FunctionId.ServiceHubRemoteHostClient_CreateAsync, KeyValueLogMessage.NoProperty, cancellationToken)) { - Logger.Log(FunctionId.RemoteHost_Bitness, KeyValueLogMessage.Create( - LogType.Trace, - m => - { - m["64bit"] = RemoteHostOptions.IsServiceHubProcess64Bit(services); - m["ServerGC"] = RemoteHostOptions.IsServiceHubProcessServerGC(services); - })); - #pragma warning disable ISB001 // Dispose of proxies #pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed var serviceBrokerClient = new ServiceBrokerClient(serviceBroker); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 38654f12bd3f8..31c14ee77ca9b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -84,8 +84,8 @@ internal enum FunctionId Workspace_ApplyChanges = 62, Workspace_TryGetDocument = 63, Workspace_TryGetDocumentFromInProgressSolution = 64, - Workspace_Solution_LinkedFileDiffMergingSession = 65, - Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup = 66, + // obsolete: Workspace_Solution_LinkedFileDiffMergingSession = 65, + // obsolete: Workspace_Solution_LinkedFileDiffMergingSession_LinkedFileGroup = 66, Workspace_Solution_Info = 67, EndConstruct_DoStatement = 68, @@ -287,7 +287,7 @@ internal enum FunctionId DiagnosticAnalyzerService_Analyzers = 230, DiagnosticAnalyzerDriver_AnalyzerCrash = 231, DiagnosticAnalyzerDriver_AnalyzerTypeCount = 232, - PersistedSemanticVersion_Info = 233, + // obsolete: PersistedSemanticVersion_Info = 233, StorageDatabase_Exceptions = 234, WorkCoordinator_ShutdownTimeout = 235, Diagnostics_HyperLink = 236, @@ -412,7 +412,7 @@ internal enum FunctionId Extension_InfoBar = 327, FxCopAnalyzersInstall = 328, AssetStorage_ForceGC = 329, - RemoteHost_Bitness = 330, + // obsolete: RemoteHost_Bitness = 330, Intellisense_Completion = 331, MetadataOnlyImage_EmitFailure = 332, LiveTableDataSource_OnDiagnosticsUpdated = 333, @@ -421,9 +421,9 @@ internal enum FunctionId Diagnostics_BadAnalyzer = 336, CodeAnalysisService_ReportAnalyzerPerformance = 337, PerformanceTrackerService_AddSnapshot = 338, - AbstractProject_SetIntelliSenseBuild = 339, - AbstractProject_Created = 340, - AbstractProject_PushedToWorkspace = 341, + // obsolete: AbstractProject_SetIntelliSenseBuild = 339, + // obsolete: AbstractProject_Created = 340, + // obsolete: AbstractProject_PushedToWorkspace = 341, ExternalErrorDiagnosticUpdateSource_AddError = 342, DiagnosticIncrementalAnalyzer_SynchronizeWithBuildAsync = 343, Completion_ExecuteCommand_TypeChar = 344, @@ -431,7 +431,7 @@ internal enum FunctionId SymbolFinder_Solution_Pattern_FindSourceDeclarationsAsync = 346, SymbolFinder_Project_Pattern_FindSourceDeclarationsAsync = 347, - Intellisense_Completion_Commit = 348, + // obsolete: Intellisense_Completion_Commit = 348, CodeCleanupInfobar_BarDisplayed = 349, CodeCleanupInfobar_ConfigureNow = 350, @@ -464,9 +464,9 @@ internal enum FunctionId RemoteHostService_IsExperimentEnabledAsync = 373, PartialLoad_FullyLoaded = 374, Liveshare_UnknownCodeAction = 375, - Liveshare_LexicalClassifications = 376, - Liveshare_SyntacticClassifications = 377, - Liveshare_SyntacticTagger = 378, + // obsolete: Liveshare_LexicalClassifications = 376, + // obsolete: Liveshare_SyntacticClassifications = 377, + // obsolete: Liveshare_SyntacticTagger = 378, CommandHandler_GoToBase = 379, From 09eff5c7c3cd17f40e6efbb09f4853ea81d26bbb Mon Sep 17 00:00:00 2001 From: David Barbet Date: Mon, 22 Mar 2021 12:49:51 -0700 Subject: [PATCH 019/220] Remove unused parameter --- .../Core/Implementation/InlineRename/InlineRenameSession.cs | 2 +- .../SyncNamespace/AbstractChangeNamespaceService.cs | 2 +- .../LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs | 5 +---- src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs | 2 +- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs index f54796ec87973..d6333b135eed0 100644 --- a/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs +++ b/src/EditorFeatures/Core/Implementation/InlineRename/InlineRenameSession.cs @@ -596,7 +596,7 @@ private void QueueApplyReplacements() private async Task<(IInlineRenameReplacementInfo replacementInfo, LinkedFileMergeSessionResult mergeResult)> ComputeMergeResultAsync(IInlineRenameReplacementInfo replacementInfo, CancellationToken cancellationToken) { - var diffMergingSession = new LinkedFileDiffMergingSession(_baseSolution, replacementInfo.NewSolution, replacementInfo.NewSolution.GetChanges(_baseSolution), logSessionInfo: true); + var diffMergingSession = new LinkedFileDiffMergingSession(_baseSolution, replacementInfo.NewSolution, replacementInfo.NewSolution.GetChanges(_baseSolution)); var mergeResult = await diffMergingSession.MergeDiffsAsync(mergeConflictHandler: null, cancellationToken: cancellationToken).ConfigureAwait(false); return (replacementInfo, mergeResult); } diff --git a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs index 09be6f5434dad..4aae3e0230ca9 100644 --- a/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs +++ b/src/Features/Core/Portable/CodeRefactorings/SyncNamespace/AbstractChangeNamespaceService.cs @@ -844,7 +844,7 @@ private static async Task AddImportsInContainersAsync( private static async Task MergeDiffAsync(Solution oldSolution, Solution newSolution, CancellationToken cancellationToken) { - var diffMergingSession = new LinkedFileDiffMergingSession(oldSolution, newSolution, newSolution.GetChanges(oldSolution), logSessionInfo: false); + var diffMergingSession = new LinkedFileDiffMergingSession(oldSolution, newSolution, newSolution.GetChanges(oldSolution)); var mergeResult = await diffMergingSession.MergeDiffsAsync(mergeConflictHandler: null, cancellationToken: cancellationToken).ConfigureAwait(false); return mergeResult.MergedSolution; } diff --git a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs index 72b1594c49394..7db0a8f527a14 100644 --- a/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs +++ b/src/Workspaces/Core/Portable/LinkedFileDiffMerging/LinkedFileDiffMergingSession.cs @@ -19,18 +19,15 @@ namespace Microsoft.CodeAnalysis { internal sealed class LinkedFileDiffMergingSession { - private readonly bool _logSessionInfo; - private readonly Solution _oldSolution; private readonly Solution _newSolution; private readonly SolutionChanges _solutionChanges; - public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges, bool logSessionInfo) + public LinkedFileDiffMergingSession(Solution oldSolution, Solution newSolution, SolutionChanges solutionChanges) { _oldSolution = oldSolution; _newSolution = newSolution; _solutionChanges = solutionChanges; - _logSessionInfo = logSessionInfo; } internal async Task MergeDiffsAsync(IMergeConflictHandler mergeConflictHandler, CancellationToken cancellationToken) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index d515dfa7419f8..c140c593a3239 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -1644,7 +1644,7 @@ internal async Task WithMergedLinkedFileChangesAsync( CancellationToken cancellationToken = default) { // we only log sessioninfo for actual changes committed to workspace which should exclude ones from preview - var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution), logSessionInfo: solutionChanges != null); + var session = new LinkedFileDiffMergingSession(oldSolution, this, solutionChanges ?? this.GetChanges(oldSolution)); return (await session.MergeDiffsAsync(mergeConflictHandler, cancellationToken).ConfigureAwait(false)).MergedSolution; } From d80fd7a9510f178484027da19b884ef198afeb69 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 23 Mar 2021 13:32:26 +0200 Subject: [PATCH 020/220] Add SimplifyObjectCreation analyzer --- .../Core/Analyzers/EnforceOnBuildValues.cs | 3 +- .../Core/Analyzers/IDEDiagnosticIds.cs | 2 + ...implifyObjectCreationDiagnosticAnalyzer.vb | 53 +++++++++ .../Analyzers/VisualBasicAnalyzers.projitems | 1 + .../VisualBasicAnalyzersResources.resx | 6 ++ .../xlf/VisualBasicAnalyzersResources.cs.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.de.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.es.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.fr.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.it.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.ja.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.ko.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.pl.xlf | 5 + .../VisualBasicAnalyzersResources.pt-BR.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.ru.xlf | 5 + .../xlf/VisualBasicAnalyzersResources.tr.xlf | 5 + .../VisualBasicAnalyzersResources.zh-Hans.xlf | 5 + .../VisualBasicAnalyzersResources.zh-Hant.xlf | 5 + ...icSimplifyObjectCreationCodeFixProvider.vb | 61 +++++++++++ .../CodeFixes/VisualBasicCodeFixes.projitems | 1 + .../SimplifyObjectCreationTests.vb | 102 ++++++++++++++++++ .../VisualBasicAnalyzers.UnitTests.projitems | 1 + 22 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb create mode 100644 src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb create mode 100644 src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 744e864cbdfe2..69df5772e4752 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -75,7 +75,8 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild RemoveUnnecessaryDiscardDesignation = /*IDE0110*/ EnforceOnBuild.Recommended; public const EnforceOnBuild InvokeDelegateWithConditionalAccess = /*IDE1005*/ EnforceOnBuild.Recommended; public const EnforceOnBuild NamingRule = /*IDE1006*/ EnforceOnBuild.Recommended; - public const EnforceOnBuild MatchFolderAndNamespace = /* IDE0130*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild MatchFolderAndNamespace = /*IDE0130*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild SimplifyObjectCreation = /*IDE0131*/ EnforceOnBuild.Recommended; /* EnforceOnBuild.WhenExplicitlyEnabled */ public const EnforceOnBuild RemoveUnnecessaryCast = /*IDE0004*/ EnforceOnBuild.WhenExplicitlyEnabled; // TODO: Move to 'Recommended' OR 'HighlyRecommended' bucket once performance problems are addressed: https://github.com/dotnet/roslyn/issues/43304 diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index fedbf3b9edc06..8bc54452e89e9 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -158,6 +158,8 @@ internal static class IDEDiagnosticIds public const string MatchFolderAndNamespaceDiagnosticId = "IDE0130"; + public const string SimplifyObjectCreationDiagnosticId = "IDE0131"; + // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; public const string AnalyzerDependencyConflictId = "IDE1002"; diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb new file mode 100644 index 0000000000000..f7de977b30479 --- /dev/null +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb @@ -0,0 +1,53 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.CodeStyle +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation + + Friend NotInheritable Class VisualBasicSimplifyObjectCreationDiagnosticAnalyzer + Inherits AbstractBuiltInCodeStyleDiagnosticAnalyzer + + Public Sub New() + MyBase.New( + diagnosticId:=IDEDiagnosticIds.SimplifyObjectCreationDiagnosticId, + enforceOnBuild:=EnforceOnBuildValues.SimplifyObjectCreation, + [option]:=Nothing, + title:=New LocalizableResourceString(NameOf(VisualBasicAnalyzersResources.Object_creation_can_be_simplified), VisualBasicAnalyzersResources.ResourceManager, GetType(VisualBasicAnalyzersResources))) + End Sub + + Protected Overrides Sub InitializeWorker(context As AnalysisContext) + context.RegisterSyntaxNodeAction(AddressOf AnalyzeVariableDeclarator, SyntaxKind.VariableDeclarator) + End Sub + + Public Overrides Function GetAnalyzerCategory() As DiagnosticAnalyzerCategory + Return DiagnosticAnalyzerCategory.SemanticSpanAnalysis + End Function + + Private Sub AnalyzeVariableDeclarator(context As SyntaxNodeAnalysisContext) + ' Finds and reports syntax on the form: + ' Dim x As SomeType = New SomeType() + ' which can be simplified to + ' Dim x As New SomeType() + + Dim variableDeclarator = DirectCast(context.Node, VariableDeclaratorSyntax) + Dim asClauseType = variableDeclarator.AsClause?.Type() + If asClauseType Is Nothing Then + Return + End If + + Dim objectCreation = TryCast(variableDeclarator.Initializer?.Value, ObjectCreationExpressionSyntax) + If objectCreation Is Nothing Then + Return + End If + + Dim symbolInfo = context.SemanticModel.GetTypeInfo(objectCreation) + If symbolInfo.Type IsNot Nothing AndAlso symbolInfo.Type.Equals(symbolInfo.ConvertedType, SymbolEqualityComparer.Default) Then + context.ReportDiagnostic(Diagnostic.Create(Descriptor, variableDeclarator.GetLocation())) + End If + End Sub + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems index 6d5f7b35a94c2..152f94c0ea124 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzers.projitems @@ -38,6 +38,7 @@ + diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx index a171b866ea844..cfdf373149e39 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx @@ -140,4 +140,10 @@ Use 'IsNot' expression {locked: IsNot}This is a Visual Basic keyword and should not be localized or have its casing changed + + Object creation can be simplified + + + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf index 7aed16da9d690..6d6f492c3509e 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.cs.xlf @@ -17,6 +17,11 @@ Příkaz Imports není potřebný. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. Klíčové slovo ByVal není zapotřebí a dá se odebrat. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf index 63d87b6def34e..f13a1d5df3d8d 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.de.xlf @@ -17,6 +17,11 @@ Imports-Anweisung ist unnötig. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. Das ByVal-Schlüsselwort ist unnötig und kann entfernt werden. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf index 57e1fe2784cc8..685a7d7b1ff1e 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.es.xlf @@ -17,6 +17,11 @@ La declaración de importaciones no es necesaria. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. La palabra clave "ByVal" no es necesaria y se puede quitar. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf index b35686fb49a03..e91f4f4a329f5 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.fr.xlf @@ -17,6 +17,11 @@ L'instruction Imports n'est pas utile. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. Le mot clé 'ByVal' est inutile et peut être supprimé. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf index 0139c32109f91..70f2d1ae03aa4 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.it.xlf @@ -17,6 +17,11 @@ L'istruzione Imports non è necessaria. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. La parola chiave 'ByVal' non è necessaria e può essere rimossa. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf index c0e2f334e81b2..f5d0f073d6705 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ja.xlf @@ -17,6 +17,11 @@ Imports ステートメントは不要です。 + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' キーワードは必要ありません。削除することができます。 diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf index 8bd041a14cf53..f97a356b0f8fa 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ko.xlf @@ -17,6 +17,11 @@ Imports 문은 필요하지 않습니다. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' 키워드는 불필요하며 제거할 수 있습니다. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf index 81be8dd4c48b8..11a5abbc28535 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pl.xlf @@ -17,6 +17,11 @@ Instrukcja imports jest niepotrzebna. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. Słowo kluczowe „ByVal” jest niepotrzebne i można je usunąć. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf index f17e36330f71f..10d8737856869 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.pt-BR.xlf @@ -17,6 +17,11 @@ A instrução Imports é desnecessária. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. A palavra-chave 'ByVal' é desnecessária e pode ser removida. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf index fb3b6dcade26f..bed45c66cd43c 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.ru.xlf @@ -17,6 +17,11 @@ Оператор Imports не нужен. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. Ключевое слово "ByVal" является необязательным и может быть удалено. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf index 7473a7934ba20..cead0730ccec4 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.tr.xlf @@ -17,6 +17,11 @@ Imports deyimi gerekli değildir. + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' anahtar sözcüğü gereksizdir ve kaldırılabilir. diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf index 3127cb198c10d..9996aa07734ab 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hans.xlf @@ -17,6 +17,11 @@ Imports 语句是不需要的。 + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. "ByVal" 关键字是不必要的,可以删除。 diff --git a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf index 218c8750bacae..46ef3c4dd054c 100644 --- a/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/VisualBasic/Analyzers/xlf/VisualBasicAnalyzersResources.zh-Hant.xlf @@ -17,6 +17,11 @@ 無須 Imports 陳述式。 + + Object creation can be simplified + Object creation can be simplified + + 'ByVal' keyword is unnecessary and can be removed. 'ByVal' 關鍵字非必要,可以移除。 diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb new file mode 100644 index 0000000000000..07115dd1da666 --- /dev/null +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb @@ -0,0 +1,61 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports System.Collections.Immutable +Imports System.Composition +Imports System.Threading +Imports Microsoft.CodeAnalysis.CodeActions +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation + + Friend Class VisualBasicSimplifyObjectCreationCodeFixProvider + Inherits SyntaxEditorBasedCodeFixProvider + + + + Public Sub New() + End Sub + + Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(IDEDiagnosticIds.SimplifyObjectCreationDiagnosticId) + + Friend Overrides ReadOnly Property CodeFixCategory As CodeFixCategory = CodeFixCategory.CodeStyle + + Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task + For Each diagnostic In context.Diagnostics + context.RegisterCodeFix(New MyCodeAction( + VisualBasicAnalyzersResources.Simplify_object_creation, + Function(ct) FixAsync(context.Document, diagnostic, ct)), + diagnostic) + Next + Return Task.CompletedTask + End Function + + Protected Overrides Async Function FixAllAsync(document As Document, diagnostics As ImmutableArray(Of Diagnostic), editor As SyntaxEditor, cancellationToken As CancellationToken) As Task + Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + For Each diagnostic In diagnostics + Dim node = DirectCast(root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie:=True), VariableDeclaratorSyntax) + Dim asNewClause = SyntaxFactory.AsNewClause(node.AsClause.AsKeyword, DirectCast(node.Initializer.Value, NewExpressionSyntax)) + Dim newNode = node.Update( + names:=node.Names, + asClause:=asNewClause, + initializer:=Nothing) + editor.ReplaceNode(node, newNode) + Next + End Function + + Private Class MyCodeAction + Inherits CustomCodeActions.DocumentChangeAction + + Friend Sub New(title As String, createChangedDocument As Func(Of CancellationToken, Task(Of Document))) + MyBase.New(title, createChangedDocument, NameOf(VisualBasicAnalyzersResources.Simplify_object_creation)) + End Sub + End Class + + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems index 7a2e82a8ab6c5..23087c51f179c 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems +++ b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixes.projitems @@ -29,6 +29,7 @@ + diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb new file mode 100644 index 0000000000000..41ad8d0dc9fcb --- /dev/null +++ b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb @@ -0,0 +1,102 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier( + Of Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationDiagnosticAnalyzer, + Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationCodeFixProvider) + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.SimplifyObjectCreation + Public Class SimplifyObjectCreationTests + + Public Async Function SimplifyObjectCreation() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S()|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result As New S() + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_CallCtorWithoutParenthesis() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result As New S + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_PreserveAsAndNewCasing() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result as S = NEW S()|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result as NEW S() + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_MultipleDeclarators() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public Shared Function Create() As S + Dim [|result as S = NEW S()|], [|result2 As S = New S|] + return result + End Function +End Class +", " +Public Class S + Public Shared Function Create() As S + Dim result as NEW S(), result2 As New S + return result + End Function +End Class +") + End Function + + + Public Async Function TypeIsConverted_NoDiagnostic() As Task + Await VerifyVB.VerifyAnalyzerAsync(" +Public Interface IInterface +End Interface + +Public Class S : Implements IInterface + Public Shared Function Create() As S + Dim result as IInterface = NEW S() + return result + End Function +End Class +") + End Function + End Class +End Namespace diff --git a/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems b/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems index bd3fca0fe0f8d..12f9e5342d913 100644 --- a/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems +++ b/src/Analyzers/VisualBasic/Tests/VisualBasicAnalyzers.UnitTests.projitems @@ -41,6 +41,7 @@ + From 8dd900155f3149b687a8eba6113f800f5938ef81 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 23 Mar 2021 13:46:35 +0200 Subject: [PATCH 021/220] Fix resources --- .../VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx | 3 --- .../VisualBasicSimplifyObjectCreationCodeFixProvider.vb | 4 ++-- .../VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx | 3 +++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf | 5 +++++ .../CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf | 5 +++++ 16 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx index cfdf373149e39..5b9f84d179fc3 100644 --- a/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx +++ b/src/Analyzers/VisualBasic/Analyzers/VisualBasicAnalyzersResources.resx @@ -143,7 +143,4 @@ Object creation can be simplified - - - \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb index 07115dd1da666..92e721831e412 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb @@ -29,7 +29,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation Public Overrides Function RegisterCodeFixesAsync(context As CodeFixContext) As Task For Each diagnostic In context.Diagnostics context.RegisterCodeFix(New MyCodeAction( - VisualBasicAnalyzersResources.Simplify_object_creation, + VisualBasicCodeFixesResources.Simplify_object_creation, Function(ct) FixAsync(context.Document, diagnostic, ct)), diagnostic) Next @@ -53,7 +53,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation Inherits CustomCodeActions.DocumentChangeAction Friend Sub New(title As String, createChangedDocument As Func(Of CancellationToken, Task(Of Document))) - MyBase.New(title, createChangedDocument, NameOf(VisualBasicAnalyzersResources.Simplify_object_creation)) + MyBase.New(title, createChangedDocument, NameOf(VisualBasicCodeFixesResources.Simplify_object_creation)) End Sub End Class diff --git a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx index 4ee2ffe18b245..55cf90dc087a5 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx +++ b/src/Analyzers/VisualBasic/CodeFixes/VisualBasicCodeFixesResources.resx @@ -126,4 +126,7 @@ Convert 'GetType' to 'NameOf' + + Simplify object creation + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf index 633380f139348..2ea8260b02ab7 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.cs.xlf @@ -17,6 +17,11 @@ Odebrat nepotřebné importy + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf index b5860ad1418f1..3297c51eca7d2 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.de.xlf @@ -17,6 +17,11 @@ Unnötige Import-Direktiven entfernen + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf index 1173f00588dea..73cb3fc1d7eaf 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.es.xlf @@ -17,6 +17,11 @@ Quitar instrucciones Import innecesarias + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf index 0331570831ce5..5404a50d5e8c1 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.fr.xlf @@ -17,6 +17,11 @@ Supprimer les importations superflues + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf index 0821f2420fde3..f794d04cd923f 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.it.xlf @@ -17,6 +17,11 @@ Rimuovi Import non necessari + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf index 99c4d80c9f4fb..dd7136e6e6bbd 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ja.xlf @@ -17,6 +17,11 @@ 不要なインポートの削除 + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf index c75af0f32aa15..1bf1ccfc6a1cc 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ko.xlf @@ -17,6 +17,11 @@ 불필요한 Imports 제거 + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf index 5e69cb9f7e4de..dbb2630b34ba3 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pl.xlf @@ -17,6 +17,11 @@ Usuń niepotrzebne importy + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf index e1c9a969ee748..95ea51f3d1f54 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.pt-BR.xlf @@ -17,6 +17,11 @@ Remover Importações Desnecessárias + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf index c506aff4a1480..d325031994315 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.ru.xlf @@ -17,6 +17,11 @@ Удалить ненужные импорты + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf index 21ba41a702369..fd08e0e900ed9 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.tr.xlf @@ -17,6 +17,11 @@ Gereksiz İçeri Aktarmaları Kaldır + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf index baec379a402f7..55c45b1197219 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hans.xlf @@ -17,6 +17,11 @@ 删除不必要的导入 + + Simplify object creation + Simplify object creation + + \ No newline at end of file diff --git a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf index 2efd5c0ed170d..7cad201ba29b1 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf +++ b/src/Analyzers/VisualBasic/CodeFixes/xlf/VisualBasicCodeFixesResources.zh-Hant.xlf @@ -17,6 +17,11 @@ 移除不必要的匯入 + + Simplify object creation + Simplify object creation + + \ No newline at end of file From 00744f2bb55d651e976b775a221b7eb042323d05 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 23 Mar 2021 14:42:11 +0200 Subject: [PATCH 022/220] Update IDEDiagnosticIDConfigurationTests.cs --- .../Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 18babf459986c..3a0f0ba652bd2 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -589,6 +589,9 @@ public void VisualBasic_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE0120 dotnet_diagnostic.IDE0120.severity = %value% +# IDE0131 +dotnet_diagnostic.IDE0131.severity = %value% + # IDE2000 dotnet_diagnostic.IDE2000.severity = %value% @@ -1209,6 +1212,9 @@ No editorconfig based code style option # IDE0120 No editorconfig based code style option +# IDE0131 +No editorconfig based code style option + # IDE2000, AllowMultipleBlankLines dotnet_style_allow_multiple_blank_lines_experimental = true From fddba3cc4b3ac1fb599f9fed94d4b7918ffa50d0 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 23 Mar 2021 17:15:25 +0000 Subject: [PATCH 023/220] Address feedback --- ...icSimplifyObjectCreationCodeFixProvider.vb | 1 - .../SimplifyObjectCreationTests.vb | 46 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb index 92e721831e412..edb520b33272d 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb @@ -56,6 +56,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation MyBase.New(title, createChangedDocument, NameOf(VisualBasicCodeFixesResources.Simplify_object_creation)) End Sub End Class - End Class End Namespace diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb index 41ad8d0dc9fcb..3b6bc36abe057 100644 --- a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb @@ -84,6 +84,52 @@ End Class ") End Function + + Public Async Function SimplifyObjectCreation_WithInitializer() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Public Class S + Public X As Integer + + Public Shared Function Create() As S + Dim [|result As S = New S() With { .X = 0 }|] + return result + End Function +End Class +", " +Public Class S + Public X As Integer + + Public Shared Function Create() As S + Dim result As New S() With { .X = 0 } + return result + End Function +End Class +") + End Function + + + Public Async Function SimplifyObjectCreation_FromCollectionInitializer() As Task + Await VerifyVB.VerifyCodeFixAsync(" +Imports System.Collections.Generic + +Public Class S + Public Shared Function Create() As List(Of Integer) + Dim [|result As List(Of Integer) = New List(Of Integer)() From { 0, 1, 2, 3 }|] + return result + End Function +End Class +", " +Imports System.Collections.Generic + +Public Class S + Public Shared Function Create() As List(Of Integer) + Dim result As New List(Of Integer)() From { 0, 1, 2, 3 } + return result + End Function +End Class +") + End Function + Public Async Function TypeIsConverted_NoDiagnostic() As Task Await VerifyVB.VerifyAnalyzerAsync(" From 88397672dcf9b43b3e30bc8b88df04d1ec004371 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 24 Mar 2021 11:14:05 +0000 Subject: [PATCH 024/220] Address feedback --- .../Core/Analyzers/IDEDiagnosticIds.cs | 2 +- ...implifyObjectCreationDiagnosticAnalyzer.vb | 17 +++++- .../SimplifyObjectCreationTests.vb | 58 +++++++++++++++++++ .../IDEDiagnosticIDConfigurationTests.cs | 8 +-- .../VisualBasic/Impl/BasicVSResources.resx | 3 + .../Impl/Options/StyleViewModel.vb | 19 ++++++ .../CodeStyle/VisualBasicCodeStyleOptions.vb | 6 ++ 7 files changed, 105 insertions(+), 8 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index 8bc54452e89e9..f6a88179a2115 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -158,7 +158,7 @@ internal static class IDEDiagnosticIds public const string MatchFolderAndNamespaceDiagnosticId = "IDE0130"; - public const string SimplifyObjectCreationDiagnosticId = "IDE0131"; + public const string SimplifyObjectCreationDiagnosticId = "IDE0140"; // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; diff --git a/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb index f7de977b30479..bfa0161a60fe3 100644 --- a/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationDiagnosticAnalyzer.vb @@ -4,6 +4,7 @@ Imports Microsoft.CodeAnalysis.CodeStyle Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic.CodeStyle Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation @@ -15,7 +16,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation MyBase.New( diagnosticId:=IDEDiagnosticIds.SimplifyObjectCreationDiagnosticId, enforceOnBuild:=EnforceOnBuildValues.SimplifyObjectCreation, - [option]:=Nothing, + [option]:=VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, + language:=LanguageNames.VisualBasic, title:=New LocalizableResourceString(NameOf(VisualBasicAnalyzersResources.Object_creation_can_be_simplified), VisualBasicAnalyzersResources.ResourceManager, GetType(VisualBasicAnalyzersResources))) End Sub @@ -33,7 +35,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation ' which can be simplified to ' Dim x As New SomeType() - Dim variableDeclarator = DirectCast(context.Node, VariableDeclaratorSyntax) + Dim node = context.Node + Dim tree = node.SyntaxTree + Dim cancellationToken = context.CancellationToken + + Dim styleOption = context.Options.GetOption(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, tree, cancellationToken) + If Not styleOption.Value Then + Return + End If + + Dim variableDeclarator = DirectCast(node, VariableDeclaratorSyntax) Dim asClauseType = variableDeclarator.AsClause?.Type() If asClauseType Is Nothing Then Return @@ -44,7 +55,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation Return End If - Dim symbolInfo = context.SemanticModel.GetTypeInfo(objectCreation) + Dim symbolInfo = context.SemanticModel.GetTypeInfo(objectCreation, cancellationToken) If symbolInfo.Type IsNot Nothing AndAlso symbolInfo.Type.Equals(symbolInfo.ConvertedType, SymbolEqualityComparer.Default) Then context.ReportDiagnostic(Diagnostic.Create(Descriptor, variableDeclarator.GetLocation())) End If diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb index 3b6bc36abe057..46aa95486734e 100644 --- a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports Microsoft.CodeAnalysis.VisualBasic.CodeStyle Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier( Of Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationDiagnosticAnalyzer, Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation.VisualBasicSimplifyObjectCreationCodeFixProvider) @@ -27,6 +28,52 @@ End Class ") End Function + + Public Async Function SimplifyObjectCreation_CodeStyleOptionTurnedOn() As Task + Dim code = " +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S()|] + return result + End Function +End Class +" + Dim fixedCode = " +Public Class S + Public Shared Function Create() As S + Dim result As New S() + return result + End Function +End Class +" + Dim test = New VerifyVB.Test With + { + .TestCode = code, + .FixedCode = fixedCode + } + test.Options.Add(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, False) + Await test.RunAsync() + End Function + + + Public Async Function SimplifyObjectCreation_CodeStyleOptionTurnedOff() As Task + Dim code = " +Public Class S + Public Shared Function Create() As S + Dim [|result As S = New S()|] + return result + End Function +End Class +" + Dim test = New VerifyVB.Test With + { + .TestCode = code, + .FixedCode = code + } + test.Options.Add(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, False) + Await test.RunAsync() + End Function + Public Async Function SimplifyObjectCreation_CallCtorWithoutParenthesis() As Task Await VerifyVB.VerifyCodeFixAsync(" @@ -142,6 +189,17 @@ Public Class S : Implements IInterface return result End Function End Class +") + End Function + + + Public Async Function ArrayCreation_NoDiagnostic() As Task + Await VerifyVB.VerifyAnalyzerAsync(" +Public Class C + Public Sub M() + Dim x As String() = New String() { } + End Sub +End Class ") End Function End Class diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index 3a0f0ba652bd2..cc2b03b5d2d9b 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -589,8 +589,8 @@ public void VisualBasic_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE0120 dotnet_diagnostic.IDE0120.severity = %value% -# IDE0131 -dotnet_diagnostic.IDE0131.severity = %value% +# IDE0140 +dotnet_diagnostic.IDE0140.severity = %value% # IDE2000 dotnet_diagnostic.IDE2000.severity = %value% @@ -1212,8 +1212,8 @@ No editorconfig based code style option # IDE0120 No editorconfig based code style option -# IDE0131 -No editorconfig based code style option +# IDE0140, PreferSimplifiedObjectCreation +visual_basic_style_prefer_simplified_object_creation = true # IDE2000, AllowMultipleBlankLines dotnet_style_allow_multiple_blank_lines_experimental = true diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx index cefbd3e299467..e0a2f6d0078e6 100644 --- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx +++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx @@ -322,4 +322,7 @@ Sort imports {Locked="Import"} 'import' is a Visual Basic keyword and should not be localized + + Prefer simplified object creation + \ No newline at end of file diff --git a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb index dc730c35e3432..44e078b28ed94 100644 --- a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb +++ b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb @@ -465,6 +465,24 @@ Class Customer End Sub End Class" + Private Shared ReadOnly s_preferSimplifiedObjectCreation As String = $" +Imports System + +Class Customer + Sub M1() +//[ + ' {ServicesVSResources.Prefer_colon} + Dim c As New Customer() +//] + End Sub + Sub M2() +//[ + ' {ServicesVSResources.Over_colon} + Dim c As Customer = New Customer() +//] + End Sub +End Class" + #Region "arithmetic binary parentheses" Private Shared ReadOnly s_arithmeticBinaryAlwaysForClarity As String = $" @@ -825,6 +843,7 @@ End Class Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferConditionalExpressionOverReturn, ServicesVSResources.Prefer_conditional_expression_over_if_with_returns, s_preferConditionalExpressionOverIfWithReturns, s_preferConditionalExpressionOverIfWithReturns, Me, optionStore, expressionPreferencesGroupTitle)) Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(CodeStyleOptions2.PreferCompoundAssignment, ServicesVSResources.Prefer_compound_assignments, s_preferCompoundAssignments, s_preferCompoundAssignments, Me, optionStore, expressionPreferencesGroupTitle)) Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(VisualBasicCodeStyleOptions.PreferIsNotExpression, BasicVSResources.Prefer_IsNot_expression, s_preferIsNotExpression, s_preferIsNotExpression, Me, optionStore, expressionPreferencesGroupTitle)) + Me.CodeStyleItems.Add(New BooleanCodeStyleOptionViewModel(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, BasicVSResources.Prefer_simplified_object_creation, s_preferSimplifiedObjectCreation, s_preferSimplifiedObjectCreation, Me, optionStore, expressionPreferencesGroupTitle)) AddUnusedValueOptions(optionStore, expressionPreferencesGroupTitle) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb index 43be5115ca79b..4447aa8c2cdaf 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/CodeStyle/VisualBasicCodeStyleOptions.vb @@ -50,6 +50,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeStyle "visual_basic_style_prefer_isnot_expression", $"TextEditor.%LANGUAGE%.Specific.{NameOf(PreferIsNotExpression)}") + Public Shared ReadOnly PreferSimplifiedObjectCreation As Option2(Of CodeStyleOption2(Of Boolean)) = CreateOption( + VisualBasicCodeStyleOptionGroups.ExpressionLevelPreferences, NameOf(PreferSimplifiedObjectCreation), + defaultValue:=New CodeStyleOption2(Of Boolean)(True, NotificationOption2.Suggestion), + "visual_basic_style_prefer_simplified_object_creation", + $"TextEditor.%LANGUAGE%.Specific.{NameOf(PreferSimplifiedObjectCreation)}") + Public Shared ReadOnly UnusedValueExpressionStatement As [Option2](Of CodeStyleOption2(Of UnusedValuePreference)) = CodeStyleHelpers.CreateUnusedExpressionAssignmentOption( group:=VisualBasicCodeStyleOptionGroups.ExpressionLevelPreferences, From 5dad410010361249ab060af1b3eef06746c7b1fa Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 27 Mar 2021 19:26:36 +0200 Subject: [PATCH 025/220] Update Xlf --- .../VisualBasic/Impl/xlf/BasicVSResources.cs.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.de.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.es.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.fr.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.it.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.ja.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.ko.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.pl.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.ru.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.tr.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf | 5 +++++ .../VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf | 5 +++++ 13 files changed, 65 insertions(+) diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf index 450745bd72c5b..a03d24f574357 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.cs.xlf @@ -52,6 +52,11 @@ U kontrol rovnosti odkazů dávat přednost možnosti Is Nothing + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf index 5da45bb62ca8d..876fab76838e1 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.de.xlf @@ -52,6 +52,11 @@ "Is Nothing" für Verweisübereinstimmungsprüfungen vorziehen + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf index 598030b04ce5b..8fd6dc564739f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.es.xlf @@ -52,6 +52,11 @@ Preferir “Is Nothing” para comprobaciones de igualdad de referencias + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf index 2b928f70613b3..c3687827ee22f 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.fr.xlf @@ -52,6 +52,11 @@ Préférer 'Is Nothing' pour les vérifications d'égalité de référence + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf index 8cddfa6355063..a2c3c7794dc5c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.it.xlf @@ -52,6 +52,11 @@ Preferisci 'Is Nothing' per i controlli di uguaglianza dei riferimenti + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf index 78148992afd11..575362b853583 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ja.xlf @@ -52,6 +52,11 @@ 参照の等値性のチェックには 'Is Nothing' を優先する + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf index cce625fd8aefa..5438db7d85dca 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ko.xlf @@ -52,6 +52,11 @@ 참조 같음 검사에 대해 'Is Nothing' 선호 + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf index bee6fbe5e00b6..c43fa110cdcf0 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pl.xlf @@ -52,6 +52,11 @@ Preferuj wyrażenie „Is Nothing” w przypadku sprawdzeń odwołań pod kątem równości + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf index 4da14073a6592..fde102c3d2565 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.pt-BR.xlf @@ -52,6 +52,11 @@ Prefira 'Is Nothing' para as verificações de igualdade de referência + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf index ecb0eb581d2a4..d866dc1c0bc6a 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.ru.xlf @@ -52,6 +52,11 @@ Предпочитать Is Nothing для проверок равенства ссылок + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf index 279bb2bd26987..25246b4d4aa42 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.tr.xlf @@ -52,6 +52,11 @@ Başvuru eşitliği denetimleri için 'Is Nothing'i tercih et + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf index 0d2bf0b6c91ca..c9bea916bb5cf 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hans.xlf @@ -52,6 +52,11 @@ 引用相等检查偏好 “Is Nothing” + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports diff --git a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf index f3263a3828c74..2e904c8a1803c 100644 --- a/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf +++ b/src/VisualStudio/VisualBasic/Impl/xlf/BasicVSResources.zh-Hant.xlf @@ -52,6 +52,11 @@ 參考相等檢查最好使用 'Is Nothing' + + Prefer simplified object creation + Prefer simplified object creation + + Remove unnecessary Imports Remove unnecessary Imports From c5716b5a5d13d632c649526586248d3d43493455 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 28 Mar 2021 05:54:57 +0200 Subject: [PATCH 026/220] Fix test --- .../Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb index 46aa95486734e..85547f3572b8e 100644 --- a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb @@ -60,7 +60,7 @@ End Class Dim code = " Public Class S Public Shared Function Create() As S - Dim [|result As S = New S()|] + Dim result As S = New S() return result End Function End Class From 3a6b9eb40127152011176ec41efde5f3a7491e1a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Apr 2021 15:07:28 +0200 Subject: [PATCH 027/220] Fix SimplifyObjectCreation_CodeStyleOptionTurnedOn --- .../Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb index 85547f3572b8e..da68833928203 100644 --- a/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb +++ b/src/Analyzers/VisualBasic/Tests/SimplifyObjectCreation/SimplifyObjectCreationTests.vb @@ -51,7 +51,7 @@ End Class .TestCode = code, .FixedCode = fixedCode } - test.Options.Add(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, False) + test.Options.Add(VisualBasicCodeStyleOptions.PreferSimplifiedObjectCreation, True) Await test.RunAsync() End Function From 6e2a4b01082e40725063f943b3721dcba251d8c2 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Apr 2021 19:29:48 +0200 Subject: [PATCH 028/220] Update BasicEditorConfigGeneratorTests.vb --- .../Core/Test/Options/BasicEditorConfigGeneratorTests.vb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb index d259ae8730756..b2bc3eb08c6d8 100644 --- a/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/BasicEditorConfigGeneratorTests.vb @@ -99,6 +99,7 @@ visual_basic_preferred_modifier_order = partial,default,private,protected,public # Expression-level preferences visual_basic_style_prefer_isnot_expression = true +visual_basic_style_prefer_simplified_object_creation = true visual_basic_style_unused_value_assignment_preference = unused_local_variable visual_basic_style_unused_value_expression_statement_preference = unused_local_variable @@ -236,6 +237,7 @@ visual_basic_preferred_modifier_order = partial,default,private,protected,public # Expression-level preferences visual_basic_style_prefer_isnot_expression = true +visual_basic_style_prefer_simplified_object_creation = true visual_basic_style_unused_value_assignment_preference = unused_local_variable visual_basic_style_unused_value_expression_statement_preference = unused_local_variable From c8f3f56c36ad432909d0a1e210b8a9938022ec39 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Apr 2021 19:34:19 +0200 Subject: [PATCH 029/220] PredefinedCodeFixProviderNames.SimplifyObjectCreation --- .../VisualBasicSimplifyObjectCreationCodeFixProvider.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb index edb520b33272d..dc95518148ff1 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/SimplifyObjectCreation/VisualBasicSimplifyObjectCreationCodeFixProvider.vb @@ -13,7 +13,7 @@ Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Namespace Microsoft.CodeAnalysis.VisualBasic.SimplifyObjectCreation - + Friend Class VisualBasicSimplifyObjectCreationCodeFixProvider Inherits SyntaxEditorBasedCodeFixProvider From 47bb540e483b08a365fd485fc08a5eb498102e5a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Apr 2021 19:34:25 +0200 Subject: [PATCH 030/220] PredefinedCodeFixProviderNames.SimplifyObjectCreation --- src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index 4b0a80d21e1bb..c14072708885b 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -80,5 +80,6 @@ internal static class PredefinedCodeFixProviderNames public const string RemoveIn = nameof(RemoveIn); public const string SimplifyLinqExpression = nameof(SimplifyLinqExpression); public const string ChangeNamespaceToMatchFolder = nameof(ChangeNamespaceToMatchFolder); + public const string SimplifyObjectCreation = nameof(SimplifyObjectCreation); } } From 0f1549e236e374c440fc6c00b8bd7d5093d5949e Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 1 Apr 2021 20:56:06 +0200 Subject: [PATCH 031/220] Update EnforceOnBuildValues.cs --- src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index 69df5772e4752..6a72420de1367 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -76,7 +76,7 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild InvokeDelegateWithConditionalAccess = /*IDE1005*/ EnforceOnBuild.Recommended; public const EnforceOnBuild NamingRule = /*IDE1006*/ EnforceOnBuild.Recommended; public const EnforceOnBuild MatchFolderAndNamespace = /*IDE0130*/ EnforceOnBuild.Recommended; - public const EnforceOnBuild SimplifyObjectCreation = /*IDE0131*/ EnforceOnBuild.Recommended; + public const EnforceOnBuild SimplifyObjectCreation = /*IDE0140*/ EnforceOnBuild.Recommended; /* EnforceOnBuild.WhenExplicitlyEnabled */ public const EnforceOnBuild RemoveUnnecessaryCast = /*IDE0004*/ EnforceOnBuild.WhenExplicitlyEnabled; // TODO: Move to 'Recommended' OR 'HighlyRecommended' bucket once performance problems are addressed: https://github.com/dotnet/roslyn/issues/43304 From d17ca65b7ea05c613426fb1b826359868cb87704 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 6 Apr 2021 15:57:57 -0700 Subject: [PATCH 032/220] Explicitly hold onto syntax tree while doing syntactic classification to make intent clear. --- ...icClassificationTaggerProvider.TagComputer.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index c50ebf1186e89..e7fdeb7b91763 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -54,13 +54,10 @@ internal partial class TagComputer // The latest data about the document being classified that we've cached. objects can // be accessed from both threads, and must be obtained when this lock is held. - // - // Note: we cache this data once we've retrieved the actual syntax tree for a document. This - // way, when we call into the actual classification service, it should be very quick for the - // it to get the tree if it needs it. private readonly object _gate = new(); private ITextSnapshot _lastProcessedSnapshot; private Document _lastProcessedDocument; + private SyntaxTree _lastProcessedSyntaxTree; private Workspace _workspace; private CancellationTokenSource _reportChangeCancellationSource; @@ -139,6 +136,7 @@ private void ResetLastParsedDocument() lock (_gate) { _lastProcessedDocument = null; + _lastProcessedSyntaxTree = null; } } @@ -203,18 +201,20 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document document, Cancella return; } + SyntaxTree syntaxTree; var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); using (latencyTracker) { // preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready. // F#/typescript and other languages that doesn't support syntax tree will return null here. - _ = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); } lock (_gate) { _lastProcessedSnapshot = snapshot; _lastProcessedDocument = document; + _lastProcessedSyntaxTree = syntaxTree; } _reportChangeCancellationSource = new CancellationTokenSource(); @@ -296,11 +296,13 @@ private void AddClassifiedSpans( // From this point on we'll do all operations over these values. ITextSnapshot lastSnapshot; Document lastDocument; + SyntaxTree lastSyntaxTree; lock (_gate) { lastSnapshot = _lastProcessedSnapshot; lastDocument = _lastProcessedDocument; + lastSyntaxTree = _lastProcessedSyntaxTree; } if (lastDocument == null) @@ -326,6 +328,9 @@ private void AddClassifiedSpans( AddClassifiedSpansForPreviousTree( classificationService, span, lastSnapshot, lastDocument, classifiedSpans); } + + // Ensure the syntax tree stays alive for as long as we're doing the processing. + GC.KeepAlive(lastSyntaxTree); } private void AddClassifiedSpansForCurrentTree( @@ -346,7 +351,6 @@ private void AddClassifiedSpansForCurrentTree( // simple case. They're asking for the classifications for a tree that we already have. // Just get the results from the tree and return them. - classifiedSpans.AddRange(tempList); } From 3bfe2283c6849dba537fd94e025d1042c3cc6d25 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 6 Apr 2021 16:01:20 -0700 Subject: [PATCH 033/220] REstore. --- .../SyntacticClassificationTaggerProvider.TagComputer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index e7fdeb7b91763..b2af2a9d3a335 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -351,6 +351,7 @@ private void AddClassifiedSpansForCurrentTree( // simple case. They're asking for the classifications for a tree that we already have. // Just get the results from the tree and return them. + classifiedSpans.AddRange(tempList); } From f5e6506ad613e2aa5619707229e3313bb7c62731 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 09:11:18 -0700 Subject: [PATCH 034/220] Add helpers --- .../CSharp/Portable/Syntax/SyntaxFactory.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index 7314c84580886..34178fbb4a434 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -2041,6 +2041,35 @@ public static bool AreEquivalent(SeparatedSyntaxList oldList, Sepa return SyntaxEquivalence.AreEquivalent(oldList.Node, newList.Node, ignoreChildNode, topLevel: false); } + /// + /// See and . + /// + public static bool AreIncrementallyIdentical(SyntaxNodeOrToken oldNodeOrToken, SyntaxNodeOrToken newNodeOrToken) + => oldNodeOrToken.UnderlyingNode == newNodeOrToken.UnderlyingNode; + + /// + /// Returns true if these two nodes are considered "incrementally identical". An incrementally identical node + /// occurs when a is incrementally parsed using + /// and the incremental parser is able to take the node from the original tree and use it in its entirety in the + /// new tree. In this case, the of each node will be the same, though + /// will have different parents, and may occur at different positions in the respective trees. If two nodes are + /// incrementally identical, all children of each node will be incrementally identical as well. + /// + public static bool AreIncrementallyIdentical(SyntaxNode oldNode, SyntaxNode newNode) + => oldNode.Green == newNode.Green; + + /// + /// Returns true if these two tokens are considered "incrementally identical". An incrementally identical token + /// occurs when a is incrementally parsed using + /// and the incremental parser is able to take the token from the original tree and use it in its entirety in the + /// new tree. In this case, the of each token will be the same, though + /// will have different parents, and may occur at different positions in the respective trees. If two tokens are + /// incrementally identical, all trivial of each node will be incrementally identical as well. + /// + public static bool AreIncrementallyIdentical(SyntaxToken oldToken, SyntaxToken newToken) + => oldToken.Node == newToken.Node; + internal static TypeSyntax? GetStandaloneType(TypeSyntax? node) { if (node != null) From c11f1c00f45fc6d4e737e6cbd626474727e44eb3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 13:36:50 -0700 Subject: [PATCH 035/220] Support reporting more accurate 'changed spans' when doing syntactic classification --- .../CSharp/Portable/Syntax/SyntaxFactory.cs | 29 --- .../Core/Portable/Syntax/SyntaxNode.cs | 11 + .../Core/Portable/Syntax/SyntaxNodeOrToken.cs | 7 + .../Core/Portable/Syntax/SyntaxToken.cs | 11 + ...lassificationTaggerProvider.TagComputer.cs | 107 +++++++-- .../FSharpClassificationService.cs | 8 +- .../AbstractClassificationService.cs | 9 + .../Classification/IClassificationService.cs | 12 + .../AbstractSyntaxClassificationService.cs | 3 + .../ISyntaxClassificationService.cs | 8 + .../SyntacticChangeRangeComputer.cs | 215 ++++++++++++++++++ 11 files changed, 364 insertions(+), 56 deletions(-) create mode 100644 src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs index 34178fbb4a434..7314c84580886 100644 --- a/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs +++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxFactory.cs @@ -2041,35 +2041,6 @@ public static bool AreEquivalent(SeparatedSyntaxList oldList, Sepa return SyntaxEquivalence.AreEquivalent(oldList.Node, newList.Node, ignoreChildNode, topLevel: false); } - /// - /// See and . - /// - public static bool AreIncrementallyIdentical(SyntaxNodeOrToken oldNodeOrToken, SyntaxNodeOrToken newNodeOrToken) - => oldNodeOrToken.UnderlyingNode == newNodeOrToken.UnderlyingNode; - - /// - /// Returns true if these two nodes are considered "incrementally identical". An incrementally identical node - /// occurs when a is incrementally parsed using - /// and the incremental parser is able to take the node from the original tree and use it in its entirety in the - /// new tree. In this case, the of each node will be the same, though - /// will have different parents, and may occur at different positions in the respective trees. If two nodes are - /// incrementally identical, all children of each node will be incrementally identical as well. - /// - public static bool AreIncrementallyIdentical(SyntaxNode oldNode, SyntaxNode newNode) - => oldNode.Green == newNode.Green; - - /// - /// Returns true if these two tokens are considered "incrementally identical". An incrementally identical token - /// occurs when a is incrementally parsed using - /// and the incremental parser is able to take the token from the original tree and use it in its entirety in the - /// new tree. In this case, the of each token will be the same, though - /// will have different parents, and may occur at different positions in the respective trees. If two tokens are - /// incrementally identical, all trivial of each node will be incrementally identical as well. - /// - public static bool AreIncrementallyIdentical(SyntaxToken oldToken, SyntaxToken newToken) - => oldToken.Node == newToken.Node; - internal static TypeSyntax? GetStandaloneType(TypeSyntax? node) { if (node != null) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 99592e0644c85..e0876b925338f 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -348,6 +348,17 @@ public bool IsEquivalentTo(SyntaxNode other) return this.Green.IsEquivalentTo(other.Green); } + /// + /// Returns true if these two nodes are considered "incrementally identical". An incrementally identical node + /// occurs when a is incrementally parsed using + /// and the incremental parser is able to take the node from the original tree and use it in its entirety in the + /// new tree. In this case, the of each node will be the same, though + /// will have different parents, and may occur at different positions in the respective trees. If two nodes are + /// incrementally identical, all children of each node will be incrementally identical as well. + /// + public bool IsIncrementallyIdenticalTo(SyntaxNode other) + => this.Green == other.Green; + /// /// Determines whether the node represents a language construct that was actually parsed /// from the source code. Missing nodes are generated by the parser in error scenarios to diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs index 9339deecdac41..63ae47b0f0c53 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs @@ -758,6 +758,13 @@ public bool IsEquivalentTo(SyntaxNodeOrToken other) return (thisUnderlying == otherUnderlying) || (thisUnderlying != null && thisUnderlying.IsEquivalentTo(otherUnderlying)); } + /// + /// See and . + /// + public bool IsIncrementallyIdenticalTo(SyntaxNodeOrToken other) + => this.UnderlyingNode == other.UnderlyingNode; + /// /// Returns a new that wraps the supplied token. /// diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs index b05a531ad3cb6..a7b438babf8b5 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs @@ -686,5 +686,16 @@ public bool IsEquivalentTo(SyntaxToken token) (Node == null && token.Node == null) || (Node != null && token.Node != null && Node.IsEquivalentTo(token.Node)); } + + /// + /// Returns true if these two tokens are considered "incrementally identical". An incrementally identical token + /// occurs when a is incrementally parsed using + /// and the incremental parser is able to take the token from the original tree and use it in its entirety in the + /// new tree. In this case, the of each token will be the same, though + /// will have different parents, and may occur at different positions in the respective trees. If two tokens are + /// incrementally identical, all trivial of each node will be incrementally identical as well. + /// + public bool IsIncrementallyIdenticalTo(SyntaxToken token) + => this.Node == token.Node; } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index b2af2a9d3a335..9fcefb5595eec 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -186,14 +186,14 @@ private void EnqueueProcessSnapshot(Document newDocument) } } - private async Task EnqueueProcessSnapshotWorkerAsync(Document document, CancellationToken cancellationToken) + private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, CancellationToken cancellationToken) { // we will enqueue new one soon, cancel pending refresh right away _reportChangeCancellationSource.Cancel(); - var newText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var snapshot = newText.FindCorrespondingEditorTextSnapshot(); - if (snapshot == null) + var currentText = await currentDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); + var currentSnapshot = currentText.FindCorrespondingEditorTextSnapshot(); + if (currentSnapshot == null) { // It's possible that we're seeing a notification for an update that happened // just before the file was opened, and so the document we're given is still the @@ -201,33 +201,91 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document document, Cancella return; } - SyntaxTree syntaxTree; + SyntaxTree currentSyntaxTree; var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); using (latencyTracker) { // preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready. // F#/typescript and other languages that doesn't support syntax tree will return null here. - syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + currentSyntaxTree = await currentDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); } + // We don't need to grab _lastProcessedDocument in a lock. We don't care which version of the previous + // doc we grab, just that we grab some prior version. This is only used + var changedSpan = await GetChangedSpanAsync(_lastProcessedDocument, currentDocument, currentSnapshot, cancellationToken).ConfigureAwait(false); + + // Once we're past this point, we're mutating our internal state so we cannot cancel past that. + cancellationToken = CancellationToken.None; + lock (_gate) { - _lastProcessedSnapshot = snapshot; - _lastProcessedDocument = document; - _lastProcessedSyntaxTree = syntaxTree; + _lastProcessedSnapshot = currentSnapshot; + _lastProcessedDocument = currentDocument; + _lastProcessedSyntaxTree = currentSyntaxTree; } _reportChangeCancellationSource = new CancellationTokenSource(); _notificationService.RegisterNotification(() => { _workQueue.AssertIsForeground(); - ReportChangedSpan(snapshot.GetFullSpan()); + ReportChangedSpan(changedSpan); }, ReportChangeDelayInMilliseconds, _listener.BeginAsyncOperation("ReportEntireFileChanged"), _reportChangeCancellationSource.Token); } + private async Task GetChangedSpanAsync( + Document previousDocument, Document currentDocument, + ITextSnapshot currentSnapshot, CancellationToken cancellationToken) + { + // Try to defer to the classification service to see if it can find a narrower range to recompute. + if (previousDocument != null) + { + var classificationService = TryGetClassificationService(currentSnapshot); + if (classificationService != null) + { + var changeRange = await ComputeChangeRangeAsync( + previousDocument, currentDocument, classificationService, cancellationToken).ConfigureAwait(false); + if (changeRange != null) + return currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength); + } + } + + // Couldn't compute a narrower range. Just the mark the entire file as changed. + return currentSnapshot.GetFullSpan(); + } + + private static async Task ComputeChangeRangeAsync( + Document previousDocument, Document currentDocument, + IClassificationService classificationService, CancellationToken cancellationToken) + { + // We want to compute a minimal change, but we don't want this to run for too long. So do the + // computation work in the threadpool, but also gate how much time we can spend here so that we can let + // the editor know about the size of the change asap. + using var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + + var computeTask = Task.Run(() => + classificationService.ComputeSyntacticChangeRangeAsync(previousDocument, currentDocument, linkedToken.Token), linkedToken.Token); + + var delayTask = Task.Delay(TaggerConstants.NearImmediateDelay, linkedToken.Token); + + var completedTask = await Task.WhenAny(computeTask, delayTask).ConfigureAwait(false); + + // Ensure that we cancel any outstanding work on the other task, once one completes. + linkedToken.Cancel(); + + // ensure that if we completed because of cancellation, we throw that up. + cancellationToken.ThrowIfCancellationRequested(); + + // If we timed out, just return nothing. We'll reclassify the full doc. + if (completedTask == delayTask) + return null; + + // Otherwise, extract out the change range that was computed by our service. + return await computeTask.ConfigureAwait(false); + } + private void ReportChangedSpan(SnapshotSpan changeSpan) { lock (_gate) @@ -251,30 +309,29 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanC { if (spans.Count > 0 && _workspace != null) { - var firstSpan = spans[0]; - var languageServices = _workspace.Services.GetLanguageServices(firstSpan.Snapshot.ContentType); - if (languageServices != null) - { - var result = GetTags(spans, languageServices); - if (result != null) - { - return result; - } - } + var result = GetTagsWorker(spans); + if (result != null) + return result; } return SpecializedCollections.EmptyEnumerable>(); } } - private IEnumerable> GetTags( - NormalizedSnapshotSpanCollection spans, HostLanguageServices languageServices) + private IClassificationService TryGetClassificationService(ITextSnapshot snapshot) + { + var languageServices = _workspace.Services.GetLanguageServices(snapshot.ContentType); + if (languageServices == null) + return null; + + return languageServices.GetService(); + } + + private IEnumerable> GetTagsWorker(NormalizedSnapshotSpanCollection spans) { - var classificationService = languageServices.GetService(); + var classificationService = TryGetClassificationService(spans[0].Snapshot); if (classificationService == null) - { return null; - } var classifiedSpans = ClassificationUtilities.GetOrCreateClassifiedSpanList(); diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index c57245eb30301..651d1c6bbeef8 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Composition; @@ -13,6 +11,7 @@ using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Classification; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Classification { @@ -48,5 +47,10 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan { return _service.AdjustStaleClassification(text, classifiedSpan); } + + public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument) + { + return SpecializedTasks.Default(); + } } } diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index 345cadc41cbad..f34e1ec0f36ff 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -120,5 +120,14 @@ protected static void AddRange(ArrayBuilder temp, List ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) + { + var classificationService = oldDocument.GetLanguageService(); + if (classificationService == null) + return SpecializedTasks.Default(); + + return classificationService.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, cancellationToken); + } } } diff --git a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs index 0875e45e1012b..5ba9eea050fc1 100644 --- a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs @@ -51,5 +51,17 @@ internal interface IClassificationService : ILanguageService /// syntactic and semantic classifications for this version later. /// ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan); + + /// + /// Determines the range of the documents that should be considered syntactically changed after an edit. In + /// language systems that can reuse major parts of a document after an edit, and which would not need to + /// recompute classifications for those reused parts, this can speed up processing on a host by not requiring + /// the host to reclassify all the source in view, but only the source that could have changed. + /// + /// If determining this is not possible, or potentially expensive, can be returned to + /// indicate that the entire document should be considered changed and should be syntactically reclassified. + /// + /// + Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs index b3a7c98fe03aa..21425f38c072b 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs @@ -53,5 +53,8 @@ public void AddSemanticClassifications( { Worker.Classify(workspace, semanticModel, textSpan, result, getNodeClassifiers, getTokenClassifiers, cancellationToken); } + + public async Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) + => await SyntacticChangeRangeComputer.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs index a92ac4cd4253e..22fe8213e31a8 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs @@ -17,16 +17,19 @@ internal interface ISyntaxClassificationService : ILanguageService { ImmutableArray GetDefaultSyntaxClassifiers(); + /// void AddLexicalClassifications(SourceText text, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); + /// void AddSyntacticClassifications(SyntaxTree syntaxTree, TextSpan textSpan, ArrayBuilder result, CancellationToken cancellationToken); + /// Task AddSemanticClassificationsAsync(Document document, TextSpan textSpan, Func> getNodeClassifiers, @@ -34,6 +37,7 @@ Task AddSemanticClassificationsAsync(Document document, ArrayBuilder result, CancellationToken cancellationToken); + /// void AddSemanticClassifications( SemanticModel semanticModel, TextSpan textSpan, @@ -43,6 +47,10 @@ void AddSemanticClassifications( ArrayBuilder result, CancellationToken cancellationToken); + /// ClassifiedSpan FixClassification(SourceText text, ClassifiedSpan classifiedSpan); + + /// + Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs new file mode 100644 index 0000000000000..ce4c4dbaaa353 --- /dev/null +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -0,0 +1,215 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Classification +{ + /// + /// Computes a syntactic text change range that determines the range of a document that was changed by an edit. The + /// portions outside this change range are guaranteed to be syntactically identical (see ). + /// + /// + /// This computation is not guaranteed to be minimal. It may return a range that includes parts that are unchanged. + /// This means it is also legal for the change range to just specify the entire file was changed. + /// + internal static class SyntacticChangeRangeComputer + { + private static readonly ObjectPool> s_pool = new(() => new()); + + public static async Task ComputeSyntacticChangeRangeAsync( + Document oldDocument, Document newDocument, CancellationToken cancellationToken) + { + if (oldDocument == newDocument) + return default; + + var oldRoot = await oldDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var newRoot = await newDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + return ComputeSyntacticChangeRange(oldRoot, newRoot, cancellationToken); + } + + private static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, CancellationToken cancellationToken) + { + if (oldRoot == newRoot) + return default; + + using var leftOldStack = s_pool.GetPooledObject(); + using var leftNewStack = s_pool.GetPooledObject(); + using var rightOldStack = s_pool.GetPooledObject(); + using var rightNewStack = s_pool.GetPooledObject(); + + leftOldStack.Object.Push(oldRoot); + leftNewStack.Object.Push(newRoot); + rightOldStack.Object.Push(oldRoot); + rightNewStack.Object.Push(newRoot); + + // We will be comparing the trees for two documents like so: + // + // -------------------------------------------- + // old: | | + // -------------------------------------------- + // + // --------------------------------------------------- + // new: | | + // --------------------------------------------------- + // + // (Note that `new` could be smaller or the same length as `old`, it makes no difference). + // + // The algorithm will sweep in from both sides, as long as the nodes and tokens it's touching on each side + // are 'identical' (i.e. are the exact same green node, and were thus reused over an incremental parse.). + // This will leave us with: + // + // -------------------------------------------- + // old: | CLW | | CRW | + // -------------------------------------------- + // | | \ \ + // --------------------------------------------------- + // new: | CLW | | CRW | + // --------------------------------------------------- + // + // Where CLW and CRW refer to the common-left-width and common-right-width respectively. The part in between + // this s the change range: + // + // -------------------------------------------- + // old: | |**********************| | + // -------------------------------------------- + // |**************************\ + // --------------------------------------------------- + // new: | |*****************************| | + // --------------------------------------------------- + // + // The Span changed will go from `[CLW, Old_Width - CRW)`, and the NewLength will be `New_Width - CLW - CRW` + + var commonLeftWidth = ComputeCommonLeftWidth(oldRoot, newRoot, leftOldStack.Object, leftNewStack.Object, cancellationToken); + var commonRightWidth = ComputeCommonRightWidth(oldRoot, newRoot, rightOldStack.Object, rightNewStack.Object, cancellationToken); + + if (commonLeftWidth == null || commonRightWidth == null) + { + // They better have both been the same, otherwise our walk was somehow non-symettric. + Contract.ThrowIfFalse(commonLeftWidth == commonRightWidth); + + // The trees were effectively identical (even if the children were different). Return that there was no + // text change. + return default; + } + + var oldRootWidth = oldRoot.FullWidth(); + var newRootWidth = newRoot.FullWidth(); + + Contract.ThrowIfFalse(commonLeftWidth > oldRootWidth); + Contract.ThrowIfFalse(commonLeftWidth > newRootWidth); + Contract.ThrowIfFalse(commonRightWidth > oldRootWidth); + Contract.ThrowIfFalse(commonRightWidth > newRootWidth); + + return new TextChangeRange( + TextSpan.FromBounds(start: commonLeftWidth.Value, end: oldRootWidth - commonRightWidth.Value), + newRootWidth - commonLeftWidth.Value - commonRightWidth.Value); + } + + private static int? ComputeCommonLeftWidth( + SyntaxNode oldRoot, + SyntaxNode newRoot, + Stack oldStack, + Stack newStack, + CancellationToken cancellationToken) + { + while (oldStack.Count > 0 && newStack.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + var currentOld = oldStack.Pop(); + var currentNew = newStack.Pop(); + Contract.ThrowIfFalse(currentOld.FullSpan.Start == currentNew.FullSpan.Start); + + // If the two nodes/tokens were the same just skip past them. They're part of the common left width. + if (currentOld.IsIncrementallyIdenticalTo(currentNew)) + continue; + + // if we reached a token for either of these, then we can't break things down any further, and we hit + // the furthest point they are common. + if (currentOld.IsToken || currentNew.IsToken) + return currentOld.FullSpan.Start; + + // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the + // class, the class node would be new, but there might be many member nodes that were the same that we'd + // want to see and skip. Crumble the node and deal with its left side. + // + // Reverse so that we process the leftmost child first and walk left to right. + foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens().Reverse()) + oldStack.Push(nodeOrToken); + + foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens().Reverse()) + newStack.Push(nodeOrToken); + } + + // If we consumed all of 'new', then the length of the new doc is what we have in common. + if (oldStack.Count > 0) + return newRoot.FullSpan.Length; + + // If we consumed all of 'old', then the length of the old doc is what we have in common. + if (newStack.Count > 0) + return oldRoot.FullSpan.Length; + + // We consumed both stacks entirely. That means the trees were identical (though the root was different). Return null to signify no change to the doc. + return null; + } + + private static int? ComputeCommonRightWidth( + SyntaxNode oldRoot, + SyntaxNode newRoot, + Stack oldStack, + Stack newStack, + CancellationToken cancellationToken) + { + while (oldStack.Count > 0 && newStack.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + var currentOld = oldStack.Pop(); + var currentNew = newStack.Pop(); + + // The width on the right we've moved past on both old/new should be teh same. + Contract.ThrowIfFalse((oldRoot.FullSpan.End - currentOld.FullSpan.End) == + (newRoot.FullSpan.End - currentNew.FullSpan.End)); + + // If the two nodes/tokens were the same just skip past them. They're part of the common left width. + if (currentOld.IsIncrementallyIdenticalTo(currentNew)) + continue; + + // if we reached a token for either of these, then we can't break things down any further, and we hit + // the furthest point they are common. + if (currentOld.IsToken || currentNew.IsToken) + return oldRoot.FullSpan.End - currentOld.FullSpan.End; + + // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the + // class, the class node would be new, but there might be many member nodes that were the same that we'd + // want to see and skip. Crumble the node and deal with its right side. + // + // Do not reverse teh chilren. We want to process the rightmost child first and walk right to left. + foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens()) + oldStack.Push(nodeOrToken); + + foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens()) + newStack.Push(nodeOrToken); + } + + // If we consumed all of 'new', then the length of the new doc is what we have in common. + if (oldStack.Count > 0) + return newRoot.FullSpan.Length; + + // If we consumed all of 'old', then the length of the old doc is what we have in common. + if (newStack.Count > 0) + return oldRoot.FullSpan.Length; + + // We consumed both stacks entirely. That means the trees were identical (though the root was different). Return null to signify no change to the doc. + return null; + } + } +} From b04456c0fb5a2c80ca7557d1055b9b77eea0ae08 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 15:29:05 -0700 Subject: [PATCH 036/220] Add tests --- .../Core/Portable/Text/TextChangeRange.cs | 5 + .../Classification/ClassificationTests.vb | 4 + .../SyntacticChangeRangeComputerTests.vb | 231 ++++++++++++++++++ .../FSharpClassificationService.cs | 2 +- .../SyntacticChangeRangeComputer.cs | 10 +- 5 files changed, 246 insertions(+), 6 deletions(-) create mode 100644 src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb diff --git a/src/Compilers/Core/Portable/Text/TextChangeRange.cs b/src/Compilers/Core/Portable/Text/TextChangeRange.cs index ce6166cfe689d..3e2bcaf8dc886 100644 --- a/src/Compilers/Core/Portable/Text/TextChangeRange.cs +++ b/src/Compilers/Core/Portable/Text/TextChangeRange.cs @@ -133,5 +133,10 @@ private string GetDebuggerDisplay() { return $"new TextChangeRange(new TextSpan({Span.Start}, {Span.Length}), {NewLength})"; } + + public override string ToString() + { + return $"TextChangeRange(Span={Span}, NewLength={NewLength})"; + } } } diff --git a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb index b76a33c1af59a..26065df8ddcd9 100644 --- a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb +++ b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb @@ -135,6 +135,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Public Function AdjustStaleClassification(text As SourceText, classifiedSpan As ClassifiedSpan) As ClassifiedSpan Implements IClassificationService.AdjustStaleClassification End Function + + Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, cancellationToken As CancellationToken) As Task(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync + Return SpecializedTasks.Default(Of TextChangeRange?) + End Function End Class End Class End Namespace diff --git a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb new file mode 100644 index 0000000000000..5b0213f6d1313 --- /dev/null +++ b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb @@ -0,0 +1,231 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. +' See the LICENSE file in the project root for more information. + +Imports Microsoft.CodeAnalysis.Classification +Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Text + +Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification + + Public Class SyntacticChangeRangeComputerTests + Private Shared Function TestCSharp(markup As String, newText As String) As Task + Return Test(markup, newText, LanguageNames.CSharp) + End Function + + Private Shared Async Function Test(markup As String, newText As String, language As String) As Task + Using workspace = TestWorkspace.Create(language, compilationOptions:=Nothing, parseOptions:=Nothing, markup) + Dim testDocument = workspace.Documents(0) + Dim startingDocument = workspace.CurrentSolution.GetDocument(testDocument.Id) + + Dim spans = testDocument.SelectedSpans + Assert.True(1 = spans.Count, "Test should have one spans in it representing the span to replace") + + Dim annotatedSpans = testDocument.AnnotatedSpans + Assert.True(1 = annotatedSpans.Count, "Test should have a single {||} span representing the change span in the final document") + Dim annotatedSpan = annotatedSpans.Single().Value.Single() + + Dim startingText = Await startingDocument.GetTextAsync() + Dim startingTree = Await startingDocument.GetSyntaxTreeAsync() + Dim startingRoot = Await startingTree.GetRootAsync() + + Dim endingText = startingText.Replace(spans(0), newText) + Dim endingTree = startingTree.WithChangedText(endingText) + Dim endingRoot = Await endingTree.GetRootAsync() + + Dim actualChange = SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(startingRoot, endingRoot, Nothing) + Dim expectedChange = New TextChangeRange( + annotatedSpan, + annotatedSpan.Length + newText.Length - spans(0).Length) + Assert.True(expectedChange = actualChange, expectedChange.ToString() & " != " & actualChange.ToString() & vbCrLf & "Changed span was" & vbCrLf & startingText.ToString(actualChange.Span)) + End Using + End Function + + + Public Async Function TestIdentifierChangeInMethod1() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: Con[||]|}.WriteLine(0); + } + + void M3() + { + } +} +", "sole") + End Function + + + Public Async Function TestIdentifierChangeInMethod2() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: Con[|sole|]|}.WriteLine(0); + } + + void M3() + { + } +} +", "") + End Function + + + Public Async Function TestSplitClass1() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } +{|changed: +[||] + + void |}M2() + { + Console.WriteLine(0); + } + + void M3() + { + } +} +", "} class C2 {") + End Function + + + Public Async Function TestMergeClass() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } +{|changed: + +[|} class C2 {|] + + void |}M2() + { + Console.WriteLine(0); + } + + void M3() + { + } +} +", "") + End Function + + + Public Async Function TestExtendComment() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [||] + } + + void M3() + { + Console.WriteLine(""*/ Console.WriteLine("") +|} } + + void M4() + { + } +} +", "/*") + End Function + + + Public Async Function TestRemoveComment() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [|/*|] + } + + void M3() + { + Console.WriteLine(""*/ Console.WriteLine("") +|} } + + void M4() + { + } +} +", "") + End Function + + + Public Async Function TestExtendCommentToEndOfFile() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [||] + } + + void M3() + { + } + + void M4() + { + } +} +|}", "/*") + End Function + End Class +End Namespace diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index 651d1c6bbeef8..d7166f328999d 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs @@ -48,7 +48,7 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan return _service.AdjustStaleClassification(text, classifiedSpan); } - public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument) + public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { return SpecializedTasks.Default(); } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index ce4c4dbaaa353..4cacc28203b3a 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -37,7 +37,7 @@ public static async Task ComputeSyntacticChangeRangeAsync( return ComputeSyntacticChangeRange(oldRoot, newRoot, cancellationToken); } - private static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, CancellationToken cancellationToken) + public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, CancellationToken cancellationToken) { if (oldRoot == newRoot) return default; @@ -105,10 +105,10 @@ private static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, S var oldRootWidth = oldRoot.FullWidth(); var newRootWidth = newRoot.FullWidth(); - Contract.ThrowIfFalse(commonLeftWidth > oldRootWidth); - Contract.ThrowIfFalse(commonLeftWidth > newRootWidth); - Contract.ThrowIfFalse(commonRightWidth > oldRootWidth); - Contract.ThrowIfFalse(commonRightWidth > newRootWidth); + Contract.ThrowIfTrue(commonLeftWidth > oldRootWidth); + Contract.ThrowIfTrue(commonLeftWidth > newRootWidth); + Contract.ThrowIfTrue(commonRightWidth > oldRootWidth); + Contract.ThrowIfTrue(commonRightWidth > newRootWidth); return new TextChangeRange( TextSpan.FromBounds(start: commonLeftWidth.Value, end: oldRootWidth - commonRightWidth.Value), From 0c06cd06fe5f0d6708e13153393ee4c8cf5afaad Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 15:34:27 -0700 Subject: [PATCH 037/220] Add to public api --- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 04b7d01e736e3..bae630a7445d9 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -23,6 +23,9 @@ Microsoft.CodeAnalysis.Operations.OperationWalker.OperationWalker() - Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordClassName = 31 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind const Microsoft.CodeAnalysis.WellKnownDiagnosticTags.CompilationEnd = "CompilationEnd" -> string! Microsoft.CodeAnalysis.SyntaxContextReceiverCreator +Microsoft.CodeAnalysis.SyntaxNode.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode! other) -> bool +Microsoft.CodeAnalysis.SyntaxNodeOrToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNodeOrToken other) -> bool +Microsoft.CodeAnalysis.SyntaxToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxToken token) -> bool static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Compare(System.ReadOnlySpan left, System.ReadOnlySpan right) -> int static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Equals(System.ReadOnlySpan left, System.ReadOnlySpan right) -> bool override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators(string! language) -> System.Collections.Immutable.ImmutableArray From dc784177adc98aaab1c52a09b278654c434540e0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 15:38:53 -0700 Subject: [PATCH 038/220] Tweaks --- .../SyntacticChangeRangeComputer.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index 4cacc28203b3a..0dee35cf00b44 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -94,7 +94,7 @@ public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, Sy if (commonLeftWidth == null || commonRightWidth == null) { - // They better have both been the same, otherwise our walk was somehow non-symettric. + // They better have both been the same, otherwise our walk was somehow non-symmetric. Contract.ThrowIfFalse(commonLeftWidth == commonRightWidth); // The trees were effectively identical (even if the children were different). Return that there was no @@ -179,7 +179,7 @@ public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, Sy Contract.ThrowIfFalse((oldRoot.FullSpan.End - currentOld.FullSpan.End) == (newRoot.FullSpan.End - currentNew.FullSpan.End)); - // If the two nodes/tokens were the same just skip past them. They're part of the common left width. + // If the two nodes/tokens were the same just skip past them. They're part of the common right width. if (currentOld.IsIncrementallyIdenticalTo(currentNew)) continue; @@ -189,10 +189,10 @@ public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, Sy return oldRoot.FullSpan.End - currentOld.FullSpan.End; // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the - // class, the class node would be new, but there might be many member nodes that were the same that we'd - // want to see and skip. Crumble the node and deal with its right side. + // class, the class node would be new, but there might be many member nodes following the edited node + // that were the same that we'd want to see and skip. Crumble the node and deal with its right side. // - // Do not reverse teh chilren. We want to process the rightmost child first and walk right to left. + // Do not reverse the children. We want to process the rightmost child first and walk right to left. foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens()) oldStack.Push(nodeOrToken); From 6611ac7cbad092663cb78cfe7d418c2cbe7f1619 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 15:41:54 -0700 Subject: [PATCH 039/220] Add docs --- .../SyntaxClassification/SyntacticChangeRangeComputer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index 0dee35cf00b44..7b64802a5cc88 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -15,7 +15,12 @@ namespace Microsoft.CodeAnalysis.Classification /// /// Computes a syntactic text change range that determines the range of a document that was changed by an edit. The /// portions outside this change range are guaranteed to be syntactically identical (see ). + /// cref="SyntaxNode.IsIncrementallyIdenticalTo"/>). This algorithm is intended to be fast. It is + /// technically linear in the number of nodes and tokens that may need to examined. However, in practice, it should + /// operate in sub-linear time as it will bail the moment tokens don't match, and it's able to skip over matching + /// nodes fully without examining the contents of those nodes. This is intended for consumers that want a + /// reasonably accurate change range computer, but do not want to spend an inordinate amount of time getting the + /// most accurate and minimal result possible. /// /// /// This computation is not guaranteed to be minimal. It may return a range that includes parts that are unchanged. @@ -175,7 +180,7 @@ public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, Sy var currentOld = oldStack.Pop(); var currentNew = newStack.Pop(); - // The width on the right we've moved past on both old/new should be teh same. + // The width on the right we've moved past on both old/new should be the same. Contract.ThrowIfFalse((oldRoot.FullSpan.End - currentOld.FullSpan.End) == (newRoot.FullSpan.End - currentNew.FullSpan.End)); From fdb694e146f43366f34711825360dc3ab63eb26b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 15:44:10 -0700 Subject: [PATCH 040/220] Add tests --- .../SyntacticChangeRangeComputerTests.vb | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb index 5b0213f6d1313..569f05a314afd 100644 --- a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb +++ b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb @@ -227,5 +227,59 @@ public class C } |}", "/*") End Function + + + Public Async Function TestDeleteFullFile() As Task + Await TestCSharp( +"{|changed:[| +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + } + + void M3() + { + } + + void M4() + { + } +} +|]|}", "") + End Function + + + Public Async Function InsertFullFile() As Task + Await TestCSharp( +"{|changed:[||]|}", " +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + } + + void M3() + { + } + + void M4() + { + } +} +") + End Function End Class End Namespace From e4c630a73e3502e90d98edc1ffa590ccafb4014c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 7 Apr 2021 19:32:08 -0700 Subject: [PATCH 041/220] Add actual test of the tagger. --- .../Classification/SyntacticTaggerTests.cs | 25 +++++++++++++------ ...lassificationTaggerProvider.TagComputer.cs | 14 ++++++++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs index f06a7f3d458ff..f77a7b5394de4 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs @@ -18,11 +18,12 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Classification { + [UseExportProvider] public class SyntacticTaggerTests { [WorkItem(1032665, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1032665")] - [WpfFact(Skip = "https://github.com/dotnet/roslyn/issues/19822"), Trait(Traits.Feature, Traits.Features.Classification)] - public async Task TestTagsChangedForEntireFile() + [WpfFact, Trait(Traits.Feature, Traits.Features.Classification)] + public async Task TestTagsChangedForPortionThatChanged() { var code = @"class Program2 @@ -33,13 +34,21 @@ public async Task TestTagsChangedForEntireFile() using var workspace = TestWorkspace.CreateCSharp(code); var document = workspace.Documents.First(); var subjectBuffer = document.GetTextBuffer(); + var checkpoint = new Checkpoint(); + + var notificationService = workspace.GetService(); var tagComputer = new SyntacticClassificationTaggerProvider.TagComputer( subjectBuffer, - workspace.GetService(), + notificationService, AsynchronousOperationListenerProvider.NullListener, - null, - new SyntacticClassificationTaggerProvider(workspace.ExportProvider.GetExportedValue(), null, null, null)); + typeMap: null, + new SyntacticClassificationTaggerProvider( + workspace.ExportProvider.GetExportedValue(), + notificationService, + typeMap: null, + AsynchronousOperationListenerProvider.NullProvider), + diffTimeout: int.MaxValue); // Capture the expected value before the await, in case it changes. var expectedLength = subjectBuffer.CurrentSnapshot.Length; @@ -55,7 +64,7 @@ public async Task TestTagsChangedForEntireFile() }; await checkpoint.Task; - Assert.Equal(1, actualVersionNumber); + Assert.Equal(0, actualVersionNumber); Assert.Equal(expectedLength, actualLength); Assert.Equal(1, callstacks.Count); @@ -69,8 +78,8 @@ public async Task TestTagsChangedForEntireFile() // assigning expected here and verifying in the event handler, because the // event handler can't run until we await. await checkpoint.Task; - Assert.Equal(2, actualVersionNumber); - Assert.Equal(expectedLength, actualLength); + Assert.Equal(1, actualVersionNumber); + Assert.Equal(37, actualLength); Assert.Equal(2, callstacks.Count); } } diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 9fcefb5595eec..ef52ea1ab4e58 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -67,6 +67,11 @@ internal partial class TagComputer private readonly ClassificationTypeMap _typeMap; private readonly SyntacticClassificationTaggerProvider _taggerProvider; + /// + /// Timeout before we cancel the work to diff and return whatever we have. + /// + private readonly int _diffTimeout; + private int _taggerReferenceCount; public TagComputer( @@ -74,14 +79,15 @@ public TagComputer( IForegroundNotificationService notificationService, IAsynchronousOperationListener asyncListener, ClassificationTypeMap typeMap, - SyntacticClassificationTaggerProvider taggerProvider) + SyntacticClassificationTaggerProvider taggerProvider, + int diffTimeout = TaggerConstants.NearImmediateDelay) { _subjectBuffer = subjectBuffer; _notificationService = notificationService; _listener = asyncListener; _typeMap = typeMap; _taggerProvider = taggerProvider; - + _diffTimeout = diffTimeout; _workQueue = new AsynchronousSerialWorkQueue(taggerProvider._threadingContext, asyncListener); _reportChangeCancellationSource = new CancellationTokenSource(); @@ -256,7 +262,7 @@ private async Task GetChangedSpanAsync( return currentSnapshot.GetFullSpan(); } - private static async Task ComputeChangeRangeAsync( + private async Task ComputeChangeRangeAsync( Document previousDocument, Document currentDocument, IClassificationService classificationService, CancellationToken cancellationToken) { @@ -268,7 +274,7 @@ private async Task GetChangedSpanAsync( var computeTask = Task.Run(() => classificationService.ComputeSyntacticChangeRangeAsync(previousDocument, currentDocument, linkedToken.Token), linkedToken.Token); - var delayTask = Task.Delay(TaggerConstants.NearImmediateDelay, linkedToken.Token); + var delayTask = Task.Delay(_diffTimeout, linkedToken.Token); var completedTask = await Task.WhenAny(computeTask, delayTask).ConfigureAwait(false); From 16dc18b6f87845d7231a7260f7e1e309443e8b7d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 09:58:33 -0700 Subject: [PATCH 042/220] Public API --- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index bae630a7445d9..fefa6282977c2 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -26,6 +26,7 @@ Microsoft.CodeAnalysis.SyntaxContextReceiverCreator Microsoft.CodeAnalysis.SyntaxNode.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode! other) -> bool Microsoft.CodeAnalysis.SyntaxNodeOrToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNodeOrToken other) -> bool Microsoft.CodeAnalysis.SyntaxToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxToken token) -> bool +override Microsoft.CodeAnalysis.Text.TextChangeRange.ToString() -> string! static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Compare(System.ReadOnlySpan left, System.ReadOnlySpan right) -> int static Microsoft.CodeAnalysis.CaseInsensitiveComparison.Equals(System.ReadOnlySpan left, System.ReadOnlySpan right) -> bool override Microsoft.CodeAnalysis.Diagnostics.AnalyzerFileReference.GetGenerators(string! language) -> System.Collections.Immutable.ImmutableArray From 1eefea97039e1573c90523fc633ab80a5c5dc382 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 11:06:32 -0700 Subject: [PATCH 043/220] Add benchmark --- .../SyntacticChangeRangeBenchmark.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs diff --git a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs new file mode 100644 index 0000000000000..6a41a1b987670 --- /dev/null +++ b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System; +using System.IO; +using System.Threading; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace IdeCoreBenchmarks +{ + [MemoryDiagnoser] + public class SyntacticChangeRangeBenchmark + { + private int _index; + private SourceText _text; + private SyntaxTree _tree; + private SyntaxNode _root; + + [GlobalSetup] + public void GlobalSetup() + { + var roslynRoot = Environment.GetEnvironmentVariable(Program.RoslynRootPathEnvVariableName); + var csFilePath = Path.Combine(roslynRoot, @"src\Compilers\CSharp\Portable\Generated\BoundNodes.xml.Generated.cs"); + + if (!File.Exists(csFilePath)) + throw new FileNotFoundException(csFilePath); + + var text = File.ReadAllText(csFilePath); + _index = text.IndexOf("switch (node.Kind)"); + if (_index < 0) + throw new ArgumentException("Code location not found"); + + _text = SourceText.From(text); + _tree = SyntaxFactory.ParseSyntaxTree(text); + _root = _tree.GetCompilationUnitRoot(); + } + + [Benchmark] + public void SimpleEditAtMiddle() + { + // this will change the switch statement to `mode.kind` instead of `node.kind`. This should be reuse most + // of the tree and should result in a very small diff. + var newText = _text.WithChanges(new TextChange(new TextSpan(8, 1), "m")); + var newTree = _tree.WithChangedText(newText); + var newRoot = newTree.GetRoot(); + + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); + } + + [Benchmark] + public void DestabalizingEditAtMiddle() + { + // this will change the switch statement to a switch expression. This may have large cascading changes. + var newText = _text.WithChanges(new TextChange(new TextSpan(_index, 0), "var v = x ")); + var newTree = _tree.WithChangedText(newText); + var newRoot = newTree.GetRoot(); + + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); + } + } +} From 72df51a409903ccef80af130ca45c6ae4523a79a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 13:55:38 -0700 Subject: [PATCH 044/220] Allow service to dictate cached data --- ...lassificationTaggerProvider.TagComputer.cs | 66 +++++++++++-------- .../Classification/ClassificationTests.vb | 4 ++ .../FSharpClassificationService.cs | 5 ++ .../SyntacticChangeRangeBenchmark.cs | 41 ++++++++++-- .../AbstractClassificationService.cs | 10 +-- .../Classification/IClassificationService.cs | 9 +++ .../Portable/Workspace/Solution/Document.cs | 7 +- 7 files changed, 101 insertions(+), 41 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index ef52ea1ab4e58..bac2ff1b72e3a 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -57,7 +57,12 @@ internal partial class TagComputer private readonly object _gate = new(); private ITextSnapshot _lastProcessedSnapshot; private Document _lastProcessedDocument; - private SyntaxTree _lastProcessedSyntaxTree; + + /// + /// Data stored by the that can be computed in the bg which it will + /// benefit from when it is called back for classifications later. + /// + private object _lastProcessedCachedData; private Workspace _workspace; private CancellationTokenSource _reportChangeCancellationSource; @@ -142,7 +147,7 @@ private void ResetLastParsedDocument() lock (_gate) { _lastProcessedDocument = null; - _lastProcessedSyntaxTree = null; + _lastProcessedCachedData = null; } } @@ -207,19 +212,25 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C return; } - SyntaxTree currentSyntaxTree; - var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); - using (latencyTracker) + var service = TryGetClassificationService(currentSnapshot); + object currentCachedData = null; + var changedSpan = currentSnapshot.GetFullSpan(); + if (service != null) { - // preemptively parse file in background so that when we are called from tagger from UI thread, we have tree ready. - // F#/typescript and other languages that doesn't support syntax tree will return null here. - currentSyntaxTree = await currentDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); + using (latencyTracker) + { + // preemptively allow the classification service to compute and cache data for this file. For + // example, in C# and VB we will parse the file so that are called from tagger from UI thread, + // we have the root of the tree ready to go. + currentCachedData = await service.GetDataToCacheAsync(currentDocument, cancellationToken).ConfigureAwait(false); + + // Query the service to determine waht span of the document actually changed and should be + // reclassified in the host editor. + changedSpan = await GetChangedSpanAsync(currentDocument, currentSnapshot, service, cancellationToken).ConfigureAwait(false); + } } - // We don't need to grab _lastProcessedDocument in a lock. We don't care which version of the previous - // doc we grab, just that we grab some prior version. This is only used - var changedSpan = await GetChangedSpanAsync(_lastProcessedDocument, currentDocument, currentSnapshot, cancellationToken).ConfigureAwait(false); - // Once we're past this point, we're mutating our internal state so we cannot cancel past that. cancellationToken = CancellationToken.None; @@ -227,7 +238,7 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C { _lastProcessedSnapshot = currentSnapshot; _lastProcessedDocument = currentDocument; - _lastProcessedSyntaxTree = currentSyntaxTree; + _lastProcessedCachedData = currentCachedData; } _reportChangeCancellationSource = new CancellationTokenSource(); @@ -242,20 +253,18 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C } private async Task GetChangedSpanAsync( - Document previousDocument, Document currentDocument, - ITextSnapshot currentSnapshot, CancellationToken cancellationToken) + Document currentDocument, ITextSnapshot currentSnapshot, + IClassificationService service, CancellationToken cancellationToken) { - // Try to defer to the classification service to see if it can find a narrower range to recompute. + // We don't need to grab _lastProcessedDocument in a lock. We don't care which version of the previous + // doc we grab, just that we grab some prior version. This is only used + var previousDocument = _lastProcessedDocument; if (previousDocument != null) { - var classificationService = TryGetClassificationService(currentSnapshot); - if (classificationService != null) - { - var changeRange = await ComputeChangeRangeAsync( - previousDocument, currentDocument, classificationService, cancellationToken).ConfigureAwait(false); - if (changeRange != null) - return currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength); - } + var changeRange = await ComputeChangeRangeAsync( + previousDocument, currentDocument, service, cancellationToken).ConfigureAwait(false); + if (changeRange != null) + return currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength); } // Couldn't compute a narrower range. Just the mark the entire file as changed. @@ -359,13 +368,13 @@ private void AddClassifiedSpans( // From this point on we'll do all operations over these values. ITextSnapshot lastSnapshot; Document lastDocument; - SyntaxTree lastSyntaxTree; + object lastCachedData; lock (_gate) { lastSnapshot = _lastProcessedSnapshot; lastDocument = _lastProcessedDocument; - lastSyntaxTree = _lastProcessedSyntaxTree; + lastCachedData = _lastProcessedCachedData; } if (lastDocument == null) @@ -392,8 +401,9 @@ private void AddClassifiedSpans( classificationService, span, lastSnapshot, lastDocument, classifiedSpans); } - // Ensure the syntax tree stays alive for as long as we're doing the processing. - GC.KeepAlive(lastSyntaxTree); + // Ensure the cached data stays alive for as long as we're calling into the classification service to do + // the computation. + GC.KeepAlive(lastCachedData); } private void AddClassifiedSpansForCurrentTree( diff --git a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb index 26065df8ddcd9..181d005b61eab 100644 --- a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb +++ b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb @@ -136,6 +136,10 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Public Function AdjustStaleClassification(text As SourceText, classifiedSpan As ClassifiedSpan) As ClassifiedSpan Implements IClassificationService.AdjustStaleClassification End Function + Public Function GetDataToCacheAsync(document As Document, cancellationToken As CancellationToken) As Task(Of Object) Implements IClassificationService.GetDataToCacheAsync + Return SpecializedTasks.Default(Of Object) + End Function + Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, cancellationToken As CancellationToken) As Task(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync Return SpecializedTasks.Default(Of TextChangeRange?) End Function diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index d7166f328999d..b615c280f70de 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs @@ -48,6 +48,11 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan return _service.AdjustStaleClassification(text, classifiedSpan); } + public Task GetDataToCacheAsync(Document document, CancellationToken cancellationToken) + { + return SpecializedTasks.Default(); + } + public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { return SpecializedTasks.Default(); diff --git a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs index 6a41a1b987670..f9cad2e664f26 100644 --- a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs +++ b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs @@ -23,6 +23,9 @@ public class SyntacticChangeRangeBenchmark private SyntaxTree _tree; private SyntaxNode _root; + private SyntaxNode _rootWithSimpleEdit; + private SyntaxNode _rootWithComplexEdit; + [GlobalSetup] public void GlobalSetup() { @@ -40,6 +43,24 @@ public void GlobalSetup() _text = SourceText.From(text); _tree = SyntaxFactory.ParseSyntaxTree(text); _root = _tree.GetCompilationUnitRoot(); + _rootWithSimpleEdit = WithSimpleEditAtMiddle(); + _rootWithComplexEdit = WithDestabalizingEditAtMiddle(); + } + + private SyntaxNode WithSimpleEditAtMiddle() + { + var newText = _text.WithChanges(new TextChange(new TextSpan(8, 1), "m")); + var newTree = _tree.WithChangedText(newText); + var newRoot = newTree.GetRoot(); + return newRoot; + } + + private SyntaxNode WithDestabalizingEditAtMiddle() + { + var newText = _text.WithChanges(new TextChange(new TextSpan(_index, 0), "var v = x ")); + var newTree = _tree.WithChangedText(newText); + var newRoot = newTree.GetRoot(); + return newRoot; } [Benchmark] @@ -47,9 +68,7 @@ public void SimpleEditAtMiddle() { // this will change the switch statement to `mode.kind` instead of `node.kind`. This should be reuse most // of the tree and should result in a very small diff. - var newText = _text.WithChanges(new TextChange(new TextSpan(8, 1), "m")); - var newTree = _tree.WithChangedText(newText); - var newRoot = newTree.GetRoot(); + var newRoot = WithSimpleEditAtMiddle(); SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); } @@ -58,11 +77,21 @@ public void SimpleEditAtMiddle() public void DestabalizingEditAtMiddle() { // this will change the switch statement to a switch expression. This may have large cascading changes. - var newText = _text.WithChanges(new TextChange(new TextSpan(_index, 0), "var v = x ")); - var newTree = _tree.WithChangedText(newText); - var newRoot = newTree.GetRoot(); + var newRoot = WithDestabalizingEditAtMiddle(); SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); } + + [Benchmark] + public void SimpleEditAtMiddle_NoParse() + { + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithSimpleEdit, CancellationToken.None); + } + + [Benchmark] + public void DestabalizingEditAtMiddle_NoParse() + { + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithComplexEdit, CancellationToken.None); + } } } diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index f34e1ec0f36ff..78cbeb280cbe8 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -121,13 +121,15 @@ protected static void AddRange(ArrayBuilder temp, List GetDataToCacheAsync(Document document, CancellationToken cancellationToken) + => await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) { var classificationService = oldDocument.GetLanguageService(); - if (classificationService == null) - return SpecializedTasks.Default(); - - return classificationService.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, cancellationToken); + return classificationService == null + ? SpecializedTasks.Default() + : classificationService.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs index 5ba9eea050fc1..280420e501cb7 100644 --- a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs @@ -52,6 +52,15 @@ internal interface IClassificationService : ILanguageService /// ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan classifiedSpan); + /// + /// This method can be called into by hosts of the . It allows instances to + /// pre-compute data that will be cached and preserved up through the calls to classification methods. + /// Implementations can use this to do things like preemptively parse the document in the background, without + /// concern that this might impact the UI thread later on when classifications are retrieved. can be returned if not data needs to be cached. + /// + Task GetDataToCacheAsync(Document document, CancellationToken cancellationToken); + /// /// Determines the range of the documents that should be considered syntactically changed after an edit. In /// language systems that can reuse major parts of a document after an edit, and which would not need to diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 7340b38f9aa5e..c2f6350dd9ac3 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -155,9 +155,10 @@ public bool SupportsSemanticModel /// Gets the for this document asynchronously. /// /// - /// The returned syntax tree can be if the returns . This function will return - /// the same value if called multiple times. + /// The returned syntax tree can be if the returns . This function will return the same value if called multiple times. Specifically, the + /// same Task instance will be returned, which will always return the same underlying + /// instance. /// public Task GetSyntaxTreeAsync(CancellationToken cancellationToken = default) { From 62d626716bb7fd72f6beb27f67e74613a33d60b5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 14:33:32 -0700 Subject: [PATCH 045/220] Fix index --- .../SyntacticChangeRangeBenchmark.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs index f9cad2e664f26..f428ba727b244 100644 --- a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs +++ b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs @@ -41,7 +41,7 @@ public void GlobalSetup() throw new ArgumentException("Code location not found"); _text = SourceText.From(text); - _tree = SyntaxFactory.ParseSyntaxTree(text); + _tree = SyntaxFactory.ParseSyntaxTree(_text); _root = _tree.GetCompilationUnitRoot(); _rootWithSimpleEdit = WithSimpleEditAtMiddle(); _rootWithComplexEdit = WithDestabalizingEditAtMiddle(); @@ -49,7 +49,9 @@ public void GlobalSetup() private SyntaxNode WithSimpleEditAtMiddle() { - var newText = _text.WithChanges(new TextChange(new TextSpan(8, 1), "m")); + // this will change the switch statement to `mode.kind` instead of `node.kind`. This should be reuse most + // of the tree and should result in a very small diff. + var newText = _text.WithChanges(new TextChange(new TextSpan(_index + 8, 1), "m")); var newTree = _tree.WithChangedText(newText); var newRoot = newTree.GetRoot(); return newRoot; @@ -57,6 +59,7 @@ private SyntaxNode WithSimpleEditAtMiddle() private SyntaxNode WithDestabalizingEditAtMiddle() { + // this will change the switch statement to a switch expression. This may have large cascading changes. var newText = _text.WithChanges(new TextChange(new TextSpan(_index, 0), "var v = x ")); var newTree = _tree.WithChangedText(newText); var newRoot = newTree.GetRoot(); @@ -66,19 +69,14 @@ private SyntaxNode WithDestabalizingEditAtMiddle() [Benchmark] public void SimpleEditAtMiddle() { - // this will change the switch statement to `mode.kind` instead of `node.kind`. This should be reuse most - // of the tree and should result in a very small diff. var newRoot = WithSimpleEditAtMiddle(); - SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); } [Benchmark] public void DestabalizingEditAtMiddle() { - // this will change the switch statement to a switch expression. This may have large cascading changes. var newRoot = WithDestabalizingEditAtMiddle(); - SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); } From 6a8d43283239b34dfd0c5a676603e501da912bf5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 17:55:54 -0700 Subject: [PATCH 046/220] Make into timespan --- .../Classification/SyntacticTaggerTests.cs | 11 ++++++----- ...ClassificationTaggerProvider.TagComputer.cs | 6 +++--- .../SyntacticClassificationTaggerProvider.cs | 18 ++++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs b/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs index f77a7b5394de4..759b9d425139d 100644 --- a/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs +++ b/src/EditorFeatures/CSharpTest/Classification/SyntacticTaggerTests.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -39,16 +40,16 @@ public async Task TestTagsChangedForPortionThatChanged() var notificationService = workspace.GetService(); var tagComputer = new SyntacticClassificationTaggerProvider.TagComputer( - subjectBuffer, - notificationService, - AsynchronousOperationListenerProvider.NullListener, - typeMap: null, new SyntacticClassificationTaggerProvider( workspace.ExportProvider.GetExportedValue(), notificationService, typeMap: null, AsynchronousOperationListenerProvider.NullProvider), - diffTimeout: int.MaxValue); + subjectBuffer, + notificationService, + AsynchronousOperationListenerProvider.NullListener, + typeMap: null, + diffTimeout: TimeSpan.MaxValue); // Capture the expected value before the await, in case it changes. var expectedLength = subjectBuffer.CurrentSnapshot.Length; diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index bac2ff1b72e3a..d5048a0d34578 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -75,17 +75,17 @@ internal partial class TagComputer /// /// Timeout before we cancel the work to diff and return whatever we have. /// - private readonly int _diffTimeout; + private readonly TimeSpan _diffTimeout; private int _taggerReferenceCount; public TagComputer( + SyntacticClassificationTaggerProvider taggerProvider, ITextBuffer subjectBuffer, IForegroundNotificationService notificationService, IAsynchronousOperationListener asyncListener, ClassificationTypeMap typeMap, - SyntacticClassificationTaggerProvider taggerProvider, - int diffTimeout = TaggerConstants.NearImmediateDelay) + TimeSpan diffTimeout) { _subjectBuffer = subjectBuffer; _notificationService = notificationService; diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs index fc864470c38f4..89c7c6c0af169 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.cs @@ -9,7 +9,9 @@ using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Options; +using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -54,7 +56,7 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag if (!_tagComputers.TryGetValue(buffer, out var tagComputer)) { - tagComputer = new TagComputer(buffer, _notificationService, _listener, _typeMap, this); + tagComputer = new TagComputer(this, buffer, _notificationService, _listener, _typeMap, TaggerDelay.NearImmediate.ComputeTimeDelay()); _tagComputers.Add(buffer, tagComputer); } @@ -62,16 +64,12 @@ public ITagger CreateTagger(ITextBuffer buffer) where T : ITag var tagger = new Tagger(tagComputer); - if (!(tagger is ITagger typedTagger)) - { - // Oops, we can't actually return this tagger, so just clean up - tagger.Dispose(); - return null; - } - else - { + if (tagger is ITagger typedTagger) return typedTagger; - } + + // Oops, we can't actually return this tagger, so just clean up + tagger.Dispose(); + return null; } private void DisconnectTagComputer(ITextBuffer buffer) From 1d3108d14b00cbf73dfe646f6f84ebe705ecc32c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 18:02:05 -0700 Subject: [PATCH 047/220] Use helper extension --- ...lassificationTaggerProvider.TagComputer.cs | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index d5048a0d34578..5508ee6e78b87 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -22,6 +22,7 @@ using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; +using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification @@ -278,27 +279,17 @@ private async Task GetChangedSpanAsync( // We want to compute a minimal change, but we don't want this to run for too long. So do the // computation work in the threadpool, but also gate how much time we can spend here so that we can let // the editor know about the size of the change asap. - using var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - - var computeTask = Task.Run(() => - classificationService.ComputeSyntacticChangeRangeAsync(previousDocument, currentDocument, linkedToken.Token), linkedToken.Token); - - var delayTask = Task.Delay(_diffTimeout, linkedToken.Token); - - var completedTask = await Task.WhenAny(computeTask, delayTask).ConfigureAwait(false); - - // Ensure that we cancel any outstanding work on the other task, once one completes. - linkedToken.Cancel(); - - // ensure that if we completed because of cancellation, we throw that up. - cancellationToken.ThrowIfCancellationRequested(); - - // If we timed out, just return nothing. We'll reclassify the full doc. - if (completedTask == delayTask) + var computeTaskWithTimeout = + classificationService.ComputeSyntacticChangeRangeAsync(previousDocument, currentDocument, cancellationToken) + .WithTimeout(_diffTimeout); + try + { + return await computeTaskWithTimeout.ConfigureAwait(false); + } + catch (TimeoutException) + { return null; - - // Otherwise, extract out the change range that was computed by our service. - return await computeTask.ConfigureAwait(false); + } } private void ReportChangedSpan(SnapshotSpan changeSpan) From 7715cee214cb2e3bb952d4b0704da90c20418a4a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 8 Apr 2021 18:28:15 -0700 Subject: [PATCH 048/220] Move timeout into diff algorithm itself --- ...lassificationTaggerProvider.TagComputer.cs | 24 +- .../Classification/ClassificationTests.vb | 2 +- .../SyntacticChangeRangeComputerTests.vb | 2 +- .../FSharpClassificationService.cs | 2 +- .../SyntacticChangeRangeBenchmark.cs | 8 +- .../AbstractClassificationService.cs | 6 +- .../Classification/IClassificationService.cs | 10 +- .../AbstractSyntaxClassificationService.cs | 4 +- .../ISyntaxClassificationService.cs | 3 +- .../SyntacticChangeRangeComputer.cs | 240 ++++++++++-------- 10 files changed, 160 insertions(+), 141 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 5508ee6e78b87..59c8b758d4f73 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -262,8 +262,8 @@ private async Task GetChangedSpanAsync( var previousDocument = _lastProcessedDocument; if (previousDocument != null) { - var changeRange = await ComputeChangeRangeAsync( - previousDocument, currentDocument, service, cancellationToken).ConfigureAwait(false); + var changeRange = await service.ComputeSyntacticChangeRangeAsync( + previousDocument, currentDocument, _diffTimeout, cancellationToken).ConfigureAwait(false); if (changeRange != null) return currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength); } @@ -272,26 +272,6 @@ private async Task GetChangedSpanAsync( return currentSnapshot.GetFullSpan(); } - private async Task ComputeChangeRangeAsync( - Document previousDocument, Document currentDocument, - IClassificationService classificationService, CancellationToken cancellationToken) - { - // We want to compute a minimal change, but we don't want this to run for too long. So do the - // computation work in the threadpool, but also gate how much time we can spend here so that we can let - // the editor know about the size of the change asap. - var computeTaskWithTimeout = - classificationService.ComputeSyntacticChangeRangeAsync(previousDocument, currentDocument, cancellationToken) - .WithTimeout(_diffTimeout); - try - { - return await computeTaskWithTimeout.ConfigureAwait(false); - } - catch (TimeoutException) - { - return null; - } - } - private void ReportChangedSpan(SnapshotSpan changeSpan) { lock (_gate) diff --git a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb index 181d005b61eab..27ef4c53373fc 100644 --- a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb +++ b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb @@ -140,7 +140,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Return SpecializedTasks.Default(Of Object) End Function - Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, cancellationToken As CancellationToken) As Task(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync + Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, timeout As TimeSpan, cancellationToken As CancellationToken) As Task(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync Return SpecializedTasks.Default(Of TextChangeRange?) End Function End Class diff --git a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb index 569f05a314afd..86e75d4e298d1 100644 --- a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb +++ b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb @@ -33,7 +33,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Dim endingTree = startingTree.WithChangedText(endingText) Dim endingRoot = Await endingTree.GetRootAsync() - Dim actualChange = SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(startingRoot, endingRoot, Nothing) + Dim actualChange = SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(startingRoot, endingRoot, TimeSpan.MaxValue, Nothing) Dim expectedChange = New TextChangeRange( annotatedSpan, annotatedSpan.Length + newText.Length - spans(0).Length) diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index b615c280f70de..1ec5476379db0 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs @@ -53,7 +53,7 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan return SpecializedTasks.Default(); } - public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) + public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) { return SpecializedTasks.Default(); } diff --git a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs index f428ba727b244..f0ebc7a40a8cd 100644 --- a/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs +++ b/src/Tools/IdeCoreBenchmarks/SyntacticChangeRangeBenchmark.cs @@ -70,26 +70,26 @@ private SyntaxNode WithDestabalizingEditAtMiddle() public void SimpleEditAtMiddle() { var newRoot = WithSimpleEditAtMiddle(); - SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, TimeSpan.MaxValue, CancellationToken.None); } [Benchmark] public void DestabalizingEditAtMiddle() { var newRoot = WithDestabalizingEditAtMiddle(); - SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, CancellationToken.None); + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, newRoot, TimeSpan.MaxValue, CancellationToken.None); } [Benchmark] public void SimpleEditAtMiddle_NoParse() { - SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithSimpleEdit, CancellationToken.None); + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithSimpleEdit, TimeSpan.MaxValue, CancellationToken.None); } [Benchmark] public void DestabalizingEditAtMiddle_NoParse() { - SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithComplexEdit, CancellationToken.None); + SyntacticChangeRangeComputer.ComputeSyntacticChangeRange(_root, _rootWithComplexEdit, TimeSpan.MaxValue, CancellationToken.None); } } } diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index 78cbeb280cbe8..b863cde35c27d 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -124,12 +125,13 @@ protected static void AddRange(ArrayBuilder temp, List GetDataToCacheAsync(Document document, CancellationToken cancellationToken) => await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) + public Task ComputeSyntacticChangeRangeAsync( + Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) { var classificationService = oldDocument.GetLanguageService(); return classificationService == null ? SpecializedTasks.Default() - : classificationService.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, cancellationToken); + : classificationService.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, timeout, cancellationToken); } } } diff --git a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs index 280420e501cb7..fb427e00f2c2f 100644 --- a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -70,7 +71,14 @@ internal interface IClassificationService : ILanguageService /// If determining this is not possible, or potentially expensive, can be returned to /// indicate that the entire document should be considered changed and should be syntactically reclassified. /// + /// + /// Implementations should attempt to abide by the provided timeout as much as they can, returning the best + /// information available at that point. As this can be called in performance critical scenarios, it is better + /// to return quickly with potentially larger change span (including that of the full document) rather than + /// spend too much time computing a very precise result. + /// /// - Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken); + Task ComputeSyntacticChangeRangeAsync( + Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs index 21425f38c072b..b7edd3f83137b 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs @@ -54,7 +54,7 @@ public void AddSemanticClassifications( Worker.Classify(workspace, semanticModel, textSpan, result, getNodeClassifiers, getTokenClassifiers, cancellationToken); } - public async Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken) - => await SyntacticChangeRangeComputer.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, cancellationToken).ConfigureAwait(false); + public async Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) + => await SyntacticChangeRangeComputer.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, timeout, cancellationToken).ConfigureAwait(false); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs index 22fe8213e31a8..21a33a29081fe 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs @@ -51,6 +51,7 @@ void AddSemanticClassifications( ClassifiedSpan FixClassification(SourceText text, ClassifiedSpan classifiedSpan); /// - Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, CancellationToken cancellationToken); + Task ComputeSyntacticChangeRangeAsync( + Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index 7b64802a5cc88..ca83b9c51cf69 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -24,25 +25,46 @@ namespace Microsoft.CodeAnalysis.Classification /// /// /// This computation is not guaranteed to be minimal. It may return a range that includes parts that are unchanged. - /// This means it is also legal for the change range to just specify the entire file was changed. + /// This means it is also legal for the change range to just specify the entire file was changed. The quality of + /// results will depend on how well the parsers did with incremental parsing, and how much time is given to do the + /// comparison. In practice, for large files (i.e. 15kloc) with standard types of edits, this generally returns + /// results in around 50-100 usecs on a i7 3GHz desktop. + /// + /// This algorithm will respect the timeout provided to the best of abilities. If any information has been computed + /// when the timeout elapses, it will be returned. + /// /// internal static class SyntacticChangeRangeComputer { private static readonly ObjectPool> s_pool = new(() => new()); - public static async Task ComputeSyntacticChangeRangeAsync( - Document oldDocument, Document newDocument, CancellationToken cancellationToken) + public static async Task ComputeSyntacticChangeRangeAsync( + Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) { + // If they're the same doc, there is no change. if (oldDocument == newDocument) - return default; + return new TextChangeRange(); + var stopwatch = SharedStopwatch.StartNew(); var oldRoot = await oldDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + // If we ran out of time, we have to assume both are completely different. + if (stopwatch.Elapsed > timeout) + return null; + var newRoot = await newDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - return ComputeSyntacticChangeRange(oldRoot, newRoot, cancellationToken); + if (stopwatch.Elapsed > timeout) + return null; + + return ComputeSyntacticChangeRange(oldRoot, newRoot, timeout, stopwatch, cancellationToken); } - public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, CancellationToken cancellationToken) + public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, CancellationToken cancellationToken) + => ComputeSyntacticChangeRange(oldRoot, newRoot, timeout, SharedStopwatch.StartNew(), cancellationToken); + + private static TextChangeRange ComputeSyntacticChangeRange( + SyntaxNode oldRoot, SyntaxNode newRoot, TimeSpan timeout, SharedStopwatch stopwatch, CancellationToken cancellationToken) { if (oldRoot == newRoot) return default; @@ -94,19 +116,19 @@ public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, Sy // // The Span changed will go from `[CLW, Old_Width - CRW)`, and the NewLength will be `New_Width - CLW - CRW` - var commonLeftWidth = ComputeCommonLeftWidth(oldRoot, newRoot, leftOldStack.Object, leftNewStack.Object, cancellationToken); - var commonRightWidth = ComputeCommonRightWidth(oldRoot, newRoot, rightOldStack.Object, rightNewStack.Object, cancellationToken); - - if (commonLeftWidth == null || commonRightWidth == null) + var commonLeftWidth = ComputeCommonLeftWidth(leftOldStack.Object, leftNewStack.Object); + if (commonLeftWidth == null) { - // They better have both been the same, otherwise our walk was somehow non-symmetric. - Contract.ThrowIfFalse(commonLeftWidth == commonRightWidth); - // The trees were effectively identical (even if the children were different). Return that there was no // text change. return default; } + // Only compute the right side if we have time for it. Otherwise, assume there is nothing in common there. + var commonRightWidth = 0; + if (stopwatch.Elapsed < timeout) + commonRightWidth = ComputeCommonRightWidth(rightOldStack.Object, rightNewStack.Object); + var oldRootWidth = oldRoot.FullWidth(); var newRootWidth = newRoot.FullWidth(); @@ -116,105 +138,111 @@ public static TextChangeRange ComputeSyntacticChangeRange(SyntaxNode oldRoot, Sy Contract.ThrowIfTrue(commonRightWidth > newRootWidth); return new TextChangeRange( - TextSpan.FromBounds(start: commonLeftWidth.Value, end: oldRootWidth - commonRightWidth.Value), - newRootWidth - commonLeftWidth.Value - commonRightWidth.Value); - } + TextSpan.FromBounds(start: commonLeftWidth.Value, end: oldRootWidth - commonRightWidth), + newRootWidth - commonLeftWidth.Value - commonRightWidth); - private static int? ComputeCommonLeftWidth( - SyntaxNode oldRoot, - SyntaxNode newRoot, - Stack oldStack, - Stack newStack, - CancellationToken cancellationToken) - { - while (oldStack.Count > 0 && newStack.Count > 0) + int? ComputeCommonLeftWidth( + Stack oldStack, + Stack newStack) { - cancellationToken.ThrowIfCancellationRequested(); - var currentOld = oldStack.Pop(); - var currentNew = newStack.Pop(); - Contract.ThrowIfFalse(currentOld.FullSpan.Start == currentNew.FullSpan.Start); - - // If the two nodes/tokens were the same just skip past them. They're part of the common left width. - if (currentOld.IsIncrementallyIdenticalTo(currentNew)) - continue; - - // if we reached a token for either of these, then we can't break things down any further, and we hit - // the furthest point they are common. - if (currentOld.IsToken || currentNew.IsToken) - return currentOld.FullSpan.Start; - - // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the - // class, the class node would be new, but there might be many member nodes that were the same that we'd - // want to see and skip. Crumble the node and deal with its left side. - // - // Reverse so that we process the leftmost child first and walk left to right. - foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens().Reverse()) - oldStack.Push(nodeOrToken); - - foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens().Reverse()) - newStack.Push(nodeOrToken); + while (oldStack.Count > 0 && newStack.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + var currentOld = oldStack.Pop(); + var currentNew = newStack.Pop(); + Contract.ThrowIfFalse(currentOld.FullSpan.Start == currentNew.FullSpan.Start); + + // If the two nodes/tokens were the same just skip past them. They're part of the common left width. + if (currentOld.IsIncrementallyIdenticalTo(currentNew)) + continue; + + // if we reached a token for either of these, then we can't break things down any further, and we hit + // the furthest point they are common. + if (currentOld.IsToken || currentNew.IsToken) + return currentOld.FullSpan.Start; + + // Similarly, if we've run out of time, just return what we've computed so far. It's not as accurate as + // we could be. But the caller wants the results asap. + if (stopwatch.Elapsed > timeout) + return currentOld.FullSpan.Start; + + // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the + // class, the class node would be new, but there might be many member nodes that were the same that we'd + // want to see and skip. Crumble the node and deal with its left side. + // + // Reverse so that we process the leftmost child first and walk left to right. + foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens().Reverse()) + oldStack.Push(nodeOrToken); + + foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens().Reverse()) + newStack.Push(nodeOrToken); + } + + // If we consumed all of 'new', then the length of the new doc is what we have in common. + if (oldStack.Count > 0) + return newRoot.FullSpan.Length; + + // If we consumed all of 'old', then the length of the old doc is what we have in common. + if (newStack.Count > 0) + return oldRoot.FullSpan.Length; + + // We consumed both stacks entirely. That means the trees were identical (though the root was different). Return null to signify no change to the doc. + return null; } - // If we consumed all of 'new', then the length of the new doc is what we have in common. - if (oldStack.Count > 0) - return newRoot.FullSpan.Length; - - // If we consumed all of 'old', then the length of the old doc is what we have in common. - if (newStack.Count > 0) - return oldRoot.FullSpan.Length; - - // We consumed both stacks entirely. That means the trees were identical (though the root was different). Return null to signify no change to the doc. - return null; - } - - private static int? ComputeCommonRightWidth( - SyntaxNode oldRoot, - SyntaxNode newRoot, - Stack oldStack, - Stack newStack, - CancellationToken cancellationToken) - { - while (oldStack.Count > 0 && newStack.Count > 0) + int ComputeCommonRightWidth( + Stack oldStack, + Stack newStack) { - cancellationToken.ThrowIfCancellationRequested(); - var currentOld = oldStack.Pop(); - var currentNew = newStack.Pop(); - - // The width on the right we've moved past on both old/new should be the same. - Contract.ThrowIfFalse((oldRoot.FullSpan.End - currentOld.FullSpan.End) == - (newRoot.FullSpan.End - currentNew.FullSpan.End)); - - // If the two nodes/tokens were the same just skip past them. They're part of the common right width. - if (currentOld.IsIncrementallyIdenticalTo(currentNew)) - continue; - - // if we reached a token for either of these, then we can't break things down any further, and we hit - // the furthest point they are common. - if (currentOld.IsToken || currentNew.IsToken) - return oldRoot.FullSpan.End - currentOld.FullSpan.End; - - // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the - // class, the class node would be new, but there might be many member nodes following the edited node - // that were the same that we'd want to see and skip. Crumble the node and deal with its right side. - // - // Do not reverse the children. We want to process the rightmost child first and walk right to left. - foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens()) - oldStack.Push(nodeOrToken); - - foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens()) - newStack.Push(nodeOrToken); + while (oldStack.Count > 0 && newStack.Count > 0) + { + cancellationToken.ThrowIfCancellationRequested(); + var currentOld = oldStack.Pop(); + var currentNew = newStack.Pop(); + + // The width on the right we've moved past on both old/new should be the same. + Contract.ThrowIfFalse((oldRoot.FullSpan.End - currentOld.FullSpan.End) == + (newRoot.FullSpan.End - currentNew.FullSpan.End)); + + // If the two nodes/tokens were the same just skip past them. They're part of the common right width. + if (currentOld.IsIncrementallyIdenticalTo(currentNew)) + continue; + + // if we reached a token for either of these, then we can't break things down any further, and we hit + // the furthest point they are common. + if (currentOld.IsToken || currentNew.IsToken) + return oldRoot.FullSpan.End - currentOld.FullSpan.End; + + // Similarly, if we've run out of time, just return what we've computed so far. It's not as accurate as + // we could be. But the caller wants the results asap. + if (stopwatch.Elapsed > timeout) + return oldRoot.FullSpan.End - currentOld.FullSpan.End; + + // we've got two nodes, but they weren't the same. For example, say we made an edit in a method in the + // class, the class node would be new, but there might be many member nodes following the edited node + // that were the same that we'd want to see and skip. Crumble the node and deal with its right side. + // + // Do not reverse the children. We want to process the rightmost child first and walk right to left. + foreach (var nodeOrToken in currentOld.AsNode()!.ChildNodesAndTokens()) + oldStack.Push(nodeOrToken); + + foreach (var nodeOrToken in currentNew.AsNode()!.ChildNodesAndTokens()) + newStack.Push(nodeOrToken); + } + + // If we consumed all of 'new', then the length of the new doc is what we have in common. + if (oldStack.Count > 0) + return newRoot.FullSpan.Length; + + // If we consumed all of 'old', then the length of the old doc is what we have in common. + if (newStack.Count > 0) + return oldRoot.FullSpan.Length; + + // We consumed both stacks entirely. That means the trees were identical (though the root was + // different). We should never get here. If we were the same, then walking from the left should have + // consumed everything and already bailed out. + throw ExceptionUtilities.Unreachable; } - - // If we consumed all of 'new', then the length of the new doc is what we have in common. - if (oldStack.Count > 0) - return newRoot.FullSpan.Length; - - // If we consumed all of 'old', then the length of the old doc is what we have in common. - if (newStack.Count > 0) - return oldRoot.FullSpan.Length; - - // We consumed both stacks entirely. That means the trees were identical (though the root was different). Return null to signify no change to the doc. - return null; } } } From 9b1223e372bc17758feb82d90eeb94a9fe326980 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 9 Apr 2021 17:31:50 +0000 Subject: [PATCH 049/220] Fix goto types to include records --- .../NavigateToItemProvider.Callback.cs | 42 +------------------ ...stractNavigateToSearchService.InProcess.cs | 5 --- .../AbstractNavigateToSearchService.cs | 1 - .../Portable/NavigateTo/NavigateToItemKind.cs | 1 - .../NavigateTo/RoslynNavigateToItem.cs | 1 - .../NavigateTo/FSharpNavigateToItemKind.cs | 1 - .../CSharpDeclaredSymbolInfoFactoryService.cs | 2 +- .../FindSymbols/DeclaredSymbolInfo.cs | 1 - .../DependentTypeFinder_ProjectIndex.cs | 7 ++-- 9 files changed, 5 insertions(+), 56 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs index 4e1c1550397f7..59d9b9a83a53f 100644 --- a/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs +++ b/src/EditorFeatures/Core.Wpf/NavigateTo/NavigateToItemProvider.Callback.cs @@ -57,7 +57,7 @@ private void ReportMatchResult(Project project, INavigateToSearchResult result) var navigateToItem = new NavigateToItem( result.Name, - GetKind(result.Kind), + result.Kind, GetNavigateToLanguage(project.Language), result.SecondarySort, result, @@ -66,46 +66,6 @@ private void ReportMatchResult(Project project, INavigateToSearchResult result) _callback.AddItem(navigateToItem); } - private static string GetKind(string kind) - => kind switch - { - CodeAnalysis.NavigateTo.NavigateToItemKind.Class - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Class, - CodeAnalysis.NavigateTo.NavigateToItemKind.Constant - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Constant, - CodeAnalysis.NavigateTo.NavigateToItemKind.Delegate - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Delegate, - CodeAnalysis.NavigateTo.NavigateToItemKind.Enum - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Enum, - CodeAnalysis.NavigateTo.NavigateToItemKind.EnumItem - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.EnumItem, - CodeAnalysis.NavigateTo.NavigateToItemKind.Event - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Event, - CodeAnalysis.NavigateTo.NavigateToItemKind.Field - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Field, - CodeAnalysis.NavigateTo.NavigateToItemKind.File - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.File, - CodeAnalysis.NavigateTo.NavigateToItemKind.Interface - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Interface, - CodeAnalysis.NavigateTo.NavigateToItemKind.Line - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Line, - CodeAnalysis.NavigateTo.NavigateToItemKind.Method - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Method, - CodeAnalysis.NavigateTo.NavigateToItemKind.Module - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Module, - CodeAnalysis.NavigateTo.NavigateToItemKind.OtherSymbol - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.OtherSymbol, - CodeAnalysis.NavigateTo.NavigateToItemKind.Property - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Property, - // VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind doesn't have a record, fall back to class. - // This should be updated whenever NavigateToItemKind has a record. - CodeAnalysis.NavigateTo.NavigateToItemKind.Record - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Class, - CodeAnalysis.NavigateTo.NavigateToItemKind.Structure - => VisualStudio.Language.NavigateTo.Interfaces.NavigateToItemKind.Structure, - _ => throw ExceptionUtilities.UnexpectedValue(kind) - }; - private static PatternMatchKind GetPatternMatchKind(NavigateToMatchKind matchKind) => matchKind switch { diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index be4b472ed866d..255802737b3a4 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -288,8 +288,6 @@ private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) { case DeclaredSymbolInfoKind.Class: return NavigateToItemKind.Class; - case DeclaredSymbolInfoKind.Record: - return NavigateToItemKind.Record; case DeclaredSymbolInfoKind.Constant: return NavigateToItemKind.Constant; case DeclaredSymbolInfoKind.Delegate: @@ -357,9 +355,6 @@ public DeclaredSymbolInfoKindSet(IEnumerable navigateToItemKinds) case NavigateToItemKind.Class: lookupTable[(int)DeclaredSymbolInfoKind.Class] = true; break; - case NavigateToItemKind.Record: - lookupTable[(int)DeclaredSymbolInfoKind.Record] = true; - break; case NavigateToItemKind.Constant: lookupTable[(int)DeclaredSymbolInfoKind.Constant] = true; break; diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs index 139d21fe1d9bf..7f77e87431778 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.cs @@ -16,7 +16,6 @@ internal abstract partial class AbstractNavigateToSearchService : INavigateToSea { public IImmutableSet KindsProvided { get; } = ImmutableHashSet.Create( NavigateToItemKind.Class, - NavigateToItemKind.Record, NavigateToItemKind.Constant, NavigateToItemKind.Delegate, NavigateToItemKind.Enum, diff --git a/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs b/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs index d2e17e5e14446..8790ecb998ca8 100644 --- a/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs +++ b/src/Features/Core/Portable/NavigateTo/NavigateToItemKind.cs @@ -11,7 +11,6 @@ internal static class NavigateToItemKind public const string Line = nameof(Line); public const string File = nameof(File); public const string Class = nameof(Class); - public const string Record = nameof(Record); public const string Structure = nameof(Structure); public const string Interface = nameof(Interface); public const string Delegate = nameof(Delegate); diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 979ee6a3c5c4d..c1ad545aad308 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -167,7 +167,6 @@ private bool IsNamedType() switch (_item.DeclaredSymbolInfo.Kind) { case DeclaredSymbolInfoKind.Class: - case DeclaredSymbolInfoKind.Record: case DeclaredSymbolInfoKind.Enum: case DeclaredSymbolInfoKind.Interface: case DeclaredSymbolInfoKind.Module: diff --git a/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs b/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs index 0b5175a2cb4dc..123c14ebb4baa 100644 --- a/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs +++ b/src/Tools/ExternalAccess/FSharp/NavigateTo/FSharpNavigateToItemKind.cs @@ -13,7 +13,6 @@ internal static class FSharpNavigateToItemKind public static string Line => NavigateToItemKind.Line; public static string File = NavigateToItemKind.File; public static string Class => NavigateToItemKind.Class; - public static string Record => NavigateToItemKind.Record; public static string Structure => NavigateToItemKind.Structure; public static string Interface => NavigateToItemKind.Interface; public static string Delegate => NavigateToItemKind.Delegate; diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs index d3fa6a2825ce3..053c258bb462c 100644 --- a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs @@ -179,7 +179,7 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod node.Kind() switch { SyntaxKind.ClassDeclaration => DeclaredSymbolInfoKind.Class, - SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Record, + SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Class, SyntaxKind.InterfaceDeclaration => DeclaredSymbolInfoKind.Interface, SyntaxKind.StructDeclaration => DeclaredSymbolInfoKind.Struct, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), diff --git a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs index 52acb4f38e8c3..8665d5d59d8f0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs @@ -30,7 +30,6 @@ internal enum DeclaredSymbolInfoKind : byte Method, Module, Property, - Record, Struct, } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs index 8141e0a29c731..bd76156755867 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs @@ -48,7 +48,7 @@ public static Task GetIndexAsync( private static async Task CreateIndexAsync(Project project, CancellationToken cancellationToken) { - var classesAndRecordsThatMayDeriveFromSystemObject = new MultiDictionary(); + var classesThatMayDeriveFromSystemObject = new MultiDictionary(); var valueTypes = new MultiDictionary(); var enums = new MultiDictionary(); var delegates = new MultiDictionary(); @@ -64,8 +64,7 @@ private static async Task CreateIndexAsync(Project project, Cancel switch (info.Kind) { case DeclaredSymbolInfoKind.Class: - case DeclaredSymbolInfoKind.Record: - classesAndRecordsThatMayDeriveFromSystemObject.Add(document, info); + classesThatMayDeriveFromSystemObject.Add(document, info); break; case DeclaredSymbolInfoKind.Enum: enums.Add(document, info); @@ -85,7 +84,7 @@ private static async Task CreateIndexAsync(Project project, Cancel } } - return new ProjectIndex(classesAndRecordsThatMayDeriveFromSystemObject, valueTypes, enums, delegates, namedTypes); + return new ProjectIndex(classesThatMayDeriveFromSystemObject, valueTypes, enums, delegates, namedTypes); } } } From 39efab5e87f239d46787ce78d9b9099511d7fe55 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 10:30:04 -0700 Subject: [PATCH 050/220] use value tasks --- .../Test2/Classification/ClassificationTests.vb | 8 ++++---- .../Classification/FSharpClassificationService.cs | 8 ++++---- .../Classification/AbstractClassificationService.cs | 6 +++--- .../Portable/Classification/IClassificationService.cs | 4 ++-- .../AbstractSyntaxClassificationService.cs | 4 ++-- .../SyntaxClassification/ISyntaxClassificationService.cs | 2 +- .../SyntaxClassification/SyntacticChangeRangeComputer.cs | 3 ++- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb index 27ef4c53373fc..c8b9896ddeab2 100644 --- a/src/EditorFeatures/Test2/Classification/ClassificationTests.vb +++ b/src/EditorFeatures/Test2/Classification/ClassificationTests.vb @@ -136,12 +136,12 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Classification Public Function AdjustStaleClassification(text As SourceText, classifiedSpan As ClassifiedSpan) As ClassifiedSpan Implements IClassificationService.AdjustStaleClassification End Function - Public Function GetDataToCacheAsync(document As Document, cancellationToken As CancellationToken) As Task(Of Object) Implements IClassificationService.GetDataToCacheAsync - Return SpecializedTasks.Default(Of Object) + Public Function GetDataToCacheAsync(document As Document, cancellationToken As CancellationToken) As ValueTask(Of Object) Implements IClassificationService.GetDataToCacheAsync + Return New ValueTask(Of Object) End Function - Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, timeout As TimeSpan, cancellationToken As CancellationToken) As Task(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync - Return SpecializedTasks.Default(Of TextChangeRange?) + Public Function ComputeSyntacticChangeRangeAsync(oldDocument As Document, newDocument As Document, timeout As TimeSpan, cancellationToken As CancellationToken) As ValueTask(Of TextChangeRange?) Implements IClassificationService.ComputeSyntacticChangeRangeAsync + Return New ValueTask(Of TextChangeRange?) End Function End Class End Class diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index 1ec5476379db0..58cbe8756a464 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs @@ -48,14 +48,14 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan return _service.AdjustStaleClassification(text, classifiedSpan); } - public Task GetDataToCacheAsync(Document document, CancellationToken cancellationToken) + public ValueTask GetDataToCacheAsync(Document document, CancellationToken cancellationToken) { - return SpecializedTasks.Default(); + return new(); } - public Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) + public ValueTask ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) { - return SpecializedTasks.Default(); + return new(); } } } diff --git a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs index b863cde35c27d..b82f62bf4e1a6 100644 --- a/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/AbstractClassificationService.cs @@ -122,15 +122,15 @@ protected static void AddRange(ArrayBuilder temp, List GetDataToCacheAsync(Document document, CancellationToken cancellationToken) + public async ValueTask GetDataToCacheAsync(Document document, CancellationToken cancellationToken) => await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - public Task ComputeSyntacticChangeRangeAsync( + public ValueTask ComputeSyntacticChangeRangeAsync( Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) { var classificationService = oldDocument.GetLanguageService(); return classificationService == null - ? SpecializedTasks.Default() + ? new((TextChangeRange?)null) : classificationService.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, timeout, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs index fb427e00f2c2f..bda1891b02258 100644 --- a/src/Workspaces/Core/Portable/Classification/IClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/IClassificationService.cs @@ -60,7 +60,7 @@ internal interface IClassificationService : ILanguageService /// concern that this might impact the UI thread later on when classifications are retrieved. can be returned if not data needs to be cached. /// - Task GetDataToCacheAsync(Document document, CancellationToken cancellationToken); + ValueTask GetDataToCacheAsync(Document document, CancellationToken cancellationToken); /// /// Determines the range of the documents that should be considered syntactically changed after an edit. In @@ -78,7 +78,7 @@ internal interface IClassificationService : ILanguageService /// spend too much time computing a very precise result. /// /// - Task ComputeSyntacticChangeRangeAsync( + ValueTask ComputeSyntacticChangeRangeAsync( Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs index b7edd3f83137b..382c9e5a5b54f 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/AbstractSyntaxClassificationService.cs @@ -54,7 +54,7 @@ public void AddSemanticClassifications( Worker.Classify(workspace, semanticModel, textSpan, result, getNodeClassifiers, getTokenClassifiers, cancellationToken); } - public async Task ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) - => await SyntacticChangeRangeComputer.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, timeout, cancellationToken).ConfigureAwait(false); + public ValueTask ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) + => SyntacticChangeRangeComputer.ComputeSyntacticChangeRangeAsync(oldDocument, newDocument, timeout, cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs index 21a33a29081fe..a0d7547b0bc4b 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/ISyntaxClassificationService.cs @@ -51,7 +51,7 @@ void AddSemanticClassifications( ClassifiedSpan FixClassification(SourceText text, ClassifiedSpan classifiedSpan); /// - Task ComputeSyntacticChangeRangeAsync( + ValueTask ComputeSyntacticChangeRangeAsync( Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index ca83b9c51cf69..42895c27b418b 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -38,7 +38,7 @@ internal static class SyntacticChangeRangeComputer { private static readonly ObjectPool> s_pool = new(() => new()); - public static async Task ComputeSyntacticChangeRangeAsync( + public static async ValueTask ComputeSyntacticChangeRangeAsync( Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) { // If they're the same doc, there is no change. @@ -46,6 +46,7 @@ internal static class SyntacticChangeRangeComputer return new TextChangeRange(); var stopwatch = SharedStopwatch.StartNew(); + var oldRoot = await oldDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); // If we ran out of time, we have to assume both are completely different. From d63e198d0615d901f707a9659073c815b6f4ff14 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 12:40:56 -0700 Subject: [PATCH 051/220] Move nav bars to be async when items are selected. --- .../CSharpEditorNavigationBarItemService.cs | 14 +++---- .../AbstractEditorNavigationBarItemService.cs | 36 ++++++++++------ .../INavigationBarItemService.cs | 4 +- .../NavigationBar/NavigationBarController.cs | 41 +++++++++++-------- .../Test2/NavigationBar/TestHelpers.vb | 4 +- ...sualBasicEditorNavigationBarItemService.vb | 23 ++++++----- ...NavigationBarItemService_CodeGeneration.vb | 32 +++++++++------ .../Editor/FSharpNavigationBarItemService.cs | 11 ++++- 8 files changed, 99 insertions(+), 66 deletions(-) diff --git a/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs b/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs index d0ce747248e8a..ad0c61989d753 100644 --- a/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs @@ -6,13 +6,12 @@ using System.Composition; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.NavigationBar; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Text.Editor; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.NavigationBar { @@ -21,14 +20,15 @@ internal class CSharpEditorNavigationBarItemService : AbstractEditorNavigationBa { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpEditorNavigationBarItemService() + public CSharpEditorNavigationBarItemService(IThreadingContext threadingContext) + : base(threadingContext) { } - protected override VirtualTreePoint? GetSymbolNavigationPoint( + protected override async Task GetSymbolNavigationPointAsync( Document document, ISymbol symbol, CancellationToken cancellationToken) { - var syntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken); + var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var location = symbol.Locations.FirstOrDefault(l => l.SourceTree!.Equals(syntaxTree)); if (location == null) @@ -40,7 +40,7 @@ public CSharpEditorNavigationBarItemService() return new VirtualTreePoint(location.SourceTree!, location.SourceTree!.GetText(cancellationToken), location.SourceSpan.Start); } - protected override void NavigateToItem(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken) - => NavigateToSymbolItem(document, (RoslynNavigationBarItem.SymbolItem)item.UnderlyingItem, cancellationToken); + protected override Task NavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken) + => NavigateToSymbolItemAsync(document, (RoslynNavigationBarItem.SymbolItem)item.UnderlyingItem, cancellationToken); } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index a94ebe47409cf..746eee9319099 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -17,8 +17,15 @@ namespace Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar { internal abstract class AbstractEditorNavigationBarItemService : INavigationBarItemService { - protected abstract VirtualTreePoint? GetSymbolNavigationPoint(Document document, ISymbol symbol, CancellationToken cancellationToken); - protected abstract void NavigateToItem(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken); + protected readonly IThreadingContext ThreadingContext; + + protected AbstractEditorNavigationBarItemService(IThreadingContext threadingContext) + { + ThreadingContext = threadingContext; + } + + protected abstract Task GetSymbolNavigationPointAsync(Document document, ISymbol symbol, CancellationToken cancellationToken); + protected abstract Task NavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken); public async Task> GetItemsAsync(Document document, CancellationToken cancellationToken) { @@ -28,16 +35,17 @@ public async Task> GetItemsAsync(Document document, Can return items.SelectAsArray(v => (NavigationBarItem)new WrappedNavigationBarItem(v)); } - public void NavigateToItem(Document document, NavigationBarItem item, ITextView textView, CancellationToken cancellationToken) - => NavigateToItem(document, (WrappedNavigationBarItem)item, textView, cancellationToken); + public Task NavigateToItemAsync(Document document, NavigationBarItem item, ITextView textView, CancellationToken cancellationToken) + => NavigateToItemAsync(document, (WrappedNavigationBarItem)item, textView, cancellationToken); - protected void NavigateToSymbolItem( + protected async Task NavigateToSymbolItemAsync( Document document, RoslynNavigationBarItem.SymbolItem item, CancellationToken cancellationToken) { Contract.ThrowIfFalse(item.Kind == RoslynNavigationBarItemKind.Symbol); var symbolNavigationService = document.Project.Solution.Workspace.Services.GetRequiredService(); - var symbolInfo = item.NavigationSymbolId.Resolve(document.Project.GetRequiredCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken), ignoreAssemblyKey: true, cancellationToken: cancellationToken); + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + var symbolInfo = item.NavigationSymbolId.Resolve(compilation, ignoreAssemblyKey: true, cancellationToken: cancellationToken); var symbol = symbolInfo.GetAnySymbol(); // Do not allow third party navigation to types or constructors @@ -49,20 +57,22 @@ protected void NavigateToSymbolItem( return; } - var navigationPoint = this.GetSymbolItemNavigationPoint(document, item, cancellationToken); - + var navigationPoint = await this.GetSymbolItemNavigationPointAsync(document, item, cancellationToken).ConfigureAwait(false); if (navigationPoint.HasValue) { - NavigateToVirtualTreePoint(document.Project.Solution, navigationPoint.Value, cancellationToken); + await NavigateToVirtualTreePointAsync(document.Project.Solution, navigationPoint.Value, cancellationToken).ConfigureAwait(false); } } - protected static void NavigateToVirtualTreePoint(Solution solution, VirtualTreePoint navigationPoint, CancellationToken cancellationToken) + protected async Task NavigateToVirtualTreePointAsync(Solution solution, VirtualTreePoint navigationPoint, CancellationToken cancellationToken) { var documentToNavigate = solution.GetRequiredDocument(navigationPoint.Tree); var workspace = solution.Workspace; var navigationService = workspace.Services.GetRequiredService(); + // Have to move back to UI thread in order to navigate. + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + if (navigationService.CanNavigateToPosition(workspace, documentToNavigate.Id, navigationPoint.Position, navigationPoint.VirtualSpaces, cancellationToken)) { navigationService.TryNavigateToPosition(workspace, documentToNavigate.Id, navigationPoint.Position, navigationPoint.VirtualSpaces, options: null, cancellationToken); @@ -77,10 +87,10 @@ protected static void NavigateToVirtualTreePoint(Solution solution, VirtualTreeP public virtual bool ShowItemGrayedIfNear(NavigationBarItem item) => true; - public VirtualTreePoint? GetSymbolItemNavigationPoint(Document document, RoslynNavigationBarItem.SymbolItem item, CancellationToken cancellationToken) + public async Task GetSymbolItemNavigationPointAsync(Document document, RoslynNavigationBarItem.SymbolItem item, CancellationToken cancellationToken) { Contract.ThrowIfFalse(item.Kind == RoslynNavigationBarItemKind.Symbol); - var compilation = document.Project.GetRequiredCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken); + var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var symbols = item.NavigationSymbolId.Resolve(compilation, cancellationToken: cancellationToken); var symbol = symbols.Symbol; @@ -96,7 +106,7 @@ public virtual bool ShowItemGrayedIfNear(NavigationBarItem item) } } - return GetSymbolNavigationPoint(document, symbol, cancellationToken); + return await GetSymbolNavigationPointAsync(document, symbol, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs index a19b14cbe0ae8..6ebbcddd874b6 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -16,6 +14,6 @@ internal interface INavigationBarItemService : ILanguageService { Task> GetItemsAsync(Document document, CancellationToken cancellationToken); bool ShowItemGrayedIfNear(NavigationBarItem item); - void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken); + Task NavigateToItemAsync(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 8206a984b9494..76b71961ed6cf 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -349,12 +349,25 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel private void OnItemSelected(object? sender, NavigationBarItemSelectedEventArgs e) { AssertIsForeground(); + _ = OnItemSelectedAsync(e.Item); + } - _waitIndicator.Wait( + private async Task OnItemSelectedAsync(NavigationBarItem item) + { + AssertIsForeground(); + using var waitContext = _waitIndicator.StartWait( EditorFeaturesResources.Navigation_Bars, EditorFeaturesResources.Refreshing_navigation_bars, allowCancel: true, - action: context => ProcessItemSelectionSynchronously(e.Item, context.CancellationToken)); + showProgress: false); + + try + { + await ProcessItemSelectionAsync(item, waitContext.CancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } } /// @@ -362,10 +375,9 @@ private void OnItemSelected(object? sender, NavigationBarItemSelectedEventArgs e /// /// The selected item. /// A cancellation token from the wait context. - private void ProcessItemSelectionSynchronously(NavigationBarItem item, CancellationToken cancellationToken) + private async Task ProcessItemSelectionAsync(NavigationBarItem item, CancellationToken cancellationToken) { AssertIsForeground(); - if (item is NavigationBarPresentedItem) { // Presented items are not navigable, but they may be selected due to a race @@ -377,8 +389,6 @@ private void ProcessItemSelectionSynchronously(NavigationBarItem item, Cancellat if (item is NavigationBarProjectItem projectItem) { projectItem.SwitchToContext(); - - // TODO: navigate to document / focus text view } else { @@ -388,20 +398,19 @@ private void ProcessItemSelectionSynchronously(NavigationBarItem item, Cancellat if (document != null) { var languageService = document.GetRequiredLanguageService(); + var snapshot = _subjectBuffer.CurrentSnapshot; + item.Spans = item.TrackingSpans.Select(ts => ts.GetSpan(snapshot).Span.ToTextSpan()).ToList(); + var view = _presenter.TryGetCurrentView(); - NavigateToItem(item, document, _subjectBuffer.CurrentSnapshot, languageService, cancellationToken); + // ConfigureAwait(true) as we have to come back to UI thread in order to kick of the refresh task below. + await languageService.NavigateToItemAsync(document, item, view, cancellationToken).ConfigureAwait(true); } } - // Now that the edit has been done, refresh to make sure everything is up-to-date. At - // this point, we now use CancellationToken.None to ensure we're properly refreshed. - UpdateDropDownsSynchronously(CancellationToken.None); - } - - private void NavigateToItem(NavigationBarItem item, Document document, ITextSnapshot snapshot, INavigationBarItemService languageService, CancellationToken cancellationToken) - { - item.Spans = item.TrackingSpans.Select(ts => ts.GetSpan(snapshot).Span.ToTextSpan()).ToList(); - languageService.NavigateToItem(document, item, _presenter.TryGetCurrentView(), cancellationToken); + // Now that the edit has been done, refresh to make sure everything is up-to-date. + // Have to make sure we come back to the main thread for this. + AssertIsForeground(); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); } } } diff --git a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb index 9d970e3e0e006..e0f8062619cc5 100644 --- a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb +++ b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb @@ -125,9 +125,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar Dim leftItem = items.Single(Function(i) i.Text = leftItemToSelectText) Dim rightItem = leftItem.ChildItems.Single(Function(i) i.Text = rightItemToSelectText) - Dim navigationPoint = service.GetSymbolItemNavigationPoint( + Dim navigationPoint = (Await service.GetSymbolItemNavigationPointAsync( sourceDocument, DirectCast(DirectCast(rightItem, WrappedNavigationBarItem).UnderlyingItem, RoslynNavigationBarItem.SymbolItem), - CancellationToken.None).Value + CancellationToken.None)).Value Dim expectedNavigationDocument = workspace.Documents.Single(Function(doc) doc.CursorPosition.HasValue) Assert.Equal(expectedNavigationDocument.FilePath, navigationPoint.Tree.FilePath) diff --git a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb index 306723ee04c69..796e868a1f549 100644 --- a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb +++ b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService.vb @@ -9,7 +9,6 @@ Imports Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.VisualBasic.Utilities Imports Microsoft.CodeAnalysis.Host.Mef -Imports Microsoft.CodeAnalysis.NavigationBar Imports Microsoft.CodeAnalysis.NavigationBar.RoslynNavigationBarItem Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Text.Editor @@ -27,7 +26,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar - Public Sub New(editorOperationsFactoryService As IEditorOperationsFactoryService, textUndoHistoryRegistry As ITextUndoHistoryRegistry) + Public Sub New( + threadingContext As IThreadingContext, + editorOperationsFactoryService As IEditorOperationsFactoryService, + textUndoHistoryRegistry As ITextUndoHistoryRegistry) + MyBase.New(threadingContext) _editorOperationsFactoryService = editorOperationsFactoryService _textUndoHistoryRegistry = textUndoHistoryRegistry End Sub @@ -37,8 +40,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Return TypeOf DirectCast(item, WrappedNavigationBarItem).UnderlyingItem Is SymbolItem End Function - Protected Overrides Function GetSymbolNavigationPoint(document As Document, symbol As ISymbol, cancellationToken As CancellationToken) As VirtualTreePoint? - Dim location As Location = GetSourceNavigationLocation(document, symbol, cancellationToken) + Protected Overrides Async Function GetSymbolNavigationPointAsync(document As Document, symbol As ISymbol, cancellationToken As CancellationToken) As Task(Of VirtualTreePoint?) + Dim location As Location = Await GetSourceNavigationLocationAsync(document, symbol, cancellationToken).ConfigureAwait(False) If location Is Nothing Then Return Nothing End If @@ -56,11 +59,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Return New VirtualTreePoint(location.SourceTree, location.SourceTree.GetText(cancellationToken), location.SourceSpan.Start) End Function - Private Shared Function GetSourceNavigationLocation(document As Document, symbol As ISymbol, cancellationToken As CancellationToken) As Location + Private Shared Async Function GetSourceNavigationLocationAsync(document As Document, symbol As ISymbol, cancellationToken As CancellationToken) As Task(Of Location) Dim sourceLocations = symbol.Locations.Where(Function(l) l.IsInSource) ' First figure out the location that we want to grab considering partial types - Dim syntaxTree = document.GetSyntaxTreeSynchronously(cancellationToken) + Dim syntaxTree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False) Dim location = sourceLocations.FirstOrDefault(Function(l) l.SourceTree.Equals(syntaxTree)) If location Is Nothing Then @@ -70,16 +73,16 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Return location End Function - Protected Overrides Sub NavigateToItem(document As Document, item As WrappedNavigationBarItem, textView As ITextView, cancellationToken As CancellationToken) + Protected Overrides Async Function NavigateToItemAsync(document As Document, item As WrappedNavigationBarItem, textView As ITextView, cancellationToken As CancellationToken) As Task Dim underlying = item.UnderlyingItem Dim generateCodeItem = TryCast(underlying, AbstractGenerateCodeItem) Dim symbolItem = TryCast(underlying, SymbolItem) If generateCodeItem IsNot Nothing Then - GenerateCodeForItem(document, generateCodeItem, textView, cancellationToken) + Await GenerateCodeForItemAsync(document, generateCodeItem, textView, cancellationToken).ConfigureAwait(False) ElseIf symbolItem IsNot Nothing Then - NavigateToSymbolItem(document, symbolItem, cancellationToken) + Await NavigateToSymbolItemAsync(document, symbolItem, cancellationToken).ConfigureAwait(False) End If - End Sub + End Function End Class End Namespace diff --git a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb index 043999dfd4adf..f7c2f4b27f487 100644 --- a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb +++ b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb @@ -20,24 +20,29 @@ Imports Microsoft.VisualStudio.Text.Editor Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Partial Friend Class VisualBasicEditorNavigationBarItemService - Private Sub GenerateCodeForItem(document As Document, generateCodeItem As AbstractGenerateCodeItem, textView As ITextView, cancellationToken As CancellationToken) + Private Async Function GenerateCodeForItemAsync(document As Document, generateCodeItem As AbstractGenerateCodeItem, textView As ITextView, cancellationToken As CancellationToken) As Task ' We'll compute everything up front before we go mutate state - Dim text = document.GetTextSynchronously(cancellationToken) - Dim newDocument = GetGeneratedDocumentAsync(document, generateCodeItem, cancellationToken).WaitAndGetResult(cancellationToken) - Dim generatedTree = newDocument.GetSyntaxRootSynchronously(cancellationToken) + Dim text = Await document.GetTextAsync(cancellationToken).ConfigureAwait(False) + Dim newDocument = Await GetGeneratedDocumentAsync(document, generateCodeItem, cancellationToken).ConfigureAwait(False) + Dim generatedTree = Await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False) + Dim generatedNode = generatedTree.GetAnnotatedNodes(GeneratedSymbolAnnotation).Single().FirstAncestorOrSelf(Of MethodBlockBaseSyntax) - Dim documentOptions = document.GetOptionsAsync(cancellationToken).WaitAndGetResult(cancellationToken) + Dim documentOptions = Await document.GetOptionsAsync(cancellationToken).ConfigureAwait(False) Dim indentSize = documentOptions.GetOption(FormattingOptions.IndentationSize) + Dim navigationPoint = NavigationPointHelpers.GetNavigationPoint(generatedTree.GetText(text.Encoding), indentSize, generatedNode) + ' switch back to ui thread to actually perform the application and navigation + Await Me.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken) + Using transaction = New CaretPreservingEditTransaction(VBEditorResources.Generate_Member, textView, _textUndoHistoryRegistry, _editorOperationsFactoryService) newDocument.Project.Solution.Workspace.ApplyDocumentChanges(newDocument, cancellationToken) - NavigateToVirtualTreePoint(newDocument.Project.Solution, navigationPoint, cancellationToken) + Await NavigateToVirtualTreePointAsync(newDocument.Project.Solution, navigationPoint, cancellationToken).ConfigureAwait(True) transaction.Complete() End Using - End Sub + End Function Public Shared Async Function GetGeneratedDocumentAsync(document As Document, generateCodeItem As RoslynNavigationBarItem, cancellationToken As CancellationToken) As Task(Of Document) Dim syntaxTree = Await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(False) @@ -49,7 +54,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Return document End If - newDocument = Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, Nothing, cancellationToken).WaitAndGetResult(cancellationToken) + newDocument = Await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, Nothing, cancellationToken).ConfigureAwait(False) Dim formatterRules = Formatter.GetDefaultFormattingRules(newDocument) If ShouldApplyLineAdjustmentFormattingRule(generateCodeItem) Then @@ -57,11 +62,12 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar End If Dim documentOptions = Await newDocument.GetOptionsAsync(cancellationToken).ConfigureAwait(False) - Return Formatter.FormatAsync(newDocument, - Formatter.Annotation, - options:=documentOptions, - cancellationToken:=cancellationToken, - rules:=formatterRules).WaitAndGetResult(cancellationToken) + Return Await Formatter.FormatAsync( + newDocument, + Formatter.Annotation, + options:=documentOptions, + cancellationToken:=cancellationToken, + rules:=formatterRules).ConfigureAwait(False) End Function Private Shared Function ShouldApplyLineAdjustmentFormattingRule(generateCodeItem As RoslynNavigationBarItem) As Boolean diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index e733f7e0c4ca8..4ff5dad3f3a91 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation; using Microsoft.CodeAnalysis.Notification; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor { @@ -24,12 +25,16 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor [ExportLanguageService(typeof(INavigationBarItemService), LanguageNames.FSharp)] internal class FSharpNavigationBarItemService : INavigationBarItemService { + private readonly IThreadingContext _threadingContext; private readonly IFSharpNavigationBarItemService _service; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FSharpNavigationBarItemService(IFSharpNavigationBarItemService service) + public FSharpNavigationBarItemService( + IThreadingContext threadingContext, + IFSharpNavigationBarItemService service) { + _threadingContext = threadingContext; _service = service; } @@ -39,7 +44,7 @@ public async Task> GetItemsAsync(Document document, Can return items?.Select(x => ConvertToNavigationBarItem(x)).ToList(); } - public void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) + public async Task NavigateToItemAsync(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) { // The logic here was ported from FSharp's implementation. The main reason was to avoid shimming INotificationService. if (item.Spans.Count > 0) @@ -48,6 +53,8 @@ public void NavigateToItem(Document document, NavigationBarItem item, ITextView var workspace = document.Project.Solution.Workspace; var navigationService = workspace.Services.GetService(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + if (navigationService.CanNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, cancellationToken)) { navigationService.TryNavigateToPosition(workspace, document.Id, span.Start, virtualSpace: 0, options: null, cancellationToken); From 18e8b7475fd58648e9bc9c6952cc8b8cc1c922ac Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 13:23:56 -0700 Subject: [PATCH 052/220] In progress --- .../NavigationBar/NavigationBarController.cs | 84 +++++-------------- ...avigationBarController_ModelComputation.cs | 3 + 2 files changed, 26 insertions(+), 61 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 76b71961ed6cf..7b96c3dd7dc16 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -66,13 +66,14 @@ public NavigationBarController( subjectBuffer.PostChanged += OnSubjectBufferPostChanged; // Initialize the tasks to be an empty model so we never have to deal with a null case. - _modelTask = Task.FromResult( - new NavigationBarModel( - SpecializedCollections.EmptyList(), - default, - null)); - - _selectedItemInfoTask = Task.FromResult(new NavigationBarSelectedTypeAndMember(null, null)); + _lastCompletedModel = new NavigationBarModel( + SpecializedCollections.EmptyList(), + semanticVersionStamp: default, + itemService: null); + _modelTask = Task.FromResult(_lastCompletedModel); + + _selectedItemInfo = new NavigationBarSelectedTypeAndMember(null, null); + _selectedItemInfoTask = Task.FromResult(_selectedItemInfo); } public void SetWorkspace(Workspace? newWorkspace) @@ -96,15 +97,9 @@ private void ConnectToWorkspace(Workspace workspace) _workspace = workspace; _workspace.WorkspaceChanged += this.OnWorkspaceChanged; - void connectToNewWorkspace() - { - // For the first time you open the file, we'll start immediately - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); - } - if (IsForeground()) { - connectToNewWorkspace(); + ConnectToNewWorkspace(); } else { @@ -113,9 +108,17 @@ void connectToNewWorkspace() { await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - connectToNewWorkspace(); + ConnectToNewWorkspace(); }).CompletesAsyncOperation(asyncToken); } + + return; + + void ConnectToNewWorkspace() + { + // For the first time you open the file, we'll start immediately + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + } } private void DisconnectFromWorkspace() @@ -221,36 +224,18 @@ private void UpdateDropDownsSynchronously(CancellationToken cancellationToken) { AssertIsForeground(); - // If the presenter already has the full list and the model is already complete, then we - // don't have to do any further computation nor push anything to the presenter - if (PresenterAlreadyHaveUpToDateFullList(cancellationToken)) - { + var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) return; - } - - // We need to ensure that all the state computation is up to date, so cancel any - // previous work and ensure the model is up to date - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: false); - - // Wait for the work to be complete. We'll wait with our cancellationToken, so if the - // user hits cancel we won't block them, but the computation can still continue - - using (Logger.LogBlock(FunctionId.NavigationBar_UpdateDropDownsSynchronously_WaitForModel, cancellationToken)) - { - _modelTask.Wait(cancellationToken); - } - - using (Logger.LogBlock(FunctionId.NavigationBar_UpdateDropDownsSynchronously_WaitForSelectedItemInfo, cancellationToken)) - { - _selectedItemInfoTask.Wait(cancellationToken); - } + // Just present whatever information we have at this point. We don't want to block the user from + // being able to open the dropdown list. GetProjectItems(out var projectItems, out var selectedProjectItem); _presenter.PresentItems( projectItems, selectedProjectItem, - _modelTask.Result.Types, + _lastCompletedModel.Types, _selectedItemInfoTask.Result.TypeItem, _selectedItemInfoTask.Result.MemberItem); _versionStampOfFullListPushedToPresenter = _modelTask.Result.SemanticVersionStamp; @@ -282,29 +267,6 @@ private void GetProjectItems(out IList projectItems, o : projectItems.First(); } - /// - /// Check if the presenter has already been pushed the full model that corresponds to the - /// current buffer's project version stamp. - /// - private bool PresenterAlreadyHaveUpToDateFullList(CancellationToken cancellationToken) - { - AssertIsForeground(); - - // If it doesn't have a full list pushed, then of course not - if (_versionStampOfFullListPushedToPresenter == null) - { - return false; - } - - var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - { - return false; - } - - return document.Project.GetDependentSemanticVersionAsync(cancellationToken).WaitAndGetResult(cancellationToken) == _versionStampOfFullListPushedToPresenter; - } - private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember selectedItems) { AssertIsForeground(); diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 3ee35f2761d2c..82f657f311eaf 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -28,6 +28,9 @@ internal partial class NavigationBarController private NavigationBarModel _lastCompletedModel; private CancellationTokenSource _modelTaskCancellationSource = new(); + private readonly object _gate = new(); + private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastModelAndSelectedInfo; + /// /// Starts a new task to compute the model based on the current text. /// From aa8917168406e6a707000c656f4d539b08478d85 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 13:29:44 -0700 Subject: [PATCH 053/220] In progress --- .../NavigationBar/NavigationBarController.cs | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 7b96c3dd7dc16..f22ccac8b8310 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -34,13 +34,6 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje private readonly IWaitIndicator _waitIndicator; private readonly IAsynchronousOperationListener _asyncListener; - /// - /// If we have pushed a full list to the presenter in response to a focus event, this - /// contains the version stamp of the list that was pushed. It is null if the last thing - /// pushed to the list was due to a caret move or file change. - /// - private VersionStamp? _versionStampOfFullListPushedToPresenter = null; - private bool _disconnected = false; private Workspace? _workspace; @@ -72,8 +65,10 @@ public NavigationBarController( itemService: null); _modelTask = Task.FromResult(_lastCompletedModel); - _selectedItemInfo = new NavigationBarSelectedTypeAndMember(null, null); - _selectedItemInfoTask = Task.FromResult(_selectedItemInfo); + var selectedItemInfo = new NavigationBarSelectedTypeAndMember(null, null); + _selectedItemInfoTask = Task.FromResult(selectedItemInfo); + + _lastModelAndSelectedInfo = (_lastCompletedModel, selectedItemInfo); } public void SetWorkspace(Workspace? newWorkspace) @@ -212,18 +207,6 @@ private void OnDropDownFocused(object? sender, EventArgs e) { AssertIsForeground(); - // Refresh the drop downs to their full information - _waitIndicator.Wait( - EditorFeaturesResources.Navigation_Bars, - EditorFeaturesResources.Refreshing_navigation_bars, - allowCancel: true, - action: context => UpdateDropDownsSynchronously(context.CancellationToken)); - } - - private void UpdateDropDownsSynchronously(CancellationToken cancellationToken) - { - AssertIsForeground(); - var document = _subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) return; @@ -232,13 +215,20 @@ private void UpdateDropDownsSynchronously(CancellationToken cancellationToken) // being able to open the dropdown list. GetProjectItems(out var projectItems, out var selectedProjectItem); + NavigationBarModel lastModel; + NavigationBarSelectedTypeAndMember selectedInfo; + lock (_gate) + { + lastModel = _lastModelAndSelectedInfo.model; + selectedInfo = _lastModelAndSelectedInfo.selectedInfo; + } + _presenter.PresentItems( projectItems, selectedProjectItem, - _lastCompletedModel.Types, - _selectedItemInfoTask.Result.TypeItem, - _selectedItemInfoTask.Result.MemberItem); - _versionStampOfFullListPushedToPresenter = _modelTask.Result.SemanticVersionStamp; + lastModel.Types, + selectedInfo.TypeItem, + selectedInfo.MemberItem); } private void GetProjectItems(out IList projectItems, out NavigationBarProjectItem? selectedProjectItem) @@ -305,7 +295,6 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel listOfLeft, newLeft, newRight); - _versionStampOfFullListPushedToPresenter = null; } private void OnItemSelected(object? sender, NavigationBarItemSelectedEventArgs e) From e04fa94f7f70046065cacd35b40dcbbf0fef91c9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 13:54:48 -0700 Subject: [PATCH 054/220] In progress --- .../NavigationBar/NavigationBarController.cs | 33 ++--- ...avigationBarController_ModelComputation.cs | 113 ++++++++++-------- 2 files changed, 77 insertions(+), 69 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index f22ccac8b8310..285e88378695d 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -29,6 +29,13 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar /// internal partial class NavigationBarController : ForegroundThreadAffinitizedObject, INavigationBarController { + private static readonly NavigationBarModel EmptyModel = new( + SpecializedCollections.EmptyList(), + semanticVersionStamp: default, + itemService: null); + + private static readonly NavigationBarSelectedTypeAndMember EmptySelectedInfo = new(typeItem: null, memberItem: null); + private readonly INavigationBarPresenter _presenter; private readonly ITextBuffer _subjectBuffer; private readonly IWaitIndicator _waitIndicator; @@ -59,16 +66,10 @@ public NavigationBarController( subjectBuffer.PostChanged += OnSubjectBufferPostChanged; // Initialize the tasks to be an empty model so we never have to deal with a null case. - _lastCompletedModel = new NavigationBarModel( - SpecializedCollections.EmptyList(), - semanticVersionStamp: default, - itemService: null); - _modelTask = Task.FromResult(_lastCompletedModel); - - var selectedItemInfo = new NavigationBarSelectedTypeAndMember(null, null); - _selectedItemInfoTask = Task.FromResult(selectedItemInfo); + _modelTask = Task.FromResult(EmptyModel); + _selectedItemInfoTask = Task.FromResult(EmptySelectedInfo); - _lastModelAndSelectedInfo = (_lastCompletedModel, selectedItemInfo); + _lastModelAndSelectedInfo = (EmptyModel, EmptySelectedInfo); } public void SetWorkspace(Workspace? newWorkspace) @@ -112,7 +113,7 @@ private void ConnectToWorkspace(Workspace workspace) void ConnectToNewWorkspace() { // For the first time you open the file, we'll start immediately - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); } } @@ -167,7 +168,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) if (currentContextDocumentId != null && currentContextDocumentId.ProjectId == args.ProjectId) { - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); } } } @@ -179,7 +180,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) if (currentContextDocumentId != null && currentContextDocumentId == args.DocumentId) { // The context has changed, so update everything. - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); } } } @@ -188,19 +189,19 @@ private void OnSubjectBufferPostChanged(object? sender, EventArgs e) { AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay, selectedItemUpdateDelay: 0); } private void OnCaretMoved(object? sender, EventArgs e) { AssertIsForeground(); - StartSelectedItemUpdateTask(delay: TaggerConstants.NearImmediateDelay, updateUIWhenDone: true); + StartSelectedItemUpdateTask(delay: TaggerConstants.NearImmediateDelay); } private void OnViewFocused(object? sender, EventArgs e) { AssertIsForeground(); - StartSelectedItemUpdateTask(delay: TaggerConstants.ShortDelay, updateUIWhenDone: true); + StartSelectedItemUpdateTask(delay: TaggerConstants.ShortDelay); } private void OnDropDownFocused(object? sender, EventArgs e) @@ -361,7 +362,7 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio // Now that the edit has been done, refresh to make sure everything is up-to-date. // Have to make sure we come back to the main thread for this. AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0, updateUIWhenDone: true); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 82f657f311eaf..6eeac03247467 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -25,7 +26,6 @@ internal partial class NavigationBarController /// The computation of the last model. /// private Task _modelTask; - private NavigationBarModel _lastCompletedModel; private CancellationTokenSource _modelTaskCancellationSource = new(); private readonly object _gate = new(); @@ -34,7 +34,7 @@ internal partial class NavigationBarController /// /// Starts a new task to compute the model based on the current text. /// - private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, int selectedItemUpdateDelay, bool updateUIWhenDone) + private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, int selectedItemUpdateDelay) { AssertIsForeground(); @@ -48,22 +48,35 @@ private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, in // Enqueue a new computation for the model var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartModelUpdateTask"); - _modelTask = - Task.Delay(modelUpdateDelay, cancellationToken) - .SafeContinueWithFromAsync( - _ => ComputeModelAsync(textSnapshot, cancellationToken), - cancellationToken, - TaskContinuationOptions.OnlyOnRanToCompletion, - TaskScheduler.Default); + _modelTask = ComputeModelAfterDelayAsync(_modelTask, textSnapshot, modelUpdateDelay, cancellationToken); _modelTask.CompletesAsyncOperation(asyncToken); - StartSelectedItemUpdateTask(selectedItemUpdateDelay, updateUIWhenDone); + StartSelectedItemUpdateTask(selectedItemUpdateDelay); + } + + private static async Task ComputeModelAfterDelayAsync( + Task modelTask, ITextSnapshot textSnapshot, int modelUpdateDelay, CancellationToken cancellationToken) + { + var previousModel = await modelTask.ConfigureAwait(false); + try + { + await Task.Delay(modelUpdateDelay, cancellationToken).ConfigureAwait(false); + return await ComputeModelAsync(previousModel, textSnapshot, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + // If we canceled, then just return along whatever we have computed so far. Note: this means the + // _modelTask task will never enter the canceled state. It always represents the last successfully + // computed model. + return previousModel; + } } /// /// Computes a model for the given snapshot. /// - private async Task ComputeModelAsync(ITextSnapshot snapshot, CancellationToken cancellationToken) + private static async Task ComputeModelAsync( + NavigationBarModel lastCompletedModel, ITextSnapshot snapshot, CancellationToken cancellationToken) { // When computing items just get the partial semantics workspace. This will ensure we can get data for this // file, and hopefully have enough loaded to get data for other files in the case of partial types. In the @@ -73,10 +86,7 @@ private async Task ComputeModelAsync(ITextSnapshot snapshot, // compilation data (like skeleton assemblies). var document = snapshot.AsText().GetDocumentWithFrozenPartialSemantics(cancellationToken); if (document == null) - { - _lastCompletedModel = null; - return _lastCompletedModel; - } + return null; // TODO: remove .FirstOrDefault() var languageService = document.GetLanguageService(); @@ -84,13 +94,13 @@ private async Task ComputeModelAsync(ITextSnapshot snapshot, { // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. // the model should be only updated here - if (_lastCompletedModel != null) + if (lastCompletedModel != null) { var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); - if (_lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(_lastCompletedModel, snapshot, cancellationToken)) + if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) { // it looks like we can re-use previous model - return _lastCompletedModel; + return lastCompletedModel; } } @@ -102,14 +112,12 @@ private async Task ComputeModelAsync(ITextSnapshot snapshot, items.Do(i => i.InitializeTrackingSpans(snapshot)); var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - _lastCompletedModel = new NavigationBarModel(items, version, languageService); - return _lastCompletedModel; + return new NavigationBarModel(items, version, languageService); } } } - _lastCompletedModel ??= new NavigationBarModel(SpecializedCollections.EmptyList(), new VersionStamp(), null); - return _lastCompletedModel; + return new NavigationBarModel(SpecializedCollections.EmptyList(), new VersionStamp(), null); } private Task _selectedItemInfoTask; @@ -118,53 +126,52 @@ private async Task ComputeModelAsync(ITextSnapshot snapshot, /// /// Starts a new task to compute what item should be selected. /// - private void StartSelectedItemUpdateTask(int delay, bool updateUIWhenDone) + private void StartSelectedItemUpdateTask(int delay) { AssertIsForeground(); - var currentView = _presenter.TryGetCurrentView(); - if (currentView == null) - { - return; - } - // Cancel off any existing work _selectedItemInfoTaskCancellationSource.Cancel(); _selectedItemInfoTaskCancellationSource = new CancellationTokenSource(); - var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; - var subjectBufferCaretPosition = currentView.GetCaretPoint(_subjectBuffer); + var currentView = _presenter.TryGetCurrentView(); + var subjectBufferCaretPosition = currentView?.GetCaretPoint(_subjectBuffer); if (!subjectBufferCaretPosition.HasValue) - { return; - } var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); - - // Enqueue a new computation for the selected item - _selectedItemInfoTask = _modelTask.ContinueWithAfterDelay( - t => t.IsCanceled ? new NavigationBarSelectedTypeAndMember(null, null) - : ComputeSelectedTypeAndMember(t.Result, subjectBufferCaretPosition.Value, cancellationToken), - cancellationToken, - delay, - TaskContinuationOptions.None, - TaskScheduler.Default); + _selectedItemInfoTask = DetermineSelectedItemInfoAsync( + _modelTask, _selectedItemInfoTask, delay, subjectBufferCaretPosition.Value, cancellationToken); _selectedItemInfoTask.CompletesAsyncOperation(asyncToken); + } - if (updateUIWhenDone) + private async Task DetermineSelectedItemInfoAsync( + Task lastModelTask, + Task lastSelectedItemTask, + int delay, + SnapshotPoint caretPosition, + CancellationToken cancellationToken) + { + var lastSelectedItem = await lastSelectedItemTask.ConfigureAwait(false); + var lastModel = await lastModelTask.ConfigureAwait(false); + try { - asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask.UpdateUI"); - _selectedItemInfoTask.SafeContinueWithFromAsync( - async t => - { - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); - PushSelectedItemsToPresenter(t.Result); - }, - cancellationToken, - TaskContinuationOptions.OnlyOnRanToCompletion | TaskContinuationOptions.ExecuteSynchronously, - TaskScheduler.Default).CompletesAsyncOperation(asyncToken); + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + + // Pushing is not cancellable. Once we have shown the items, we want our model to always represent this. + PushSelectedItemsToPresenter(currentSelectedItem); + return currentSelectedItem; + } + catch (OperationCanceledException) + { + // If we canceled, then just return along whatever we have computed so far. Note: this means the + // _selectedItemInfoTask will never enter the canceled state. It always represents the last + // successfully computed type/member. + return lastSelectedItem; } } From c4e02a5006ca6266ae721439dde6ef0c07073786 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 13:59:16 -0700 Subject: [PATCH 055/220] Make async --- ...avigationBarController_ModelComputation.cs | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 6eeac03247467..424ad47b69cdf 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -120,7 +120,7 @@ private static async Task ComputeModelAsync( return new NavigationBarModel(SpecializedCollections.EmptyList(), new VersionStamp(), null); } - private Task _selectedItemInfoTask; + private Task _selectedItemInfoTask; private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); /// @@ -130,48 +130,40 @@ private void StartSelectedItemUpdateTask(int delay) { AssertIsForeground(); - // Cancel off any existing work - _selectedItemInfoTaskCancellationSource.Cancel(); - _selectedItemInfoTaskCancellationSource = new CancellationTokenSource(); - var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; - var currentView = _presenter.TryGetCurrentView(); var subjectBufferCaretPosition = currentView?.GetCaretPoint(_subjectBuffer); if (!subjectBufferCaretPosition.HasValue) return; + // Cancel off any existing work + _selectedItemInfoTaskCancellationSource.Cancel(); + _selectedItemInfoTaskCancellationSource = new CancellationTokenSource(); + var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; + var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); _selectedItemInfoTask = DetermineSelectedItemInfoAsync( - _modelTask, _selectedItemInfoTask, delay, subjectBufferCaretPosition.Value, cancellationToken); + _modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); _selectedItemInfoTask.CompletesAsyncOperation(asyncToken); } - private async Task DetermineSelectedItemInfoAsync( + private async Task DetermineSelectedItemInfoAsync( Task lastModelTask, - Task lastSelectedItemTask, int delay, SnapshotPoint caretPosition, CancellationToken cancellationToken) { - var lastSelectedItem = await lastSelectedItemTask.ConfigureAwait(false); var lastModel = await lastModelTask.ConfigureAwait(false); - try - { - await Task.Delay(delay, cancellationToken).ConfigureAwait(false); - var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); + await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - // Pushing is not cancellable. Once we have shown the items, we want our model to always represent this. - PushSelectedItemsToPresenter(currentSelectedItem); - return currentSelectedItem; - } - catch (OperationCanceledException) + PushSelectedItemsToPresenter(currentSelectedItem); + + // Once we've pushed, update our state to reflect that + lock (_gate) { - // If we canceled, then just return along whatever we have computed so far. Note: this means the - // _selectedItemInfoTask will never enter the canceled state. It always represents the last - // successfully computed type/member. - return lastSelectedItem; + _lastModelAndSelectedInfo = (lastModel, currentSelectedItem); } } From ad0c4214b457e312bd9affae4ed862195ed2a101 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 14:12:01 -0700 Subject: [PATCH 056/220] Report errors --- .../Implementation/NavigationBar/NavigationBarController.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 285e88378695d..2246c0b55b23c 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -320,6 +321,9 @@ private async Task OnItemSelectedAsync(NavigationBarItem item) catch (OperationCanceledException) { } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } } /// From 58b5e9a9b9b2327807d7ac6ea5f3167089f23ee3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 14:15:15 -0700 Subject: [PATCH 057/220] docs --- .../NavigationBarController_ModelComputation.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 424ad47b69cdf..68158d062d05a 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -28,6 +28,11 @@ internal partial class NavigationBarController private Task _modelTask; private CancellationTokenSource _modelTaskCancellationSource = new(); + /// + /// Latest model and selected items produced once completes and presents the + /// single item to the view. These can then be read in when the dropdown is expanded and we want to show all + /// items. + /// private readonly object _gate = new(); private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastModelAndSelectedInfo; From 7813cc21b9fe0aa9f18a5e83d68e50686f70cf84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 14:16:51 -0700 Subject: [PATCH 058/220] Simplify --- .../NavigationBar/NavigationBarController_ModelComputation.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 68158d062d05a..dba96f06b27f1 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -146,8 +146,7 @@ private void StartSelectedItemUpdateTask(int delay) var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); - _selectedItemInfoTask = DetermineSelectedItemInfoAsync( - _modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); + _selectedItemInfoTask = DetermineSelectedItemInfoAsync(_modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); _selectedItemInfoTask.CompletesAsyncOperation(asyncToken); } From 37e7d4a7993c5ace87b2f3138d1cca5b1d7e630e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 14:20:24 -0700 Subject: [PATCH 059/220] REmove lock --- .../NavigationBar/NavigationBarController.cs | 11 ++--------- .../NavigationBarController_ModelComputation.cs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 2246c0b55b23c..e6616948e0fd9 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -70,7 +70,7 @@ public NavigationBarController( _modelTask = Task.FromResult(EmptyModel); _selectedItemInfoTask = Task.FromResult(EmptySelectedInfo); - _lastModelAndSelectedInfo = (EmptyModel, EmptySelectedInfo); + _lastModelAndSelectedInfo_OnlyAccessOnUIThread = (EmptyModel, EmptySelectedInfo); } public void SetWorkspace(Workspace? newWorkspace) @@ -217,14 +217,7 @@ private void OnDropDownFocused(object? sender, EventArgs e) // being able to open the dropdown list. GetProjectItems(out var projectItems, out var selectedProjectItem); - NavigationBarModel lastModel; - NavigationBarSelectedTypeAndMember selectedInfo; - lock (_gate) - { - lastModel = _lastModelAndSelectedInfo.model; - selectedInfo = _lastModelAndSelectedInfo.selectedInfo; - } - + var (lastModel, selectedInfo) = _lastModelAndSelectedInfo_OnlyAccessOnUIThread; _presenter.PresentItems( projectItems, selectedProjectItem, diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index dba96f06b27f1..98b77ba86ab4e 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -33,8 +33,7 @@ internal partial class NavigationBarController /// single item to the view. These can then be read in when the dropdown is expanded and we want to show all /// items. /// - private readonly object _gate = new(); - private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastModelAndSelectedInfo; + private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastModelAndSelectedInfo_OnlyAccessOnUIThread; /// /// Starts a new task to compute the model based on the current text. @@ -162,13 +161,14 @@ private async Task DetermineSelectedItemInfoAsync( await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); - PushSelectedItemsToPresenter(currentSelectedItem); + AssertIsForeground(); - // Once we've pushed, update our state to reflect that - lock (_gate) - { - _lastModelAndSelectedInfo = (lastModel, currentSelectedItem); - } + // Update the UI to show *just* the type/member that was selected. We don't need it to know about all items + // as the user can only see one at a time as they're editing in a document. However, once we've done this, + // store the full list of items as well so that if the user expands the dropdown, we can take all those + // values and shove them in so it appears as if the lists were always fully realized. + _lastModelAndSelectedInfo_OnlyAccessOnUIThread = (lastModel, currentSelectedItem); + PushSelectedItemsToPresenter(currentSelectedItem); } internal static NavigationBarSelectedTypeAndMember ComputeSelectedTypeAndMember(NavigationBarModel model, SnapshotPoint caretPosition, CancellationToken cancellationToken) From dd8bdafbeb426077103290a9b75567b6e1e217ce Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 15:06:54 -0700 Subject: [PATCH 060/220] Switch to immutable arrays --- .../NavigationBar/NavigationBarController.cs | 9 +++++---- .../NavigationBarController_ModelComputation.cs | 5 +++-- .../Implementation/NavigationBar/NavigationBarModel.cs | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index e6616948e0fd9..05788a757a987 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -31,7 +32,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar internal partial class NavigationBarController : ForegroundThreadAffinitizedObject, INavigationBarController { private static readonly NavigationBarModel EmptyModel = new( - SpecializedCollections.EmptyList(), + ImmutableArray.Empty, semanticVersionStamp: default, itemService: null); @@ -226,12 +227,12 @@ private void OnDropDownFocused(object? sender, EventArgs e) selectedInfo.MemberItem); } - private void GetProjectItems(out IList projectItems, out NavigationBarProjectItem? selectedProjectItem) + private void GetProjectItems(out ImmutableArray projectItems, out NavigationBarProjectItem? selectedProjectItem) { var documents = _subjectBuffer.CurrentSnapshot.GetRelatedDocumentsWithChanges(); if (!documents.Any()) { - projectItems = SpecializedCollections.EmptyList(); + projectItems = ImmutableArray.Empty; selectedProjectItem = null; return; } @@ -242,7 +243,7 @@ private void GetProjectItems(out IList projectItems, o d.Project.GetGlyph(), workspace: d.Project.Solution.Workspace, documentId: d.Id, - language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToList(); + language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToImmutableArray(); projectItems.Do(i => i.InitializeTrackingSpans(_subjectBuffer.CurrentSnapshot)); diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 98b77ba86ab4e..3c782a353c38d 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -116,12 +117,12 @@ private static async Task ComputeModelAsync( items.Do(i => i.InitializeTrackingSpans(snapshot)); var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - return new NavigationBarModel(items, version, languageService); + return new NavigationBarModel(items.ToImmutableArray(), version, languageService); } } } - return new NavigationBarModel(SpecializedCollections.EmptyList(), new VersionStamp(), null); + return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } private Task _selectedItemInfoTask; diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs index e7739b569b41b..3f27e7d0b54a9 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs @@ -5,13 +5,14 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar { internal sealed class NavigationBarModel { - public IList Types { get; } + public ImmutableArray Types { get; } /// /// The VersionStamp of the project when this model was computed. @@ -20,7 +21,7 @@ internal sealed class NavigationBarModel public INavigationBarItemService ItemService { get; } - public NavigationBarModel(IList types, VersionStamp semanticVersionStamp, INavigationBarItemService itemService) + public NavigationBarModel(ImmutableArray types, VersionStamp semanticVersionStamp, INavigationBarItemService itemService) { Contract.ThrowIfNull(types); From 65f66302263511249a08efff33b5a667ddb611d5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 15:51:58 -0700 Subject: [PATCH 061/220] Update tests to be ok with async processing. --- .../NavigationBar/INavigationBarPresenter.cs | 14 ++--- .../NavigationBar/NavigationBarProjectItem.cs | 32 +++++++--- .../NavigationBar/NavigationBarController.cs | 60 +++++++++++++++---- ...avigationBarController_ModelComputation.cs | 9 +-- .../NavigationBar/NavigationBarModel.cs | 3 - .../MockNavigationBarPresenter.vb | 12 ++-- .../NavigationBarPresenterTests.vb | 30 ++++++++-- .../NavigationBar/NavigationBarClient.cs | 5 +- 8 files changed, 116 insertions(+), 49 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs index 8312916d65c45..c6751ffa7d3c3 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarPresenter.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.CodeAnalysis.Editor @@ -15,11 +13,11 @@ internal interface INavigationBarPresenter void Disconnect(); void PresentItems( - IList projects, - NavigationBarProjectItem selectedProject, - IList typesWithMembers, - NavigationBarItem selectedType, - NavigationBarItem selectedMember); + ImmutableArray projects, + NavigationBarProjectItem? selectedProject, + ImmutableArray typesWithMembers, + NavigationBarItem? selectedType, + NavigationBarItem? selectedMember); ITextView TryGetCurrentView(); diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs index 2b6e64e37e7cd..12408c4c5ff92 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs @@ -2,17 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor { - internal sealed class NavigationBarProjectItem : NavigationBarItem + internal sealed class NavigationBarProjectItem : NavigationBarItem, IEquatable { - public DocumentId DocumentId { get; } public Workspace Workspace { get; } + public DocumentId DocumentId { get; } public string Language { get; } public NavigationBarProjectItem( @@ -20,11 +19,9 @@ public NavigationBarProjectItem( Glyph glyph, Workspace workspace, DocumentId documentId, - string language, - int indent = 0, - bool bolded = false, - bool grayed = false) - : base(text, glyph, SpecializedCollections.EmptyList(), /*childItems:*/ null, indent, bolded, grayed) + string language) + : base(text, glyph, SpecializedCollections.EmptyList(), + childItems: null, indent: 0, bolded: false, grayed: false) { this.Workspace = workspace; this.DocumentId = documentId; @@ -39,5 +36,22 @@ internal void SwitchToContext() this.Workspace.SetDocumentContext(DocumentId); } } + + public override bool Equals(object? obj) + => Equals(obj as NavigationBarProjectItem); + + public bool Equals(NavigationBarProjectItem? item) + => item is not null && + Text == item.Text && + Glyph == item.Glyph && + Workspace == item.Workspace && + DocumentId == item.DocumentId && + Language == item.Language; + + public override int GetHashCode() + => Hash.Combine(Text, + Hash.Combine((int)Glyph, + Hash.Combine(Workspace, + Hash.Combine(DocumentId, Language.GetHashCode())))); } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 05788a757a987..f14ff07767c86 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; @@ -32,9 +33,9 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar internal partial class NavigationBarController : ForegroundThreadAffinitizedObject, INavigationBarController { private static readonly NavigationBarModel EmptyModel = new( - ImmutableArray.Empty, - semanticVersionStamp: default, - itemService: null); + ImmutableArray.Empty, + semanticVersionStamp: default, + itemService: null!); private static readonly NavigationBarSelectedTypeAndMember EmptySelectedInfo = new(typeItem: null, memberItem: null); @@ -46,6 +47,19 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje private bool _disconnected = false; private Workspace? _workspace; + /// + /// Latest model and selected items produced once completes and presents the + /// single item to the view. These can then be read in when the dropdown is expanded and we want to show all + /// items. + /// + private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _latestModelAndSelectedInfo_OnlyAccessOnUIThread; + + /// + /// The last full information we have presented. If we end up wanting to present the same thing again, we can + /// just skip doing that as the UI will already know about this. + /// + private (ImmutableArray projectItems, NavigationBarProjectItem? selectedProjectItem, NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastPresentedInfo; + public NavigationBarController( IThreadingContext threadingContext, INavigationBarPresenter presenter, @@ -71,7 +85,7 @@ public NavigationBarController( _modelTask = Task.FromResult(EmptyModel); _selectedItemInfoTask = Task.FromResult(EmptySelectedInfo); - _lastModelAndSelectedInfo_OnlyAccessOnUIThread = (EmptyModel, EmptySelectedInfo); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread = (EmptyModel, EmptySelectedInfo); } public void SetWorkspace(Workspace? newWorkspace) @@ -94,6 +108,7 @@ private void ConnectToWorkspace(Workspace workspace) _workspace = workspace; _workspace.WorkspaceChanged += this.OnWorkspaceChanged; + _workspace.DocumentActiveContextChanged += this.OnDocumentActiveContextChanged; if (IsForeground()) { @@ -123,6 +138,7 @@ private void DisconnectFromWorkspace() { if (_workspace != null) { + _workspace.DocumentActiveContextChanged -= this.OnDocumentActiveContextChanged; _workspace.WorkspaceChanged -= this.OnWorkspaceChanged; _workspace = null; } @@ -187,6 +203,20 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) } } + private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs args) + { + if (args.Solution.Workspace != _workspace) + return; + + var currentContextDocumentId = _workspace.GetDocumentIdInCurrentContext(_subjectBuffer.AsTextContainer()); + if (args.NewActiveContextDocumentId == currentContextDocumentId || + args.OldActiveContextDocumentId == currentContextDocumentId) + { + // if the active context changed, recompute the types/member as they may be changed as well. + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); + } + } + private void OnSubjectBufferPostChanged(object? sender, EventArgs e) { AssertIsForeground(); @@ -214,17 +244,27 @@ private void OnDropDownFocused(object? sender, EventArgs e) if (document == null) return; - // Just present whatever information we have at this point. We don't want to block the user from - // being able to open the dropdown list. + // Grab and present whatever information we have at this point. GetProjectItems(out var projectItems, out var selectedProjectItem); + var (model, selectedInfo) = _latestModelAndSelectedInfo_OnlyAccessOnUIThread; + + if (Equals(model, _lastPresentedInfo.model) && + Equals(selectedInfo, _lastPresentedInfo.selectedInfo) && + Equals(selectedProjectItem, _lastPresentedInfo.selectedProjectItem) && + projectItems.SequenceEqual(_lastPresentedInfo.projectItems)) + { + // Nothing changed, so we can skip presenting these items. + return; + } - var (lastModel, selectedInfo) = _lastModelAndSelectedInfo_OnlyAccessOnUIThread; _presenter.PresentItems( projectItems, selectedProjectItem, - lastModel.Types, + model.Types, selectedInfo.TypeItem, selectedInfo.MemberItem); + + _lastPresentedInfo = (projectItems, selectedProjectItem, model, selectedInfo); } private void GetProjectItems(out ImmutableArray projectItems, out NavigationBarProjectItem? selectedProjectItem) @@ -262,7 +302,7 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel NavigationBarItem? newLeft = null; NavigationBarItem? newRight = null; - var listOfLeft = new List(); + using var _1 = ArrayBuilder.GetInstance(out var listOfLeft); var listOfRight = new List(); if (oldRight != null) @@ -288,7 +328,7 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel _presenter.PresentItems( projectItems, selectedProjectItem, - listOfLeft, + listOfLeft.ToImmutable(), newLeft, newRight); } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 3c782a353c38d..3d690058b1bf4 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -29,13 +29,6 @@ internal partial class NavigationBarController private Task _modelTask; private CancellationTokenSource _modelTaskCancellationSource = new(); - /// - /// Latest model and selected items produced once completes and presents the - /// single item to the view. These can then be read in when the dropdown is expanded and we want to show all - /// items. - /// - private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _lastModelAndSelectedInfo_OnlyAccessOnUIThread; - /// /// Starts a new task to compute the model based on the current text. /// @@ -168,7 +161,7 @@ private async Task DetermineSelectedItemInfoAsync( // as the user can only see one at a time as they're editing in a document. However, once we've done this, // store the full list of items as well so that if the user expands the dropdown, we can take all those // values and shove them in so it appears as if the lists were always fully realized. - _lastModelAndSelectedInfo_OnlyAccessOnUIThread = (lastModel, currentSelectedItem); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread = (lastModel, currentSelectedItem); PushSelectedItemsToPresenter(currentSelectedItem); } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs index 3f27e7d0b54a9..99faf5fad32cd 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarModel.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; using System.Collections.Immutable; using Roslyn.Utilities; diff --git a/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb b/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb index 237167c4a5cf3..160317af06e3a 100644 --- a/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb +++ b/src/EditorFeatures/Test2/NavigationBar/MockNavigationBarPresenter.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports Microsoft.VisualStudio.Text.Editor Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar @@ -35,11 +36,12 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar End Sub - Public Sub PresentItems(projects As IList(Of NavigationBarProjectItem), - selectedProject As NavigationBarProjectItem, - typesWithMembers As IList(Of NavigationBarItem), - selectedType As NavigationBarItem, - selectedMember As NavigationBarItem) Implements INavigationBarPresenter.PresentItems + Public Sub PresentItems( + projects As ImmutableArray(Of NavigationBarProjectItem), + selectedProject As NavigationBarProjectItem, + typesWithMembers As ImmutableArray(Of NavigationBarItem), + selectedType As NavigationBarItem, + selectedMember As NavigationBarItem) Implements INavigationBarPresenter.PresentItems If _presentItemsCallback IsNot Nothing Then _presentItemsCallback() End If diff --git a/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb b/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb index d5c649aac4aef..cfb2f43dd403a 100644 --- a/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb +++ b/src/EditorFeatures/Test2/NavigationBar/NavigationBarPresenterTests.vb @@ -75,7 +75,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar End Function - Public Sub TestNavigationBarInCSharpLinkedFiles() + Public Async Function TestNavigationBarInCSharpLinkedFiles() As Task Using workspace = TestWorkspace.Create( @@ -117,6 +117,14 @@ class C Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, baseDocument.GetTextBuffer()) + controller.SetWorkspace(workspace) + + Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider).Value + Dim workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) + Dim navbarWaiter = listenerProvider.GetWaiter(FeatureAttribute.NavigationBar) + + Await navbarWaiter.ExpeditedWaitAsync() + memberName = Nothing mockPresenter.RaiseDropDownFocused() Assert.Equal("M1(int x)", memberName) @@ -124,15 +132,18 @@ class C workspace.SetDocumentContext(linkDocument.Id) + Await workspaceWaiter.ExpeditedWaitAsync() + Await navbarWaiter.ExpeditedWaitAsync() + memberName = Nothing mockPresenter.RaiseDropDownFocused() Assert.Equal("M2(int x)", memberName) Assert.Equal(projectGlyph, Glyph.CSharpProject) End Using - End Sub + End Function - Public Sub TestNavigationBarInVisualBasicLinkedFiles() + Public Async Function TestNavigationBarInVisualBasicLinkedFiles() As Task Using workspace = TestWorkspace.Create( @@ -175,6 +186,14 @@ End Class Dim controllerFactory = workspace.GetService(Of INavigationBarControllerFactoryService)() Dim controller = controllerFactory.CreateController(mockPresenter, baseDocument.GetTextBuffer()) + controller.SetWorkspace(workspace) + + Dim listenerProvider = workspace.ExportProvider.GetExport(Of IAsynchronousOperationListenerProvider).Value + Dim workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace) + Dim navbarWaiter = listenerProvider.GetWaiter(FeatureAttribute.NavigationBar) + + Await navbarWaiter.ExpeditedWaitAsync() + memberNames = Nothing mockPresenter.RaiseDropDownFocused() Assert.Contains("M1", memberNames) @@ -183,13 +202,16 @@ End Class workspace.SetDocumentContext(linkDocument.Id) + Await workspaceWaiter.ExpeditedWaitAsync() + Await navbarWaiter.ExpeditedWaitAsync() + memberNames = Nothing mockPresenter.RaiseDropDownFocused() Assert.Contains("M2", memberNames) Assert.DoesNotContain("M1", memberNames) Assert.Equal(projectGlyph, Glyph.BasicProject) End Using - End Sub + End Function Public Sub TestProjectItemsAreSortedCSharp() diff --git a/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs b/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs index f70c5d55544d7..26ab727a9b1d5 100644 --- a/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.Internal.VisualStudio.Shell; @@ -321,9 +322,9 @@ void INavigationBarPresenter.Disconnect() } void INavigationBarPresenter.PresentItems( - IList projects, + ImmutableArray projects, NavigationBarProjectItem selectedProject, - IList types, + ImmutableArray types, NavigationBarItem selectedType, NavigationBarItem selectedMember) { From 4fe53463006f2734164cf2a1d8e71e80be529a40 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 16:04:06 -0700 Subject: [PATCH 062/220] Update SyntaxNode.cs --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index e0876b925338f..e0c1ed2154330 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -353,9 +353,15 @@ public bool IsEquivalentTo(SyntaxNode other) /// occurs when a is incrementally parsed using /// and the incremental parser is able to take the node from the original tree and use it in its entirety in the /// new tree. In this case, the of each node will be the same, though - /// will have different parents, and may occur at different positions in the respective trees. If two nodes are + /// will have different parents, and may occur at different positions in their respective trees. If two nodes are /// incrementally identical, all children of each node will be incrementally identical as well. /// + /// + /// Incrementally identical nodes can also appear within the same syntax tree, or syntax trees that did not arise + /// from . This can happen as the parser is allowed to construct parse + /// trees from shared nodes for efficiency. In all these cases though, it will still remain true that the incrementally + /// identical nodes will have different parents and nd may occur at different positions in their respective trees. + /// public bool IsIncrementallyIdenticalTo(SyntaxNode other) => this.Green == other.Green; From 6c23c357925d9df65423a169883ee8fcbd18c3d1 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 16:04:40 -0700 Subject: [PATCH 063/220] Update src/Compilers/Core/Portable/Syntax/SyntaxNode.cs --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index e0c1ed2154330..6f1a5d0e3abd2 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -363,7 +363,7 @@ public bool IsEquivalentTo(SyntaxNode other) /// identical nodes will have different parents and nd may occur at different positions in their respective trees. /// public bool IsIncrementallyIdenticalTo(SyntaxNode other) - => this.Green == other.Green; + => this.Green != null && this.Green == other.Green; /// /// Determines whether the node represents a language construct that was actually parsed From 2184758d0f5faa365abe7d39237e5f43e1744b2b Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 16:04:56 -0700 Subject: [PATCH 064/220] Update src/Compilers/Core/Portable/Syntax/SyntaxToken.cs --- src/Compilers/Core/Portable/Syntax/SyntaxToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs index a7b438babf8b5..134dc3db12752 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs @@ -696,6 +696,6 @@ public bool IsEquivalentTo(SyntaxToken token) /// incrementally identical, all trivial of each node will be incrementally identical as well. /// public bool IsIncrementallyIdenticalTo(SyntaxToken token) - => this.Node == token.Node; + => this.Node != null && this.Node == token.Node; } } From af8d17a9afdef7928e13b4a8521aecfc9486339e Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 16:05:40 -0700 Subject: [PATCH 065/220] Update src/Compilers/Core/Portable/Syntax/SyntaxToken.cs --- src/Compilers/Core/Portable/Syntax/SyntaxToken.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs index 134dc3db12752..95e84b4627d37 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs @@ -695,6 +695,12 @@ public bool IsEquivalentTo(SyntaxToken token) /// will have different parents, and may occur at different positions in the respective trees. If two tokens are /// incrementally identical, all trivial of each node will be incrementally identical as well. /// + /// + /// Incrementally identical tokens can also appear within the same syntax tree, or syntax trees that did not arise + /// from . This can happen as the parser is allowed to construct parse + /// trees using shared tokens for efficiency. In all these cases though, it will still remain true that the incrementally + /// identical tokens will have different parents and may occur at different positions in their respective trees. + /// public bool IsIncrementallyIdenticalTo(SyntaxToken token) => this.Node != null && this.Node == token.Node; } From fe1f2914be87a7896b9c0bb995942d10e4f728e0 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 16:06:15 -0700 Subject: [PATCH 066/220] Update src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs --- src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs index 63ae47b0f0c53..873229aae175f 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs @@ -763,7 +763,7 @@ public bool IsEquivalentTo(SyntaxNodeOrToken other) /// cref="SyntaxToken.IsIncrementallyIdenticalTo"/>. /// public bool IsIncrementallyIdenticalTo(SyntaxNodeOrToken other) - => this.UnderlyingNode == other.UnderlyingNode; + => this.UnderlingNode != null && this.UnderlyingNode == other.UnderlyingNode; /// /// Returns a new that wraps the supplied token. From 2fde2041ee170f76660ec2e3c7a406e10b69064b Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 16:08:12 -0700 Subject: [PATCH 067/220] Update src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs --- src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs index 873229aae175f..f7b9040f8027f 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs @@ -759,8 +759,7 @@ public bool IsEquivalentTo(SyntaxNodeOrToken other) } /// - /// See and . + /// See and . /// public bool IsIncrementallyIdenticalTo(SyntaxNodeOrToken other) => this.UnderlingNode != null && this.UnderlyingNode == other.UnderlyingNode; From 65e228766c7cf949bf3ab81ea9d451851eb75cbc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:07:57 -0700 Subject: [PATCH 068/220] Fix --- src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs index f7b9040f8027f..9b55b1f47556f 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNodeOrToken.cs @@ -762,7 +762,7 @@ public bool IsEquivalentTo(SyntaxNodeOrToken other) /// See and . /// public bool IsIncrementallyIdenticalTo(SyntaxNodeOrToken other) - => this.UnderlingNode != null && this.UnderlyingNode == other.UnderlyingNode; + => this.UnderlyingNode != null && this.UnderlyingNode == other.UnderlyingNode; /// /// Returns a new that wraps the supplied token. From 9b8474c2843b062709d3930e5f7a3048251c0cfb Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Fri, 9 Apr 2021 17:08:30 -0700 Subject: [PATCH 069/220] Update src/Compilers/Core/Portable/Syntax/SyntaxNode.cs --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 6f1a5d0e3abd2..da2ebb7117ac4 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -360,7 +360,7 @@ public bool IsEquivalentTo(SyntaxNode other) /// Incrementally identical nodes can also appear within the same syntax tree, or syntax trees that did not arise /// from . This can happen as the parser is allowed to construct parse /// trees from shared nodes for efficiency. In all these cases though, it will still remain true that the incrementally - /// identical nodes will have different parents and nd may occur at different positions in their respective trees. + /// identical nodes will have different parents and may occur at different positions in their respective trees. /// public bool IsIncrementallyIdenticalTo(SyntaxNode other) => this.Green != null && this.Green == other.Green; From 0f52f1340f6788fc599ccade9aa7ad339a5d7d56 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:15:47 -0700 Subject: [PATCH 070/220] Fix --- src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb index e0f8062619cc5..e62d8a5dcc361 100644 --- a/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb +++ b/src/EditorFeatures/Test2/NavigationBar/TestHelpers.vb @@ -2,6 +2,7 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. +Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar @@ -60,7 +61,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.NavigationBar items.Do(Sub(i) i.InitializeTrackingSpans(snapshot)) Dim hostDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) - Dim model As New NavigationBarModel(items, VersionStamp.Create(), service) + Dim model As New NavigationBarModel(items.ToImmutableArray(), VersionStamp.Create(), service) Dim selectedItems = NavigationBarController.ComputeSelectedTypeAndMember(model, New SnapshotPoint(hostDocument.GetTextBuffer().CurrentSnapshot, hostDocument.CursorPosition.Value), Nothing) Dim isCaseSensitive = document.GetLanguageService(Of ISyntaxFactsService)().IsCaseSensitive From 56a98c4310fc436a010d36f48fb01b4cc60786c6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:19:45 -0700 Subject: [PATCH 071/220] UPdate check --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 6f1a5d0e3abd2..7bb5f7c4e9f58 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -333,7 +333,7 @@ public SourceText GetText(Encoding? encoding = null, SourceHashAlgorithm checksu /// /// Determine whether this node is structurally equivalent to another. /// - public bool IsEquivalentTo(SyntaxNode other) + public bool IsEquivalentTo([NotNullWhen(true)] SyntaxNode other) { if (this == other) { @@ -360,10 +360,10 @@ public bool IsEquivalentTo(SyntaxNode other) /// Incrementally identical nodes can also appear within the same syntax tree, or syntax trees that did not arise /// from . This can happen as the parser is allowed to construct parse /// trees from shared nodes for efficiency. In all these cases though, it will still remain true that the incrementally - /// identical nodes will have different parents and nd may occur at different positions in their respective trees. + /// identical nodes will have different parents and may occur at different positions in their respective trees. /// - public bool IsIncrementallyIdenticalTo(SyntaxNode other) - => this.Green != null && this.Green == other.Green; + public bool IsIncrementallyIdenticalTo([NotNullWhen(true)] SyntaxNode? other) + => this.Green != null && this.Green == other?.Green; /// /// Determines whether the node represents a language construct that was actually parsed From 67669debf3c050d731e8bc2d553876d1777b47f8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:32:41 -0700 Subject: [PATCH 072/220] Fix api --- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index fefa6282977c2..7a1493948a97d 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -23,7 +23,7 @@ Microsoft.CodeAnalysis.Operations.OperationWalker.OperationWalker() - Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordClassName = 31 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind const Microsoft.CodeAnalysis.WellKnownDiagnosticTags.CompilationEnd = "CompilationEnd" -> string! Microsoft.CodeAnalysis.SyntaxContextReceiverCreator -Microsoft.CodeAnalysis.SyntaxNode.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode! other) -> bool +Microsoft.CodeAnalysis.SyntaxNode.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode? other) -> bool Microsoft.CodeAnalysis.SyntaxNodeOrToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNodeOrToken other) -> bool Microsoft.CodeAnalysis.SyntaxToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxToken token) -> bool override Microsoft.CodeAnalysis.Text.TextChangeRange.ToString() -> string! From 758168948f919be182eab9f09773eaddec5bf518 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:36:53 -0700 Subject: [PATCH 073/220] simplify --- ...avigationBarController_ModelComputation.cs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 3d690058b1bf4..4a47a6dddf808 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -56,18 +56,22 @@ private static async Task ComputeModelAfterDelayAsync( Task modelTask, ITextSnapshot textSnapshot, int modelUpdateDelay, CancellationToken cancellationToken) { var previousModel = await modelTask.ConfigureAwait(false); - try + if (!cancellationToken.IsCancellationRequested) { - await Task.Delay(modelUpdateDelay, cancellationToken).ConfigureAwait(false); - return await ComputeModelAsync(previousModel, textSnapshot, cancellationToken).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - // If we canceled, then just return along whatever we have computed so far. Note: this means the - // _modelTask task will never enter the canceled state. It always represents the last successfully - // computed model. - return previousModel; + try + { + await Task.Delay(modelUpdateDelay, cancellationToken).ConfigureAwait(false); + return await ComputeModelAsync(previousModel, textSnapshot, cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + } } + + // If we canceled, then just return along whatever we have computed so far. Note: this means the + // _modelTask task will never enter the canceled state. It always represents the last successfully + // computed model. + return previousModel; } /// @@ -118,7 +122,6 @@ private static async Task ComputeModelAsync( return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } - private Task _selectedItemInfoTask; private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); /// @@ -139,8 +142,8 @@ private void StartSelectedItemUpdateTask(int delay) var cancellationToken = _selectedItemInfoTaskCancellationSource.Token; var asyncToken = _asyncListener.BeginAsyncOperation(GetType().Name + ".StartSelectedItemUpdateTask"); - _selectedItemInfoTask = DetermineSelectedItemInfoAsync(_modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); - _selectedItemInfoTask.CompletesAsyncOperation(asyncToken); + var selectedItemInfoTask = DetermineSelectedItemInfoAsync(_modelTask, delay, subjectBufferCaretPosition.Value, cancellationToken); + selectedItemInfoTask.CompletesAsyncOperation(asyncToken); } private async Task DetermineSelectedItemInfoAsync( @@ -150,6 +153,9 @@ private async Task DetermineSelectedItemInfoAsync( CancellationToken cancellationToken) { var lastModel = await lastModelTask.ConfigureAwait(false); + if (cancellationToken.IsCancellationRequested) + return; + await Task.Delay(delay, cancellationToken).ConfigureAwait(false); var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); From 7169bc094187b5b2745d94e4d46a10c4b25db7da Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:37:29 -0700 Subject: [PATCH 074/220] Wrap --- .../Extensibility/NavigationBar/NavigationBarProjectItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs index 12408c4c5ff92..ab6b69c3523e3 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs @@ -52,6 +52,7 @@ public override int GetHashCode() => Hash.Combine(Text, Hash.Combine((int)Glyph, Hash.Combine(Workspace, - Hash.Combine(DocumentId, Language.GetHashCode())))); + Hash.Combine(DocumentId, + Language.GetHashCode())))); } } From 22ec67a79bf96d22f9c975da8d0928b7381c9319 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:41:15 -0700 Subject: [PATCH 075/220] Simplify --- .../NavigationBar/NavigationBarController.cs | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index f14ff07767c86..6bf22d24f48ae 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -32,13 +32,6 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.NavigationBar /// internal partial class NavigationBarController : ForegroundThreadAffinitizedObject, INavigationBarController { - private static readonly NavigationBarModel EmptyModel = new( - ImmutableArray.Empty, - semanticVersionStamp: default, - itemService: null!); - - private static readonly NavigationBarSelectedTypeAndMember EmptySelectedInfo = new(typeItem: null, memberItem: null); - private readonly INavigationBarPresenter _presenter; private readonly ITextBuffer _subjectBuffer; private readonly IWaitIndicator _waitIndicator; @@ -48,9 +41,9 @@ internal partial class NavigationBarController : ForegroundThreadAffinitizedObje private Workspace? _workspace; /// - /// Latest model and selected items produced once completes and presents the - /// single item to the view. These can then be read in when the dropdown is expanded and we want to show all - /// items. + /// Latest model and selected items produced once completes and + /// presents the single item to the view. These can then be read in when the dropdown is expanded and we want + /// to show all items. /// private (NavigationBarModel model, NavigationBarSelectedTypeAndMember selectedInfo) _latestModelAndSelectedInfo_OnlyAccessOnUIThread; @@ -82,10 +75,13 @@ public NavigationBarController( subjectBuffer.PostChanged += OnSubjectBufferPostChanged; // Initialize the tasks to be an empty model so we never have to deal with a null case. - _modelTask = Task.FromResult(EmptyModel); - _selectedItemInfoTask = Task.FromResult(EmptySelectedInfo); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread.model = new( + ImmutableArray.Empty, + semanticVersionStamp: default, + itemService: null!); + _latestModelAndSelectedInfo_OnlyAccessOnUIThread.selectedInfo = new(typeItem: null, memberItem: null); - _latestModelAndSelectedInfo_OnlyAccessOnUIThread = (EmptyModel, EmptySelectedInfo); + _modelTask = Task.FromResult(_latestModelAndSelectedInfo_OnlyAccessOnUIThread.model); } public void SetWorkspace(Workspace? newWorkspace) From 30f9301113234872272394ffdcd7d62f3c019e10 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:48:37 -0700 Subject: [PATCH 076/220] Reorder --- .../NavigationBarController_ModelComputation.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 4a47a6dddf808..e61c72507af71 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -152,11 +152,14 @@ private async Task DetermineSelectedItemInfoAsync( SnapshotPoint caretPosition, CancellationToken cancellationToken) { + // First wait the delay before doing any other work. That way if we get canceled due to other events (like + // the user moving around), we don't end up doing anything, and the next task can take over. + await Task.Delay(delay, cancellationToken).ConfigureAwait(false); + var lastModel = await lastModelTask.ConfigureAwait(false); if (cancellationToken.IsCancellationRequested) return; - await Task.Delay(delay, cancellationToken).ConfigureAwait(false); var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); From 0881afeb4a158ee6978fba421cfcfff3fca7bafd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 17:51:06 -0700 Subject: [PATCH 077/220] simplify --- .../NavigationBar/NavigationBarController_ModelComputation.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index e61c72507af71..269f3715b749c 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -27,7 +27,9 @@ internal partial class NavigationBarController /// The computation of the last model. /// private Task _modelTask; + private CancellationTokenSource _modelTaskCancellationSource = new(); + private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); /// /// Starts a new task to compute the model based on the current text. @@ -122,8 +124,6 @@ private static async Task ComputeModelAsync( return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } - private CancellationTokenSource _selectedItemInfoTaskCancellationSource = new(); - /// /// Starts a new task to compute what item should be selected. /// From 98ed9451430417e50727a0759852550da8917867 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 23:30:23 -0700 Subject: [PATCH 078/220] Remove unused parameter --- .../NavigationBar/NavigationBarController.cs | 12 ++++++------ .../NavigationBarController_ModelComputation.cs | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 6bf22d24f48ae..120c1a6a011e4 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -126,7 +126,7 @@ private void ConnectToWorkspace(Workspace workspace) void ConnectToNewWorkspace() { // For the first time you open the file, we'll start immediately - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } @@ -182,7 +182,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) if (currentContextDocumentId != null && currentContextDocumentId.ProjectId == args.ProjectId) { - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } } @@ -194,7 +194,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) if (currentContextDocumentId != null && currentContextDocumentId == args.DocumentId) { // The context has changed, so update everything. - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } } @@ -209,7 +209,7 @@ private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContex args.OldActiveContextDocumentId == currentContextDocumentId) { // if the active context changed, recompute the types/member as they may be changed as well. - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } @@ -217,7 +217,7 @@ private void OnSubjectBufferPostChanged(object? sender, EventArgs e) { AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay, selectedItemUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay); } private void OnCaretMoved(object? sender, EventArgs e) @@ -396,7 +396,7 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio // Now that the edit has been done, refresh to make sure everything is up-to-date. // Have to make sure we come back to the main thread for this. AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0, selectedItemUpdateDelay: 0); + StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 269f3715b749c..0bbbeb717cd87 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -34,7 +34,7 @@ internal partial class NavigationBarController /// /// Starts a new task to compute the model based on the current text. /// - private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, int selectedItemUpdateDelay) + private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay) { AssertIsForeground(); @@ -51,7 +51,7 @@ private void StartModelUpdateAndSelectedItemUpdateTasks(int modelUpdateDelay, in _modelTask = ComputeModelAfterDelayAsync(_modelTask, textSnapshot, modelUpdateDelay, cancellationToken); _modelTask.CompletesAsyncOperation(asyncToken); - StartSelectedItemUpdateTask(selectedItemUpdateDelay); + StartSelectedItemUpdateTask(delay: 0); } private static async Task ComputeModelAfterDelayAsync( From ad058b0815881d9fc72d5f941c369a9a68af4b54 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 23:39:32 -0700 Subject: [PATCH 079/220] doc --- ...avigationBarController_ModelComputation.cs | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 0bbbeb717cd87..80344a8c18c3e 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -94,34 +94,32 @@ private static async Task ComputeModelAsync( // TODO: remove .FirstOrDefault() var languageService = document.GetLanguageService(); - if (languageService != null) + if (languageService == null) + return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); + + // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. + // the model should be only updated here + if (lastCompletedModel != null) { - // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. - // the model should be only updated here - if (lastCompletedModel != null) + var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); + if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) { - var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); - if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) - { - // it looks like we can re-use previous model - return lastCompletedModel; - } + // it looks like we can re-use previous model + return lastCompletedModel; } + } - using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) + using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) + { + var items = await languageService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); + if (items != null) { - var items = await languageService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); - if (items != null) - { - items.Do(i => i.InitializeTrackingSpans(snapshot)); - var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + items.Do(i => i.InitializeTrackingSpans(snapshot)); + var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - return new NavigationBarModel(items.ToImmutableArray(), version, languageService); - } + return new NavigationBarModel(items.ToImmutableArray(), version, languageService); } } - - return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } /// @@ -164,6 +162,11 @@ private async Task DetermineSelectedItemInfoAsync( await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + // After we switch to the main thread, we may discover the previous work on the main thread canceled us and + // enqueued another task to determine the selected item. Bail out and let that task proceed. + if (cancellationToken.IsCancellationRequested) + return; + AssertIsForeground(); // Update the UI to show *just* the type/member that was selected. We don't need it to know about all items From 63b2961367d32636b3d8cc12edb125ccac813cdd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 23:40:32 -0700 Subject: [PATCH 080/220] no need to yield, we're not on the Ui thread here. --- .../NavigationBar/NavigationBarController_ModelComputation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 80344a8c18c3e..925cc81344fd0 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -160,7 +160,7 @@ private async Task DetermineSelectedItemInfoAsync( var currentSelectedItem = ComputeSelectedTypeAndMember(lastModel, caretPosition, cancellationToken); - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); + await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); // After we switch to the main thread, we may discover the previous work on the main thread canceled us and // enqueued another task to determine the selected item. Bail out and let that task proceed. From afda8333017d3fd8497f7ce66075523a1dd73932 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 23:42:58 -0700 Subject: [PATCH 081/220] Simplify --- .../Core/Implementation/NavigationBar/NavigationBarController.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 120c1a6a011e4..414b005221622 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -216,7 +216,6 @@ private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContex private void OnSubjectBufferPostChanged(object? sender, EventArgs e) { AssertIsForeground(); - StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: TaggerConstants.MediumDelay); } From b51524be68b5b6a76ad147d7cf3b58beffad2ad9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 23:52:45 -0700 Subject: [PATCH 082/220] Revert change to support typescript --- .../AbstractEditorNavigationBarItemService.cs | 10 +++-- .../INavigationBarItemService.cs | 9 +++++ .../NavigationBar/NavigationBarController.cs | 13 +++++-- ...avigationBarController_ModelComputation.cs | 38 ++++++++++--------- .../Editor/FSharpNavigationBarItemService.cs | 3 ++ 5 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index 746eee9319099..4252c13ec9161 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -15,18 +16,19 @@ namespace Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar { - internal abstract class AbstractEditorNavigationBarItemService : INavigationBarItemService + internal abstract class AbstractEditorNavigationBarItemService : INavigationBarItemService2 { protected readonly IThreadingContext ThreadingContext; protected AbstractEditorNavigationBarItemService(IThreadingContext threadingContext) - { - ThreadingContext = threadingContext; - } + => ThreadingContext = threadingContext; protected abstract Task GetSymbolNavigationPointAsync(Document document, ISymbol symbol, CancellationToken cancellationToken); protected abstract Task NavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken); + public void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) + => throw new NotSupportedException($"Caller should call {nameof(NavigateToItemAsync)} instead"); + public async Task> GetItemsAsync(Document document, CancellationToken cancellationToken) { var service = document.GetRequiredLanguageService(); diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs index 6ebbcddd874b6..5007f314b80d6 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs @@ -14,6 +14,15 @@ internal interface INavigationBarItemService : ILanguageService { Task> GetItemsAsync(Document document, CancellationToken cancellationToken); bool ShowItemGrayedIfNear(NavigationBarItem item); + + /// + /// Legacy api for TypeScript. Needed until we can move them to EA pattern for navbars. + /// + void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken); + } + + internal interface INavigationBarItemService2 : INavigationBarItemService + { Task NavigateToItemAsync(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken); } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 414b005221622..0c1fbb91cae25 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -382,13 +382,20 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio var document = _subjectBuffer.CurrentSnapshot.AsText().GetDocumentWithFrozenPartialSemantics(cancellationToken); if (document != null) { - var languageService = document.GetRequiredLanguageService(); + var navBarService = document.GetRequiredLanguageService(); var snapshot = _subjectBuffer.CurrentSnapshot; item.Spans = item.TrackingSpans.Select(ts => ts.GetSpan(snapshot).Span.ToTextSpan()).ToList(); var view = _presenter.TryGetCurrentView(); - // ConfigureAwait(true) as we have to come back to UI thread in order to kick of the refresh task below. - await languageService.NavigateToItemAsync(document, item, view, cancellationToken).ConfigureAwait(true); + if (navBarService is INavigationBarItemService2 navBarService2) + { + // ConfigureAwait(true) as we have to come back to UI thread in order to kick of the refresh task below. + await navBarService2.NavigateToItemAsync(document, item, view, cancellationToken).ConfigureAwait(true); + } + else + { + navBarService.NavigateToItem(document, item, view, cancellationToken); + } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 925cc81344fd0..713ec9a4fd450 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -94,32 +94,34 @@ private static async Task ComputeModelAsync( // TODO: remove .FirstOrDefault() var languageService = document.GetLanguageService(); - if (languageService == null) - return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); - - // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. - // the model should be only updated here - if (lastCompletedModel != null) + if (languageService != null) { - var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); - if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) + // check whether we can re-use lastCompletedModel. otherwise, update lastCompletedModel here. + // the model should be only updated here + if (lastCompletedModel != null) { - // it looks like we can re-use previous model - return lastCompletedModel; + var semanticVersion = await document.Project.GetDependentSemanticVersionAsync(CancellationToken.None).ConfigureAwait(false); + if (lastCompletedModel.SemanticVersionStamp == semanticVersion && SpanStillValid(lastCompletedModel, snapshot, cancellationToken)) + { + // it looks like we can re-use previous model + return lastCompletedModel; + } } - } - using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) - { - var items = await languageService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); - if (items != null) + using (Logger.LogBlock(FunctionId.NavigationBar_ComputeModelAsync, cancellationToken)) { - items.Do(i => i.InitializeTrackingSpans(snapshot)); - var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); + var items = await languageService.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); + if (items != null) + { + items.Do(i => i.InitializeTrackingSpans(snapshot)); + var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false); - return new NavigationBarModel(items.ToImmutableArray(), version, languageService); + return new NavigationBarModel(items.ToImmutableArray(), version, languageService); + } } } + + return new NavigationBarModel(ImmutableArray.Empty, new VersionStamp(), null); } /// diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index 4ff5dad3f3a91..82b09e98b9781 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -44,6 +44,9 @@ public async Task> GetItemsAsync(Document document, Can return items?.Select(x => ConvertToNavigationBarItem(x)).ToList(); } + public void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) + => throw new NotSupportedException($"Caller should call {nameof(NavigateToItemAsync)} instead"); + public async Task NavigateToItemAsync(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) { // The logic here was ported from FSharp's implementation. The main reason was to avoid shimming INotificationService. From 7e353aeeddbf15e6f276477f02b348361d7758bb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 9 Apr 2021 23:56:11 -0700 Subject: [PATCH 083/220] Use immutable arrays --- .../NavigationBar/NavigationBarItem.cs | 24 +++++++------------ .../NavigationBar/NavigationBarController.cs | 2 +- .../Editor/FSharpNavigationBarItemService.cs | 2 +- .../NavigationBar/NavigationBarClient.cs | 2 +- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs index 5c615e521bc2c..d6087c13325b4 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; -using System.Linq; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; @@ -20,24 +18,24 @@ internal abstract class NavigationBarItem public bool Bolded { get; } public bool Grayed { get; } public int Indent { get; } - public IList ChildItems { get; } + public ImmutableArray ChildItems { get; } - public IList Spans { get; internal set; } - internal IList TrackingSpans { get; set; } + public ImmutableArray Spans { get; internal set; } + internal ImmutableArray TrackingSpans { get; set; } = ImmutableArray.Empty; public NavigationBarItem( string text, Glyph glyph, IList spans, - IList childItems = null, + IList? childItems = null, int indent = 0, bool bolded = false, bool grayed = false) { this.Text = text; this.Glyph = glyph; - this.Spans = spans; - this.ChildItems = childItems ?? SpecializedCollections.EmptyList(); + this.Spans = spans.ToImmutableArray(); + this.ChildItems = childItems.ToImmutableArrayOrEmpty(); this.Indent = indent; this.Bolded = bolded; this.Grayed = grayed; @@ -45,12 +43,8 @@ public NavigationBarItem( internal void InitializeTrackingSpans(ITextSnapshot textSnapshot) { - this.TrackingSpans = this.Spans.Select(s => textSnapshot.CreateTrackingSpan(s.ToSpan(), SpanTrackingMode.EdgeExclusive)).ToList(); - - if (this.ChildItems != null) - { - this.ChildItems.Do(i => i.InitializeTrackingSpans(textSnapshot)); - } + this.TrackingSpans = this.Spans.SelectAsArray(s => textSnapshot.CreateTrackingSpan(s.ToSpan(), SpanTrackingMode.EdgeExclusive)); + this.ChildItems.Do(i => i.InitializeTrackingSpans(textSnapshot)); } } } diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 0c1fbb91cae25..fc8dec49cc55e 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -384,7 +384,7 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio { var navBarService = document.GetRequiredLanguageService(); var snapshot = _subjectBuffer.CurrentSnapshot; - item.Spans = item.TrackingSpans.Select(ts => ts.GetSpan(snapshot).Span.ToTextSpan()).ToList(); + item.Spans = item.TrackingSpans.SelectAsArray(ts => ts.GetSpan(snapshot).Span.ToTextSpan()); var view = _presenter.TryGetCurrentView(); if (navBarService is INavigationBarItemService2 navBarService2) diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index 82b09e98b9781..bf2eceb777170 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -50,7 +50,7 @@ public void NavigateToItem(Document document, NavigationBarItem item, ITextView public async Task NavigateToItemAsync(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) { // The logic here was ported from FSharp's implementation. The main reason was to avoid shimming INotificationService. - if (item.Spans.Count > 0) + if (!item.Spans.IsEmpty) { var span = item.Spans.First(); var workspace = document.Project.Solution.Workspace; diff --git a/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs b/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs index 26ab727a9b1d5..49ac0e51e673d 100644 --- a/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/NavigationBar/NavigationBarClient.cs @@ -128,7 +128,7 @@ int IVsDropdownBarClient.GetComboAttributes(int iCombo, out uint pcEntries, out var currentTypeItem = GetCurrentTypeItem(); pcEntries = currentTypeItem != null - ? (uint)currentTypeItem.ChildItems.Count + ? (uint)currentTypeItem.ChildItems.Length : 0; break; From 81bbc3ba8c5f122f5d49864111a356deb35312d6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 10 Apr 2021 00:06:35 -0700 Subject: [PATCH 084/220] Use immutable arrays --- .../AbstractEditorNavigationBarItemService.cs | 2 +- .../INavigationBarItemService.cs | 2 +- .../NavigationBar/NavigationBarItem.cs | 14 +++-- .../NavigationBarPresentedItem.cs | 5 +- .../NavigationBar/NavigationBarProjectItem.cs | 3 +- .../NavigationBar/WrappedNavigationBarItem.cs | 3 +- .../NavigationBar/NavigationBarController.cs | 6 +-- .../Handler/Symbols/DocumentSymbolsHandler.cs | 4 +- .../Editor/FSharpNavigationBarItemService.cs | 51 +++++++++---------- 9 files changed, 44 insertions(+), 46 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index 4252c13ec9161..f23ac8b52ba18 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -29,7 +29,7 @@ protected AbstractEditorNavigationBarItemService(IThreadingContext threadingCont public void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) => throw new NotSupportedException($"Caller should call {nameof(NavigateToItemAsync)} instead"); - public async Task> GetItemsAsync(Document document, CancellationToken cancellationToken) + public async Task?> GetItemsAsync(Document document, CancellationToken cancellationToken) { var service = document.GetRequiredLanguageService(); var workspaceSupportsDocumentChanges = document.Project.Solution.Workspace.CanApplyChange(ApplyChangesKind.ChangeDocument); diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs index 5007f314b80d6..a80f5cd1986ec 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/INavigationBarItemService.cs @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Editor { internal interface INavigationBarItemService : ILanguageService { - Task> GetItemsAsync(Document document, CancellationToken cancellationToken); + Task?> GetItemsAsync(Document document, CancellationToken cancellationToken); bool ShowItemGrayedIfNear(NavigationBarItem item); /// diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs index d6087c13325b4..a520c14ab7d85 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs @@ -23,19 +23,25 @@ internal abstract class NavigationBarItem public ImmutableArray Spans { get; internal set; } internal ImmutableArray TrackingSpans { get; set; } = ImmutableArray.Empty; + // Legacy constructor for TypeScript. + public NavigationBarItem(string text, Glyph glyph, IList spans, IList? childItems = null, int indent = 0, bool bolded = false, bool grayed = false) + : this(text, glyph, spans.ToImmutableArrayOrEmpty(), childItems.ToImmutableArrayOrEmpty(), indent, bolded, grayed) + { + } + public NavigationBarItem( string text, Glyph glyph, - IList spans, - IList? childItems = null, + ImmutableArray spans, + ImmutableArray childItems = default, int indent = 0, bool bolded = false, bool grayed = false) { this.Text = text; this.Glyph = glyph; - this.Spans = spans.ToImmutableArray(); - this.ChildItems = childItems.ToImmutableArrayOrEmpty(); + this.Spans = spans; + this.ChildItems = childItems.NullToEmpty(); this.Indent = indent; this.Bolded = bolded; this.Grayed = grayed; diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs index ba1440dffd135..db0b86b37f7c0 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Editor @@ -18,8 +19,8 @@ internal class NavigationBarPresentedItem : NavigationBarItem public NavigationBarPresentedItem( string text, Glyph glyph, - IList spans, - IList childItems = null, + ImmutableArray spans, + ImmutableArray childItems = default, bool bolded = false, bool grayed = false) : base( diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs index ab6b69c3523e3..d52ab3f230aed 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -20,7 +21,7 @@ public NavigationBarProjectItem( Workspace workspace, DocumentId documentId, string language) - : base(text, glyph, SpecializedCollections.EmptyList(), + : base(text, glyph, ImmutableArray.Empty, childItems: null, indent: 0, bolded: false, grayed: false) { this.Workspace = workspace; diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs index fe06e8bdf9c93..70eee1fbfd9c7 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/WrappedNavigationBarItem.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; using Microsoft.CodeAnalysis.NavigationBar; namespace Microsoft.CodeAnalysis.Editor @@ -19,7 +18,7 @@ internal WrappedNavigationBarItem(RoslynNavigationBarItem underlyingItem) underlyingItem.Text, underlyingItem.Glyph, underlyingItem.Spans, - underlyingItem.ChildItems.Select(v => new WrappedNavigationBarItem(v)).ToList(), + underlyingItem.ChildItems.SelectAsArray(v => (NavigationBarItem)new WrappedNavigationBarItem(v)), underlyingItem.Indent, underlyingItem.Bolded, underlyingItem.Grayed) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index fc8dec49cc55e..5ac1041669882 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -280,8 +280,6 @@ private void GetProjectItems(out ImmutableArray projec documentId: d.Id, language: d.Project.Language)).OrderBy(projectItem => projectItem.Text).ToImmutableArray(); - projectItems.Do(i => i.InitializeTrackingSpans(_subjectBuffer.CurrentSnapshot)); - var document = _subjectBuffer.AsTextContainer().GetOpenDocumentInCurrentContext(); selectedProjectItem = document != null ? projectItems.FirstOrDefault(p => p.Text == document.Project.Name) ?? projectItems.First() @@ -298,7 +296,7 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel NavigationBarItem? newLeft = null; NavigationBarItem? newRight = null; using var _1 = ArrayBuilder.GetInstance(out var listOfLeft); - var listOfRight = new List(); + using var _2 = ArrayBuilder.GetInstance(out var listOfRight); if (oldRight != null) { @@ -311,7 +309,7 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel if (oldLeft != null) { - newLeft = new NavigationBarPresentedItem(oldLeft.Text, oldLeft.Glyph, oldLeft.Spans, listOfRight, oldLeft.Bolded, oldLeft.Grayed || selectedItems.ShowTypeItemGrayed) + newLeft = new NavigationBarPresentedItem(oldLeft.Text, oldLeft.Glyph, oldLeft.Spans, listOfRight.ToImmutable(), oldLeft.Bolded, oldLeft.Grayed || selectedItems.ShowTypeItemGrayed) { TrackingSpans = oldLeft.TrackingSpans }; diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs index 58c306aa4f4d0..adba3aff1454c 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs @@ -96,10 +96,8 @@ public override async Task HandleRequestAsync(DocumentSymbolParams req private static SymbolInformation GetSymbolInformation(NavigationBarItem item, Compilation compilation, SyntaxTree tree, Document document, SourceText text, CancellationToken cancellationToken, string containerName = null) { - if (item.Spans.Count == 0) - { + if (item.Spans.IsEmpty) return null; - } var location = GetLocation(item, compilation, tree, cancellationToken); diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index bf2eceb777170..ac10bacb1a310 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -2,22 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Linq; -using System.Composition; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Notification; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor { @@ -38,7 +38,7 @@ public FSharpNavigationBarItemService( _service = service; } - public async Task> GetItemsAsync(Document document, CancellationToken cancellationToken) + public async Task?> GetItemsAsync(Document document, CancellationToken cancellationToken) { var items = await _service.GetItemsAsync(document, cancellationToken).ConfigureAwait(false); return items?.Select(x => ConvertToNavigationBarItem(x)).ToList(); @@ -54,7 +54,7 @@ public async Task NavigateToItemAsync(Document document, NavigationBarItem item, { var span = item.Spans.First(); var workspace = document.Project.Solution.Workspace; - var navigationService = workspace.Services.GetService(); + var navigationService = workspace.Services.GetRequiredService(); await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); @@ -64,7 +64,7 @@ public async Task NavigateToItemAsync(Document document, NavigationBarItem item, } else { - var notificationService = workspace.Services.GetService(); + var notificationService = workspace.Services.GetRequiredService(); notificationService.SendNotification(EditorFeaturesResources.The_definition_of_the_object_is_hidden, severity: NotificationSeverity.Error); } } @@ -77,27 +77,22 @@ public bool ShowItemGrayedIfNear(NavigationBarItem item) private static NavigationBarItem ConvertToNavigationBarItem(FSharpNavigationBarItem item) { - return - new InternalNavigationBarItem( - item.Text, - FSharpGlyphHelpers.ConvertTo(item.Glyph), - item.Spans, - item.ChildItems?.Select(x => ConvertToNavigationBarItem(x)).ToList(), - item.Indent, - item.Bolded, - item.Grayed); + var childItems = item.ChildItems ?? SpecializedCollections.EmptyList(); + + return new InternalNavigationBarItem( + item.Text, + FSharpGlyphHelpers.ConvertTo(item.Glyph), + item.Spans.ToImmutableArrayOrEmpty(), + childItems.SelectAsArray(x => ConvertToNavigationBarItem(x)), + item.Indent, + item.Bolded, + item.Grayed); } private class InternalNavigationBarItem : NavigationBarItem { - public InternalNavigationBarItem( - string text, - Glyph glyph, - IList spans, - IList childItems, - int indent, - bool bolded, - bool grayed) : base(text, glyph, spans, childItems, indent, bolded, grayed) + public InternalNavigationBarItem(string text, Glyph glyph, ImmutableArray spans, ImmutableArray childItems, int indent, bool bolded, bool grayed) + : base(text, glyph, spans, childItems, indent, bolded, grayed) { } } From e9315fa31e2126e936a1886e3516b2a3f9d58f71 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 10 Apr 2021 00:10:31 -0700 Subject: [PATCH 085/220] nrt --- .../CSharpEditorNavigationBarItemService.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs b/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs index ad0c61989d753..63e8ea0c06471 100644 --- a/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/CSharp/NavigationBar/CSharpEditorNavigationBarItemService.cs @@ -11,7 +11,9 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.NavigationBar; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.CSharp.NavigationBar { @@ -28,16 +30,18 @@ public CSharpEditorNavigationBarItemService(IThreadingContext threadingContext) protected override async Task GetSymbolNavigationPointAsync( Document document, ISymbol symbol, CancellationToken cancellationToken) { - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var location = symbol.Locations.FirstOrDefault(l => l.SourceTree!.Equals(syntaxTree)); - - if (location == null) - location = symbol.Locations.FirstOrDefault(); + var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var location = + symbol.Locations.FirstOrDefault(l => Equals(l.SourceTree, syntaxTree)) ?? + symbol.Locations.FirstOrDefault(l => l.SourceTree != null); if (location == null) return null; - return new VirtualTreePoint(location.SourceTree!, location.SourceTree!.GetText(cancellationToken), location.SourceSpan.Start); + var tree = location.SourceTree; + Contract.ThrowIfNull(tree); + + return new VirtualTreePoint(tree, tree.GetText(cancellationToken), location.SourceSpan.Start); } protected override Task NavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken) From 9065f1da7ed87cefa14f413ce11ad2c03a8af871 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 10 Apr 2021 00:18:13 -0700 Subject: [PATCH 086/220] revert --- .../Protocol/Handler/Symbols/DocumentSymbolsHandler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs index d2a9d9fffcc12..71fcfe8c202e1 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/DocumentSymbolsHandler.cs @@ -92,7 +92,9 @@ public override async Task HandleRequestAsync(DocumentSymbolParams req SourceText text, CancellationToken cancellationToken, string? containerName = null) { if (item.Spans.IsEmpty) + { return null; + } var location = GetLocation(item, compilation, tree, cancellationToken); From 6fa321767d4079bf221e39528ee755d64546103e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 10 Apr 2021 00:18:40 -0700 Subject: [PATCH 087/220] Implement proper interface --- .../FSharp/Internal/Editor/FSharpNavigationBarItemService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs index ac10bacb1a310..1cf14d1783952 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Editor/FSharpNavigationBarItemService.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Editor { [Shared] [ExportLanguageService(typeof(INavigationBarItemService), LanguageNames.FSharp)] - internal class FSharpNavigationBarItemService : INavigationBarItemService + internal class FSharpNavigationBarItemService : INavigationBarItemService2 { private readonly IThreadingContext _threadingContext; private readonly IFSharpNavigationBarItemService _service; From 6711fa4c5fb1e202f6ee6ae0766292e3d928cb2c Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 10 Apr 2021 17:45:53 +0200 Subject: [PATCH 088/220] Fix tuple naming --- .../Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs index 49724219205b3..9c1c265e461d7 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/SyntaxNodeExtensions.cs @@ -933,7 +933,7 @@ public static (SyntaxToken openParen, SyntaxToken closeParen) GetParentheses(thi } } - public static (SyntaxToken openBrace, SyntaxToken closeBrace) GetBrackets(this SyntaxNode node) + public static (SyntaxToken openBracket, SyntaxToken closeBracket) GetBrackets(this SyntaxNode node) { switch (node) { From d72004d856b06653c8beb63f19d9053c716fa2b9 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 10 Apr 2021 20:20:01 +0200 Subject: [PATCH 089/220] Add DeclaredSymbolInfoKind.Record back --- .../NavigateTo/AbstractNavigateToSearchService.InProcess.cs | 2 ++ src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs | 1 + .../FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs | 2 +- src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs | 1 + .../FindReferences/DependentTypeFinder_ProjectIndex.cs | 1 + 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs index 255802737b3a4..62f63b5399cbd 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.InProcess.cs @@ -287,6 +287,7 @@ private static string GetItemKind(DeclaredSymbolInfo declaredSymbolInfo) switch (declaredSymbolInfo.Kind) { case DeclaredSymbolInfoKind.Class: + case DeclaredSymbolInfoKind.Record: return NavigateToItemKind.Class; case DeclaredSymbolInfoKind.Constant: return NavigateToItemKind.Constant; @@ -354,6 +355,7 @@ public DeclaredSymbolInfoKindSet(IEnumerable navigateToItemKinds) { case NavigateToItemKind.Class: lookupTable[(int)DeclaredSymbolInfoKind.Class] = true; + lookupTable[(int)DeclaredSymbolInfoKind.Record] = true; break; case NavigateToItemKind.Constant: lookupTable[(int)DeclaredSymbolInfoKind.Constant] = true; diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index c1ad545aad308..979ee6a3c5c4d 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -167,6 +167,7 @@ private bool IsNamedType() switch (_item.DeclaredSymbolInfo.Kind) { case DeclaredSymbolInfoKind.Class: + case DeclaredSymbolInfoKind.Record: case DeclaredSymbolInfoKind.Enum: case DeclaredSymbolInfoKind.Interface: case DeclaredSymbolInfoKind.Module: diff --git a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs index 053c258bb462c..d3fa6a2825ce3 100644 --- a/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs +++ b/src/Workspaces/CSharp/Portable/FindSymbols/CSharpDeclaredSymbolInfoFactoryService.cs @@ -179,7 +179,7 @@ public override bool TryGetDeclaredSymbolInfo(StringTable stringTable, SyntaxNod node.Kind() switch { SyntaxKind.ClassDeclaration => DeclaredSymbolInfoKind.Class, - SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Class, + SyntaxKind.RecordDeclaration => DeclaredSymbolInfoKind.Record, SyntaxKind.InterfaceDeclaration => DeclaredSymbolInfoKind.Interface, SyntaxKind.StructDeclaration => DeclaredSymbolInfoKind.Struct, _ => throw ExceptionUtilities.UnexpectedValue(node.Kind()), diff --git a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs index 8665d5d59d8f0..52acb4f38e8c3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/DeclaredSymbolInfo.cs @@ -30,6 +30,7 @@ internal enum DeclaredSymbolInfoKind : byte Method, Module, Property, + Record, Struct, } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs index bd76156755867..af301e97b1b92 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder_ProjectIndex.cs @@ -64,6 +64,7 @@ private static async Task CreateIndexAsync(Project project, Cancel switch (info.Kind) { case DeclaredSymbolInfoKind.Class: + case DeclaredSymbolInfoKind.Record: classesThatMayDeriveFromSystemObject.Add(document, info); break; case DeclaredSymbolInfoKind.Enum: From 14b6e4e42dd46a749e9e91908249812d343f6d88 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sun, 11 Apr 2021 23:12:05 -0400 Subject: [PATCH 090/220] Suggest simplifying ToString with invariant provider inside FormattableString.Invariant --- .../SimplifyInterpolationTests.cs | 68 +++++++++++++++++ .../SimplifyInterpolation/Helpers.cs | 73 ++++++++++++++----- 2 files changed, 124 insertions(+), 17 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs b/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs index c57ba2958339c..038b41f34f7f8 100644 --- a/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs +++ b/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs @@ -214,6 +214,74 @@ void M(System.DateTime someValue) }"); } + [Fact] + public async Task ToStringWithInvariantCultureInsideFormattableStringInvariant() + { + // Invariance remains explicit, so this is okay. + + await TestInRegularAndScript1Async( +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(System.Globalization.CultureInfo.InvariantCulture)|}} suffix""); + } +}", +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue} suffix""); + } +}"); + } + + [Fact] + public async Task ToStringWithInvariantCultureOutsideFormattableStringInvariant() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void M(System.DateTime someValue) + { + _ = $""prefix {someValue[||].ToString(System.Globalization.CultureInfo.InvariantCulture)} suffix""; + } +}"); + } + + [Fact] + public async Task ToStringWithFormatAndInvariantCultureInsideFormattableStringInvariant() + { + await TestInRegularAndScript1Async( +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(""|}some format code{|Unnecessary:"", System.Globalization.CultureInfo.InvariantCulture)|}} suffix""); + } +}", +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue:some format code} suffix""); + } +}"); + } + + [Fact] + public async Task ToStringWithFormatAndInvariantCultureOutsideFormattableStringInvariant() + { + await TestMissingInRegularAndScriptAsync( +@"class C +{ + void M(System.DateTime someValue) + { + _ = $""prefix {someValue[||].ToString(""some format code"", System.Globalization.CultureInfo.InvariantCulture)} suffix""; + } +}"); + } + [Fact] public async Task PadLeftWithIntegerLiteral() { diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index 9e8606d72f1fc..cdd872d285325 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; using Microsoft.CodeAnalysis.EmbeddedLanguages.VirtualChars; using Microsoft.CodeAnalysis.LanguageServices; @@ -63,17 +66,21 @@ public static void UnwrapInterpolation(unwrapped); - unnecessarySpans.AddRange(invocation.Syntax.Span - .Subtract(unwrappedSyntax.FullSpan) - .Subtract(GetSpanWithinLiteralQuotes(virtualCharService, literal.Syntax.GetFirstToken()))); - return; + if (invocation.Arguments[0].Value is ILiteralOperation { ConstantValue: { HasValue: true, Value: string value } } literal && + invocation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.IFormattable).FullName!) is { } systemIFormattable && + invocation.Instance.Type.Implements(systemIFormattable)) + { + unwrapped = invocation.Instance; + formatString = value; + + var unwrappedSyntax = GetPreservedInterpolationExpressionSyntax(unwrapped); + unnecessarySpans.AddRange(invocation.Syntax.Span + .Subtract(unwrappedSyntax.FullSpan) + .Subtract(GetSpanWithinLiteralQuotes(virtualCharService, literal.Syntax.GetFirstToken()))); + return; + } } var method = invocation.TargetMethod; @@ -113,8 +123,8 @@ private static void UnwrapFormatString().FirstOrDefault(); + + return Unwrap(interpolatedStringOperation?.Parent, towardsParent: true) is IArgumentOperation + { + Parent: IInvocationOperation + { + TargetMethod: { Name: nameof(FormattableString.Invariant), ContainingType: var containingType }, + }, + } && SymbolEqualityComparer.Default.Equals(containingType, operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.FormattableString).FullName!)); + } + + private static IEnumerable AncestorsAndSelf(IOperation operation) + { + for (var current = operation; current is not null; current = current.Parent) + { + yield return current; + } + } + private static TextSpan GetSpanWithinLiteralQuotes(IVirtualCharService virtualCharService, SyntaxToken formatToken) { var sequence = virtualCharService.TryConvertToVirtualChars(formatToken); From 44c69234637a011ad18b134ef10a9383ccfd9a40 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Sun, 11 Apr 2021 23:15:30 -0400 Subject: [PATCH 091/220] Extract implementation details --- .../Analyzers/SimplifyInterpolation/Helpers.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index cdd872d285325..dca37c78dfee5 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -117,13 +117,7 @@ private static void UnwrapFormatString Date: Sun, 11 Apr 2021 23:23:15 -0400 Subject: [PATCH 092/220] Recognize other ways to specify invariant culture --- .../SimplifyInterpolationTests.cs | 40 +++++++++++++++++++ .../SimplifyInterpolation/Helpers.cs | 23 +++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs b/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs index 038b41f34f7f8..a4d4186aba205 100644 --- a/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs +++ b/src/Analyzers/CSharp/Tests/SimplifyInterpolation/SimplifyInterpolationTests.cs @@ -236,6 +236,46 @@ void M(System.DateTime someValue) }"); } + [Fact] + public async Task DateTimeFormatInfoInvariantInfoIsRecognized() + { + await TestInRegularAndScript1Async( +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(System.Globalization.DateTimeFormatInfo.InvariantInfo)|}} suffix""); + } +}", +@"class C +{ + void M(System.DateTime someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue} suffix""); + } +}"); + } + + [Fact] + public async Task NumberFormatInfoInvariantInfoIsRecognized() + { + await TestInRegularAndScript1Async( +@"class C +{ + void M(int someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue[||]{|Unnecessary:.ToString(System.Globalization.NumberFormatInfo.InvariantInfo)|}} suffix""); + } +}", +@"class C +{ + void M(int someValue) + { + _ = System.FormattableString.Invariant($""prefix {someValue} suffix""); + } +}"); + } + [Fact] public async Task ToStringWithInvariantCultureOutsideFormattableStringInvariant() { diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index dca37c78dfee5..45944711043ca 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -147,10 +147,25 @@ private static bool IsObjectToStringOverride(IMethodSymbol method) private static bool IsInvariantCultureReference(IOperation operation) { - return Unwrap(operation) is IPropertyReferenceOperation { Member: { Name: nameof(CultureInfo.InvariantCulture), ContainingType: var containingType } } - && SymbolEqualityComparer.Default.Equals( - containingType, - operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.CultureInfo).FullName!)); + if (Unwrap(operation) is not IPropertyReferenceOperation { Member: { } member }) + return false; + + return member.Name switch + { + nameof(CultureInfo.InvariantCulture) => SymbolEqualityComparer.Default.Equals( + member.ContainingType, + operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.CultureInfo).FullName!)), + + "InvariantInfo" => + SymbolEqualityComparer.Default.Equals( + member.ContainingType, + operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.NumberFormatInfo).FullName!)) + || SymbolEqualityComparer.Default.Equals( + member.ContainingType, + operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.DateTimeFormatInfo).FullName!)), + + _ => false, + }; } private static bool IsInsideFormattableStringInvariant(IOperation operation) From a3f7291987eec688cca635ae88eb2b3153c8ec89 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Mon, 12 Apr 2021 10:54:32 -0400 Subject: [PATCH 093/220] Extraction requested in review --- .../Core/Analyzers/SimplifyInterpolation/Helpers.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index 45944711043ca..d2ba3d2379067 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -100,7 +100,7 @@ private static void UnwrapFormatString Date: Mon, 12 Apr 2021 11:31:16 -0400 Subject: [PATCH 094/220] Refactor to a statement form per review --- .../SimplifyInterpolation/Helpers.cs | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index d2ba3d2379067..782e1cf988bb2 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -153,25 +153,27 @@ private static bool UsesInvariantCultureReferenceInsideFormattableStringInvarian private static bool IsInvariantCultureReference(IOperation operation) { - if (Unwrap(operation) is not IPropertyReferenceOperation { Member: { } member }) - return false; - - return member.Name switch + if (Unwrap(operation) is IPropertyReferenceOperation { Member: { } member }) { - nameof(CultureInfo.InvariantCulture) => SymbolEqualityComparer.Default.Equals( - member.ContainingType, - operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.CultureInfo).FullName!)), - - "InvariantInfo" => - SymbolEqualityComparer.Default.Equals( - member.ContainingType, - operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.NumberFormatInfo).FullName!)) - || SymbolEqualityComparer.Default.Equals( + if (member.Name == nameof(CultureInfo.InvariantCulture)) + { + return SymbolEqualityComparer.Default.Equals( member.ContainingType, - operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.DateTimeFormatInfo).FullName!)), + operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.CultureInfo).FullName!)); + } - _ => false, - }; + if (member.Name == "InvariantInfo") + { + return SymbolEqualityComparer.Default.Equals( + member.ContainingType, + operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.NumberFormatInfo).FullName!)) + || SymbolEqualityComparer.Default.Equals( + member.ContainingType, + operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.Globalization.DateTimeFormatInfo).FullName!)); + } + } + + return false; } private static bool IsInsideFormattableStringInvariant(IOperation operation) From 4d828ba543e9c948f8fd9259da1df79e8774f045 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Mon, 12 Apr 2021 11:46:00 -0400 Subject: [PATCH 095/220] It's a failure on the part of the caller if no semantic model is available --- .../Analyzers/SimplifyInterpolation/Helpers.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index 782e1cf988bb2..1ddf88af48acb 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -94,6 +94,8 @@ private static void UnwrapFormatString().FirstOrDefault(); return Unwrap(interpolatedStringOperation?.Parent, towardsParent: true) is IArgumentOperation @@ -186,7 +192,7 @@ private static bool IsInsideFormattableStringInvariant(IOperation operation) { TargetMethod: { Name: nameof(FormattableString.Invariant), ContainingType: var containingType }, }, - } && SymbolEqualityComparer.Default.Equals(containingType, operation.SemanticModel!.Compilation.GetTypeByMetadataName(typeof(System.FormattableString).FullName!)); + } && SymbolEqualityComparer.Default.Equals(containingType, operation.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.FormattableString).FullName!)); } private static IEnumerable AncestorsAndSelf(IOperation operation) From bffbe0b7f90b92a6b1291f2b426c1d2b9f9011a1 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Mon, 12 Apr 2021 12:16:07 -0400 Subject: [PATCH 096/220] Extract helper to compare types by metadata name --- .../SimplifyInterpolation/Helpers.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index 1ddf88af48acb..ba1d25991ff2a 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -161,19 +161,13 @@ private static bool IsInvariantCultureReference(IOperation operation) { if (member.Name == nameof(CultureInfo.InvariantCulture)) { - return SymbolEqualityComparer.Default.Equals( - member.ContainingType, - operation.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.Globalization.CultureInfo).FullName!)); + return IsType(member.ContainingType, operation.SemanticModel); } if (member.Name == "InvariantInfo") { - return SymbolEqualityComparer.Default.Equals( - member.ContainingType, - operation.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.Globalization.NumberFormatInfo).FullName!)) - || SymbolEqualityComparer.Default.Equals( - member.ContainingType, - operation.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.Globalization.DateTimeFormatInfo).FullName!)); + return IsType(member.ContainingType, operation.SemanticModel) + || IsType(member.ContainingType, operation.SemanticModel); } } @@ -192,7 +186,12 @@ private static bool IsInsideFormattableStringInvariant(IOperation operation) { TargetMethod: { Name: nameof(FormattableString.Invariant), ContainingType: var containingType }, }, - } && SymbolEqualityComparer.Default.Equals(containingType, operation.SemanticModel.Compilation.GetTypeByMetadataName(typeof(System.FormattableString).FullName!)); + } && IsType(containingType, operation.SemanticModel); + } + + private static bool IsType(INamedTypeSymbol type, SemanticModel semanticModel) + { + return SymbolEqualityComparer.Default.Equals(type, semanticModel.Compilation.GetTypeByMetadataName(typeof(T).FullName!)); } private static IEnumerable AncestorsAndSelf(IOperation operation) From 200390b5618f0518992b36a54289e6a291416e51 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Mon, 12 Apr 2021 12:57:28 -0400 Subject: [PATCH 097/220] Centralize remaining usage of GetTypeByMetadataName --- .../Core/Analyzers/SimplifyInterpolation/Helpers.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index ba1d25991ff2a..beef4a13dc876 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -105,7 +105,7 @@ private static void UnwrapFormatString(expression.SemanticModel) is { } systemIFormattable && invocation.Instance.Type.Implements(systemIFormattable)) { unwrapped = invocation.Instance; @@ -191,7 +191,12 @@ private static bool IsInsideFormattableStringInvariant(IOperation operation) private static bool IsType(INamedTypeSymbol type, SemanticModel semanticModel) { - return SymbolEqualityComparer.Default.Equals(type, semanticModel.Compilation.GetTypeByMetadataName(typeof(T).FullName!)); + return SymbolEqualityComparer.Default.Equals(type, FindType(semanticModel)); + } + + private static INamedTypeSymbol? FindType(SemanticModel semanticModel) + { + return semanticModel.Compilation.GetTypeByMetadataName(typeof(T).FullName!); } private static IEnumerable AncestorsAndSelf(IOperation operation) From 42e61aa6a84b32e48112c6cbc0f5a611bc686732 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 12 Apr 2021 10:02:43 -0700 Subject: [PATCH 098/220] Update src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs --- src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs index beef4a13dc876..81fd3062fa3d8 100644 --- a/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs +++ b/src/Analyzers/Core/Analyzers/SimplifyInterpolation/Helpers.cs @@ -105,8 +105,8 @@ private static void UnwrapFormatString(expression.SemanticModel) is { } systemIFormattable && - invocation.Instance.Type.Implements(systemIFormattable)) + FindType(expression.SemanticModel) is { } systemIFormattable && + invocation.Instance.Type.Implements(systemIFormattable)) { unwrapped = invocation.Instance; formatString = value; From 34f0a15dad443d39321048add6bfff3528ee9297 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 12 Apr 2021 16:33:28 -0700 Subject: [PATCH 099/220] Update src/Compilers/Core/Portable/Syntax/SyntaxNode.cs Co-authored-by: Fred Silberberg --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 7bb5f7c4e9f58..d3f9dbf45f435 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -353,7 +353,7 @@ public bool IsEquivalentTo([NotNullWhen(true)] SyntaxNode other) /// occurs when a is incrementally parsed using /// and the incremental parser is able to take the node from the original tree and use it in its entirety in the /// new tree. In this case, the of each node will be the same, though - /// will have different parents, and may occur at different positions in their respective trees. If two nodes are + /// they could have different parents, and may occur at different positions in their respective trees. If two nodes are /// incrementally identical, all children of each node will be incrementally identical as well. /// /// From e85d7d2ad1e910989db4c5172c62b29956191100 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 12 Apr 2021 16:33:34 -0700 Subject: [PATCH 100/220] Update src/Compilers/Core/Portable/Syntax/SyntaxToken.cs Co-authored-by: Fred Silberberg --- src/Compilers/Core/Portable/Syntax/SyntaxToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs index 95e84b4627d37..284e4d7f5648a 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs @@ -699,7 +699,7 @@ public bool IsEquivalentTo(SyntaxToken token) /// Incrementally identical tokens can also appear within the same syntax tree, or syntax trees that did not arise /// from . This can happen as the parser is allowed to construct parse /// trees using shared tokens for efficiency. In all these cases though, it will still remain true that the incrementally - /// identical tokens will have different parents and may occur at different positions in their respective trees. + /// identical tokens could have different parents and may occur at different positions in their respective trees. /// public bool IsIncrementallyIdenticalTo(SyntaxToken token) => this.Node != null && this.Node == token.Node; From 3a6a891b0ebc5f1bd0f8fc67a1704d7770833767 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 12 Apr 2021 16:33:41 -0700 Subject: [PATCH 101/220] Update src/Compilers/Core/Portable/Syntax/SyntaxToken.cs Co-authored-by: Fred Silberberg --- src/Compilers/Core/Portable/Syntax/SyntaxToken.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs index 284e4d7f5648a..6053d33c739a1 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxToken.cs @@ -692,7 +692,7 @@ public bool IsEquivalentTo(SyntaxToken token) /// occurs when a is incrementally parsed using /// and the incremental parser is able to take the token from the original tree and use it in its entirety in the /// new tree. In this case, the of each token will be the same, though - /// will have different parents, and may occur at different positions in the respective trees. If two tokens are + /// they could have different parents, and may occur at different positions in the respective trees. If two tokens are /// incrementally identical, all trivial of each node will be incrementally identical as well. /// /// From a1301b488d3cea0d94575f9353261d437491d0a4 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Mon, 12 Apr 2021 16:35:01 -0700 Subject: [PATCH 102/220] Update src/Compilers/Core/Portable/Syntax/SyntaxNode.cs Co-authored-by: Fred Silberberg --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index d3f9dbf45f435..652dbcca140ff 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -360,7 +360,7 @@ public bool IsEquivalentTo([NotNullWhen(true)] SyntaxNode other) /// Incrementally identical nodes can also appear within the same syntax tree, or syntax trees that did not arise /// from . This can happen as the parser is allowed to construct parse /// trees from shared nodes for efficiency. In all these cases though, it will still remain true that the incrementally - /// identical nodes will have different parents and may occur at different positions in their respective trees. + /// identical nodes could have different parents and may occur at different positions in their respective trees. /// public bool IsIncrementallyIdenticalTo([NotNullWhen(true)] SyntaxNode? other) => this.Green != null && this.Green == other?.Green; From 4a363b485d61b972c554d652dea2798e40efe970 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 12 Apr 2021 23:40:21 -0700 Subject: [PATCH 103/220] Add tests --- .../Test/Syntax/Syntax/SyntaxNodeTests.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs index 9cffea78ee445..ffaca6b7089bb 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/SyntaxNodeTests.cs @@ -693,6 +693,70 @@ public void TestTreesWithDifferentTriviaAreNotEquivalent() Assert.False(tree1.GetCompilationUnitRoot().IsEquivalentTo(tree2.GetCompilationUnitRoot())); } + [Fact] + public void TestNodeIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.True(tree.GetCompilationUnitRoot().IsIncrementallyIdenticalTo(tree.GetCompilationUnitRoot())); + } + + [Fact] + public void TestTokenIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.True(tree.GetCompilationUnitRoot().EndOfFileToken.IsIncrementallyIdenticalTo(tree.GetCompilationUnitRoot().EndOfFileToken)); + } + + [Fact] + public void TestDifferentTokensFromSameTreeNotIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree = SyntaxFactory.ParseSyntaxTree(text); + Assert.False(tree.GetCompilationUnitRoot().GetFirstToken().IsIncrementallyIdenticalTo(tree.GetCompilationUnitRoot().GetFirstToken().GetNextToken())); + } + + [Fact] + public void TestCachedTokensFromDifferentTreesIncrementallyEquivalentToSelf() + { + var text = "class goo { }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = SyntaxFactory.ParseSyntaxTree(text); + Assert.True(tree1.GetCompilationUnitRoot().GetFirstToken().IsIncrementallyIdenticalTo(tree2.GetCompilationUnitRoot().GetFirstToken())); + } + + [Fact] + public void TestNodesFromSameContentNotIncrementallyParsedNotIncrementallyEquivalent() + { + var text = "class goo { }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = SyntaxFactory.ParseSyntaxTree(text); + Assert.False(tree1.GetCompilationUnitRoot().IsIncrementallyIdenticalTo(tree2.GetCompilationUnitRoot())); + } + + [Fact] + public void TestNodesFromIncrementalParseIncrementallyEquivalent1() + { + var text = "class goo { void M() { } }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = tree1.WithChangedText(tree1.GetText().WithChanges(new TextChange(default, " "))); + Assert.True( + tree1.GetCompilationUnitRoot().DescendantNodes().OfType().Single().IsIncrementallyIdenticalTo( + tree2.GetCompilationUnitRoot().DescendantNodes().OfType().Single())); + } + + [Fact] + public void TestNodesFromIncrementalParseNotIncrementallyEquivalent1() + { + var text = "class goo { void M() { } }"; + var tree1 = SyntaxFactory.ParseSyntaxTree(text); + var tree2 = tree1.WithChangedText(tree1.GetText().WithChanges(new TextChange(new TextSpan(22, 0), " return; "))); + Assert.False( + tree1.GetCompilationUnitRoot().DescendantNodes().OfType().Single().IsIncrementallyIdenticalTo( + tree2.GetCompilationUnitRoot().DescendantNodes().OfType().Single())); + } + [Fact, WorkItem(536664, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/536664")] public void TestTriviaNodeCached() { From 243c33c54d18d899be552c45193ec27543e8c249 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 13 Apr 2021 08:49:14 +0200 Subject: [PATCH 104/220] Address feedback --- .../UnusedReferences/RemoveUnusedReferencesCommandHandler.cs | 1 - .../Implementation/Workspace/SourceGeneratedFileManager.cs | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs index 5c9d28e434b09..4e90a832da777 100644 --- a/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs +++ b/src/VisualStudio/Core/Def/Implementation/UnusedReferences/RemoveUnusedReferencesCommandHandler.cs @@ -41,7 +41,6 @@ internal sealed class RemoveUnusedReferencesCommandHandler [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RemoveUnusedReferencesCommandHandler( RemoveUnusedReferencesDialogProvider unusedReferenceDialogProvider, - IVsHierarchyItemManager vsHierarchyItemManager, IUIThreadOperationExecutor threadOperationExecutor, VisualStudioWorkspace workspace) { diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index f41ead0041887..bd9820dc4919f 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -44,6 +44,8 @@ internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventLis private readonly ITextDocumentFactoryService _textDocumentFactoryService; private readonly VisualStudioDocumentNavigationService _visualStudioDocumentNavigationService; + private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; + /// /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. /// @@ -102,7 +104,7 @@ public SourceGeneratedFileManager( // The IVsRunningDocumentTable is a free-threaded VS service that allows fetching of the service and advising events // to be done without implicitly marshalling to the UI thread. _runningDocumentTable = _serviceProvider.GetService(); - _ = new RunningDocumentTableEventTracker( + _runningDocumentTableEventTracker = new RunningDocumentTableEventTracker( threadingContext, editorAdaptersFactoryService, _runningDocumentTable, From a43819698afcfe2ec771d9268b43826f2b5988e7 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 13 Apr 2021 08:50:53 +0200 Subject: [PATCH 105/220] Remove unused parameter --- .../Snippets/AbstractSnippetExpansionClient.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs index d05d04721374c..98e880c9fa3e2 100644 --- a/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/Snippets/AbstractSnippetExpansionClient.cs @@ -535,7 +535,7 @@ private bool TryInsertArgumentCompletionSnippet(SnapshotSpan triggerSpan, Snapsh { // This is the method name as it appears in source text var methodName = dataBufferSpan.GetText(); - var snippet = CreateMethodCallSnippet(methodName, includeMethod: true, ImmutableArray.Empty, ImmutableDictionary.Empty, cancellationToken); + var snippet = CreateMethodCallSnippet(methodName, includeMethod: true, ImmutableArray.Empty, ImmutableDictionary.Empty); var doc = new DOMDocumentClass(); if (doc.loadXML(snippet.ToString(SaveOptions.OmitDuplicateNamespaces))) @@ -613,7 +613,7 @@ static void EnsureRegisteredForModelUpdatedEvents(AbstractSnippetExpansionClient /// The parameters to the method. If the specific target of the invocation is not /// known, an empty array may be passed to create a template with a placeholder where arguments will eventually /// go. - private static XDocument CreateMethodCallSnippet(string methodName, bool includeMethod, ImmutableArray parameters, ImmutableDictionary parameterValues, CancellationToken cancellationToken) + private static XDocument CreateMethodCallSnippet(string methodName, bool includeMethod, ImmutableArray parameters, ImmutableDictionary parameterValues) { XNamespace snippetNamespace = "http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet"; @@ -896,7 +896,7 @@ public void MoveToSpecificMethod(IMethodSymbol method, CancellationToken cancell newArguments = newArguments.SetItem(parameter.Name, value); } - var snippet = CreateMethodCallSnippet(method.Name, includeMethod: false, method.Parameters, newArguments, cancellationToken); + var snippet = CreateMethodCallSnippet(method.Name, includeMethod: false, method.Parameters, newArguments); var doc = new DOMDocumentClass(); if (doc.loadXML(snippet.ToString(SaveOptions.OmitDuplicateNamespaces))) { From aa29bd765ee2206da2e46c6e88c68a1f1d5785f5 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 13 Apr 2021 10:02:42 +0200 Subject: [PATCH 106/220] More fixes --- .../Implementation/Workspace/SourceGeneratedFileManager.cs | 2 ++ .../Core/Def/Packaging/PackageInstallerServiceFactory.cs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs index bd9820dc4919f..bc657d1877fcf 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/SourceGeneratedFileManager.cs @@ -44,7 +44,9 @@ internal sealed class SourceGeneratedFileManager : IRunningDocumentTableEventLis private readonly ITextDocumentFactoryService _textDocumentFactoryService; private readonly VisualStudioDocumentNavigationService _visualStudioDocumentNavigationService; +#pragma warning disable IDE0052 // Remove unread private members private readonly RunningDocumentTableEventTracker _runningDocumentTableEventTracker; +#pragma warning restore IDE0052 // Remove unread private members /// /// The temporary directory that we'll create file names under to act as a prefix we can later recognize and use. diff --git a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs index 99050d8eb4648..388e5f57eced7 100644 --- a/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Packaging/PackageInstallerServiceFactory.cs @@ -211,7 +211,7 @@ bool IPackageInstallerService.IsEnabled(ProjectId projectId) return true; } - private async ValueTask GetPackageSourceProviderAsync(CancellationToken cancellationToken) + private async ValueTask GetPackageSourceProviderAsync() { Contract.ThrowIfFalse(IsEnabled); @@ -232,7 +232,7 @@ protected override async Task EnableServiceAsync(CancellationToken cancellationT } // Continue on captured context since EnableServiceAsync is part of a UI thread initialization sequence - var packageSourceProvider = await GetPackageSourceProviderAsync(cancellationToken).ConfigureAwait(true); + var packageSourceProvider = await GetPackageSourceProviderAsync().ConfigureAwait(true); // Start listening to additional events workspace changes. _workspace.WorkspaceChanged += OnWorkspaceChanged; From 94ac917f3f0b86244c9b313ecb8bbc26fa009cd7 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 13 Apr 2021 11:29:22 +0200 Subject: [PATCH 107/220] Fix CA1012 --- src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs index 08c5bbf2a404e..b955d02ba0fae 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPageControl.cs @@ -23,7 +23,7 @@ public abstract class AbstractOptionPageControl : UserControl internal readonly OptionStore OptionStore; private readonly List _bindingExpressions = new List(); - public AbstractOptionPageControl(OptionStore optionStore) + protected AbstractOptionPageControl(OptionStore optionStore) { InitializeStyles(); From cfcd06a8616f5aef70535dec2f28a59dd9a0e731 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 13 Apr 2021 12:31:44 +0200 Subject: [PATCH 108/220] Fix CA1012 --- .../CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs b/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs index 1f63e7c6a3b9a..37c6513339b0b 100644 --- a/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs +++ b/src/VisualStudio/CSharp/Test/CodeModel/AbstractFileCodeElementTests.cs @@ -25,7 +25,7 @@ public abstract class AbstractFileCodeElementTests : IDisposable private readonly string _contents; private (TestWorkspace workspace, VisualStudioWorkspace extraWorkspaceToDisposeButNotUse, FileCodeModel fileCodeModel)? _workspaceAndCodeModel; - public AbstractFileCodeElementTests(string contents) + protected AbstractFileCodeElementTests(string contents) { _contents = contents; } From e551f4fc76cd9e1744a2a8cb2ab758163aaae546 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 13 Apr 2021 13:55:11 +0200 Subject: [PATCH 109/220] Fix CA1012 --- .../IntegrationTests/CSharp/CSharpSquigglesCommon.cs | 2 +- .../IntegrationTests/VisualBasic/BasicSquigglesCommon.cs | 2 +- .../IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs index 3e026ba6f2184..0396b709a5b54 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpSquigglesCommon.cs @@ -11,7 +11,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.CSharp { public abstract class CSharpSquigglesCommon : AbstractEditorTest { - public CSharpSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) + protected CSharpSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) : base(instanceFactory, nameof(CSharpSquigglesCommon), projectTemplate) { } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs index 74851dee01c39..fdf19bf35c4da 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/VisualBasic/BasicSquigglesCommon.cs @@ -9,7 +9,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.VisualBasic { public abstract class BasicSquigglesCommon : AbstractEditorTest { - public BasicSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) + protected BasicSquigglesCommon(VisualStudioInstanceFactory instanceFactory, string projectTemplate) : base(instanceFactory, nameof(BasicSquigglesCommon), projectTemplate) { } diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs index ff9076bd72256..b481269f5abf1 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/Workspace/WorkspaceBase.cs @@ -16,7 +16,7 @@ namespace Roslyn.VisualStudio.IntegrationTests.Workspace { public abstract class WorkspaceBase : AbstractEditorTest { - public WorkspaceBase(VisualStudioInstanceFactory instanceFactory, string projectTemplate) + protected WorkspaceBase(VisualStudioInstanceFactory instanceFactory, string projectTemplate) : base(instanceFactory, nameof(WorkspaceBase), projectTemplate) { DefaultProjectTemplate = projectTemplate; From ce227dc6833079ee4b8dd3701a38ff27124151e3 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Tue, 13 Apr 2021 16:02:17 +0100 Subject: [PATCH 110/220] Add AddUnderlyingEnumType in SymbolVisitor and tests --- .../SymbolDisplayVisitor.Types.cs | 12 ++++ .../SymbolDisplay/SymbolDisplayTests.cs | 70 +++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index 8b568e69e1e92..c9ca68c8e9678 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -395,6 +395,7 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol) AddDelegateParameters(symbol); } + AddUnderlyingEnumType(symbol); // Only the compiler can set the internal option and the compiler doesn't use other implementations of INamedTypeSymbol. if (underlyingTypeSymbol?.OriginalDefinition is MissingMetadataTypeSymbol && format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes)) @@ -406,6 +407,17 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol) } } + private void AddUnderlyingEnumType(INamedTypeSymbol symbol) + { + if (symbol.TypeKind == TypeKind.Enum && symbol.EnumUnderlyingType.SpecialType != SpecialType.System_Int32) + { + AddSpace(); + AddPunctuation(SyntaxKind.ColonToken); + AddSpace(); + AddSpecialTypeKeyword(symbol.EnumUnderlyingType); + } + } + private ImmutableArray> GetTypeArgumentsModifiers(NamedTypeSymbol underlyingTypeSymbol) { if (this.format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers)) diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index f56b37d03ca46..f3965da7031b8 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -7736,5 +7736,75 @@ class A { format, "delegate*"); } + + [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + [InlineData("byte", "byte")] + [InlineData("byte", "System.Byte")] + [InlineData("sbyte", "sbyte")] + [InlineData("sbyte", "System.SByte")] + [InlineData("short", "short")] + [InlineData("short", "System.Int16")] + [InlineData("ushort", "ushort")] + [InlineData("ushort", "System.UInt16")] + // int is the default type and is not shown + [InlineData("uint", "uint")] + [InlineData("uint", "System.UInt32")] + [InlineData("long", "long")] + [InlineData("long", "System.Int64")] + [InlineData("ulong", "ulong")] + [InlineData("ulong", "System.UInt64")] + public void TestEnumWithUnderlyingType_ShowForNonDefaultTypes(string displayTypeName, string underlyingTypeName) + { + var text = @$" +enum E : {underlyingTypeName} +{{ + A, B +}}"; + + Func findSymbol = global => + global.GetTypeMembers("E", 0).Single(); + + var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); + + TestSymbolDescription( + text, + findSymbol, + format, + $"enum E : {displayTypeName}", + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.EnumName, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.Punctuation, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.Keyword); + } + + [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + [InlineData("")] + [InlineData(": int")] + [InlineData(": System.Int32")] + public void TestEnumWithUnderlyingType_DontShowForDefaultType(string defaultType) + { + var text = @$" +enum E {defaultType} +{{ + A, B +}}"; + + Func findSymbol = global => + global.GetTypeMembers("E", 0).Single(); + + var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); + + TestSymbolDescription( + text, + findSymbol, + format, + $"enum E", + SymbolDisplayPartKind.Keyword, + SymbolDisplayPartKind.Space, + SymbolDisplayPartKind.EnumName); + } } } From d43e5612096c1a87370f1cb0ea2ee9daabe16c0b Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 13 Apr 2021 09:42:19 -0700 Subject: [PATCH 111/220] Correctly supress/elevate diagnostics produced by source generators (#52544) * Correctly supress/elevate diagnostics produced by source generators --- .../SourceGeneration/GeneratorDriverTests.cs | 133 ++++++++++++++++++ .../SourceGeneration/GeneratorDriver.cs | 22 ++- .../SourceGeneration/GeneratorDriverTests.vb | 117 +++++++++++++++ 3 files changed, 269 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs index 7bc0335b15d1d..49a62ab5fc23f 100644 --- a/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs @@ -1358,5 +1358,138 @@ class C { } Assert.True(tree.TryGetRoot(out var rootFromTryGetRoot)); Assert.Same(rootFromGetRoot, rootFromTryGetRoot); } + + [Fact] + public void Diagnostics_Respect_Suppression() + { + var source = @" +class C { } +"; + var parseOptions = TestOptions.Regular; + Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); + compilation.VerifyDiagnostics(); + + Assert.Single(compilation.SyntaxTrees); + CallbackGenerator gen = new CallbackGenerator((c) => { }, (c) => + { + c.ReportDiagnostic(CSDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 2)); + c.ReportDiagnostic(CSDiagnostic.Create("GEN002", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 3)); + }); + + var options = ((CSharpCompilationOptions)compilation.Options); + + // generator driver diagnostics are reported seperately from the compilation + verifyDiagnosticsWithOptions(options, + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1)); + + // warnings can be individually suppressed + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN001", ReportDiagnostic.Suppress), + Diagnostic("GEN002").WithLocation(1, 1)); + + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN002", ReportDiagnostic.Suppress), + Diagnostic("GEN001").WithLocation(1, 1)); + + // warning level is respected + verifyDiagnosticsWithOptions(options.WithWarningLevel(0)); + + verifyDiagnosticsWithOptions(options.WithWarningLevel(2), + Diagnostic("GEN001").WithLocation(1, 1)); + + verifyDiagnosticsWithOptions(options.WithWarningLevel(3), + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1)); + + // warnings can be upgraded to errors + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN001", ReportDiagnostic.Error), + Diagnostic("GEN001").WithLocation(1, 1).WithWarningAsError(true), + Diagnostic("GEN002").WithLocation(1, 1)); + + verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN002", ReportDiagnostic.Error), + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1).WithWarningAsError(true)); + + + void verifyDiagnosticsWithOptions(CompilationOptions options, params DiagnosticDescription[] expected) + { + GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions: parseOptions); + var updatedCompilation = compilation.WithOptions(options); + + driver.RunGeneratorsAndUpdateCompilation(updatedCompilation, out var outputCompilation, out var diagnostics); + outputCompilation.VerifyDiagnostics(); + diagnostics.Verify(expected); + } + } + + [Fact] + public void Diagnostics_Respect_Pragma_Suppression() + { + var gen001 = CSDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 2); + + // reported diagnostics can have a location in source + verifyDiagnosticsWithSource("//comment", + new[] { (gen001, TextSpan.FromBounds(2, 5)) }, + Diagnostic("GEN001", "com").WithLocation(1, 3)); + + // diagnostics are suppressed via #pragma + verifyDiagnosticsWithSource( +@"#pragma warning disable +//comment", + new[] { (gen001, TextSpan.FromBounds(27, 30)) }, + Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3)); + + // but not when they don't have a source location + verifyDiagnosticsWithSource( +@"#pragma warning disable +//comment", + new[] { (gen001, new TextSpan(0, 0)) }, + Diagnostic("GEN001").WithLocation(1, 1)); + + // can be suppressed explicitly + verifyDiagnosticsWithSource( +@"#pragma warning disable GEN001 +//comment", + new[] { (gen001, TextSpan.FromBounds(34, 37)) }, + Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3)); + + // suppress + restore + verifyDiagnosticsWithSource( +@"#pragma warning disable GEN001 +//comment +#pragma warning restore GEN001 +//another", + new[] { (gen001, TextSpan.FromBounds(34, 37)), (gen001, TextSpan.FromBounds(77, 80)) }, + Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3), + Diagnostic("GEN001", "ano").WithLocation(4, 3)); + + void verifyDiagnosticsWithSource(string source, (Diagnostic, TextSpan)[] reportDiagnostics, params DiagnosticDescription[] expected) + { + var parseOptions = TestOptions.Regular; + source = source.Replace(Environment.NewLine, "\r\n"); + Compilation compilation = CreateCompilation(source, sourceFileName: "sourcefile.cs", options: TestOptions.DebugDll, parseOptions: parseOptions); + compilation.VerifyDiagnostics(); + Assert.Single(compilation.SyntaxTrees); + + CallbackGenerator gen = new CallbackGenerator((c) => { }, (c) => + { + foreach ((var d, var l) in reportDiagnostics) + { + if (l.IsEmpty) + { + c.ReportDiagnostic(d); + } + else + { + c.ReportDiagnostic(d.WithLocation(Location.Create(c.Compilation.SyntaxTrees.First(), l))); + } + } + }); + GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions: parseOptions); + + driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); + outputCompilation.VerifyDiagnostics(); + diagnostics.Verify(expected); + } + } } } diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index d0f5b0248c38d..e100917237d3a 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -302,9 +302,10 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos continue; } - (var sources, var diagnostics) = context.ToImmutableAndFree(); - stateBuilder[i] = new GeneratorState(generatorState.Info, generatorState.PostInitTrees, ParseAdditionalSources(generator, sources, cancellationToken), diagnostics); - diagnosticsBag?.AddRange(diagnostics); + (var sources, var generatorDiagnostics) = context.ToImmutableAndFree(); + generatorDiagnostics = FilterDiagnostics(compilation, generatorDiagnostics, driverDiagnostics: diagnosticsBag, cancellationToken); + + stateBuilder[i] = new GeneratorState(generatorState.Info, generatorState.PostInitTrees, ParseAdditionalSources(generator, sources, cancellationToken), generatorDiagnostics); } state = state.With(generatorStates: stateBuilder.ToImmutableAndFree()); return state; @@ -438,6 +439,21 @@ private static GeneratorState SetGeneratorException(CommonMessageProvider provid return new GeneratorState(generatorState.Info, e, diagnostic); } + private static ImmutableArray FilterDiagnostics(Compilation compilation, ImmutableArray generatorDiagnostics, DiagnosticBag? driverDiagnostics, CancellationToken cancellationToken) + { + ArrayBuilder filteredDiagnostics = ArrayBuilder.GetInstance(); + foreach (var diag in generatorDiagnostics) + { + var filtered = compilation.Options.FilterDiagnostic(diag, cancellationToken); + if (filtered is object) + { + filteredDiagnostics.Add(filtered); + driverDiagnostics?.Add(filtered); + } + } + return filteredDiagnostics.ToImmutableAndFree(); + } + internal static string GetFilePathPrefixForGenerator(ISourceGenerator generator) { var type = generator.GetType(); diff --git a/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb b/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb index ffb06b5ba9a6c..17b3d68434888 100644 --- a/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb +++ b/src/Compilers/VisualBasic/Test/Semantic/SourceGeneration/GeneratorDriverTests.vb @@ -3,6 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Roslyn.Test.Utilities.TestGenerators @@ -179,6 +181,80 @@ End Namespace Assert.Same(rootFromGetRoot, rootFromTryGetRoot) End Sub + + Public Sub Diagnostics_Respect_Suppression() + + Dim compilation As Compilation = GetCompilation(TestOptions.Regular) + + Dim gen As CallbackGenerator = New CallbackGenerator(Sub(c) + End Sub, + Sub(c) + c.ReportDiagnostic(VBDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 2)) + c.ReportDiagnostic(VBDiagnostic.Create("GEN002", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 3)) + End Sub) + + VerifyDiagnosticsWithOptions(gen, compilation, + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1)) + + + Dim warnings As IDictionary(Of String, ReportDiagnostic) = New Dictionary(Of String, ReportDiagnostic)() + warnings.Add("GEN001", ReportDiagnostic.Suppress) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN002").WithLocation(1, 1)) + + warnings.Clear() + warnings.Add("GEN002", ReportDiagnostic.Suppress) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN001").WithLocation(1, 1)) + + warnings.Clear() + warnings.Add("GEN001", ReportDiagnostic.Error) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN001").WithLocation(1, 1).WithWarningAsError(True), + Diagnostic("GEN002").WithLocation(1, 1)) + + warnings.Clear() + warnings.Add("GEN002", ReportDiagnostic.Error) + VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)), + Diagnostic("GEN001").WithLocation(1, 1), + Diagnostic("GEN002").WithLocation(1, 1).WithWarningAsError(True)) + End Sub + + + Public Sub Diagnostics_Respect_Pragma_Suppression() + + Dim gen001 = VBDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 2) + + VerifyDiagnosticsWithSource("'comment", + gen001, TextSpan.FromBounds(1, 4), + Diagnostic("GEN001", "com").WithLocation(1, 2)) + + VerifyDiagnosticsWithSource("#disable warning +'comment", + gen001, TextSpan.FromBounds(19, 22), + Diagnostic("GEN001", "com", isSuppressed:=True).WithLocation(2, 2)) + + VerifyDiagnosticsWithSource("#disable warning +'comment", + gen001, New TextSpan(0, 0), + Diagnostic("GEN001").WithLocation(1, 1)) + + VerifyDiagnosticsWithSource("#disable warning GEN001 +'comment", + gen001, TextSpan.FromBounds(26, 29), + Diagnostic("GEN001", "com", isSuppressed:=True).WithLocation(2, 2)) + + VerifyDiagnosticsWithSource("#disable warning GEN001 +'comment +#enable warning GEN001 +'another", + gen001, TextSpan.FromBounds(60, 63), + Diagnostic("GEN001", "ano").WithLocation(4, 2)) + + End Sub + + Shared Function GetCompilation(parseOptions As VisualBasicParseOptions, Optional source As String = "") As Compilation If (String.IsNullOrWhiteSpace(source)) Then source = " @@ -194,6 +270,47 @@ End Class Return compilation End Function + Shared Sub VerifyDiagnosticsWithOptions(generator As ISourceGenerator, compilation As Compilation, ParamArray expected As DiagnosticDescription()) + + compilation.VerifyDiagnostics() + Assert.Single(compilation.SyntaxTrees) + + Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(generator), parseOptions:=TestOptions.Regular) + + Dim outputCompilation As Compilation = Nothing + Dim diagnostics As ImmutableArray(Of Diagnostic) = Nothing + driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, diagnostics) + outputCompilation.VerifyDiagnostics() + + diagnostics.Verify(expected) + End Sub + + Shared Sub VerifyDiagnosticsWithSource(source As String, diag As Diagnostic, location As TextSpan, ParamArray expected As DiagnosticDescription()) + Dim parseOptions = TestOptions.Regular + source = source.Replace(Environment.NewLine, vbCrLf) + Dim compilation As Compilation = CreateCompilation(source) + compilation.VerifyDiagnostics() + Assert.Single(compilation.SyntaxTrees) + + Dim gen As ISourceGenerator = New CallbackGenerator(Sub(c) + End Sub, + Sub(c) + If location.IsEmpty Then + c.ReportDiagnostic(diag) + Else + c.ReportDiagnostic(diag.WithLocation(CodeAnalysis.Location.Create(c.Compilation.SyntaxTrees.First(), location))) + End If + End Sub) + + Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions:=TestOptions.Regular) + + Dim outputCompilation As Compilation = Nothing + Dim diagnostics As ImmutableArray(Of Diagnostic) = Nothing + driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, diagnostics) + outputCompilation.VerifyDiagnostics() + + diagnostics.Verify(expected) + End Sub End Class From b150a0f01dbdbfd82725ba343418d12eda55157a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 11:27:32 -0700 Subject: [PATCH 112/220] Initial modeling of the symbol group --- .../FindReferencesSearchEngine.cs | 14 ++-- ...ferencesSearchEngine_DocumentProcessing.cs | 11 ++- .../FindReferencesSearchEngine_MapCreation.cs | 62 +++++++++------- ...eferencesSearchEngine_ProjectProcessing.cs | 2 +- .../Finders/LinkedFileReferenceFinder.cs | 74 +++++++++---------- .../Finders/ReferenceFinders.cs | 4 +- .../NoOpStreamingFindReferencesProgress.cs | 4 +- .../StreamingFindReferencesProgress.cs | 16 ++-- .../FindSymbols/IRemoteSymbolFinderService.cs | 4 +- .../IStreamingFindReferencesProgress.cs | 49 +++++++++++- .../FindSymbols/StreamingProgressCollector.cs | 11 +-- .../SymbolFinder.CallbackDispatcher.cs | 8 +- ...mbolFinder.FindReferencesServerCallback.cs | 37 +++++----- .../Core/Portable/Remote/RemoteArguments.cs | 70 ++++++++++++++++++ 14 files changed, 241 insertions(+), 125 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 17a89c1b595d0..819023d8c780f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using ProjectToDocumentMap = Dictionary>>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { @@ -111,7 +111,7 @@ private async Task ProcessAsync(ProjectToDocumentMap projectToDocumentMap) private static void ValidateProjectToDocumentMap( ProjectToDocumentMap projectToDocumentMap) { - var set = new HashSet<(ISymbol symbol, IReferenceFinder finder)>(); + var set = new HashSet<(SymbolGroup group, ISymbol symbol, IReferenceFinder finder)>(); foreach (var documentMap in projectToDocumentMap.Values) { @@ -119,15 +119,13 @@ private static void ValidateProjectToDocumentMap( { set.Clear(); - foreach (var finder in documentToFinderList.Value) - { - Debug.Assert(set.Add(finder)); - } + foreach (var tuple in documentToFinderList.Value) + Debug.Assert(set.Add(tuple)); } } } - private ValueTask HandleLocationAsync(ISymbol symbol, ReferenceLocation location) - => _progress.OnReferenceFoundAsync(symbol, location); + private ValueTask HandleLocationAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location) + => _progress.OnReferenceFoundAsync(group, symbol, location); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs index 2c3ce2468a163..fe5a3e7ed4d4c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_DocumentProcessing.cs @@ -15,7 +15,7 @@ internal partial class FindReferencesSearchEngine { private async Task ProcessDocumentQueueAsync( Document document, - HashSet<(ISymbol symbol, IReferenceFinder finder)> documentQueue) + HashSet<(SymbolGroup group, ISymbol symbol, IReferenceFinder finder)> documentQueue) { await _progress.OnFindInDocumentStartedAsync(document).ConfigureAwait(false); @@ -27,10 +27,8 @@ private async Task ProcessDocumentQueueAsync( // start cache for this semantic model FindReferenceCache.Start(model); - foreach (var (symbol, finder) in documentQueue) - { - await ProcessDocumentAsync(document, model, symbol, finder).ConfigureAwait(false); - } + foreach (var (group, symbol, finder) in documentQueue) + await ProcessDocumentAsync(document, model, group, symbol, finder).ConfigureAwait(false); } finally { @@ -48,6 +46,7 @@ private async Task ProcessDocumentQueueAsync( private async Task ProcessDocumentAsync( Document document, SemanticModel semanticModel, + SymbolGroup group, ISymbol symbol, IReferenceFinder finder) { @@ -59,7 +58,7 @@ private async Task ProcessDocumentAsync( symbol, document, semanticModel, _options, _cancellationToken).ConfigureAwait(false); foreach (var (_, location) in references) { - await HandleLocationAsync(symbol, location).ConfigureAwait(false); + await HandleLocationAsync(group, symbol, location).ConfigureAwait(false); } } finally diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index 1525056574707..49cd9d8617342 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -16,9 +16,9 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = Dictionary>; - using ProjectMap = Dictionary>; - using ProjectToDocumentMap = Dictionary>>; + using DocumentMap = Dictionary>; + using ProjectMap = Dictionary>; + using ProjectToDocumentMap = Dictionary>>; internal partial class FindReferencesSearchEngine { @@ -28,26 +28,26 @@ private async Task CreateProjectToDocumentMapAsync(Project { using (Logger.LogBlock(FunctionId.FindReference_CreateDocumentMapAsync, _cancellationToken)) { - using var _ = ArrayBuilder, ISymbol, IReferenceFinder)>>.GetInstance(out var tasks); + using var _ = ArrayBuilder, SymbolGroup, ISymbol, IReferenceFinder)>>.GetInstance(out var tasks); foreach (var (project, projectQueue) in projectMap) { - foreach (var (symbol, finder) in projectQueue) + foreach (var (group, symbol, finder) in projectQueue) { tasks.Add(Task.Factory.StartNew(() => - DetermineDocumentsToSearchAsync(project, symbol, finder), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); + DetermineDocumentsToSearchAsync(project, group, symbol, finder), _cancellationToken, TaskCreationOptions.None, _scheduler).Unwrap()); } } var results = await Task.WhenAll(tasks).ConfigureAwait(false); var finalMap = new ProjectToDocumentMap(); - foreach (var (documents, symbol, finder) in results) + foreach (var (documents, group, symbol, finder) in results) { foreach (var document in documents) { finalMap.GetOrAdd(document.Project, s_createDocumentMap) - .MultiAdd(document, (symbol, finder)); + .MultiAdd(document, (group, symbol, finder)); } } @@ -62,35 +62,36 @@ private async Task CreateProjectToDocumentMapAsync(Project } } - private async Task<(ImmutableArray, ISymbol, IReferenceFinder)> DetermineDocumentsToSearchAsync( - Project project, ISymbol symbol, IReferenceFinder finder) + private async Task<(ImmutableArray, SymbolGroup, ISymbol, IReferenceFinder)> DetermineDocumentsToSearchAsync( + Project project, SymbolGroup group, ISymbol symbol, IReferenceFinder finder) { var documents = await finder.DetermineDocumentsToSearchAsync( symbol, project, _documents, _options, _cancellationToken).ConfigureAwait(false); var finalDocs = documents.WhereNotNull().Distinct().Where( d => _documents == null || _documents.Contains(d)).ToImmutableArray(); - return (finalDocs, symbol, finder); + return (finalDocs, group, symbol, finder); } - private async Task CreateProjectMapAsync(ConcurrentSet symbols) + private async Task CreateProjectMapAsync(ConcurrentSet symbolGroups) { using (Logger.LogBlock(FunctionId.FindReference_CreateProjectMapAsync, _cancellationToken)) { var projectMap = new ProjectMap(); var scope = _documents?.Select(d => d.Project).ToImmutableHashSet(); - foreach (var symbol in symbols) + foreach (var symbolGroup in symbolGroups) { - foreach (var finder in _finders) + foreach (var symbol in symbolGroup.Symbols) { - _cancellationToken.ThrowIfCancellationRequested(); - - var projects = await finder.DetermineProjectsToSearchAsync(symbol, _solution, scope, _cancellationToken).ConfigureAwait(false); - foreach (var project in projects.Distinct().WhereNotNull()) + foreach (var finder in _finders) { - if (scope == null || scope.Contains(project)) + _cancellationToken.ThrowIfCancellationRequested(); + + var projects = await finder.DetermineProjectsToSearchAsync(symbol, _solution, scope, _cancellationToken).ConfigureAwait(false); + foreach (var project in projects.Distinct().WhereNotNull()) { - projectMap.MultiAdd(project, (symbol, finder)); + if (scope == null || scope.Contains(project)) + projectMap.MultiAdd(project, (symbolGroup, symbol, finder)); } } } @@ -101,19 +102,19 @@ private async Task CreateProjectMapAsync(ConcurrentSet symb } } - private async Task> DetermineAllSymbolsAsync( + private async Task> DetermineAllSymbolsAsync( ISymbol symbol, FindReferencesCascadeDirection cascadeDirection) { using (Logger.LogBlock(FunctionId.FindReference_DetermineAllSymbolsAsync, _cancellationToken)) { - var result = new ConcurrentSet(MetadataUnifyingEquivalenceComparer.Instance); + var result = new ConcurrentSet(); await DetermineAllSymbolsCoreAsync(symbol, cascadeDirection, result).ConfigureAwait(false); return result; } } private async Task DetermineAllSymbolsCoreAsync( - ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, ConcurrentSet result) + ISymbol symbol, FindReferencesCascadeDirection cascadeDirection, ConcurrentSet result) { _cancellationToken.ThrowIfCancellationRequested(); @@ -122,14 +123,14 @@ private async Task DetermineAllSymbolsCoreAsync( // 2) Try to map this back to source symbol if this was a metadata symbol. var sourceSymbol = await SymbolFinder.FindSourceDefinitionAsync(searchSymbol, _solution, _cancellationToken).ConfigureAwait(false); if (sourceSymbol != null) - { searchSymbol = sourceSymbol; - } Contract.ThrowIfNull(searchSymbol); - if (result.Add(searchSymbol)) + + var group = await DetermineSymbolGroupAsync(searchSymbol).ConfigureAwait(false); + if (result.Add(group)) { - await _progress.OnDefinitionFoundAsync(searchSymbol).ConfigureAwait(false); + await _progress.OnDefinitionFoundAsync(group).ConfigureAwait(false); // get project to search var projects = GetProjectScope(); @@ -166,8 +167,13 @@ private async Task DetermineAllSymbolsCoreAsync( } } + private Task DetermineSymbolGroupAsync(ISymbol searchSymbol) + { + throw new NotImplementedException(); + } + private void AddSymbolTasks( - ConcurrentSet result, + ConcurrentSet result, ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)> symbols, ArrayBuilder symbolTasks) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs index e35744ee0a02d..097eff45212f5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_ProjectProcessing.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols { - using DocumentMap = Dictionary>; + using DocumentMap = Dictionary>; internal partial class FindReferencesSearchEngine { diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs index 90ac75ed84fa8..91e9dd222fdd5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs @@ -1,43 +1,43 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +//// Licensed to the .NET Foundation under one or more agreements. +//// The .NET Foundation licenses this file to you under the MIT license. +//// See the LICENSE file in the project root for more information. -using System.Collections.Immutable; -using System.Threading; -using System.Threading.Tasks; -using Roslyn.Utilities; +//using System.Collections.Immutable; +//using System.Threading; +//using System.Threading.Tasks; +//using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.FindSymbols.Finders -{ - internal class LinkedFileReferenceFinder : IReferenceFinder - { - public async Task> DetermineCascadedSymbolsAsync( - ISymbol symbol, Solution solution, IImmutableSet? projects, - FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, - CancellationToken cancellationToken) - { - if (!options.Cascade) - return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; +//namespace Microsoft.CodeAnalysis.FindSymbols.Finders +//{ +// internal class LinkedFileReferenceFinder : IReferenceFinder +// { +// public async Task> DetermineCascadedSymbolsAsync( +// ISymbol symbol, Solution solution, IImmutableSet? projects, +// FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, +// CancellationToken cancellationToken) +// { +// if (!options.Cascade) +// return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; - var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false); - return linkedSymbols.SelectAsArray(s => (s, cascadeDirection)); - } +// var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false); +// return linkedSymbols.SelectAsArray(s => (s, cascadeDirection)); +// } - public Task> DetermineDocumentsToSearchAsync( - ISymbol symbol, Project project, IImmutableSet? documents, - FindReferencesSearchOptions options, CancellationToken cancellationToken) - { - return SpecializedTasks.EmptyImmutableArray(); - } +// public Task> DetermineDocumentsToSearchAsync( +// ISymbol symbol, Project project, IImmutableSet? documents, +// FindReferencesSearchOptions options, CancellationToken cancellationToken) +// { +// return SpecializedTasks.EmptyImmutableArray(); +// } - public Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) - => SpecializedTasks.EmptyImmutableArray(); +// public Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) +// => SpecializedTasks.EmptyImmutableArray(); - public ValueTask> FindReferencesInDocumentAsync( - ISymbol symbol, Document document, SemanticModel semanticModel, - FindReferencesSearchOptions options, CancellationToken cancellationToken) - { - return new ValueTask>(ImmutableArray.Empty); - } - } -} +// public ValueTask> FindReferencesInDocumentAsync( +// ISymbol symbol, Document document, SemanticModel semanticModel, +// FindReferencesSearchOptions options, CancellationToken cancellationToken) +// { +// return new ValueTask>(ImmutableArray.Empty); +// } +// } +//} diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs index c36ff233c8e61..4855bae98215c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs @@ -16,7 +16,7 @@ internal static class ReferenceFinders public static readonly IReferenceFinder Event = new EventSymbolReferenceFinder(); public static readonly IReferenceFinder Field = new FieldSymbolReferenceFinder(); public static readonly IReferenceFinder Label = new LabelSymbolReferenceFinder(); - public static readonly IReferenceFinder LinkedFiles = new LinkedFileReferenceFinder(); + // public static readonly IReferenceFinder LinkedFiles = new LinkedFileReferenceFinder(); public static readonly IReferenceFinder Local = new LocalSymbolReferenceFinder(); public static readonly IReferenceFinder MethodTypeParameter = new MethodTypeParameterSymbolReferenceFinder(); public static readonly IReferenceFinder NamedType = new NamedTypeSymbolReferenceFinder(); @@ -47,7 +47,7 @@ static ReferenceFinders() ExplicitInterfaceMethod, Field, Label, - LinkedFiles, + // LinkedFiles, Local, MethodTypeParameter, NamedType, diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 2ccc29a638c01..00d1374a82c96 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -30,8 +30,8 @@ private NoOpStreamingFindReferencesProgress() public ValueTask OnCompletedAsync() => default; public ValueTask OnStartedAsync() => default; - public ValueTask OnDefinitionFoundAsync(ISymbol symbol) => default; - public ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location) => default; + public ValueTask OnDefinitionFoundAsync(SymbolGroup group) => default; + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location) => default; public ValueTask OnFindInDocumentStartedAsync(Document document) => default; public ValueTask OnFindInDocumentCompletedAsync(Document document) => default; diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index bf314ea7990ba..c311db326485b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -35,12 +35,6 @@ public ValueTask OnCompletedAsync() return default; } - public ValueTask OnDefinitionFoundAsync(ISymbol symbol) - { - _progress.OnDefinitionFound(symbol); - return default; - } - public ValueTask OnFindInDocumentCompletedAsync(Document document) { _progress.OnFindInDocumentCompleted(document); @@ -53,7 +47,15 @@ public ValueTask OnFindInDocumentStartedAsync(Document document) return default; } - public ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location) + public ValueTask OnDefinitionFoundAsync(SymbolGroup group) + { + foreach (var symbol in group.Symbols) + _progress.OnDefinitionFound(symbol); + + return default; + } + + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location) { _progress.OnReferenceFound(symbol, location); return default; diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 529739f91cb43..c09fc4c08c20b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -23,8 +23,8 @@ internal interface ICallback ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId); ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId); ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId); - ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition); - ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference); + ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group); + ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup group, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference); ValueTask AddLiteralItemsAsync(RemoteServiceCallbackId callbackId, int count); ValueTask LiteralItemCompletedAsync(RemoteServiceCallbackId callbackId); diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 27d9e4a7c2d8f..7ecd288ae597c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -2,14 +2,55 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; +using System.Collections.Immutable; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { + internal class SymbolGroup : IEquatable + { + /// + /// The main symbol of the group (normally the symbol that was searched for). + /// + public ISymbol Symbol { get; } + + /// + /// All the symbols in the group. Will include . + /// + public ImmutableHashSet Symbols { get; } + + private int _hashCode; + + public SymbolGroup(ISymbol symbol, ImmutableArray symbols) + { + Contract.ThrowIfTrue(symbols.IsDefaultOrEmpty); + Symbol = symbol; + Symbols = ImmutableHashSet.CreateRange( + MetadataUnifyingEquivalenceComparer.Instance, symbols); + } + + public override bool Equals(object? obj) + => obj is SymbolGroup group && Equals(group); + + public bool Equals(SymbolGroup? group) + => this == group || (group != null && Symbols.SetEquals(group.Symbols)); + + public override int GetHashCode() + { + if (_hashCode == 0) + { + foreach (var symbol in Symbols) + _hashCode += MetadataUnifyingEquivalenceComparer.Instance.GetHashCode(symbol); + } + + return _hashCode; + } + } + /// /// Reports the progress of the FindReferences operation. Note: these methods may be called on /// any thread. @@ -24,8 +65,8 @@ internal interface IStreamingFindReferencesProgress ValueTask OnFindInDocumentStartedAsync(Document document); ValueTask OnFindInDocumentCompletedAsync(Document document); - ValueTask OnDefinitionFoundAsync(ISymbol symbol); - ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location); + ValueTask OnDefinitionFoundAsync(SymbolGroup group); + ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol symbol, ReferenceLocation location); } internal interface IStreamingFindLiteralReferencesProgress diff --git a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs index 671c5308fdb96..87c7f1a426535 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/StreamingProgressCollector.cs @@ -59,24 +59,25 @@ public ImmutableArray GetReferencedSymbols() public ValueTask OnFindInDocumentCompletedAsync(Document document) => _underlyingProgress.OnFindInDocumentCompletedAsync(document); public ValueTask OnFindInDocumentStartedAsync(Document document) => _underlyingProgress.OnFindInDocumentStartedAsync(document); - public ValueTask OnDefinitionFoundAsync(ISymbol definition) + public ValueTask OnDefinitionFoundAsync(SymbolGroup group) { lock (_gate) { - _symbolToLocations[definition] = new List(); + foreach (var definition in group.Symbols) + _symbolToLocations[definition] = new List(); } - return _underlyingProgress.OnDefinitionFoundAsync(definition); + return _underlyingProgress.OnDefinitionFoundAsync(group); } - public ValueTask OnReferenceFoundAsync(ISymbol definition, ReferenceLocation location) + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location) { lock (_gate) { _symbolToLocations[definition].Add(location); } - return _underlyingProgress.OnReferenceFoundAsync(definition, location); + return _underlyingProgress.OnReferenceFoundAsync(group, definition, location); } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs index a1d4f5963b9c2..42c8c28691b7f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.CallbackDispatcher.cs @@ -39,8 +39,8 @@ public ValueTask ReferenceItemCompletedAsync(RemoteServiceCallbackId callbackId) public ValueTask OnCompletedAsync(RemoteServiceCallbackId callbackId) => GetFindReferencesCallback(callbackId).OnCompletedAsync(); - public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition) - => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(definition); + public ValueTask OnDefinitionFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup) + => GetFindReferencesCallback(callbackId).OnDefinitionFoundAsync(symbolGroup); public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId) => GetFindReferencesCallback(callbackId).OnFindInDocumentCompletedAsync(documentId); @@ -48,8 +48,8 @@ public ValueTask OnFindInDocumentCompletedAsync(RemoteServiceCallbackId callback public ValueTask OnFindInDocumentStartedAsync(RemoteServiceCallbackId callbackId, DocumentId documentId) => GetFindReferencesCallback(callbackId).OnFindInDocumentStartedAsync(documentId); - public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference) - => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(definition, reference); + public ValueTask OnReferenceFoundAsync(RemoteServiceCallbackId callbackId, SerializableSymbolGroup symbolGroup, SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference) + => GetFindReferencesCallback(callbackId).OnReferenceFoundAsync(symbolGroup, definition, reference); public ValueTask OnStartedAsync(RemoteServiceCallbackId callbackId) => GetFindReferencesCallback(callbackId).OnStartedAsync(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index bc687183e8c50..4f8da62bc1292 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -5,6 +5,7 @@ #nullable disable using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Remote; @@ -17,14 +18,15 @@ public static partial class SymbolFinder /// Callback object we pass to the OOP server to hear about the result /// of the FindReferencesEngine as it executes there. /// - internal sealed class FindReferencesServerCallback : IEqualityComparer + internal sealed class FindReferencesServerCallback { private readonly Solution _solution; private readonly IStreamingFindReferencesProgress _progress; private readonly CancellationToken _cancellationToken; private readonly object _gate = new(); - private readonly Dictionary _definitionMap; + private readonly Dictionary _groupMap = new(); + private readonly Dictionary _definitionMap = new(); public FindReferencesServerCallback( Solution solution, @@ -34,7 +36,6 @@ public FindReferencesServerCallback( _solution = solution; _progress = progress; _cancellationToken = cancellationToken; - _definitionMap = new Dictionary(this); } public ValueTask AddItemsAsync(int count) @@ -61,25 +62,26 @@ public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId) return _progress.OnFindInDocumentCompletedAsync(document); } - public async ValueTask OnDefinitionFoundAsync(SerializableSymbolAndProjectId definition) + public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup serializableSymbolGroup) { - var symbol = await definition.TryRehydrateAsync( - _solution, _cancellationToken).ConfigureAwait(false); - - if (symbol == null) + var symbolGroup = await serializableSymbolGroup.TryRehydrateAsync(_solution, _cancellationToken).ConfigureAwait(false); + if (symbolGroup == null) return; lock (_gate) { - _definitionMap[definition] = symbol; + _groupMap[serializableSymbolGroup] = symbolGroup; } - await _progress.OnDefinitionFoundAsync(symbol).ConfigureAwait(false); + await _progress.OnDefinitionFoundAsync(symbolGroup).ConfigureAwait(false); } public async ValueTask OnReferenceFoundAsync( - SerializableSymbolAndProjectId definition, SerializableReferenceLocation reference) + SerializableSymbolGroup serializableSymbolGroup, + SerializableSymbolAndProjectId serializableSymbol, + SerializableReferenceLocation reference) { + SymbolGroup symbolGroup; ISymbol symbol; lock (_gate) { @@ -90,21 +92,18 @@ public async ValueTask OnReferenceFoundAsync( // definition so we can track down that issue. // 2. NFE'ing and failing to show a result, is much better than NFE'ing and then crashing // immediately afterwards. - if (!_definitionMap.TryGetValue(definition, out symbol)) + if (!_groupMap.TryGetValue(serializableSymbolGroup, out symbolGroup) || + !_definitionMap.TryGetValue(serializableSymbol, out symbol)) + { return; + } } var referenceLocation = await reference.RehydrateAsync( _solution, _cancellationToken).ConfigureAwait(false); - await _progress.OnReferenceFoundAsync(symbol, referenceLocation).ConfigureAwait(false); + await _progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation).ConfigureAwait(false); } - - bool IEqualityComparer.Equals(SerializableSymbolAndProjectId x, SerializableSymbolAndProjectId y) - => y.SymbolKeyData.Equals(x.SymbolKeyData); - - int IEqualityComparer.GetHashCode(SerializableSymbolAndProjectId obj) - => obj.SymbolKeyData.GetHashCode(); } } } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index ee978a90e8820..224bcdc05d7f2 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -5,12 +5,15 @@ #nullable disable using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -80,6 +83,7 @@ public static bool TryCreate( result = new SerializableSymbolAndProjectId(SymbolKey.CreateString(symbol, cancellationToken), project.Id); return true; } + public async Task TryRehydrateAsync( Solution solution, CancellationToken cancellationToken) { @@ -192,5 +196,71 @@ private async Task RehydrateAliasAsync( } } + [DataContract] + internal class SerializableSymbolGroup : IEquatable, IEqualityComparer + { + [DataMember(Order = 0)] + public readonly SerializableSymbolAndProjectId Symbol; + [DataMember(Order = 1)] + public readonly HashSet Symbols; + + private int _hashCode; + + public SerializableSymbolGroup( + SerializableSymbolAndProjectId symbol, + HashSet symbols) + { + Symbol = symbol; + Symbols = new HashSet(symbols, this); + } + + bool IEqualityComparer.Equals(SerializableSymbolAndProjectId x, SerializableSymbolAndProjectId y) + => y.SymbolKeyData.Equals(x.SymbolKeyData); + + int IEqualityComparer.GetHashCode(SerializableSymbolAndProjectId obj) + => obj.SymbolKeyData.GetHashCode(); + + public override bool Equals(object obj) + => obj is SerializableSymbolGroup group && Equals(group); + + public bool Equals(SerializableSymbolGroup other) + { + if (this == other) + return true; + + return other != null && this.Symbols.SetEquals(other.Symbols); + } + + public override int GetHashCode() + { + if (_hashCode == 0) + { + foreach (var symbol in Symbols) + _hashCode += symbol.SymbolKeyData.GetHashCode(); + } + + return _hashCode; + } + + public async Task TryRehydrateAsync(Solution solution, CancellationToken cancellationToken) + { + var symbol = await this.Symbol.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (symbol == null) + return null; + + using var _ = ArrayBuilder.GetInstance(out var symbols); + foreach (var symbolAndProjectId in this.Symbols) + { + symbol = await symbolAndProjectId.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (symbol == null) + return null; + + symbols.Add(symbol); + } + + return new SymbolGroup(symbol, symbols.ToImmutable()); + } + } + #endregion } From 6ebeea7a938ee40861f8b36540742688da38ffd8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 11:31:43 -0700 Subject: [PATCH 113/220] Implement cascadign logic --- .../FindReferencesSearchEngine_MapCreation.cs | 10 ++++- .../Finders/LinkedFileReferenceFinder.cs | 43 ------------------- .../IStreamingFindReferencesProgress.cs | 1 + 3 files changed, 9 insertions(+), 45 deletions(-) delete mode 100644 src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index 49cd9d8617342..3daf777afe3d6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -167,9 +167,15 @@ private async Task DetermineAllSymbolsCoreAsync( } } - private Task DetermineSymbolGroupAsync(ISymbol searchSymbol) + private async Task DetermineSymbolGroupAsync(ISymbol searchSymbol) { - throw new NotImplementedException(); + if (!_options.Cascade) + return new SymbolGroup(searchSymbol, ImmutableArray.Create(searchSymbol)); + + var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync( + searchSymbol, _solution, _cancellationToken).ConfigureAwait(false); + + return new SymbolGroup(searchSymbol, linkedSymbols); } private void AddSymbolTasks( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs deleted file mode 100644 index 91e9dd222fdd5..0000000000000 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/LinkedFileReferenceFinder.cs +++ /dev/null @@ -1,43 +0,0 @@ -//// Licensed to the .NET Foundation under one or more agreements. -//// The .NET Foundation licenses this file to you under the MIT license. -//// See the LICENSE file in the project root for more information. - -//using System.Collections.Immutable; -//using System.Threading; -//using System.Threading.Tasks; -//using Roslyn.Utilities; - -//namespace Microsoft.CodeAnalysis.FindSymbols.Finders -//{ -// internal class LinkedFileReferenceFinder : IReferenceFinder -// { -// public async Task> DetermineCascadedSymbolsAsync( -// ISymbol symbol, Solution solution, IImmutableSet? projects, -// FindReferencesSearchOptions options, FindReferencesCascadeDirection cascadeDirection, -// CancellationToken cancellationToken) -// { -// if (!options.Cascade) -// return ImmutableArray<(ISymbol symbol, FindReferencesCascadeDirection cascadeDirection)>.Empty; - -// var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync(symbol, solution, cancellationToken).ConfigureAwait(false); -// return linkedSymbols.SelectAsArray(s => (s, cascadeDirection)); -// } - -// public Task> DetermineDocumentsToSearchAsync( -// ISymbol symbol, Project project, IImmutableSet? documents, -// FindReferencesSearchOptions options, CancellationToken cancellationToken) -// { -// return SpecializedTasks.EmptyImmutableArray(); -// } - -// public Task> DetermineProjectsToSearchAsync(ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) -// => SpecializedTasks.EmptyImmutableArray(); - -// public ValueTask> FindReferencesInDocumentAsync( -// ISymbol symbol, Document document, SemanticModel semanticModel, -// FindReferencesSearchOptions options, CancellationToken cancellationToken) -// { -// return new ValueTask>(ImmutableArray.Empty); -// } -// } -//} diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 7ecd288ae597c..89409071f304a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -28,6 +28,7 @@ internal class SymbolGroup : IEquatable public SymbolGroup(ISymbol symbol, ImmutableArray symbols) { Contract.ThrowIfTrue(symbols.IsDefaultOrEmpty); + Contract.ThrowIfFalse(symbols.Contains(symbol)); Symbol = symbol; Symbols = ImmutableHashSet.CreateRange( MetadataUnifyingEquivalenceComparer.Instance, symbols); From d85af7c965b580476cbbfd352b9dc5307e5ea4d2 Mon Sep 17 00:00:00 2001 From: Chris Sienkiewicz Date: Tue, 13 Apr 2021 11:46:24 -0700 Subject: [PATCH 114/220] Add sg v2 status (#52611) --- docs/Language Feature Status.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index ee0786ba3d7ba..5416425dc3d70 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -29,6 +29,7 @@ efforts behind them. | [List patterns](https://github.com/dotnet/csharplang/issues/3435) | [list-patterns](https://github.com/dotnet/roslyn/tree/features/list-patterns) | [In Progress](https://github.com/dotnet/roslyn/issues/51289) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | | [Extended property patterns](https://github.com/dotnet/csharplang/issues/4394) | [extended-property-patterns](https://github.com/dotnet/roslyn/tree/features/extended-property-patterns) | In Progress | [alrz](https://github.com/alrz) | [333fred](https://github.com/333fred) (Tentative) | [333fred](https://github.com/333fred) | | [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174) | main | [In Progress](https://github.com/dotnet/roslyn/issues/52031) | [thomaslevesque](https://github.com/thomaslevesque/) | [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | +| [Source Generator V2 APIs](https://github.com/dotnet/roslyn/issues/51257) | [features/source-generators](https://github.com/dotnet/roslyn/tree/features/source-generators) | [In Progress](https://github.com/dotnet/roslyn/issues/51257) | [chsienki](https://github.com/chsienki/) | [rikkigibson](https://github.com/rikkigibson), [jaredpar](https://github.com/jaredpar), [cston](https://github.com/cston) | N/A | # VB 16.9 From 6f2a28664c60cd29bcc6af3582ade3782d4a4084 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 12:40:24 -0700 Subject: [PATCH 115/220] Create definition item from symbol group --- ...stractFindUsagesService.ProgressAdapter.cs | 21 ++- .../IDefinitionsAndReferencesFactory.cs | 133 +++++++++++++++++- .../IStreamingFindReferencesProgress.cs | 16 ++- .../Core/Portable/Remote/RemoteArguments.cs | 29 ++-- .../SymbolFinder/RemoteSymbolFinderService.cs | 13 +- 5 files changed, 175 insertions(+), 37 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs index 7b0f007352b28..e605ce3a0ef3f 100644 --- a/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs +++ b/src/EditorFeatures/Core/FindUsages/AbstractFindUsagesService.ProgressAdapter.cs @@ -66,8 +66,7 @@ private class FindReferencesProgressAdapter : IStreamingFindReferencesProgress /// This dictionary allows us to make that mapping once and then keep it around for /// all future callbacks. /// - private readonly Dictionary _definitionToItem = - new(MetadataUnifyingEquivalenceComparer.Instance); + private readonly Dictionary _definitionToItem = new(); private readonly SemaphoreSlim _gate = new(initialCount: 1); @@ -93,44 +92,42 @@ public FindReferencesProgressAdapter( // used by the FAR engine to the INavigableItems used by the streaming FAR // feature. - private async ValueTask GetDefinitionItemAsync(ISymbol definition) + private async ValueTask GetDefinitionItemAsync(SymbolGroup group) { var cancellationToken = _context.CancellationToken; using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - if (!_definitionToItem.TryGetValue(definition, out var definitionItem)) + if (!_definitionToItem.TryGetValue(group, out var definitionItem)) { - definitionItem = await definition.ToClassifiedDefinitionItemAsync( + definitionItem = await group.ToClassifiedDefinitionItemAsync( _solution, isPrimary: _definitionToItem.Count == 0, includeHiddenLocations: false, _options, _context.CancellationToken).ConfigureAwait(false); - _definitionToItem[definition] = definitionItem; + _definitionToItem[group] = definitionItem; } return definitionItem; } } - public async ValueTask OnDefinitionFoundAsync(ISymbol definition) + public async ValueTask OnDefinitionFoundAsync(SymbolGroup group) { - var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false); + var definitionItem = await GetDefinitionItemAsync(group).ConfigureAwait(false); await _context.OnDefinitionFoundAsync(definitionItem).ConfigureAwait(false); } - public async ValueTask OnReferenceFoundAsync(ISymbol definition, ReferenceLocation location) + public async ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation location) { - var definitionItem = await GetDefinitionItemAsync(definition).ConfigureAwait(false); + var definitionItem = await GetDefinitionItemAsync(group).ConfigureAwait(false); var referenceItem = await location.TryCreateSourceReferenceItemAsync( definitionItem, includeHiddenLocations: false, cancellationToken: _context.CancellationToken).ConfigureAwait(false); if (referenceItem != null) - { await _context.OnReferenceFoundAsync(referenceItem).ConfigureAwait(false); - } } } } diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 2daaaabe6682a..7a1adbb318d08 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Composition; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -17,6 +18,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; @@ -83,8 +85,111 @@ public static Task ToClassifiedDefinitionItemAsync( options, cancellationToken); } + public static Task ToClassifiedDefinitionItemAsync( + this SymbolGroup group, + Solution solution, + bool isPrimary, + bool includeHiddenLocations, + FindReferencesSearchOptions options, + CancellationToken cancellationToken) + { + return ToDefinitionItemAsync( + group, solution, isPrimary, + includeHiddenLocations, includeClassifiedSpans: true, + options, cancellationToken); + } + private static async Task ToDefinitionItemAsync( - this ISymbol definition, + this SymbolGroup group, + Solution solution, + bool isPrimary, + bool includeHiddenLocations, + bool includeClassifiedSpans, + FindReferencesSearchOptions options, + CancellationToken cancellationToken) + { + var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); + return await ToDefinitionItemAsync( + group.PrimarySymbol, + allLocations, + GetAdditionalDisplayParts(solution, group, allLocations), + solution, + isPrimary, + includeHiddenLocations, + includeClassifiedSpans, + options, + cancellationToken).ConfigureAwait(false); + } + + private static ImmutableArray GetAdditionalDisplayParts( + Solution solution, SymbolGroup group, ImmutableArray allLocations) + { + // For linked symbols, add a suffix to the symbol display to say all the projects it was defined in. + if (group.Symbols.Count >= 2) + { + var primarySourceLocation = group.PrimarySymbol.Locations.FirstOrDefault(loc => loc.IsInSource); + var primaryDocument = solution.GetDocument(primarySourceLocation?.SourceTree); + if (primaryDocument != null) + { + var firstProject = primaryDocument.Project; + var (firstProjectName, firstProjectFlavor) = firstProject.State.NameAndFlavor; + + if (firstProjectName != null) + { + using var _ = ArrayBuilder.GetInstance(out var flavors); + flavors.Add(firstProjectFlavor!); + + // Now, do the same for the other projects where we had a match. As above, if we can't figure out the + // simple name/flavor, or if the simple project name doesn't match the simple project name we started + // with then we can't merge these. + foreach (var location in allLocations) + { + var document = solution.GetDocument(location.SourceTree); + var project = document?.Project; + if (project != null) + { + var (projectName, projectFlavor) = project.State.NameAndFlavor; + if (projectName == firstProjectName) + flavors.Add(projectFlavor!); + } + } + + flavors.RemoveDuplicates(); + flavors.Sort(); + + return ImmutableArray.Create(new TaggedText(TextTags.Text, $" ({string.Join(", ", flavors)})")); + } + } + } + + return ImmutableArray.Empty; + } + + private static Task ToDefinitionItemAsync( + ISymbol definition, + Solution solution, + bool isPrimary, + bool includeHiddenLocations, + bool includeClassifiedSpans, + FindReferencesSearchOptions options, + CancellationToken cancellationToken) + { + return ToDefinitionItemAsync( + definition, + definition.Locations, + ImmutableArray.Empty, + solution, + isPrimary, + includeHiddenLocations, + includeClassifiedSpans, + options, + cancellationToken); + } + + private static async Task ToDefinitionItemAsync( + ISymbol definition, + ImmutableArray locations, + ImmutableArray additionalDisplayParts, Solution solution, bool isPrimary, bool includeHiddenLocations, @@ -105,23 +210,22 @@ private static async Task ToDefinitionItemAsync( definition = definition.OriginalDefinition; } - var displayParts = GetDisplayParts(definition); + var displayParts = GetDisplayParts(definition).AddRange(additionalDisplayParts); var nameDisplayParts = definition.ToDisplayParts(s_namePartsFormat).ToTaggedText(); var tags = GlyphTags.GetTags(definition.GetGlyph()); var displayIfNoReferences = definition.ShouldShowWithNoReferenceLocations( options, showMetadataSymbolsWithoutReferences: false); - using var sourceLocationsDisposer = ArrayBuilder.GetInstance(out var sourceLocations); - var properties = GetProperties(definition, isPrimary); // If it's a namespace, don't create any normal location. Namespaces // come from many different sources, but we'll only show a single // root definition node for it. That node won't be navigable. + using var _ = ArrayBuilder.GetInstance(out var sourceLocations); if (definition.Kind != SymbolKind.Namespace) { - foreach (var location in definition.Locations) + foreach (var location in locations) { if (location.IsInMetadata) { @@ -140,6 +244,11 @@ private static async Task ToDefinitionItemAsync( var document = solution.GetDocument(location.SourceTree); if (document != null) { + // If we have linked symbols, they may provide locations collide with what we already have + // entries for. Specifically, they're the same *file*/*span*, but not the same *doc*/*span. + if (CollidesWithLinkedLocation(sourceLocations, document, location.SourceSpan)) + continue; + var documentLocation = !includeClassifiedSpans ? new DocumentSpan(document, location.SourceSpan) : await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync( @@ -166,6 +275,20 @@ private static async Task ToDefinitionItemAsync( return DefinitionItem.Create( tags, displayParts, sourceLocations.ToImmutable(), nameDisplayParts, properties, displayableProperties, displayIfNoReferences); + + static bool CollidesWithLinkedLocation(ArrayBuilder sourceLocations, Document document, TextSpan sourceSpan) + { + foreach (var existingLoc in sourceLocations) + { + if (sourceSpan == existingLoc.SourceSpan && + existingLoc.Document.FilePath == document.FilePath) + { + return true; + } + } + + return false; + } } private static ImmutableDictionary GetProperties(ISymbol definition, bool isPrimary) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 89409071f304a..02b79baf673d8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; @@ -16,20 +17,25 @@ internal class SymbolGroup : IEquatable /// /// The main symbol of the group (normally the symbol that was searched for). /// - public ISymbol Symbol { get; } + public ISymbol PrimarySymbol { get; } /// - /// All the symbols in the group. Will include . + /// All the symbols in the group. Will include . /// public ImmutableHashSet Symbols { get; } private int _hashCode; - public SymbolGroup(ISymbol symbol, ImmutableArray symbols) + public SymbolGroup(ISymbol primarySymbol, ImmutableArray symbols) { Contract.ThrowIfTrue(symbols.IsDefaultOrEmpty); - Contract.ThrowIfFalse(symbols.Contains(symbol)); - Symbol = symbol; + Contract.ThrowIfFalse(symbols.Contains(primarySymbol)); + + // We should only get an actual group of symbols if these were from source. + // Metadata symbols never form a group. + Contract.ThrowIfTrue(symbols.Length >= 2 && symbols.Any(s => s.Locations.Any(loc => loc.IsInMetadata))); + + PrimarySymbol = primarySymbol; Symbols = ImmutableHashSet.CreateRange( MetadataUnifyingEquivalenceComparer.Instance, symbols); } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 224bcdc05d7f2..34b0eb683adbb 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -200,17 +200,17 @@ private async Task RehydrateAliasAsync( internal class SerializableSymbolGroup : IEquatable, IEqualityComparer { [DataMember(Order = 0)] - public readonly SerializableSymbolAndProjectId Symbol; + public readonly SerializableSymbolAndProjectId PrimarySymbol; [DataMember(Order = 1)] public readonly HashSet Symbols; private int _hashCode; public SerializableSymbolGroup( - SerializableSymbolAndProjectId symbol, + SerializableSymbolAndProjectId primarySymbol, HashSet symbols) { - Symbol = symbol; + PrimarySymbol = primarySymbol; Symbols = new HashSet(symbols, this); } @@ -242,23 +242,32 @@ public override int GetHashCode() return _hashCode; } + public static SerializableSymbolGroup Dehydrate(Solution solution, SymbolGroup group, CancellationToken cancellationToken) + { + return new SerializableSymbolGroup( + SerializableSymbolAndProjectId.Dehydrate(solution, group.PrimarySymbol, cancellationToken), + new HashSet( + group.Symbols.Select( + s => SerializableSymbolAndProjectId.Dehydrate(solution, s, cancellationToken)))); + } + public async Task TryRehydrateAsync(Solution solution, CancellationToken cancellationToken) { - var symbol = await this.Symbol.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); - if (symbol == null) + var primarySymbol = await this.PrimarySymbol.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (primarySymbol == null) return null; using var _ = ArrayBuilder.GetInstance(out var symbols); foreach (var symbolAndProjectId in this.Symbols) { - symbol = await symbolAndProjectId.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); - if (symbol == null) + primarySymbol = await symbolAndProjectId.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (primarySymbol == null) return null; - symbols.Add(symbol); + symbols.Add(primarySymbol); } - return new SymbolGroup(symbol, symbols.ToImmutable()); + return new SymbolGroup(primarySymbol, symbols.ToImmutable()); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs index c51648acb00ea..83208f13592f1 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/SymbolFinder/RemoteSymbolFinderService.cs @@ -237,18 +237,21 @@ public ValueTask OnFindInDocumentStartedAsync(Document document) public ValueTask OnFindInDocumentCompletedAsync(Document document) => _callback.InvokeAsync((callback, cancellationToken) => callback.OnFindInDocumentCompletedAsync(_callbackId, document.Id), _cancellationToken); - public ValueTask OnDefinitionFoundAsync(ISymbol definition) + public ValueTask OnDefinitionFoundAsync(SymbolGroup group) { - var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, _cancellationToken); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedDefinition), _cancellationToken); + var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, _cancellationToken); + return _callback.InvokeAsync( + (callback, cancellationToken) => callback.OnDefinitionFoundAsync(_callbackId, dehydratedGroup), _cancellationToken); } - public ValueTask OnReferenceFoundAsync(ISymbol definition, ReferenceLocation reference) + public ValueTask OnReferenceFoundAsync(SymbolGroup group, ISymbol definition, ReferenceLocation reference) { + var dehydratedGroup = SerializableSymbolGroup.Dehydrate(_solution, group, _cancellationToken); var dehydratedDefinition = SerializableSymbolAndProjectId.Dehydrate(_solution, definition, _cancellationToken); var dehydratedReference = SerializableReferenceLocation.Dehydrate(reference, _cancellationToken); - return _callback.InvokeAsync((callback, cancellationToken) => callback.OnReferenceFoundAsync(_callbackId, dehydratedDefinition, dehydratedReference), _cancellationToken); + return _callback.InvokeAsync( + (callback, cancellationToken) => callback.OnReferenceFoundAsync(_callbackId, dehydratedGroup, dehydratedDefinition, dehydratedReference), _cancellationToken); } public ValueTask AddItemsAsync(int count) From ae3c9fb8b169d733b99afa208691aac5b8cc5c5d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 14:38:36 -0700 Subject: [PATCH 116/220] Working --- .../IDefinitionsAndReferencesFactory.cs | 19 ---- ...bstractTableDataSourceFindUsagesContext.cs | 25 +---- .../WithReferencesFindUsagesContext.cs | 41 ++++++--- .../WithoutReferencesFindUsagesContext.cs | 3 +- .../Entries/AbstractDocumentSpanEntry.cs | 9 +- .../Entries/DefinitionItemEntry.cs | 10 +- .../Entries/DocumentSpanEntry.cs | 91 +++++++++++++++++-- .../FindReferences/RoslynDefinitionBucket.cs | 17 ++++ .../StreamingFindUsagesPresenter.cs | 18 ++++ .../Core/CompilerExtensions.projitems | 3 + 10 files changed, 166 insertions(+), 70 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 7a1adbb318d08..6154fdeea3346 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -244,11 +244,6 @@ private static async Task ToDefinitionItemAsync( var document = solution.GetDocument(location.SourceTree); if (document != null) { - // If we have linked symbols, they may provide locations collide with what we already have - // entries for. Specifically, they're the same *file*/*span*, but not the same *doc*/*span. - if (CollidesWithLinkedLocation(sourceLocations, document, location.SourceSpan)) - continue; - var documentLocation = !includeClassifiedSpans ? new DocumentSpan(document, location.SourceSpan) : await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync( @@ -275,20 +270,6 @@ private static async Task ToDefinitionItemAsync( return DefinitionItem.Create( tags, displayParts, sourceLocations.ToImmutable(), nameDisplayParts, properties, displayableProperties, displayIfNoReferences); - - static bool CollidesWithLinkedLocation(ArrayBuilder sourceLocations, Document document, TextSpan sourceSpan) - { - foreach (var existingLoc in sourceLocations) - { - if (sourceSpan == existingLoc.SourceSpan && - existingLoc.Document.FilePath == document.FilePath) - { - return true; - } - } - - return false; - } } private static ImmutableDictionary GetProperties(ISymbol definition, bool isPrimary) diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs index 962439e04a93c..4dc76773b746b 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/AbstractTableDataSourceFindUsagesContext.cs @@ -317,23 +317,6 @@ public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definitio protected abstract ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem definition); - protected async Task<(Guid, string projectName, SourceText)> GetGuidAndProjectNameAndSourceTextAsync(Document document) - { - // The FAR system needs to know the guid for the project that a def/reference is - // from (to support features like filtering). Normally that would mean we could - // only support this from a VisualStudioWorkspace. However, we want till work - // in cases like Any-Code (which does not use a VSWorkspace). So we are tolerant - // when we have another type of workspace. This means we will show results, but - // certain features (like filtering) may not work in that context. - var vsWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspace; - - var projectName = document.Project.Name; - var guid = vsWorkspace?.GetProjectGuid(document.Project.Id) ?? Guid.Empty; - - var sourceText = await document.GetTextAsync(CancellationToken).ConfigureAwait(false); - return (guid, projectName, sourceText); - } - protected async Task TryCreateDocumentSpanEntryAsync( RoslynDefinitionBucket definitionBucket, DocumentSpan documentSpan, @@ -341,8 +324,7 @@ public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definitio SymbolUsageInfo symbolUsageInfo, ImmutableDictionary additionalProperties) { - var document = documentSpan.Document; - var (guid, projectName, sourceText) = await GetGuidAndProjectNameAndSourceTextAsync(document).ConfigureAwait(false); + var sourceText = await documentSpan.Document.GetTextAsync(CancellationToken).ConfigureAwait(false); var (excerptResult, lineText) = await ExcerptAsync(sourceText, documentSpan).ConfigureAwait(false); var mappedDocumentSpan = await AbstractDocumentSpanEntry.TryMapAndGetFirstAsync(documentSpan, sourceText, CancellationToken).ConfigureAwait(false); @@ -352,12 +334,11 @@ public sealed override ValueTask OnDefinitionFoundAsync(DefinitionItem definitio return null; } - return new DocumentSpanEntry( + return DocumentSpanEntry.TryCreate( this, definitionBucket, + documentSpan, spanKind, - projectName, - guid, mappedDocumentSpan.Value, excerptResult, lineText, diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs index 1ce447931d99b..3bb10f34631f2 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.DocumentHighlighting; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; using Roslyn.Utilities; @@ -64,9 +65,21 @@ private async Task AddDeclarationEntriesAsync(DefinitionItem definition, bool ex // lock, and I'd like to avoid that. That does mean that we might do extra // work if multiple threads end up down this path. But only one of them will // win when we access the lock below. - using var _ = ArrayBuilder.GetInstance(out var declarations); + + using var _1 = ArrayBuilder.GetInstance(out var declarations); + using var _2 = PooledHashSet<(string? filePath, TextSpan span)>.GetInstance(out var seenLocations); foreach (var declarationLocation in definition.SourceSpans) { + // Because of things like linked files, we may have a source symbol showing up in multiple + // different locations that are effectively at the exact same navigation location for the user. + // i.e. they're the same file/span. Showing multiple entries for these is just noisy and + // gets worse and worse with shared projects and whatnot. So, we collapse things down to + // only show a single entry for each unique file/span pair. Note that we check filepath, + // not 'Document' as linked files will have unique Document instances in each project context + // they are linked into. + if (!seenLocations.Add((declarationLocation.Document.FilePath, declarationLocation.SourceSpan))) + continue; + var definitionEntry = await TryCreateDocumentSpanEntryAsync( definitionBucket, declarationLocation, HighlightSpanKind.Definition, SymbolUsageInfo.None, additionalProperties: definition.DisplayableProperties).ConfigureAwait(false); declarations.AddIfNotNull(definitionEntry); @@ -117,7 +130,7 @@ protected override ValueTask OnReferenceFoundWorkerAsync(SourceReferenceItem ref addToEntriesWhenNotGroupingByDefinition: true); } - protected async ValueTask OnEntryFoundAsync( + private async ValueTask OnEntryFoundAsync( DefinitionItem definition, Func> createEntryAsync, bool addToEntriesWhenGroupingByDefinition, @@ -136,22 +149,24 @@ protected async ValueTask OnEntryFoundAsync( // First find the bucket corresponding to our definition. var definitionBucket = GetOrCreateDefinitionBucket(definition, expandedByDefault); var entry = await createEntryAsync(definitionBucket).ConfigureAwait(false); - if (entry == null) - { - return; - } + + // Proceed, even if we didn't create an entry. It's possible that we augmented + // an existing entry and we want the UI to refresh to show the results of that. lock (Gate) { - // Once we can make the new entry, add it to the appropriate list. - if (addToEntriesWhenGroupingByDefinition) + if (entry != null) { - EntriesWhenGroupingByDefinition = EntriesWhenGroupingByDefinition.Add(entry); - } + // Once we can make the new entry, add it to the appropriate list. + if (addToEntriesWhenGroupingByDefinition) + { + EntriesWhenGroupingByDefinition = EntriesWhenGroupingByDefinition.Add(entry); + } - if (addToEntriesWhenNotGroupingByDefinition) - { - EntriesWhenNotGroupingByDefinition = EntriesWhenNotGroupingByDefinition.Add(entry); + if (addToEntriesWhenNotGroupingByDefinition) + { + EntriesWhenNotGroupingByDefinition = EntriesWhenNotGroupingByDefinition.Add(entry); + } } CurrentVersionNumber++; diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs index 8380f1718b23e..cadbe4c513b33 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithoutReferencesFindUsagesContext.cs @@ -99,7 +99,8 @@ protected override async ValueTask OnDefinitionFoundWorkerAsync(DefinitionItem d RoslynDefinitionBucket definitionBucket, DefinitionItem definition) { var documentSpan = definition.SourceSpans[0]; - var (guid, projectName, sourceText) = await GetGuidAndProjectNameAndSourceTextAsync(documentSpan.Document).ConfigureAwait(false); + var (guid, projectName, _) = GetGuidAndProjectInfo(documentSpan.Document); + var sourceText = await documentSpan.Document.GetTextAsync(CancellationToken).ConfigureAwait(false); var lineText = AbstractDocumentSpanEntry.GetLineContainingPosition(sourceText, documentSpan.SourceSpan.Start); var mappedDocumentSpan = await AbstractDocumentSpanEntry.TryMapAndGetFirstAsync(documentSpan, sourceText, CancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs index 39f065ff9731a..b047a5fa182fe 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/AbstractDocumentSpanEntry.cs @@ -17,12 +17,11 @@ internal partial class StreamingFindUsagesPresenter { /// /// Base type of all s that represent some source location in - /// a . Navigation to that location is provided by this type. + /// a . Navigation to that location is provided by this type. /// Subclasses can be used to provide customized line text to display in the entry. /// private abstract class AbstractDocumentSpanEntry : AbstractItemEntry { - private readonly string _projectName; private readonly object _boxedProjectGuid; private readonly SourceText _lineText; @@ -31,26 +30,26 @@ private abstract class AbstractDocumentSpanEntry : AbstractItemEntry protected AbstractDocumentSpanEntry( AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, - string projectName, Guid projectGuid, SourceText lineText, MappedSpanResult mappedSpanResult) : base(definitionBucket, context.Presenter) { - _projectName = projectName; _boxedProjectGuid = projectGuid; _lineText = lineText; _mappedSpanResult = mappedSpanResult; } + protected abstract string GetProjectName(); + protected override object? GetValueWorker(string keyName) => keyName switch { StandardTableKeyNames.DocumentName => _mappedSpanResult.FilePath, StandardTableKeyNames.Line => _mappedSpanResult.LinePositionSpan.Start.Line, StandardTableKeyNames.Column => _mappedSpanResult.LinePositionSpan.Start.Character, - StandardTableKeyNames.ProjectName => _projectName, + StandardTableKeyNames.ProjectName => GetProjectName(), StandardTableKeyNames.ProjectGuid => _boxedProjectGuid, StandardTableKeyNames.Text => _lineText.ToString().Trim(), _ => null, diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs index 972c15de286f0..109a9fd0db4c5 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DefinitionItemEntry.cs @@ -20,17 +20,23 @@ internal partial class StreamingFindUsagesPresenter /// private class DefinitionItemEntry : AbstractDocumentSpanEntry { + private readonly string _projectName; + public DefinitionItemEntry( AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, - string documentName, + string projectName, Guid projectGuid, SourceText lineText, MappedSpanResult mappedSpanResult) - : base(context, definitionBucket, documentName, projectGuid, lineText, mappedSpanResult) + : base(context, definitionBucket, projectGuid, lineText, mappedSpanResult) { + _projectName = projectName; } + protected override string GetProjectName() + => _projectName; + protected override IList CreateLineTextInlines() => DefinitionBucket.DefinitionItem.DisplayParts.ToInlines(Presenter.ClassificationFormatMap, Presenter.TypeMap); } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index bd869293347ba..f6b9490f85f27 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -41,28 +41,103 @@ private class DocumentSpanEntry : AbstractDocumentSpanEntry, ISupportsNavigation private readonly SymbolReferenceKinds _symbolReferenceKinds; private readonly ImmutableDictionary _customColumnsData; - public DocumentSpanEntry( + private readonly string _rawProjectName; + private readonly List _flavors = new(); + + private string? _cachedProjectName; + + private DocumentSpanEntry( AbstractTableDataSourceFindUsagesContext context, RoslynDefinitionBucket definitionBucket, - HighlightSpanKind spanKind, - string documentName, + string rawProjectName, + string? projectFlavor, Guid projectGuid, + HighlightSpanKind spanKind, MappedSpanResult mappedSpanResult, ExcerptResult excerptResult, SourceText lineText, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary customColumnsData) : base(context, - definitionBucket, - documentName, - projectGuid, - lineText, - mappedSpanResult) + definitionBucket, + projectGuid, + lineText, + mappedSpanResult) { _spanKind = spanKind; _excerptResult = excerptResult; _symbolReferenceKinds = symbolUsageInfo.ToSymbolReferenceKinds(); _customColumnsData = customColumnsData; + + _rawProjectName = rawProjectName; + this.AddFlavor(projectFlavor); + } + + protected override string GetProjectName() + { + lock (_flavors) + { + if (_cachedProjectName == null) + { + _cachedProjectName = _flavors.Count < 2 + ? _rawProjectName + : $"{_rawProjectName} ({string.Join(", ", _flavors)})"; + } + + return _cachedProjectName; + } + } + + private void AddFlavor(string? projectFlavor) + { + if (projectFlavor == null) + return; + + lock (_flavors) + { + if (_flavors.Contains(projectFlavor)) + return; + + _flavors.Add(projectFlavor); + _cachedProjectName = null; + } + } + + public static DocumentSpanEntry? TryCreate( + AbstractTableDataSourceFindUsagesContext context, + RoslynDefinitionBucket definitionBucket, + DocumentSpan documentSpan, + HighlightSpanKind spanKind, + MappedSpanResult mappedSpanResult, + ExcerptResult excerptResult, + SourceText lineText, + SymbolUsageInfo symbolUsageInfo, + ImmutableDictionary customColumnsData) + { + var document = documentSpan.Document; + var (guid, projectName, projectFlavor) = GetGuidAndProjectInfo(document); + var entry = new DocumentSpanEntry( + context, definitionBucket, + projectName, projectFlavor, guid, + spanKind, mappedSpanResult, excerptResult, + lineText, symbolUsageInfo, customColumnsData); + + // Because of things like linked files, we may have a reference up in multiple + // different locations that are effectively at the exact same navigation location + // for the user. i.e. they're the same file/span. Showing multiple entries for these + // is just noisy and gets worse and worse with shared projects and whatnot. So, we + // collapse things down to only show a single entry for each unique file/span pair. + var winningEntry = definitionBucket.GetOrAddEntry(documentSpan, entry); + + // If we were the one that successfully added this entry to the bucket, then pass out to + // be put in the ui. + if (winningEntry == entry) + return entry; + + // We were not the winner. Add our flavor to the entry that already exists, but throw + // away the item we created as we do not want to add it to the ui. + winningEntry.AddFlavor(projectFlavor); + return null; } protected override IList CreateLineTextInlines() diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs index 59c9464dfce9d..f0f66726823b5 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/RoslynDefinitionBucket.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Wpf; using Microsoft.CodeAnalysis.FindUsages; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Shell.FindAllReferences; using Microsoft.VisualStudio.Shell.TableControl; using Microsoft.VisualStudio.Shell.TableManager; @@ -24,6 +25,13 @@ private class RoslynDefinitionBucket : DefinitionBucket, ISupportsNavigation public readonly DefinitionItem DefinitionItem; + /// + /// Due to linked files, we may have results for several locations that are all effectively + /// the same file/span. So we represent this as one entry with several project flavors. If + /// we get more than one flavor, we'll show that the user in the UI. + /// + private readonly Dictionary<(string? filePath, TextSpan span), DocumentSpanEntry> _locationToEntry = new(); + public RoslynDefinitionBucket( string name, bool expandedByDefault, @@ -105,6 +113,15 @@ public override bool TryCreateStringContent(out string? content) return null; } + + public DocumentSpanEntry GetOrAddEntry(DocumentSpan documentSpan, DocumentSpanEntry entry) + { + var key = (documentSpan.Document.FilePath, documentSpan.SourceSpan); + lock (_locationToEntry) + { + return _locationToEntry.GetOrAdd(key, entry); + } + } } } } diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs index ca0c054e18dba..78ac1d6adacc2 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/StreamingFindUsagesPresenter.cs @@ -255,5 +255,23 @@ private void SetDefinitionGroupingPriority(IFindAllReferencesWindow window, int tableControl.SetColumnStates(newColumns); } + + protected static (Guid, string projectName, string? projectFlavor) GetGuidAndProjectInfo(Document document) + { + // The FAR system needs to know the guid for the project that a def/reference is + // from (to support features like filtering). Normally that would mean we could + // only support this from a VisualStudioWorkspace. However, we want till work + // in cases like Any-Code (which does not use a VSWorkspace). So we are tolerant + // when we have another type of workspace. This means we will show results, but + // certain features (like filtering) may not work in that context. + var vsWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspace; + + var (projectName, projectFlavor) = document.Project.State.NameAndFlavor; + projectName ??= document.Project.Name; + + var guid = vsWorkspace?.GetProjectGuid(document.Project.Id) ?? Guid.Empty; + + return (guid, projectName, projectFlavor); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index 0cd48b4c2c189..a5f7a791adce2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -55,6 +55,9 @@ Collections\ArrayBuilderExtensions.cs + + Collections\DictionaryExtensions.cs + Collections\Boxes.cs From 04b0d7bb418e711b14bf5ce9b21f48d0aa1f34fc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:16:51 -0700 Subject: [PATCH 117/220] Share code --- .../IDefinitionsAndReferencesFactory.cs | 53 ++++++------------- .../NavigateTo/RoslynNavigateToItem.cs | 38 +------------ .../Shared/Extensions/ProjectExtensions.cs | 51 ++++++++++++++++++ .../Core/CompilerExtensions.projitems | 2 +- 4 files changed, 70 insertions(+), 74 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 6154fdeea3346..550eb8366e290 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -112,57 +112,38 @@ private static async Task ToDefinitionItemAsync( return await ToDefinitionItemAsync( group.PrimarySymbol, allLocations, - GetAdditionalDisplayParts(solution, group, allLocations), + GetAdditionalDisplayParts(), solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken).ConfigureAwait(false); - } - private static ImmutableArray GetAdditionalDisplayParts( - Solution solution, SymbolGroup group, ImmutableArray allLocations) - { - // For linked symbols, add a suffix to the symbol display to say all the projects it was defined in. - if (group.Symbols.Count >= 2) + ImmutableArray GetAdditionalDisplayParts() { - var primarySourceLocation = group.PrimarySymbol.Locations.FirstOrDefault(loc => loc.IsInSource); - var primaryDocument = solution.GetDocument(primarySourceLocation?.SourceTree); - if (primaryDocument != null) + // For linked symbols, add a suffix to the symbol display to say all the projects it was defined in. + if (group.Symbols.Count >= 2) { - var firstProject = primaryDocument.Project; - var (firstProjectName, firstProjectFlavor) = firstProject.State.NameAndFlavor; - - if (firstProjectName != null) + var primarySourceLocation = group.PrimarySymbol.Locations.FirstOrDefault(loc => loc.IsInSource); + var primaryDocument = solution.GetDocument(primarySourceLocation?.SourceTree); + var primaryProject = primaryDocument?.Project; + if (primaryProject != null) { - using var _ = ArrayBuilder.GetInstance(out var flavors); - flavors.Add(firstProjectFlavor!); - - // Now, do the same for the other projects where we had a match. As above, if we can't figure out the - // simple name/flavor, or if the simple project name doesn't match the simple project name we started - // with then we can't merge these. - foreach (var location in allLocations) - { - var document = solution.GetDocument(location.SourceTree); - var project = document?.Project; - if (project != null) - { - var (projectName, projectFlavor) = project.State.NameAndFlavor; - if (projectName == firstProjectName) - flavors.Add(projectFlavor!); - } - } - - flavors.RemoveDuplicates(); - flavors.Sort(); + var allProjectIds = allLocations.Where(loc => loc.IsInSource) + .Select(loc => solution.GetDocument(loc.SourceTree)) + .Select(d => d?.Project.Id) + .WhereNotNull() + .ToImmutableArray(); + using var _ = ArrayBuilder.GetInstance(out var flavors); + primaryProject.GetAllFlavors(allProjectIds, flavors); return ImmutableArray.Create(new TaggedText(TextTags.Text, $" ({string.Join(", ", flavors)})")); } } - } - return ImmutableArray.Empty; + return ImmutableArray.Empty; + } } private static Task ToDefinitionItemAsync( diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 979ee6a3c5c4d..c587d7ee9e69e 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -119,43 +119,7 @@ private string ComputeAdditionalInformation() } private string ComputeCombinedProjectName() - { - // If there aren't any additional matches in other projects, we don't need to merge anything. - if (_item.AdditionalMatchingProjects.Length > 0) - { - // First get the simple project name and flavor for the actual project we got a hit in. If we can't - // figure this out, we can't create a merged name. - var firstProject = _document.Project; - var (firstProjectName, firstProjectFlavor) = firstProject.State.NameAndFlavor; - - if (firstProjectName != null) - { - var solution = firstProject.Solution; - - using var _ = ArrayBuilder.GetInstance(out var flavors); - flavors.Add(firstProjectFlavor!); - - // Now, do the same for the other projects where we had a match. As above, if we can't figure out the - // simple name/flavor, or if the simple project name doesn't match the simple project name we started - // with then we can't merge these. - foreach (var additionalProjectId in _item.AdditionalMatchingProjects) - { - var additionalProject = solution.GetRequiredProject(additionalProjectId); - var (projectName, projectFlavor) = additionalProject.State.NameAndFlavor; - if (projectName == firstProjectName) - flavors.Add(projectFlavor!); - } - - flavors.RemoveDuplicates(); - flavors.Sort(); - - return $"{firstProjectName} ({string.Join(", ", flavors)})"; - } - } - - // Couldn't compute a merged project name (or only had one project). Just return the name of hte project itself. - return _document.Project.Name; - } + => _document.Project.GetNameWithAllFlavors(_item.AdditionalMatchingProjects); string INavigateToSearchResult.AdditionalInformation => _additionalInformation; diff --git a/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs index 1df24cf4ee104..1b50540584c4e 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs @@ -4,6 +4,9 @@ #nullable disable +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.PooledObjects; + namespace Microsoft.CodeAnalysis.Shared.Extensions { internal static class ProjectExtensions @@ -15,5 +18,53 @@ public static Glyph GetGlyph(this Project project) project.Language == LanguageNames.VisualBasic ? Glyph.BasicProject : Glyph.Assembly; } + + public static string GetNameWithAllFlavors(this Project project, ImmutableArray allProjectIds) + { + // If there aren't any additional matches in other projects, we don't need to merge anything. + if (allProjectIds.Length > 0) + { + var (firstProjectName, _) = project.State.NameAndFlavor; + if (firstProjectName != null) + { + // First get the simple project name and flavor for the actual project we got a hit in. If we can't + // figure this out, we can't create a merged name. + using var _ = ArrayBuilder.GetInstance(out var flavors); + project.GetAllFlavors(allProjectIds, flavors); + + if (flavors.Count > 1) + return $"{firstProjectName} ({string.Join(", ", flavors)})"; + } + } + + // Couldn't compute a merged project name (or only had one project). Just return the name of hte project itself. + return project.Name; + } + + public static void GetAllFlavors( + this Project project, ImmutableArray allProjectIds, ArrayBuilder flavors) + { + var solution = project.Solution; + + var (firstProjectName, firstProjectFlavor) = project.State.NameAndFlavor; + if (firstProjectName != null) + { + flavors.Add(firstProjectFlavor!); + + // Now, do the same for the other projects where we had a match. As above, if we can't figure out the + // simple name/flavor, or if the simple project name doesn't match the simple project name we started + // with then we can't merge these. + foreach (var projectId in allProjectIds) + { + var otherProject = solution.GetRequiredProject(projectId); + var (projectName, projectFlavor) = otherProject.State.NameAndFlavor; + if (projectName == firstProjectName) + flavors.Add(projectFlavor!); + } + + flavors.RemoveDuplicates(); + flavors.Sort(); + } + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems index a5f7a791adce2..b3dc1f9270cc4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CompilerExtensions.projitems @@ -55,7 +55,7 @@ Collections\ArrayBuilderExtensions.cs - + Collections\DictionaryExtensions.cs From a59999821c842aabe35af6f40b9108ab765e9941 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:17:59 -0700 Subject: [PATCH 118/220] Simplify --- .../IDefinitionsAndReferencesFactory.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 550eb8366e290..e2997c5b2432c 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -147,24 +147,10 @@ ImmutableArray GetAdditionalDisplayParts() } private static Task ToDefinitionItemAsync( - ISymbol definition, - Solution solution, - bool isPrimary, - bool includeHiddenLocations, - bool includeClassifiedSpans, - FindReferencesSearchOptions options, - CancellationToken cancellationToken) + ISymbol definition, Solution solution, bool isPrimary, bool includeHiddenLocations, bool includeClassifiedSpans, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return ToDefinitionItemAsync( - definition, - definition.Locations, - ImmutableArray.Empty, - solution, - isPrimary, - includeHiddenLocations, - includeClassifiedSpans, - options, - cancellationToken); + definition, definition.Locations, ImmutableArray.Empty, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken); } private static async Task ToDefinitionItemAsync( From dd2d32553690eb0d9e23aff636646e6a36d9f0ba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:18:25 -0700 Subject: [PATCH 119/220] Simplify --- .../FindUsages/IDefinitionsAndReferencesFactory.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index e2997c5b2432c..35453152186cd 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -86,17 +86,9 @@ public static Task ToClassifiedDefinitionItemAsync( } public static Task ToClassifiedDefinitionItemAsync( - this SymbolGroup group, - Solution solution, - bool isPrimary, - bool includeHiddenLocations, - FindReferencesSearchOptions options, - CancellationToken cancellationToken) + this SymbolGroup group, Solution solution, bool isPrimary, bool includeHiddenLocations, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return ToDefinitionItemAsync( - group, solution, isPrimary, - includeHiddenLocations, includeClassifiedSpans: true, - options, cancellationToken); + return ToDefinitionItemAsync(group, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); } private static async Task ToDefinitionItemAsync( From 120b10d59b93245bb42a20a06a37c919fb6640b9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:19:41 -0700 Subject: [PATCH 120/220] Simplify --- .../IDefinitionsAndReferencesFactory.cs | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 35453152186cd..b52a2464ca52d 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -92,25 +92,14 @@ public static Task ToClassifiedDefinitionItemAsync( } private static async Task ToDefinitionItemAsync( - this SymbolGroup group, - Solution solution, - bool isPrimary, - bool includeHiddenLocations, - bool includeClassifiedSpans, - FindReferencesSearchOptions options, - CancellationToken cancellationToken) + this SymbolGroup group, Solution solution, bool isPrimary, bool includeHiddenLocations, bool includeClassifiedSpans, FindReferencesSearchOptions options, CancellationToken cancellationToken) { + // Make a single definition item that knows about all the locations of all the symbols in the group. + // If we had symbols from different project flavors, add the project flavor info into the definition + // item name to show the user. var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); return await ToDefinitionItemAsync( - group.PrimarySymbol, - allLocations, - GetAdditionalDisplayParts(), - solution, - isPrimary, - includeHiddenLocations, - includeClassifiedSpans, - options, - cancellationToken).ConfigureAwait(false); + group.PrimarySymbol, allLocations, GetAdditionalDisplayParts(), solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken).ConfigureAwait(false); ImmutableArray GetAdditionalDisplayParts() { From 5aaa84512c44305c3dbc50d46c455f8a89ca676c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:22:10 -0700 Subject: [PATCH 121/220] Simplify --- .../FindReferences/Entries/DocumentSpanEntry.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index f6b9490f85f27..6426dd9aeb770 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -42,7 +42,7 @@ private class DocumentSpanEntry : AbstractDocumentSpanEntry, ISupportsNavigation private readonly ImmutableDictionary _customColumnsData; private readonly string _rawProjectName; - private readonly List _flavors = new(); + private readonly List _projectFlavors = new(); private string? _cachedProjectName; @@ -75,13 +75,15 @@ private DocumentSpanEntry( protected override string GetProjectName() { - lock (_flavors) + // Check if we have any flavors. If we have at least 2, combine with the project name + // so the user can know htat in the UI. + lock (_projectFlavors) { if (_cachedProjectName == null) { - _cachedProjectName = _flavors.Count < 2 + _cachedProjectName = _projectFlavors.Count < 2 ? _rawProjectName - : $"{_rawProjectName} ({string.Join(", ", _flavors)})"; + : $"{_rawProjectName} ({string.Join(", ", _projectFlavors)})"; } return _cachedProjectName; @@ -93,12 +95,12 @@ private void AddFlavor(string? projectFlavor) if (projectFlavor == null) return; - lock (_flavors) + lock (_projectFlavors) { - if (_flavors.Contains(projectFlavor)) + if (_projectFlavors.Contains(projectFlavor)) return; - _flavors.Add(projectFlavor); + _projectFlavors.Add(projectFlavor); _cachedProjectName = null; } } From b87e067a85a9f25d7089a5d897b6403188f54f9c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:22:30 -0700 Subject: [PATCH 122/220] Simplify --- .../FindReferences/Entries/DocumentSpanEntry.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index 6426dd9aeb770..25fdf3ebbec75 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -58,11 +58,7 @@ private DocumentSpanEntry( SourceText lineText, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary customColumnsData) - : base(context, - definitionBucket, - projectGuid, - lineText, - mappedSpanResult) + : base(context, definitionBucket, projectGuid, lineText, mappedSpanResult) { _spanKind = spanKind; _excerptResult = excerptResult; From efae208819f7154a2fc30f995cef9f8d1a8ecee7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:22:59 -0700 Subject: [PATCH 123/220] Simplify --- .../FindReferences/Entries/DocumentSpanEntry.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index 25fdf3ebbec75..868af4526906f 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -127,8 +127,8 @@ private void AddFlavor(string? projectFlavor) // collapse things down to only show a single entry for each unique file/span pair. var winningEntry = definitionBucket.GetOrAddEntry(documentSpan, entry); - // If we were the one that successfully added this entry to the bucket, then pass out to - // be put in the ui. + // If we were the one that successfully added this entry to the bucket, then pass us + // back out to be put in the ui. if (winningEntry == entry) return entry; From 977a6415d6df455cad043d8b71be87a934104bbc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:27:21 -0700 Subject: [PATCH 124/220] Only add if we have more than 1 --- .../Core/FindUsages/IDefinitionsAndReferencesFactory.cs | 4 +++- .../Core/Portable/Shared/Extensions/ProjectExtensions.cs | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index b52a2464ca52d..10c3f4c31134d 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -119,7 +119,9 @@ ImmutableArray GetAdditionalDisplayParts() using var _ = ArrayBuilder.GetInstance(out var flavors); primaryProject.GetAllFlavors(allProjectIds, flavors); - return ImmutableArray.Create(new TaggedText(TextTags.Text, $" ({string.Join(", ", flavors)})")); + return flavors.Count > 1 + ? ImmutableArray.Create(new TaggedText(TextTags.Text, $" ({string.Join(", ", flavors)})")) + : ImmutableArray.Empty; } } diff --git a/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs index 1b50540584c4e..db6d7cef5fe65 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs @@ -32,8 +32,9 @@ public static string GetNameWithAllFlavors(this Project project, ImmutableArray< using var _ = ArrayBuilder.GetInstance(out var flavors); project.GetAllFlavors(allProjectIds, flavors); - if (flavors.Count > 1) - return $"{firstProjectName} ({string.Join(", ", flavors)})"; + return flavors.Count > 1 + ? $"{firstProjectName} ({string.Join(", ", flavors)})" + : firstProjectName; } } From f8891cf61d9f235660eccdcb7d1ae6bb3526fd6c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:28:10 -0700 Subject: [PATCH 125/220] Simplify --- .../Contexts/WithReferencesFindUsagesContext.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs index 3bb10f34631f2..c8223b3778fe8 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Contexts/WithReferencesFindUsagesContext.cs @@ -159,14 +159,10 @@ private async ValueTask OnEntryFoundAsync( { // Once we can make the new entry, add it to the appropriate list. if (addToEntriesWhenGroupingByDefinition) - { EntriesWhenGroupingByDefinition = EntriesWhenGroupingByDefinition.Add(entry); - } if (addToEntriesWhenNotGroupingByDefinition) - { EntriesWhenNotGroupingByDefinition = EntriesWhenNotGroupingByDefinition.Add(entry); - } } CurrentVersionNumber++; From 4c7fece0bdb9aa5f5fdf9cc60b10fe1d0f723e81 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Apr 2021 15:30:43 -0700 Subject: [PATCH 126/220] Update src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs --- .../FindSymbols/FindReferences/Finders/ReferenceFinders.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs index 4855bae98215c..d329a11cf507c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs @@ -16,7 +16,6 @@ internal static class ReferenceFinders public static readonly IReferenceFinder Event = new EventSymbolReferenceFinder(); public static readonly IReferenceFinder Field = new FieldSymbolReferenceFinder(); public static readonly IReferenceFinder Label = new LabelSymbolReferenceFinder(); - // public static readonly IReferenceFinder LinkedFiles = new LinkedFileReferenceFinder(); public static readonly IReferenceFinder Local = new LocalSymbolReferenceFinder(); public static readonly IReferenceFinder MethodTypeParameter = new MethodTypeParameterSymbolReferenceFinder(); public static readonly IReferenceFinder NamedType = new NamedTypeSymbolReferenceFinder(); From e59d792d51b70c63cc1eff444cc4000d2485b089 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Apr 2021 15:31:08 -0700 Subject: [PATCH 127/220] Update src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs --- .../FindSymbols/FindReferences/Finders/ReferenceFinders.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs index d329a11cf507c..7afe3bc6cb545 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ReferenceFinders.cs @@ -46,7 +46,6 @@ static ReferenceFinders() ExplicitInterfaceMethod, Field, Label, - // LinkedFiles, Local, MethodTypeParameter, NamedType, From 5e5bf4f379da888097bb3183c8d8ae5f28191a4e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:41:43 -0700 Subject: [PATCH 128/220] Add docs --- .../FindSymbols/IStreamingFindReferencesProgress.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 02b79baf673d8..926f29ca98c64 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -12,6 +12,13 @@ namespace Microsoft.CodeAnalysis.FindSymbols { + /// + /// Represents a group of s that should be treated as a single entity for + /// the purposes of presentation in a Find UI. For example, when a symbol is defined in a file + /// that is linked into multiple project contexts, there will be several unique symbols created + /// that we search for. Placing these in a group allows the final consumer to know that these + /// symbols can be merged together. + /// internal class SymbolGroup : IEquatable { /// From e120c87525138481afa5a039274d20f03bcef7d0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 15:53:24 -0700 Subject: [PATCH 129/220] Fix hydration --- ...mbolFinder.FindReferencesServerCallback.cs | 25 ++++++++++++++++--- .../Core/Portable/Remote/RemoteArguments.cs | 19 -------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 4f8da62bc1292..92d0a09d9fd18 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -8,7 +8,9 @@ using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { @@ -62,15 +64,30 @@ public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId) return _progress.OnFindInDocumentCompletedAsync(document); } - public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup serializableSymbolGroup) + public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated) { - var symbolGroup = await serializableSymbolGroup.TryRehydrateAsync(_solution, _cancellationToken).ConfigureAwait(false); - if (symbolGroup == null) + var primarySymbol = await dehydrated.PrimarySymbol.TryRehydrateAsync(_solution, _cancellationToken).ConfigureAwait(false); + if (primarySymbol == null) return; + using var _1 = ArrayBuilder.GetInstance(out var symbols); + using var _2 = ArrayBuilder<(SerializableSymbolAndProjectId, ISymbol)>.GetInstance(out var pairs); + foreach (var symbolAndProjectId in dehydrated.Symbols) + { + var symbol = await symbolAndProjectId.TryRehydrateAsync(_solution, _cancellationToken).ConfigureAwait(false); + if (symbol == null) + return; + + symbols.Add(primarySymbol); + pairs.Add((symbolAndProjectId, symbol)); + } + + var symbolGroup = new SymbolGroup(primarySymbol, symbols.ToImmutable()); lock (_gate) { - _groupMap[serializableSymbolGroup] = symbolGroup; + _groupMap[dehydrated] = symbolGroup; + foreach (var pair in pairs) + _definitionMap[pair.Item1] = pair.Item2; } await _progress.OnDefinitionFoundAsync(symbolGroup).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 34b0eb683adbb..d45b5964db975 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -250,25 +250,6 @@ public static SerializableSymbolGroup Dehydrate(Solution solution, SymbolGroup g group.Symbols.Select( s => SerializableSymbolAndProjectId.Dehydrate(solution, s, cancellationToken)))); } - - public async Task TryRehydrateAsync(Solution solution, CancellationToken cancellationToken) - { - var primarySymbol = await this.PrimarySymbol.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); - if (primarySymbol == null) - return null; - - using var _ = ArrayBuilder.GetInstance(out var symbols); - foreach (var symbolAndProjectId in this.Symbols) - { - primarySymbol = await symbolAndProjectId.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false); - if (primarySymbol == null) - return null; - - symbols.Add(primarySymbol); - } - - return new SymbolGroup(primarySymbol, symbols.ToImmutable()); - } } #endregion From 720bad7199ae45aa8635fd26534c14882963f6f7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 16:14:50 -0700 Subject: [PATCH 130/220] Simplify --- .../Core/Portable/Remote/RemoteArguments.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index d45b5964db975..41f1eaa921a90 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -22,7 +22,7 @@ namespace Microsoft.CodeAnalysis.Remote #region FindReferences [DataContract] - internal sealed class SerializableSymbolAndProjectId + internal sealed class SerializableSymbolAndProjectId : IEquatable { [DataMember(Order = 0)] public readonly string SymbolKeyData; @@ -36,6 +36,15 @@ public SerializableSymbolAndProjectId(string symbolKeyData, ProjectId projectId) ProjectId = projectId; } + public override bool Equals(object obj) + => Equals(obj as SerializableSymbolAndProjectId); + + public bool Equals(SerializableSymbolAndProjectId other) + => (this == other) || this.SymbolKeyData == other?.SymbolKeyData; + + public override int GetHashCode() + => this.SymbolKeyData.GetHashCode(); + public static SerializableSymbolAndProjectId Dehydrate( IAliasSymbol alias, Document document, CancellationToken cancellationToken) { @@ -197,7 +206,7 @@ private async Task RehydrateAliasAsync( } [DataContract] - internal class SerializableSymbolGroup : IEquatable, IEqualityComparer + internal class SerializableSymbolGroup : IEquatable { [DataMember(Order = 0)] public readonly SerializableSymbolAndProjectId PrimarySymbol; @@ -211,15 +220,9 @@ public SerializableSymbolGroup( HashSet symbols) { PrimarySymbol = primarySymbol; - Symbols = new HashSet(symbols, this); + Symbols = new HashSet(symbols); } - bool IEqualityComparer.Equals(SerializableSymbolAndProjectId x, SerializableSymbolAndProjectId y) - => y.SymbolKeyData.Equals(x.SymbolKeyData); - - int IEqualityComparer.GetHashCode(SerializableSymbolAndProjectId obj) - => obj.SymbolKeyData.GetHashCode(); - public override bool Equals(object obj) => obj is SerializableSymbolGroup group && Equals(group); From a285b014de4ebc72000e79c294f1cfa0c4a10065 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 16:48:57 -0700 Subject: [PATCH 131/220] Don't create multiple mappings --- .../SymbolFinder.FindReferencesServerCallback.cs | 15 ++++++++------- .../Core/Portable/Remote/RemoteArguments.cs | 1 - 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 92d0a09d9fd18..73036ba48a897 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -66,28 +66,29 @@ public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId) public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated) { - var primarySymbol = await dehydrated.PrimarySymbol.TryRehydrateAsync(_solution, _cancellationToken).ConfigureAwait(false); - if (primarySymbol == null) - return; + Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); using var _1 = ArrayBuilder.GetInstance(out var symbols); - using var _2 = ArrayBuilder<(SerializableSymbolAndProjectId, ISymbol)>.GetInstance(out var pairs); + using var _2 = PooledDictionary.GetInstance(out var pairs); + foreach (var symbolAndProjectId in dehydrated.Symbols) { var symbol = await symbolAndProjectId.TryRehydrateAsync(_solution, _cancellationToken).ConfigureAwait(false); if (symbol == null) return; - symbols.Add(primarySymbol); - pairs.Add((symbolAndProjectId, symbol)); + symbols.Add(symbol); + pairs[symbolAndProjectId] = symbol; } + var primarySymbol = pairs[dehydrated.PrimarySymbol]; + var symbolGroup = new SymbolGroup(primarySymbol, symbols.ToImmutable()); lock (_gate) { _groupMap[dehydrated] = symbolGroup; foreach (var pair in pairs) - _definitionMap[pair.Item1] = pair.Item2; + _definitionMap[pair.Key] = pair.Value; } await _progress.OnDefinitionFoundAsync(symbolGroup).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 41f1eaa921a90..110adc68d4eb2 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -13,7 +13,6 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; -using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; From a5b7794ff23a9f5536adb3284fbf0ce18064490a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 16:52:35 -0700 Subject: [PATCH 132/220] Sort the flavors --- .../Implementation/FindReferences/Entries/DocumentSpanEntry.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs index 868af4526906f..71a1bf7ab6966 100644 --- a/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs +++ b/src/VisualStudio/Core/Def/Implementation/FindReferences/Entries/DocumentSpanEntry.cs @@ -97,6 +97,7 @@ private void AddFlavor(string? projectFlavor) return; _projectFlavors.Add(projectFlavor); + _projectFlavors.Sort(); _cachedProjectName = null; } } From bbcc4ad56b8fab3f0f7fec6e92a3b166552e9318 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 16:58:21 -0700 Subject: [PATCH 133/220] Property equality --- src/Workspaces/Core/Portable/Remote/RemoteArguments.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 110adc68d4eb2..078060029bc7c 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -39,10 +39,16 @@ public override bool Equals(object obj) => Equals(obj as SerializableSymbolAndProjectId); public bool Equals(SerializableSymbolAndProjectId other) - => (this == other) || this.SymbolKeyData == other?.SymbolKeyData; + { + if (this == other) + return true; + + return this.ProjectId == other.ProjectId && + this.SymbolKeyData == other?.SymbolKeyData; + } public override int GetHashCode() - => this.SymbolKeyData.GetHashCode(); + => Hash.Combine(this.SymbolKeyData, this.ProjectId.GetHashCode()); public static SerializableSymbolAndProjectId Dehydrate( IAliasSymbol alias, Document document, CancellationToken cancellationToken) From ed1298d94f2f911ee9057d4e0531ece24723ca75 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 17:24:07 -0700 Subject: [PATCH 134/220] Add lsp filtering --- .../CustomProtocol/FindUsagesLSPContext.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs b/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs index db212888c6c45..736038559acf5 100644 --- a/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs +++ b/src/Features/LanguageServer/Protocol/CustomProtocol/FindUsagesLSPContext.cs @@ -47,6 +47,18 @@ internal class FindUsagesLSPContext : FindUsagesContext /// private readonly Dictionary _definitionsWithoutReference = new(); + /// + /// Set of the locations we've found references at. We may end up with multiple references + /// being reported for the same location. For example, this can happen in multi-targetting + /// scenarios when there are symbols in files linked into multiple projects. Those symbols + /// may have references that themselves are in linked locations, leading to multiple references + /// found at different virtual locations that the user considers at the same physical location. + /// For now we filter out these duplicates to not clutter the UI. If LSP supports the ability + /// to override an already reported VSReferenceItem, we could also reissue the item with the + /// additional information about all the projects it is found in. + /// + private readonly HashSet<(string? filePath, TextSpan span)> _referenceLocations = new(); + /// /// We report the results in chunks. A batch, if it contains results, is reported every 0.5s. /// @@ -120,9 +132,12 @@ public override async ValueTask OnReferenceFoundAsync(SourceReferenceItem refere // Each reference should be associated with a definition. If this somehow isn't the // case, we bail out early. if (!_definitionToId.TryGetValue(reference.Definition, out var definitionId)) - { return; - } + + // If this is reference to the same physical location we've already reported, just + // filter this out. it will clutter the UI to show the same places. + if (!_referenceLocations.Add((reference.SourceSpan.Document.FilePath, reference.SourceSpan.SourceSpan))) + return; // If the definition hasn't been reported yet, add it to our list of references to report. if (_definitionsWithoutReference.TryGetValue(definitionId, out var definition)) From 935b65460e53c3ef1a8c07d461048881480c326f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 17:34:23 -0700 Subject: [PATCH 135/220] Mark as nullable --- src/Compilers/Core/Portable/Syntax/SyntaxNode.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs index 652dbcca140ff..17ea066d21b8e 100644 --- a/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs +++ b/src/Compilers/Core/Portable/Syntax/SyntaxNode.cs @@ -333,7 +333,7 @@ public SourceText GetText(Encoding? encoding = null, SourceHashAlgorithm checksu /// /// Determine whether this node is structurally equivalent to another. /// - public bool IsEquivalentTo([NotNullWhen(true)] SyntaxNode other) + public bool IsEquivalentTo([NotNullWhen(true)] SyntaxNode? other) { if (this == other) { From 86b558faa1c70c44c9e262eefd4f83897562f487 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Apr 2021 17:34:45 -0700 Subject: [PATCH 136/220] Update src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs Co-authored-by: Andrew Hall --- .../SyntacticClassificationTaggerProvider.TagComputer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 59c8b758d4f73..a2ccbb05cb945 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -216,7 +216,7 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C var service = TryGetClassificationService(currentSnapshot); object currentCachedData = null; var changedSpan = currentSnapshot.GetFullSpan(); - if (service != null) + if (service is not null) { var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); using (latencyTracker) From 00a09e21031218f79e02f13e8e0266cddc896148 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 17:35:38 -0700 Subject: [PATCH 137/220] Simplify --- ...lassificationTaggerProvider.TagComputer.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 59c8b758d4f73..4d3b6e0aad360 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -218,18 +218,16 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C var changedSpan = currentSnapshot.GetFullSpan(); if (service != null) { - var latencyTracker = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); - using (latencyTracker) - { - // preemptively allow the classification service to compute and cache data for this file. For - // example, in C# and VB we will parse the file so that are called from tagger from UI thread, - // we have the root of the tree ready to go. - currentCachedData = await service.GetDataToCacheAsync(currentDocument, cancellationToken).ConfigureAwait(false); - - // Query the service to determine waht span of the document actually changed and should be - // reclassified in the host editor. - changedSpan = await GetChangedSpanAsync(currentDocument, currentSnapshot, service, cancellationToken).ConfigureAwait(false); - } + using var _ = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); + + // preemptively allow the classification service to compute and cache data for this file. For + // example, in C# and VB we will parse the file so that are called from tagger from UI thread, + // we have the root of the tree ready to go. + currentCachedData = await service.GetDataToCacheAsync(currentDocument, cancellationToken).ConfigureAwait(false); + + // Query the service to determine waht span of the document actually changed and should be + // reclassified in the host editor. + changedSpan = await GetChangedSpanAsync(currentDocument, currentSnapshot, service, cancellationToken).ConfigureAwait(false); } // Once we're past this point, we're mutating our internal state so we cannot cancel past that. From 3f88d99a3353f3446f8a5413817d4ddea0d7452d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 17:36:50 -0700 Subject: [PATCH 138/220] FInish comment --- .../SyntacticClassificationTaggerProvider.TagComputer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 4d3b6e0aad360..1877cb5d72d7f 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -256,7 +256,8 @@ private async Task GetChangedSpanAsync( IClassificationService service, CancellationToken cancellationToken) { // We don't need to grab _lastProcessedDocument in a lock. We don't care which version of the previous - // doc we grab, just that we grab some prior version. This is only used + // doc we grab, just that we grab some prior version. This is only used to narrow down the changed range we + // specify, so it's ok if it's slightly larger because we read in a change from a couple of edits ago. var previousDocument = _lastProcessedDocument; if (previousDocument != null) { From 9d3baecb3deaabe4b711bdbbf3f492c198eb9a20 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 17:43:13 -0700 Subject: [PATCH 139/220] Simplify code --- ...lassificationTaggerProvider.TagComputer.cs | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index f15d603c7f2d5..8054c0b62a4ed 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; @@ -14,15 +13,12 @@ using Microsoft.CodeAnalysis.Editor.Shared.Tagging; using Microsoft.CodeAnalysis.Editor.Shared.Threading; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; -using Microsoft.CodeAnalysis.Experiments; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Tagging; -using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification @@ -213,26 +209,25 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C return; } - var service = TryGetClassificationService(currentSnapshot); - object currentCachedData = null; - var changedSpan = currentSnapshot.GetFullSpan(); - if (service is not null) + object currentCachedData; + SnapshotSpan changedSpan; + + using (var _ = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger)) { - using var _ = new RequestLatencyTracker(SyntacticLspLogger.RequestType.SyntacticTagger); + var service = TryGetClassificationService(currentSnapshot); // preemptively allow the classification service to compute and cache data for this file. For // example, in C# and VB we will parse the file so that are called from tagger from UI thread, // we have the root of the tree ready to go. - currentCachedData = await service.GetDataToCacheAsync(currentDocument, cancellationToken).ConfigureAwait(false); + currentCachedData = service == null + ? null + : await service.GetDataToCacheAsync(currentDocument, cancellationToken).ConfigureAwait(false); // Query the service to determine waht span of the document actually changed and should be // reclassified in the host editor. - changedSpan = await GetChangedSpanAsync(currentDocument, currentSnapshot, service, cancellationToken).ConfigureAwait(false); + changedSpan = await GetChangedSpanAsync(currentDocument, currentSnapshot, cancellationToken).ConfigureAwait(false); } - // Once we're past this point, we're mutating our internal state so we cannot cancel past that. - cancellationToken = CancellationToken.None; - lock (_gate) { _lastProcessedSnapshot = currentSnapshot; @@ -252,8 +247,7 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C } private async Task GetChangedSpanAsync( - Document currentDocument, ITextSnapshot currentSnapshot, - IClassificationService service, CancellationToken cancellationToken) + Document currentDocument, ITextSnapshot currentSnapshot, CancellationToken cancellationToken) { // We don't need to grab _lastProcessedDocument in a lock. We don't care which version of the previous // doc we grab, just that we grab some prior version. This is only used to narrow down the changed range we @@ -261,10 +255,14 @@ private async Task GetChangedSpanAsync( var previousDocument = _lastProcessedDocument; if (previousDocument != null) { - var changeRange = await service.ComputeSyntacticChangeRangeAsync( - previousDocument, currentDocument, _diffTimeout, cancellationToken).ConfigureAwait(false); - if (changeRange != null) - return currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength); + var service = TryGetClassificationService(currentSnapshot); + if (service != null) + { + var changeRange = await service.ComputeSyntacticChangeRangeAsync( + previousDocument, currentDocument, _diffTimeout, cancellationToken).ConfigureAwait(false); + if (changeRange != null) + return currentSnapshot.GetSpan(changeRange.Value.Span.Start, changeRange.Value.NewLength); + } } // Couldn't compute a narrower range. Just the mark the entire file as changed. From 8de0e62e2880b56aa7c13910c22d99174003633a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 17:44:20 -0700 Subject: [PATCH 140/220] Simplify --- .../Classification/FSharpClassificationService.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs index 58cbe8756a464..2527ce5d9ae2d 100644 --- a/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs +++ b/src/Tools/ExternalAccess/FSharp/Internal/Classification/FSharpClassificationService.cs @@ -11,7 +11,6 @@ using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Classification; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Classification { @@ -49,13 +48,9 @@ public ClassifiedSpan AdjustStaleClassification(SourceText text, ClassifiedSpan } public ValueTask GetDataToCacheAsync(Document document, CancellationToken cancellationToken) - { - return new(); - } + => new(); public ValueTask ComputeSyntacticChangeRangeAsync(Document oldDocument, Document newDocument, TimeSpan timeout, CancellationToken cancellationToken) - { - return new(); - } + => new(); } } From 57eb3f413cb00eb5af61576ea5f5fd082fa7fd3f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 18:01:35 -0700 Subject: [PATCH 141/220] Simplify --- .../FindUsages/IDefinitionsAndReferencesFactory.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 10c3f4c31134d..a65af5189162d 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -87,19 +87,13 @@ public static Task ToClassifiedDefinitionItemAsync( public static Task ToClassifiedDefinitionItemAsync( this SymbolGroup group, Solution solution, bool isPrimary, bool includeHiddenLocations, FindReferencesSearchOptions options, CancellationToken cancellationToken) - { - return ToDefinitionItemAsync(group, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); - } - - private static async Task ToDefinitionItemAsync( - this SymbolGroup group, Solution solution, bool isPrimary, bool includeHiddenLocations, bool includeClassifiedSpans, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // Make a single definition item that knows about all the locations of all the symbols in the group. // If we had symbols from different project flavors, add the project flavor info into the definition // item name to show the user. var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); - return await ToDefinitionItemAsync( - group.PrimarySymbol, allLocations, GetAdditionalDisplayParts(), solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken).ConfigureAwait(false); + return ToDefinitionItemAsync( + group.PrimarySymbol, allLocations, GetAdditionalDisplayParts(), solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); ImmutableArray GetAdditionalDisplayParts() { From 8d1852690352ec6f591941c48baa20c25a57fc53 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 18:05:29 -0700 Subject: [PATCH 142/220] Fix api --- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 7a1493948a97d..4f19366b1ea3c 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -23,6 +23,7 @@ Microsoft.CodeAnalysis.Operations.OperationWalker.OperationWalker() - Microsoft.CodeAnalysis.SymbolDisplayPartKind.RecordClassName = 31 -> Microsoft.CodeAnalysis.SymbolDisplayPartKind const Microsoft.CodeAnalysis.WellKnownDiagnosticTags.CompilationEnd = "CompilationEnd" -> string! Microsoft.CodeAnalysis.SyntaxContextReceiverCreator +Microsoft.CodeAnalysis.SyntaxNode.IsEquivalentTo(Microsoft.CodeAnalysis.SyntaxNode? other) -> bool Microsoft.CodeAnalysis.SyntaxNode.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNode? other) -> bool Microsoft.CodeAnalysis.SyntaxNodeOrToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxNodeOrToken other) -> bool Microsoft.CodeAnalysis.SyntaxToken.IsIncrementallyIdenticalTo(Microsoft.CodeAnalysis.SyntaxToken token) -> bool From 2680ed64d641cdc46852f02a3d3db78bb3194e83 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 19:31:20 -0700 Subject: [PATCH 143/220] Remove item --- src/Compilers/Core/Portable/PublicAPI.Unshipped.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 4f19366b1ea3c..e4b56d0250f85 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +*REMOVED*Microsoft.CodeAnalysis.SyntaxNode.IsEquivalentTo(Microsoft.CodeAnalysis.SyntaxNode! other) -> bool Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! Microsoft.CodeAnalysis.IFieldSymbol.IsExplicitlyNamedTupleElement.get -> bool From f1110cb4986a5541b984b1d6811b34c07fbd7d11 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 19:34:10 -0700 Subject: [PATCH 144/220] Make method obsolete --- .../NavigationBar/AbstractEditorNavigationBarItemService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index f23ac8b52ba18..acb60a4e97bc1 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -26,6 +26,7 @@ protected AbstractEditorNavigationBarItemService(IThreadingContext threadingCont protected abstract Task GetSymbolNavigationPointAsync(Document document, ISymbol symbol, CancellationToken cancellationToken); protected abstract Task NavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken); + [Obsolete("Caller should call NavigateToItemAsync instead", error: true)] public void NavigateToItem(Document document, NavigationBarItem item, ITextView view, CancellationToken cancellationToken) => throw new NotSupportedException($"Caller should call {nameof(NavigateToItemAsync)} instead"); From 76d2397aa0ff57c20bdb1913f33208df5885ac24 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 19:35:40 -0700 Subject: [PATCH 145/220] Make obsolete --- .../Core/Extensibility/NavigationBar/NavigationBarItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs index a520c14ab7d85..068a165972daf 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; @@ -24,6 +25,7 @@ internal abstract class NavigationBarItem internal ImmutableArray TrackingSpans { get; set; } = ImmutableArray.Empty; // Legacy constructor for TypeScript. + [Obsolete("Use the constructor that uses ImmutableArray")] public NavigationBarItem(string text, Glyph glyph, IList spans, IList? childItems = null, int indent = 0, bool bolded = false, bool grayed = false) : this(text, glyph, spans.ToImmutableArrayOrEmpty(), childItems.ToImmutableArrayOrEmpty(), indent, bolded, grayed) { From 7e56c2b77ef4b897090aa7560776cbc3e2460c51 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 19:36:26 -0700 Subject: [PATCH 146/220] NRT --- .../Extensibility/NavigationBar/NavigationBarPresentedItem.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs index db0b86b37f7c0..b4a6123de0a3a 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarPresentedItem.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - -using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; From 1cb33bb7f65b248efb5940b203689705518fbfaf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 19:37:35 -0700 Subject: [PATCH 147/220] Add assert --- .../Core/Implementation/NavigationBar/NavigationBarController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 5ac1041669882..6acdcdda26b2e 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -201,6 +201,7 @@ private void OnWorkspaceChanged(object? sender, WorkspaceChangeEventArgs args) private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs args) { + AssertIsForeground(); if (args.Solution.Workspace != _workspace) return; From f3666ca8c36642597c034bc4831b045308784839 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Tue, 13 Apr 2021 19:41:39 -0700 Subject: [PATCH 148/220] Update src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs Co-authored-by: David Barbet --- .../Implementation/NavigationBar/NavigationBarController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index 6acdcdda26b2e..aedde35958370 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -399,7 +399,7 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio } // Now that the edit has been done, refresh to make sure everything is up-to-date. - // Have to make sure we come back to the main thread for this. + // Have to make sure we're still on the main thread for this. AssertIsForeground(); StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } From e520166615c3cfd7e9c1d536d2c08bd9e7f4ff9b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 19:49:55 -0700 Subject: [PATCH 149/220] Add async token --- .../Implementation/NavigationBar/NavigationBarController.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs index aedde35958370..571a271c968f1 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController.cs @@ -330,7 +330,9 @@ private void PushSelectedItemsToPresenter(NavigationBarSelectedTypeAndMember sel private void OnItemSelected(object? sender, NavigationBarItemSelectedEventArgs e) { AssertIsForeground(); - _ = OnItemSelectedAsync(e.Item); + var token = _asyncListener.BeginAsyncOperation(nameof(OnItemSelected)); + var task = OnItemSelectedAsync(e.Item); + _ = task.CompletesAsyncOperation(token); } private async Task OnItemSelectedAsync(NavigationBarItem item) @@ -399,7 +401,7 @@ private async Task ProcessItemSelectionAsync(NavigationBarItem item, Cancellatio } // Now that the edit has been done, refresh to make sure everything is up-to-date. - // Have to make sure we're still on the main thread for this. + // Have to make sure we come back to the main thread for this. AssertIsForeground(); StartModelUpdateAndSelectedItemUpdateTasks(modelUpdateDelay: 0); } From 1fb7242e487a98054bf675728a8225b24bf51f13 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Tue, 13 Apr 2021 20:13:03 -0700 Subject: [PATCH 150/220] Escape special characters in section name of editor config (#52515) * Escape special characters in section name of editor config * Address feedback from peer review * Clean up code comments and vars * Escape '!' characters in section names * Add more test coverage for special characters --- .../Analyzers/AnalyzerConfigTests.cs | 36 +++++++++++++++++++ .../GenerateMSBuildEditorConfig.cs | 27 ++++++++++++-- .../GenerateMSBuildEditorConfigTests.cs | 29 +++++++++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs index 1624752b3330e..b3b540ef660da 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs @@ -57,6 +57,42 @@ public void SimpleCase() Assert.Equal("/bogus", config.NormalizedDirectory); } + [Fact] + [WorkItem(52469, "https://github.com/dotnet/roslyn/issues/52469")] + public void ConfigWithEscapedValues() + { + var config = ParseConfigFile(@"is_global = true + +[c:/\{f\*i\?le1\}.cs] +build_metadata.Compile.ToRetrieve = abc123 + +[c:/f\,ile\#2.cs] +build_metadata.Compile.ToRetrieve = def456 + +[c:/f\;i\!le\[3\].cs] +build_metadata.Compile.ToRetrieve = ghi789 +"); + + var namedSections = config.NamedSections; + Assert.Equal("c:/\\{f\\*i\\?le1\\}.cs", namedSections[0].Name); + AssertEx.Equal( + new[] { KeyValuePair.Create("build_metadata.compile.toretrieve", "abc123") }, + namedSections[0].Properties + ); + + Assert.Equal("c:/f\\,ile\\#2.cs", namedSections[1].Name); + AssertEx.Equal( + new[] { KeyValuePair.Create("build_metadata.compile.toretrieve", "def456") }, + namedSections[1].Properties + ); + + Assert.Equal("c:/f\\;i\\!le\\[3\\].cs", namedSections[2].Name); + AssertEx.Equal( + new[] { KeyValuePair.Create("build_metadata.compile.toretrieve", "ghi789") }, + namedSections[2].Properties + ); + } + [ConditionalFact(typeof(WindowsOnly))] public void WindowsPath() { diff --git a/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs b/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs index 85c4f573c0f3c..d0eead0082aa4 100644 --- a/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs +++ b/src/Compilers/Core/MSBuildTask/GenerateMSBuildEditorConfig.cs @@ -77,9 +77,9 @@ public override bool Execute() { // write the section for this item builder.AppendLine() - .Append("[") - .Append(group.Key) - .AppendLine("]"); + .Append("["); + EncodeString(builder, group.Key); + builder.AppendLine("]"); foreach (var item in group) { @@ -101,6 +101,27 @@ public override bool Execute() return true; } + /// + /// Filenames with special characters like '#' and'{' get written + /// into the section names in the resulting .editorconfig file. Later, + /// when the file is parsed in configuration options these special + /// characters are interpretted as invalid values and ignored by the + /// processor. We encode the special characters in these strings + /// before writing them here. + /// + + private static void EncodeString(StringBuilder builder, string value) + { + foreach (var c in value) + { + if (c is '*' or '?' or '{' or ',' or ';' or '}' or '[' or ']' or '#' or '!') + { + builder.Append("\\"); + } + builder.Append(c); + } + } + /// /// Equivalent to Roslyn.Utilities.PathUtilities.NormalizeWithForwardSlash /// Both methods should be kept in sync. diff --git a/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs b/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs index 19a0af562c043..5ce1dc1604995 100644 --- a/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs +++ b/src/Compilers/Core/MSBuildTaskTests/GenerateMSBuildEditorConfigTests.cs @@ -97,6 +97,35 @@ public void MultipleItemMetaDataCreatesSections() ", result); } + [Fact] + [WorkItem(52469, "https://github.com/dotnet/roslyn/issues/52469")] + public void MultipleSpecialCharacterItemMetaDataCreatesSections() + { + ITaskItem item1 = MSBuildUtil.CreateTaskItem("c:/{f*i?le1}.cs", new Dictionary { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "abc123" } }); + ITaskItem item2 = MSBuildUtil.CreateTaskItem("c:/f,ile#2.cs", new Dictionary { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "def456" } }); + ITaskItem item3 = MSBuildUtil.CreateTaskItem("c:/f;i!le[3].cs", new Dictionary { { "ItemType", "Compile" }, { "MetadataName", "ToRetrieve" }, { "ToRetrieve", "ghi789" } }); + + GenerateMSBuildEditorConfig configTask = new GenerateMSBuildEditorConfig() + { + MetadataItems = new[] { item1, item2, item3 } + }; + configTask.Execute(); + + var result = configTask.ConfigFileContents; + + Assert.Equal(@"is_global = true + +[c:/\{f\*i\?le1\}.cs] +build_metadata.Compile.ToRetrieve = abc123 + +[c:/f\,ile\#2.cs] +build_metadata.Compile.ToRetrieve = def456 + +[c:/f\;i\!le\[3\].cs] +build_metadata.Compile.ToRetrieve = ghi789 +", result); + } + [Fact] public void DuplicateItemSpecsAreCombinedInSections() { From 3ca0325e50349de9dffb05b6fed717b862ec4038 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 22:42:09 -0700 Subject: [PATCH 151/220] Remove extra display parts --- .../IDefinitionsAndReferencesFactory.cs | 37 ++----------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index a65af5189162d..5891fbf3051b4 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -18,8 +18,6 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; -using Microsoft.CodeAnalysis.Text; -using Microsoft.CodeAnalysis.Utilities; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages @@ -93,47 +91,18 @@ public static Task ToClassifiedDefinitionItemAsync( // item name to show the user. var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); return ToDefinitionItemAsync( - group.PrimarySymbol, allLocations, GetAdditionalDisplayParts(), solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); - - ImmutableArray GetAdditionalDisplayParts() - { - // For linked symbols, add a suffix to the symbol display to say all the projects it was defined in. - if (group.Symbols.Count >= 2) - { - var primarySourceLocation = group.PrimarySymbol.Locations.FirstOrDefault(loc => loc.IsInSource); - var primaryDocument = solution.GetDocument(primarySourceLocation?.SourceTree); - var primaryProject = primaryDocument?.Project; - if (primaryProject != null) - { - var allProjectIds = allLocations.Where(loc => loc.IsInSource) - .Select(loc => solution.GetDocument(loc.SourceTree)) - .Select(d => d?.Project.Id) - .WhereNotNull() - .ToImmutableArray(); - - using var _ = ArrayBuilder.GetInstance(out var flavors); - primaryProject.GetAllFlavors(allProjectIds, flavors); - return flavors.Count > 1 - ? ImmutableArray.Create(new TaggedText(TextTags.Text, $" ({string.Join(", ", flavors)})")) - : ImmutableArray.Empty; - } - } - - return ImmutableArray.Empty; - } + group.PrimarySymbol, allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); } private static Task ToDefinitionItemAsync( ISymbol definition, Solution solution, bool isPrimary, bool includeHiddenLocations, bool includeClassifiedSpans, FindReferencesSearchOptions options, CancellationToken cancellationToken) { - return ToDefinitionItemAsync( - definition, definition.Locations, ImmutableArray.Empty, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken); + return ToDefinitionItemAsync(definition, definition.Locations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans, options, cancellationToken); } private static async Task ToDefinitionItemAsync( ISymbol definition, ImmutableArray locations, - ImmutableArray additionalDisplayParts, Solution solution, bool isPrimary, bool includeHiddenLocations, @@ -154,7 +123,7 @@ private static async Task ToDefinitionItemAsync( definition = definition.OriginalDefinition; } - var displayParts = GetDisplayParts(definition).AddRange(additionalDisplayParts); + var displayParts = GetDisplayParts(definition); var nameDisplayParts = definition.ToDisplayParts(s_namePartsFormat).ToTaggedText(); var tags = GlyphTags.GetTags(definition.GetGlyph()); From d11c0b7eb175f5757562ddac2ae5ad760247fc45 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 22:49:38 -0700 Subject: [PATCH 152/220] revert' --- .../NavigateTo/RoslynNavigateToItem.cs | 38 +++++++++++++- .../Shared/Extensions/ProjectExtensions.cs | 52 ------------------- 2 files changed, 37 insertions(+), 53 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index c587d7ee9e69e..979ee6a3c5c4d 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -119,7 +119,43 @@ private string ComputeAdditionalInformation() } private string ComputeCombinedProjectName() - => _document.Project.GetNameWithAllFlavors(_item.AdditionalMatchingProjects); + { + // If there aren't any additional matches in other projects, we don't need to merge anything. + if (_item.AdditionalMatchingProjects.Length > 0) + { + // First get the simple project name and flavor for the actual project we got a hit in. If we can't + // figure this out, we can't create a merged name. + var firstProject = _document.Project; + var (firstProjectName, firstProjectFlavor) = firstProject.State.NameAndFlavor; + + if (firstProjectName != null) + { + var solution = firstProject.Solution; + + using var _ = ArrayBuilder.GetInstance(out var flavors); + flavors.Add(firstProjectFlavor!); + + // Now, do the same for the other projects where we had a match. As above, if we can't figure out the + // simple name/flavor, or if the simple project name doesn't match the simple project name we started + // with then we can't merge these. + foreach (var additionalProjectId in _item.AdditionalMatchingProjects) + { + var additionalProject = solution.GetRequiredProject(additionalProjectId); + var (projectName, projectFlavor) = additionalProject.State.NameAndFlavor; + if (projectName == firstProjectName) + flavors.Add(projectFlavor!); + } + + flavors.RemoveDuplicates(); + flavors.Sort(); + + return $"{firstProjectName} ({string.Join(", ", flavors)})"; + } + } + + // Couldn't compute a merged project name (or only had one project). Just return the name of hte project itself. + return _document.Project.Name; + } string INavigateToSearchResult.AdditionalInformation => _additionalInformation; diff --git a/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs index db6d7cef5fe65..1df24cf4ee104 100644 --- a/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/ProjectExtensions.cs @@ -4,9 +4,6 @@ #nullable disable -using System.Collections.Immutable; -using Microsoft.CodeAnalysis.PooledObjects; - namespace Microsoft.CodeAnalysis.Shared.Extensions { internal static class ProjectExtensions @@ -18,54 +15,5 @@ public static Glyph GetGlyph(this Project project) project.Language == LanguageNames.VisualBasic ? Glyph.BasicProject : Glyph.Assembly; } - - public static string GetNameWithAllFlavors(this Project project, ImmutableArray allProjectIds) - { - // If there aren't any additional matches in other projects, we don't need to merge anything. - if (allProjectIds.Length > 0) - { - var (firstProjectName, _) = project.State.NameAndFlavor; - if (firstProjectName != null) - { - // First get the simple project name and flavor for the actual project we got a hit in. If we can't - // figure this out, we can't create a merged name. - using var _ = ArrayBuilder.GetInstance(out var flavors); - project.GetAllFlavors(allProjectIds, flavors); - - return flavors.Count > 1 - ? $"{firstProjectName} ({string.Join(", ", flavors)})" - : firstProjectName; - } - } - - // Couldn't compute a merged project name (or only had one project). Just return the name of hte project itself. - return project.Name; - } - - public static void GetAllFlavors( - this Project project, ImmutableArray allProjectIds, ArrayBuilder flavors) - { - var solution = project.Solution; - - var (firstProjectName, firstProjectFlavor) = project.State.NameAndFlavor; - if (firstProjectName != null) - { - flavors.Add(firstProjectFlavor!); - - // Now, do the same for the other projects where we had a match. As above, if we can't figure out the - // simple name/flavor, or if the simple project name doesn't match the simple project name we started - // with then we can't merge these. - foreach (var projectId in allProjectIds) - { - var otherProject = solution.GetRequiredProject(projectId); - var (projectName, projectFlavor) = otherProject.State.NameAndFlavor; - if (projectName == firstProjectName) - flavors.Add(projectFlavor!); - } - - flavors.RemoveDuplicates(); - flavors.Sort(); - } - } } } From 51ac5f73da777365cac8c09c113bb2b586867e55 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 22:55:26 -0700 Subject: [PATCH 153/220] Warning --- .../Extensibility/NavigationBar/NavigationBarProjectItem.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs index d52ab3f230aed..ce9fd73b51b90 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarProjectItem.cs @@ -21,8 +21,10 @@ public NavigationBarProjectItem( Workspace workspace, DocumentId documentId, string language) - : base(text, glyph, ImmutableArray.Empty, - childItems: null, indent: 0, bolded: false, grayed: false) + : base(text, glyph, + spans: ImmutableArray.Empty, + childItems: ImmutableArray.Empty, + indent: 0, bolded: false, grayed: false) { this.Workspace = workspace; this.DocumentId = documentId; From fe85bc4b5f620896720d256d7690e3ec89e2ea08 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:08:03 -0700 Subject: [PATCH 154/220] Simplify --- .../Core/FindUsages/IDefinitionsAndReferencesFactory.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 5891fbf3051b4..4b8cca67710a1 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -90,8 +90,7 @@ public static Task ToClassifiedDefinitionItemAsync( // If we had symbols from different project flavors, add the project flavor info into the definition // item name to show the user. var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); - return ToDefinitionItemAsync( - group.PrimarySymbol, allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); + return ToDefinitionItemAsync(group.PrimarySymbol, allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); } private static Task ToDefinitionItemAsync( From dee03b45bde0101e3ad3ec6515afd6b4b533858b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:09:35 -0700 Subject: [PATCH 155/220] Temp array --- .../Core/FindUsages/IDefinitionsAndReferencesFactory.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index 4b8cca67710a1..d4deae63d775e 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Collections; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; @@ -134,7 +135,7 @@ private static async Task ToDefinitionItemAsync( // If it's a namespace, don't create any normal location. Namespaces // come from many different sources, but we'll only show a single // root definition node for it. That node won't be navigable. - using var _ = ArrayBuilder.GetInstance(out var sourceLocations); + using var sourceLocations = TemporaryArray.Empty; if (definition.Kind != SymbolKind.Namespace) { foreach (var location in locations) @@ -180,7 +181,7 @@ private static async Task ToDefinitionItemAsync( var displayableProperties = AbstractReferenceFinder.GetAdditionalFindUsagesProperties(definition); return DefinitionItem.Create( - tags, displayParts, sourceLocations.ToImmutable(), + tags, displayParts, sourceLocations.ToImmutableAndClear(), nameDisplayParts, properties, displayableProperties, displayIfNoReferences); } From 305a77f96a42323a4f9e55721abee5764cd2e60d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:16:15 -0700 Subject: [PATCH 156/220] Simplify --- .../FindUsages/IDefinitionsAndReferencesFactory.cs | 2 +- .../FindReferencesSearchEngine_MapCreation.cs | 4 ++-- .../IStreamingFindReferencesProgress.cs | 11 ++--------- .../SymbolFinder.FindReferencesServerCallback.cs | 4 +--- .../Core/Portable/Remote/RemoteArguments.cs | 14 +++----------- 5 files changed, 9 insertions(+), 26 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index d4deae63d775e..bdb50e18b650c 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -91,7 +91,7 @@ public static Task ToClassifiedDefinitionItemAsync( // If we had symbols from different project flavors, add the project flavor info into the definition // item name to show the user. var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); - return ToDefinitionItemAsync(group.PrimarySymbol, allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); + return ToDefinitionItemAsync(group.Symbols.First(), allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); } private static Task ToDefinitionItemAsync( diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index 3daf777afe3d6..fea0eb45e3e5a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -170,12 +170,12 @@ private async Task DetermineAllSymbolsCoreAsync( private async Task DetermineSymbolGroupAsync(ISymbol searchSymbol) { if (!_options.Cascade) - return new SymbolGroup(searchSymbol, ImmutableArray.Create(searchSymbol)); + return new SymbolGroup(ImmutableArray.Create(searchSymbol)); var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync( searchSymbol, _solution, _cancellationToken).ConfigureAwait(false); - return new SymbolGroup(searchSymbol, linkedSymbols); + return new SymbolGroup(linkedSymbols); } private void AddSymbolTasks( diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 926f29ca98c64..54e506ba8fcad 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -22,27 +22,20 @@ namespace Microsoft.CodeAnalysis.FindSymbols internal class SymbolGroup : IEquatable { /// - /// The main symbol of the group (normally the symbol that was searched for). - /// - public ISymbol PrimarySymbol { get; } - - /// - /// All the symbols in the group. Will include . + /// All the symbols in the group. /// public ImmutableHashSet Symbols { get; } private int _hashCode; - public SymbolGroup(ISymbol primarySymbol, ImmutableArray symbols) + public SymbolGroup(ImmutableArray symbols) { Contract.ThrowIfTrue(symbols.IsDefaultOrEmpty); - Contract.ThrowIfFalse(symbols.Contains(primarySymbol)); // We should only get an actual group of symbols if these were from source. // Metadata symbols never form a group. Contract.ThrowIfTrue(symbols.Length >= 2 && symbols.Any(s => s.Locations.Any(loc => loc.IsInMetadata))); - PrimarySymbol = primarySymbol; Symbols = ImmutableHashSet.CreateRange( MetadataUnifyingEquivalenceComparer.Instance, symbols); } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 73036ba48a897..79e1459044d5c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -81,9 +81,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated pairs[symbolAndProjectId] = symbol; } - var primarySymbol = pairs[dehydrated.PrimarySymbol]; - - var symbolGroup = new SymbolGroup(primarySymbol, symbols.ToImmutable()); + var symbolGroup = new SymbolGroup(symbols.ToImmutable()); lock (_gate) { _groupMap[dehydrated] = symbolGroup; diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 078060029bc7c..4facbb2993fc2 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -214,17 +214,12 @@ private async Task RehydrateAliasAsync( internal class SerializableSymbolGroup : IEquatable { [DataMember(Order = 0)] - public readonly SerializableSymbolAndProjectId PrimarySymbol; - [DataMember(Order = 1)] public readonly HashSet Symbols; private int _hashCode; - public SerializableSymbolGroup( - SerializableSymbolAndProjectId primarySymbol, - HashSet symbols) + public SerializableSymbolGroup(HashSet symbols) { - PrimarySymbol = primarySymbol; Symbols = new HashSet(symbols); } @@ -252,11 +247,8 @@ public override int GetHashCode() public static SerializableSymbolGroup Dehydrate(Solution solution, SymbolGroup group, CancellationToken cancellationToken) { - return new SerializableSymbolGroup( - SerializableSymbolAndProjectId.Dehydrate(solution, group.PrimarySymbol, cancellationToken), - new HashSet( - group.Symbols.Select( - s => SerializableSymbolAndProjectId.Dehydrate(solution, s, cancellationToken)))); + return new SerializableSymbolGroup(new HashSet( + group.Symbols.Select(s => SerializableSymbolAndProjectId.Dehydrate(solution, s, cancellationToken)))); } } From 5794d4d27fc482738e55a2dd0dc8c38df9f7fff5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:22:49 -0700 Subject: [PATCH 157/220] Simplify --- .../SymbolFinder.FindReferencesServerCallback.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 79e1459044d5c..3642c5e68aba1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -68,8 +68,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated { Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); - using var _1 = ArrayBuilder.GetInstance(out var symbols); - using var _2 = PooledDictionary.GetInstance(out var pairs); + using var _1 = PooledDictionary.GetInstance(out var map); foreach (var symbolAndProjectId in dehydrated.Symbols) { @@ -77,15 +76,14 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated if (symbol == null) return; - symbols.Add(symbol); - pairs[symbolAndProjectId] = symbol; + map[symbolAndProjectId] = symbol; } - var symbolGroup = new SymbolGroup(symbols.ToImmutable()); + var symbolGroup = new SymbolGroup(map.Values.ToImmutableArray()); lock (_gate) { _groupMap[dehydrated] = symbolGroup; - foreach (var pair in pairs) + foreach (var pair in map) _definitionMap[pair.Key] = pair.Value; } From f445d594acd5006934e0bc7f7890da60774415a8 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:24:33 -0700 Subject: [PATCH 158/220] Simplify --- .../Core/FindUsages/IDefinitionsAndReferencesFactory.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs index bdb50e18b650c..c4c9ac23ce673 100644 --- a/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs +++ b/src/EditorFeatures/Core/FindUsages/IDefinitionsAndReferencesFactory.cs @@ -88,8 +88,6 @@ public static Task ToClassifiedDefinitionItemAsync( this SymbolGroup group, Solution solution, bool isPrimary, bool includeHiddenLocations, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // Make a single definition item that knows about all the locations of all the symbols in the group. - // If we had symbols from different project flavors, add the project flavor info into the definition - // item name to show the user. var allLocations = group.Symbols.SelectMany(s => s.Locations).ToImmutableArray(); return ToDefinitionItemAsync(group.Symbols.First(), allLocations, solution, isPrimary, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); } From f8a3a0e423a8ff1094e220ffc8478f9642e4090e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:31:27 -0700 Subject: [PATCH 159/220] Simplify --- .../FindReferencesSearchEngine_MapCreation.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs index fea0eb45e3e5a..804ddbe60269d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine_MapCreation.cs @@ -172,10 +172,8 @@ private async Task DetermineSymbolGroupAsync(ISymbol searchSymbol) if (!_options.Cascade) return new SymbolGroup(ImmutableArray.Create(searchSymbol)); - var linkedSymbols = await SymbolFinder.FindLinkedSymbolsAsync( - searchSymbol, _solution, _cancellationToken).ConfigureAwait(false); - - return new SymbolGroup(linkedSymbols); + return new SymbolGroup( + await SymbolFinder.FindLinkedSymbolsAsync(searchSymbol, _solution, _cancellationToken).ConfigureAwait(false)); } private void AddSymbolTasks( From 3e56c1777fa1f49c2f9055ab5990e4fdf8b8f46c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:33:25 -0700 Subject: [PATCH 160/220] Thread safety --- .../Portable/FindSymbols/IStreamingFindReferencesProgress.cs | 4 +++- src/Workspaces/Core/Portable/Remote/RemoteArguments.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs index 54e506ba8fcad..5c7da911bedc0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IStreamingFindReferencesProgress.cs @@ -50,8 +50,10 @@ public override int GetHashCode() { if (_hashCode == 0) { + var hashCode = 0; foreach (var symbol in Symbols) - _hashCode += MetadataUnifyingEquivalenceComparer.Instance.GetHashCode(symbol); + hashCode += MetadataUnifyingEquivalenceComparer.Instance.GetHashCode(symbol); + _hashCode = hashCode; } return _hashCode; diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 4facbb2993fc2..e346fe058fbc1 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -238,8 +238,10 @@ public override int GetHashCode() { if (_hashCode == 0) { + var hashCode = 0; foreach (var symbol in Symbols) - _hashCode += symbol.SymbolKeyData.GetHashCode(); + hashCode += symbol.SymbolKeyData.GetHashCode(); + _hashCode = hashCode; } return _hashCode; From 67e3d9e80ad03afe98eb0ce2996b335bc8676291 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:34:22 -0700 Subject: [PATCH 161/220] Thread safety --- .../FindSymbols/SymbolFinder.FindReferencesServerCallback.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 3642c5e68aba1..d19ca96433e90 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -68,7 +68,7 @@ public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated { Contract.ThrowIfTrue(dehydrated.Symbols.Count == 0); - using var _1 = PooledDictionary.GetInstance(out var map); + using var _ = PooledDictionary.GetInstance(out var map); foreach (var symbolAndProjectId in dehydrated.Symbols) { From 59b385398a710f779eb0e0f2677583530684f594 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 13 Apr 2021 23:35:14 -0700 Subject: [PATCH 162/220] Fix --- src/Workspaces/Core/Portable/Remote/RemoteArguments.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index e346fe058fbc1..ee27e9772c9be 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -43,7 +43,7 @@ public bool Equals(SerializableSymbolAndProjectId other) if (this == other) return true; - return this.ProjectId == other.ProjectId && + return this.ProjectId == other?.ProjectId && this.SymbolKeyData == other?.SymbolKeyData; } From e15103cfa572370de4bf7762c06ab5bb4083d1e4 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 11:54:27 +0100 Subject: [PATCH 163/220] Revert changes in SymbolDisplayVisitor --- .../SymbolDisplay/SymbolDisplayVisitor.Types.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs index c9ca68c8e9678..8b568e69e1e92 100644 --- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs +++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplayVisitor.Types.cs @@ -395,7 +395,6 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol) AddDelegateParameters(symbol); } - AddUnderlyingEnumType(symbol); // Only the compiler can set the internal option and the compiler doesn't use other implementations of INamedTypeSymbol. if (underlyingTypeSymbol?.OriginalDefinition is MissingMetadataTypeSymbol && format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.FlagMissingMetadataTypes)) @@ -407,17 +406,6 @@ private void AddNameAndTypeArgumentsOrParameters(INamedTypeSymbol symbol) } } - private void AddUnderlyingEnumType(INamedTypeSymbol symbol) - { - if (symbol.TypeKind == TypeKind.Enum && symbol.EnumUnderlyingType.SpecialType != SpecialType.System_Int32) - { - AddSpace(); - AddPunctuation(SyntaxKind.ColonToken); - AddSpace(); - AddSpecialTypeKeyword(symbol.EnumUnderlyingType); - } - } - private ImmutableArray> GetTypeArgumentsModifiers(NamedTypeSymbol underlyingTypeSymbol) { if (this.format.CompilerInternalOptions.IncludesOption(SymbolDisplayCompilerInternalOptions.IncludeCustomModifiers)) From 91ac34957a2760140a590be3c9f15db962f54fd2 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 11:54:49 +0100 Subject: [PATCH 164/220] Add test in SemanticQuickInfoSourceTests --- .../CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index 5cf608e1aa700..71f0892808572 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -1651,6 +1651,13 @@ await TestInMethodAsync(@"Consol$$eColor c", MainDescription("enum System.ConsoleColor")); } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + public async Task EnumNonDefaultUnderlyingType() + { + await TestInClassAsync(@"enum E$$ : byte { A, B }", + MainDescription("enum C.E : byte")); + } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] public async Task EnumMemberNameFromMetadata() { From 4c2660c6c2d4fad727380b2fc840401e4dfa45f0 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 11:56:04 +0100 Subject: [PATCH 165/220] Remove tests in SymbolDisplayTests --- .../SymbolDisplay/SymbolDisplayTests.cs | 70 ------------------- 1 file changed, 70 deletions(-) diff --git a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs index f3965da7031b8..f56b37d03ca46 100644 --- a/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/SymbolDisplay/SymbolDisplayTests.cs @@ -7736,75 +7736,5 @@ class A { format, "delegate*"); } - - [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] - [InlineData("byte", "byte")] - [InlineData("byte", "System.Byte")] - [InlineData("sbyte", "sbyte")] - [InlineData("sbyte", "System.SByte")] - [InlineData("short", "short")] - [InlineData("short", "System.Int16")] - [InlineData("ushort", "ushort")] - [InlineData("ushort", "System.UInt16")] - // int is the default type and is not shown - [InlineData("uint", "uint")] - [InlineData("uint", "System.UInt32")] - [InlineData("long", "long")] - [InlineData("long", "System.Int64")] - [InlineData("ulong", "ulong")] - [InlineData("ulong", "System.UInt64")] - public void TestEnumWithUnderlyingType_ShowForNonDefaultTypes(string displayTypeName, string underlyingTypeName) - { - var text = @$" -enum E : {underlyingTypeName} -{{ - A, B -}}"; - - Func findSymbol = global => - global.GetTypeMembers("E", 0).Single(); - - var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); - - TestSymbolDescription( - text, - findSymbol, - format, - $"enum E : {displayTypeName}", - SymbolDisplayPartKind.Keyword, - SymbolDisplayPartKind.Space, - SymbolDisplayPartKind.EnumName, - SymbolDisplayPartKind.Space, - SymbolDisplayPartKind.Punctuation, - SymbolDisplayPartKind.Space, - SymbolDisplayPartKind.Keyword); - } - - [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] - [InlineData("")] - [InlineData(": int")] - [InlineData(": System.Int32")] - public void TestEnumWithUnderlyingType_DontShowForDefaultType(string defaultType) - { - var text = @$" -enum E {defaultType} -{{ - A, B -}}"; - - Func findSymbol = global => - global.GetTypeMembers("E", 0).Single(); - - var format = new SymbolDisplayFormat(memberOptions: SymbolDisplayMemberOptions.IncludeType, kindOptions: SymbolDisplayKindOptions.IncludeTypeKeyword); - - TestSymbolDescription( - text, - findSymbol, - format, - $"enum E", - SymbolDisplayPartKind.Keyword, - SymbolDisplayPartKind.Space, - SymbolDisplayPartKind.EnumName); - } } } From e6d44d7874b7e859b2dcb520501e12a8bd314ce8 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 11:56:17 +0100 Subject: [PATCH 166/220] Add WorkItem to test --- .../CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index 71f0892808572..f42cae37dc2e5 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -1652,6 +1652,7 @@ await TestInMethodAsync(@"Consol$$eColor c", } [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] public async Task EnumNonDefaultUnderlyingType() { await TestInClassAsync(@"enum E$$ : byte { A, B }", From 19588982784f1c9adb68be7afdcf49d1c781f599 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 12:24:41 +0100 Subject: [PATCH 167/220] Add underlying type handling to AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder --- ...lDisplayService.AbstractSymbolDescriptionBuilder.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index 191cdcc46d648..8eae6176a0251 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -465,6 +465,16 @@ private void AddDescriptionForNamedType(INamedTypeSymbol symbol) AddTypeParameterMapPart(allTypeParameters, allTypeArguments); } + + if (symbol.IsEnumType() && symbol.EnumUnderlyingType.SpecialType != SpecialType.System_Int32) + { + var underlyingTypeDisplayParts = symbol.EnumUnderlyingType.ToDisplayParts(s_descriptionStyle.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes)); + AddToGroup(SymbolDescriptionGroups.MainDescription, + Space(), + Punctuation(":"), + Space(), + underlyingTypeDisplayParts); + } } private void AddSymbolDescription(INamedTypeSymbol symbol) From f3424aa49064b0b5610f743224b26699c66bac45 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 13:38:47 +0100 Subject: [PATCH 168/220] Add C# tests for different type positions. --- .../QuickInfo/SemanticQuickInfoSourceTests.cs | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index f42cae37dc2e5..5ac5a833d736d 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -1653,12 +1653,90 @@ await TestInMethodAsync(@"Consol$$eColor c", [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] - public async Task EnumNonDefaultUnderlyingType() + public async Task EnumNonDefaultUnderlyingType_Definition() { await TestInClassAsync(@"enum E$$ : byte { A, B }", MainDescription("enum C.E : byte")); } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsField() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private E$$ _E; +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsProperty() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private E$$ E{ get; set; }; +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsParameter() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M(E$$ e) { } +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsReturnType() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private E$$ M() { } +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_AsLocal() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M() +{ + E$$ e = default; +} +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_NotOnMemberAccess() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M() +{ + var ea = E.A$$; +} +", + MainDescription("E.A = 0")); + } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] public async Task EnumMemberNameFromMetadata() { From 2612d587666039e9b12bf6f55c87695252b6d6b4 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 13:47:54 +0100 Subject: [PATCH 169/220] Add more C# tests. --- .../QuickInfo/SemanticQuickInfoSourceTests.cs | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs index 5ac5a833d736d..5643fa7ce7227 100644 --- a/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs +++ b/src/EditorFeatures/CSharpTest/QuickInfo/SemanticQuickInfoSourceTests.cs @@ -1724,7 +1724,22 @@ private void M() [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] - public async Task EnumNonDefaultUnderlyingType_NotOnMemberAccess() + public async Task EnumNonDefaultUnderlyingType_OnMemberAccessOnType() + { + await TestInClassAsync(@" +enum E : byte { A, B } + +private void M() +{ + var ea = E$$.A; +} +", + MainDescription("enum C.E : byte")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] + [WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + public async Task EnumNonDefaultUnderlyingType_NotOnMemberAccessOnMember() { await TestInClassAsync(@" enum E : byte { A, B } @@ -1737,6 +1752,46 @@ private void M() MainDescription("E.A = 0")); } + [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + [InlineData("byte", "byte")] + [InlineData("byte", "System.Byte")] + [InlineData("sbyte", "sbyte")] + [InlineData("sbyte", "System.SByte")] + [InlineData("short", "short")] + [InlineData("short", "System.Int16")] + [InlineData("ushort", "ushort")] + [InlineData("ushort", "System.UInt16")] + // int is the default type and is not shown + [InlineData("uint", "uint")] + [InlineData("uint", "System.UInt32")] + [InlineData("long", "long")] + [InlineData("long", "System.Int64")] + [InlineData("ulong", "ulong")] + [InlineData("ulong", "System.UInt64")] + public async Task EnumNonDefaultUnderlyingType_ShowForNonDefaultTypes(string displayTypeName, string underlyingTypeName) + { + await TestInClassAsync(@$" +enum E$$ : {underlyingTypeName} +{{ + A, B +}}", + MainDescription($"enum C.E : {displayTypeName}")); + } + + [Theory, WorkItem(52490, "https://github.com/dotnet/roslyn/issues/52490")] + [InlineData("")] + [InlineData(": int")] + [InlineData(": System.Int32")] + public async Task EnumNonDefaultUnderlyingType_DontShowForDefaultType(string defaultType) + { + await TestInClassAsync(@$" +enum E$$ {defaultType} +{{ + A, B +}}", + MainDescription("enum C.E")); + } + [Fact, Trait(Traits.Feature, Traits.Features.QuickInfo)] public async Task EnumMemberNameFromMetadata() { From 60be5886ac326512bc3fe3e46e8a9a3a5d385a36 Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 14:28:35 +0100 Subject: [PATCH 170/220] Add text classification test --- .../IntellisenseQuickInfoBuilderTests.vb | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb index b03efe20b2bda..e2b9e34fd61d9 100644 --- a/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/IntellisenseQuickInfoBuilderTests.vb @@ -1158,5 +1158,39 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.IntelliSense ToolTipAssert.EqualContent(expected, container) End Function + + + + Public Async Function QuickInfoForUnderlyingEnumTypes() As Task + Dim workspace = + + + + public enum E$$ : byte { A, B } + + + + + Dim intellisenseQuickInfo = Await GetQuickInfoItemAsync(workspace, LanguageNames.CSharp) + Assert.NotNull(intellisenseQuickInfo) + + Dim container = Assert.IsType(Of ContainerElement)(intellisenseQuickInfo.Item) + + Dim expected = New ContainerElement( + ContainerElementStyle.Stacked Or ContainerElementStyle.VerticalPadding, + New ContainerElement( + ContainerElementStyle.Wrapped, + New ImageElement(New ImageId(KnownImageIds.ImageCatalogGuid, KnownImageIds.EnumerationPublic)), + New ClassifiedTextElement( + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "enum"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.EnumName, "E", navigationAction:=Sub() Return, "E"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Punctuation, ":"), + New ClassifiedTextRun(ClassificationTypeNames.WhiteSpace, " "), + New ClassifiedTextRun(ClassificationTypeNames.Keyword, "byte", navigationAction:=Sub() Return, "byte")))) + + ToolTipAssert.EqualContent(expected, container) + End Function End Class End Namespace From 4c26426a3378208cf20fc42d57e2aa688a790acb Mon Sep 17 00:00:00 2001 From: Martin Strecker Date: Wed, 14 Apr 2021 14:59:39 +0100 Subject: [PATCH 171/220] Add support for VB. --- .../QuickInfo/SemanticQuickInfoSourceTests.vb | 16 ++++++++++++++++ ...bolDisplayService.SymbolDescriptionBuilder.cs | 8 ++++++++ ...ayService.AbstractSymbolDescriptionBuilder.cs | 8 +++----- ...bolDisplayService.SymbolDescriptionBuilder.vb | 7 +++++++ 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb b/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb index 6c5d91b17b6da..0102a14a1426a 100644 --- a/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb +++ b/src/EditorFeatures/VisualBasicTest/QuickInfo/SemanticQuickInfoSourceTests.vb @@ -1515,6 +1515,22 @@ End Class MainDescription("Goo.B = 1")) End Function + + + Public Async Function EnumNonDefaultUnderlyingType() As Task + Dim code = + +Enum Goo$$ As Byte + A + B + C +End Enum +.NormalizedValue() + + Await TestAsync(code, + MainDescription("Enum Goo As Byte")) + End Function + Public Async Function TestTextOnlyDocComment() As Task Await TestAsync(> GetInitializerSourcePartsAsync( ISymbol symbol) { diff --git a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs index 8eae6176a0251..265ba16820196 100644 --- a/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs +++ b/src/Features/Core/Portable/LanguageServices/SymbolDisplayService/AbstractSymbolDisplayService.AbstractSymbolDescriptionBuilder.cs @@ -109,6 +109,7 @@ protected AbstractSymbolDescriptionBuilder( protected abstract void AddAwaitablePrefix(); protected abstract void AddAwaitableExtensionPrefix(); protected abstract void AddDeprecatedPrefix(); + protected abstract void AddEnumUnderlyingTypeSeparator(); protected abstract Task> GetInitializerSourcePartsAsync(ISymbol symbol); protected abstract ImmutableArray ToMinimalDisplayParts(ISymbol symbol, SemanticModel semanticModel, int position, SymbolDisplayFormat format); protected abstract string GetNavigationHint(ISymbol symbol); @@ -468,12 +469,9 @@ private void AddDescriptionForNamedType(INamedTypeSymbol symbol) if (symbol.IsEnumType() && symbol.EnumUnderlyingType.SpecialType != SpecialType.System_Int32) { + AddEnumUnderlyingTypeSeparator(); var underlyingTypeDisplayParts = symbol.EnumUnderlyingType.ToDisplayParts(s_descriptionStyle.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseSpecialTypes)); - AddToGroup(SymbolDescriptionGroups.MainDescription, - Space(), - Punctuation(":"), - Space(), - underlyingTypeDisplayParts); + AddToGroup(SymbolDescriptionGroups.MainDescription, underlyingTypeDisplayParts); } } diff --git a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb index 7f6753a278bf7..c6c4cc2710b2a 100644 --- a/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb +++ b/src/Features/VisualBasic/Portable/LanguageServices/VisualBasicSymbolDisplayService.SymbolDescriptionBuilder.vb @@ -66,6 +66,13 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LanguageServices Space()) End Sub + Protected Overrides Sub AddEnumUnderlyingTypeSeparator() + AddToGroup(SymbolDescriptionGroups.MainDescription, + Space(), + Keyword("As"), + Space()) + End Sub + Protected Overrides Function GetInitializerSourcePartsAsync(symbol As ISymbol) As Task(Of ImmutableArray(Of SymbolDisplayPart)) If TypeOf symbol Is IParameterSymbol Then Return GetInitializerSourcePartsAsync(DirectCast(symbol, IParameterSymbol)) From b77d3bb8aafcaa0cf9148dd1c7e0ee1ebb8055de Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Wed, 14 Apr 2021 09:59:41 -0700 Subject: [PATCH 172/220] Improve Server Logging (#52572) * Improve compiler server logging The goal if this PR is to change the logging so that: 1. It's easier to identify the process writing the message. The prefix for messages will be prefixed with either "VBCSCompiler" or "MSBuild ". 1. Add a `Guid` to a logical build request that is included in many messages. This will make it easier to track requests as they travel between the MSBuild and VBCSCompiler processes. 1. Ensure all failure states have an explicit log call The motivation for this change is we are seeing the compiler server crash in build correctness and source build legs. From the log it's hard to see what is happening. The failure is essentially silent. This is an attempt to get more data so we can see what is actually going on during failure. * More * Capture dumps when the compiler crashes * PR feedback --- azure-pipelines.yml | 2 +- eng/test-build-correctness.ps1 | 15 +++ src/Compilers/CSharp/csc/Program.cs | 5 +- .../Core/CommandLine/BuildProtocol.cs | 105 +++++++++--------- .../Core/CommandLine/CompilerServerLogger.cs | 11 +- .../Core/MSBuildTask/ManagedCompiler.cs | 34 +++--- .../Server/VBCSCompiler/BuildProtocolUtil.cs | 2 +- .../VBCSCompiler/ClientConnectionHandler.cs | 23 ++-- .../VBCSCompiler/CompilerRequestHandler.cs | 48 +++++--- .../Server/VBCSCompiler/IClientConnection.cs | 6 - .../VBCSCompiler/ICompilerServerHost.cs | 2 +- .../VBCSCompiler/NamedPipeClientConnection.cs | 10 +- .../NamedPipeClientConnectionHost.cs | 12 +- .../Server/VBCSCompiler/VBCSCompiler.cs | 2 +- .../VBCSCompilerTests/BuildProtocolTest.cs | 3 +- .../CompilerServerApiTest.cs | 11 +- .../VBCSCompilerTests/CompilerServerTests.cs | 2 +- .../Server/VBCSCompilerTests/ServerUtil.cs | 6 +- .../TestableCompilerServerHost.cs | 2 +- src/Compilers/Shared/BuildClient.cs | 70 ++++-------- src/Compilers/Shared/BuildServerConnection.cs | 28 +++-- src/Compilers/VisualBasic/vbc/Program.cs | 5 +- 22 files changed, 194 insertions(+), 210 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 13d6ece1c57a7..003ac28af6fc3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -174,7 +174,7 @@ jobs: steps: - template: eng/pipelines/checkout-windows-task.yml - - script: eng/test-build-correctness.cmd -configuration Release + - script: eng/test-build-correctness.cmd -configuration Release -enableDumps displayName: Build - Validate correctness - template: eng/pipelines/publish-logs.yml diff --git a/eng/test-build-correctness.ps1 b/eng/test-build-correctness.ps1 index 539cd70b88a3c..a8e9ecb67ebfe 100644 --- a/eng/test-build-correctness.ps1 +++ b/eng/test-build-correctness.ps1 @@ -12,6 +12,7 @@ [CmdletBinding(PositionalBinding=$false)] param( [string]$configuration = "Debug", + [switch]$enableDumps = $false, [switch]$help) Set-StrictMode -version 2.0 @@ -33,6 +34,14 @@ try { . (Join-Path $PSScriptRoot "build-utils.ps1") Push-Location $RepoRoot + if ($enableDumps) { + $key = "HKLM:\\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" + New-Item -Path $key -ErrorAction SilentlyContinue + New-ItemProperty -Path $key -Name 'DumpType' -PropertyType 'DWord' -Value 2 -Force + New-ItemProperty -Path $key -Name 'DumpCount' -PropertyType 'DWord' -Value 2 -Force + New-ItemProperty -Path $key -Name 'DumpFolder' -PropertyType 'String' -Value $LogDir -Force + } + # Verify no PROTOTYPE marker left in main if ($env:SYSTEM_PULLREQUEST_TARGETBRANCH -eq "main") { Write-Host "Checking no PROTOTYPE markers in compiler source" @@ -67,5 +76,11 @@ catch [exception] { exit 1 } finally { + if ($enableDumps) { + $key = "HKLM:\\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" + Remove-ItemProperty -Path $key -Name 'DumpType' + Remove-ItemProperty -Path $key -Name 'DumpCount' + Remove-ItemProperty -Path $key -Name 'DumpFolder' + } Pop-Location } diff --git a/src/Compilers/CSharp/csc/Program.cs b/src/Compilers/CSharp/csc/Program.cs index 1051f25d7ed4b..0b6bb4b6a2784 100644 --- a/src/Compilers/CSharp/csc/Program.cs +++ b/src/Compilers/CSharp/csc/Program.cs @@ -33,8 +33,9 @@ private static int MainCore(string[] args) ExitingTraceListener.Install(); #endif - using var logger = new CompilerServerLogger(); - return BuildClient.Run(args, RequestLanguage.CSharpCompile, Csc.Run, logger); + var requestId = Guid.NewGuid(); + using var logger = new CompilerServerLogger($"csc {requestId}"); + return BuildClient.Run(args, RequestLanguage.CSharpCompile, Csc.Run, logger, requestId); } public static int Run(string[] args, string clientDir, string workingDir, string sdkDir, string tempDir, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader) diff --git a/src/Compilers/Core/CommandLine/BuildProtocol.cs b/src/Compilers/Core/CommandLine/BuildProtocol.cs index 6da53beb1f746..650b403ba4350 100644 --- a/src/Compilers/Core/CommandLine/BuildProtocol.cs +++ b/src/Compilers/Core/CommandLine/BuildProtocol.cs @@ -33,7 +33,7 @@ namespace Microsoft.CodeAnalysis.CommandLine /// Field Name Type Size (bytes) /// ---------------------------------------------------- /// Length Integer 4 - /// ProtocolVersion Integer 4 + /// RequestId Guid 16 /// Language RequestLanguage 4 /// CompilerHash String Variable /// Argument Count UInteger 4 @@ -45,17 +45,17 @@ namespace Microsoft.CodeAnalysis.CommandLine /// internal class BuildRequest { - public readonly uint ProtocolVersion; + public readonly Guid RequestId; public readonly RequestLanguage Language; public readonly ReadOnlyCollection Arguments; public readonly string CompilerHash; - public BuildRequest(uint protocolVersion, - RequestLanguage language, + public BuildRequest(RequestLanguage language, string compilerHash, - IEnumerable arguments) + IEnumerable arguments, + Guid? requestId = null) { - ProtocolVersion = protocolVersion; + RequestId = requestId ?? Guid.Empty; Language = language; Arguments = new ReadOnlyCollection(arguments.ToList()); CompilerHash = compilerHash; @@ -75,6 +75,7 @@ public static BuildRequest Create(RequestLanguage language, string workingDirectory, string tempDirectory, string compilerHash, + Guid? requestId = null, string? keepAlive = null, string? libDirectory = null) { @@ -102,13 +103,13 @@ public static BuildRequest Create(RequestLanguage language, requestArgs.Add(new Argument(ArgumentId.CommandLineArgument, i, arg)); } - return new BuildRequest(BuildProtocolConstants.ProtocolVersion, language, compilerHash, requestArgs); + return new BuildRequest(language, compilerHash, requestArgs, requestId); } public static BuildRequest CreateShutdown() { var requestArgs = new[] { new Argument(ArgumentId.Shutdown, argumentIndex: 0, value: "") }; - return new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, GetCommitHash() ?? "", requestArgs); + return new BuildRequest(RequestLanguage.CSharpCompile, GetCommitHash() ?? "", requestArgs); } /// @@ -139,25 +140,34 @@ public static async Task ReadAsync(Stream inStream, CancellationTo cancellationToken.ThrowIfCancellationRequested(); // Parse the request into the Request data structure. - using (var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode)) + using var reader = new BinaryReader(new MemoryStream(requestBuffer), Encoding.Unicode); + var requestId = readGuid(reader); + var language = (RequestLanguage)reader.ReadUInt32(); + var compilerHash = reader.ReadString(); + uint argumentCount = reader.ReadUInt32(); + var argumentsBuilder = new List((int)argumentCount); + + for (int i = 0; i < argumentCount; i++) { - var protocolVersion = reader.ReadUInt32(); - var language = (RequestLanguage)reader.ReadUInt32(); - var compilerHash = reader.ReadString(); - uint argumentCount = reader.ReadUInt32(); + cancellationToken.ThrowIfCancellationRequested(); + argumentsBuilder.Add(BuildRequest.Argument.ReadFromBinaryReader(reader)); + } - var argumentsBuilder = new List((int)argumentCount); + return new BuildRequest(language, + compilerHash, + argumentsBuilder, + requestId); - for (int i = 0; i < argumentCount; i++) + static Guid readGuid(BinaryReader reader) + { + const int size = 16; + var bytes = new byte[size]; + if (size != reader.Read(bytes, 0, size)) { - cancellationToken.ThrowIfCancellationRequested(); - argumentsBuilder.Add(BuildRequest.Argument.ReadFromBinaryReader(reader)); + throw new InvalidOperationException(); } - return new BuildRequest(protocolVersion, - language, - compilerHash, - argumentsBuilder); + return new Guid(bytes); } } @@ -166,37 +176,35 @@ public static async Task ReadAsync(Stream inStream, CancellationTo /// public async Task WriteAsync(Stream outStream, CancellationToken cancellationToken = default(CancellationToken)) { - using (var memoryStream = new MemoryStream()) - using (var writer = new BinaryWriter(memoryStream, Encoding.Unicode)) + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream, Encoding.Unicode); + writer.Write(RequestId.ToByteArray()); + writer.Write((uint)Language); + writer.Write(CompilerHash); + writer.Write(Arguments.Count); + foreach (Argument arg in Arguments) { - writer.Write(ProtocolVersion); - writer.Write((uint)Language); - writer.Write(CompilerHash); - writer.Write(Arguments.Count); - foreach (Argument arg in Arguments) - { - cancellationToken.ThrowIfCancellationRequested(); - arg.WriteToBinaryWriter(writer); - } - writer.Flush(); - cancellationToken.ThrowIfCancellationRequested(); + arg.WriteToBinaryWriter(writer); + } + writer.Flush(); - // Write the length of the request - int length = checked((int)memoryStream.Length); - - // Back out if the request is > 1 MB - if (memoryStream.Length > 0x100000) - { - throw new ArgumentOutOfRangeException("Request is over 1MB in length"); - } + cancellationToken.ThrowIfCancellationRequested(); - await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4, - cancellationToken).ConfigureAwait(false); + // Write the length of the request + int length = checked((int)memoryStream.Length); - memoryStream.Position = 0; - await memoryStream.CopyToAsync(outStream, bufferSize: length, cancellationToken: cancellationToken).ConfigureAwait(false); + // Back out if the request is > 1 MB + if (memoryStream.Length > 0x100000) + { + throw new ArgumentOutOfRangeException("Request is over 1MB in length"); } + + await outStream.WriteAsync(BitConverter.GetBytes(length), 0, 4, + cancellationToken).ConfigureAwait(false); + + memoryStream.Position = 0; + await memoryStream.CopyToAsync(outStream, bufferSize: length, cancellationToken: cancellationToken).ConfigureAwait(false); } /// @@ -521,11 +529,6 @@ internal enum RequestLanguage /// internal static class BuildProtocolConstants { - /// - /// The version number for this protocol. - /// - public const uint ProtocolVersion = 3; - // Arguments for CSharp and VB Compiler public enum ArgumentId { diff --git a/src/Compilers/Core/CommandLine/CompilerServerLogger.cs b/src/Compilers/Core/CommandLine/CompilerServerLogger.cs index a08aabfc9f591..75a261b22c195 100644 --- a/src/Compilers/Core/CommandLine/CompilerServerLogger.cs +++ b/src/Compilers/Core/CommandLine/CompilerServerLogger.cs @@ -102,16 +102,17 @@ internal sealed class CompilerServerLogger : ICompilerServerLogger, IDisposable internal const string LoggingPrefix = "---"; private Stream? _loggingStream; - private readonly int _processId; + private readonly string _identifier; public bool IsLogging => _loggingStream is object; /// /// Static class initializer that initializes logging. /// - public CompilerServerLogger() + public CompilerServerLogger(string identifier) { - _processId = Process.GetCurrentProcess().Id; + _identifier = identifier; + var processId = Process.GetCurrentProcess().Id; try { @@ -123,7 +124,7 @@ public CompilerServerLogger() // Otherwise, assume that the environment variable specifies the name of the log file. if (Directory.Exists(loggingFileName)) { - loggingFileName = Path.Combine(loggingFileName, $"server.{_processId}.log"); + loggingFileName = Path.Combine(loggingFileName, $"server.{processId}.log"); } // Open allowing sharing. We allow multiple processes to log to the same file, so we use share mode to allow that. @@ -147,7 +148,7 @@ public void Log(string message) if (_loggingStream is object) { var threadId = Environment.CurrentManagedThreadId; - var prefix = $"PID={_processId} TID={threadId} Ticks={Environment.TickCount} "; + var prefix = $"ID={_identifier} TID={threadId}: "; string output = prefix + message + Environment.NewLine; byte[] bytes = Encoding.UTF8.GetBytes(output); diff --git a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs index 4832c81ef61e9..17f19bfbc4a16 100644 --- a/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs +++ b/src/Compilers/Core/MSBuildTask/ManagedCompiler.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -501,15 +502,16 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand try { - using var logger = new CompilerServerLogger(); + using var logger = new CompilerServerLogger($"MSBuild {Process.GetCurrentProcess().Id}"); string workingDir = CurrentDirectoryToUse(); string? tempDir = BuildServerConnection.GetTempPath(workingDir); + var requestId = Guid.NewGuid(); if (!UseSharedCompilation || HasToolBeenOverridden || !BuildServerConnection.IsCompilerServerSupported) { - LogCompilationMessage(logger, CompilationKind.Tool, $"using command line tool by design '{pathToTool}'"); + LogCompilationMessage(logger, requestId, CompilationKind.Tool, $"using command line tool by design '{pathToTool}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -520,7 +522,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand var clientDir = Path.GetDirectoryName(PathToManagedTool); if (clientDir is null || tempDir is null) { - LogCompilationMessage(logger, CompilationKind.Tool, $"using command line tool because we could not find client directory '{PathToManagedTool}'"); + LogCompilationMessage(logger, requestId, CompilationKind.Tool, $"using command line tool because we could not find client directory '{PathToManagedTool}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -535,6 +537,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand // commandLineCommands (the parameter) may have been mucked with // (to support using the dotnet cli) var responseTask = BuildServerConnection.RunServerCompilationAsync( + requestId, Language, RoslynString.IsNullOrEmpty(SharedCompilationId) ? null : SharedCompilationId, GetArguments(ToolArguments, responseFileCommands).ToList(), @@ -546,7 +549,7 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand responseTask.Wait(_sharedCompileCts.Token); - ExitCode = HandleResponse(responseTask.Result, pathToTool, responseFileCommands, commandLineCommands, logger); + ExitCode = HandleResponse(requestId, responseTask.Result, pathToTool, responseFileCommands, commandLineCommands, logger); } catch (OperationCanceledException) { @@ -630,11 +633,11 @@ private string CurrentDirectoryToUse() /// Handle a response from the server, reporting messages and returning /// the appropriate exit code. /// - private int HandleResponse(BuildResponse? response, string pathToTool, string responseFileCommands, string commandLineCommands, ICompilerServerLogger logger) + private int HandleResponse(Guid requestId, BuildResponse? response, string pathToTool, string responseFileCommands, string commandLineCommands, ICompilerServerLogger logger) { if (response is null) { - LogCompilationMessage(logger, CompilationKind.ToolFallback, "could not launch server"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, "could not launch server"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } @@ -648,30 +651,30 @@ private int HandleResponse(BuildResponse? response, string pathToTool, string re case BuildResponse.ResponseType.Completed: var completedResponse = (CompletedBuildResponse)response; LogCompilerOutput(completedResponse.Output, StandardOutputImportanceToUse); - LogCompilationMessage(logger, CompilationKind.Server, "server processed compilation"); + LogCompilationMessage(logger, requestId, CompilationKind.Server, "server processed compilation"); return completedResponse.ReturnCode; case BuildResponse.ResponseType.MismatchedVersion: - LogCompilationMessage(logger, CompilationKind.FatalError, "server reports different protocol version than build task"); + LogCompilationMessage(logger, requestId, CompilationKind.FatalError, "server reports different protocol version than build task"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); case BuildResponse.ResponseType.IncorrectHash: - LogCompilationMessage(logger, CompilationKind.FatalError, "server reports different hash version than build task"); + LogCompilationMessage(logger, requestId, CompilationKind.FatalError, "server reports different hash version than build task"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); case BuildResponse.ResponseType.Rejected: var rejectedResponse = (RejectedBuildResponse)response; - LogCompilationMessage(logger, CompilationKind.ToolFallback, $"server rejected the request '{rejectedResponse.Reason}'"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, $"server rejected the request '{rejectedResponse.Reason}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); case BuildResponse.ResponseType.AnalyzerInconsistency: var analyzerResponse = (AnalyzerInconsistencyBuildResponse)response; var combinedMessage = string.Join(", ", analyzerResponse.ErrorMessages.ToArray()); - LogCompilationMessage(logger, CompilationKind.ToolFallback, $"server rejected the request due to analyzer / generator issues '{combinedMessage}'"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, $"server rejected the request due to analyzer / generator issues '{combinedMessage}'"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); default: - LogCompilationMessage(logger, CompilationKind.ToolFallback, $"server gave an unrecognized response"); + LogCompilationMessage(logger, requestId, CompilationKind.ToolFallback, $"server gave an unrecognized response"); return base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands); } } @@ -689,7 +692,7 @@ private int HandleResponse(BuildResponse? response, string pathToTool, string re /// These are intended to be processed by automation in the binlog hence do not change the structure of /// the messages here. /// - private void LogCompilationMessage(ICompilerServerLogger logger, CompilationKind kind, string diagnostic) + private void LogCompilationMessage(ICompilerServerLogger logger, Guid requestId, CompilationKind kind, string diagnostic) { var category = kind switch { @@ -700,14 +703,15 @@ private void LogCompilationMessage(ICompilerServerLogger logger, CompilationKind _ => throw new Exception($"Unexpected value {kind}"), }; - var message = $"CompilerServer: {category} - {diagnostic}"; - logger.LogError(message); + var message = $"CompilerServer: {category} - {diagnostic} - {requestId}"; if (kind == CompilationKind.FatalError) { + logger.LogError(message); Log.LogError(message); } else { + logger.Log(message); Log.LogMessage(message); } } diff --git a/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs b/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs index 8fee1d6614ec4..c362d53678f2f 100644 --- a/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs +++ b/src/Compilers/Server/VBCSCompiler/BuildProtocolUtil.cs @@ -32,7 +32,7 @@ internal static RunRequest GetRunRequest(BuildRequest req) break; } - return new RunRequest(language, currentDirectory, tempDirectory, libDirectory, arguments); + return new RunRequest(req.RequestId, language, currentDirectory, tempDirectory, libDirectory, arguments); } internal static string[] GetCommandLineArguments(BuildRequest req, out string? currentDirectory, out string? tempDirectory, out string? libDirectory) diff --git a/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs b/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs index 3ada2bf762f97..dabe7afa82613 100644 --- a/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs +++ b/src/Compilers/Server/VBCSCompiler/ClientConnectionHandler.cs @@ -51,20 +51,13 @@ async Task ProcessCore() { using var clientConnection = await clientConnectionTask.ConfigureAwait(false); var request = await clientConnection.ReadBuildRequestAsync(cancellationToken).ConfigureAwait(false); - - if (request.ProtocolVersion != BuildProtocolConstants.ProtocolVersion) - { - return await WriteBuildResponseAsync( - clientConnection, - new MismatchedVersionBuildResponse(), - CompletionData.RequestError, - cancellationToken).ConfigureAwait(false); - } + Logger.Log($"Received request {request.RequestId} of type {request.GetType()}"); if (!string.Equals(request.CompilerHash, BuildProtocolConstants.GetCommitHash(), StringComparison.OrdinalIgnoreCase)) { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new IncorrectHashBuildResponse(), CompletionData.RequestError, cancellationToken).ConfigureAwait(false); @@ -74,6 +67,7 @@ async Task ProcessCore() { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new ShutdownBuildResponse(Process.GetCurrentProcess().Id), new CompletionData(CompletionReason.RequestCompleted, shutdownRequested: true), cancellationToken).ConfigureAwait(false); @@ -83,6 +77,7 @@ async Task ProcessCore() { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new RejectedBuildResponse("Compilation not allowed at this time"), CompletionData.RequestCompleted, cancellationToken).ConfigureAwait(false); @@ -92,6 +87,7 @@ async Task ProcessCore() { return await WriteBuildResponseAsync( clientConnection, + request.RequestId, new RejectedBuildResponse("Not enough resources to accept connection"), CompletionData.RequestError, cancellationToken).ConfigureAwait(false); @@ -101,12 +97,12 @@ async Task ProcessCore() } } - private async Task WriteBuildResponseAsync(IClientConnection clientConnection, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) + private async Task WriteBuildResponseAsync(IClientConnection clientConnection, Guid requestId, BuildResponse response, CompletionData completionData, CancellationToken cancellationToken) { var message = response switch { - RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {clientConnection.LoggingIdentifier}", - _ => $"Writing {response.Type} response for {clientConnection.LoggingIdentifier}" + RejectedBuildResponse r => $"Writing {r.Type} response '{r.Reason}' for {requestId}", + _ => $"Writing {response.Type} response for {requestId}" }; Logger.Log(message); await clientConnection.WriteBuildResponseAsync(response, cancellationToken).ConfigureAwait(false); @@ -143,13 +139,14 @@ private async Task ProcessCompilationRequestAsync(IClientConnect { // The compilation task should never throw. If it does we need to assume that the compiler is // in a bad state and need to issue a RequestError - Logger.LogException(ex, $"Exception running compilation for {clientConnection.LoggingIdentifier}"); + Logger.LogException(ex, $"Exception running compilation for {request.RequestId}"); response = new RejectedBuildResponse($"Exception during compilation: {ex.Message}"); completionData = CompletionData.RequestError; } return await WriteBuildResponseAsync( clientConnection, + request.RequestId, response, completionData, cancellationToken).ConfigureAwait(false); diff --git a/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs b/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs index 49d14abe47b1d..06dd245c9283f 100644 --- a/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs +++ b/src/Compilers/Server/VBCSCompiler/CompilerRequestHandler.cs @@ -21,14 +21,16 @@ namespace Microsoft.CodeAnalysis.CompilerServer { internal readonly struct RunRequest { + public Guid RequestId { get; } public string Language { get; } public string? WorkingDirectory { get; } public string? TempDirectory { get; } public string? LibDirectory { get; } public string[] Arguments { get; } - public RunRequest(string language, string? workingDirectory, string? tempDirectory, string? libDirectory, string[] arguments) + public RunRequest(Guid requestId, string language, string? workingDirectory, string? tempDirectory, string? libDirectory, string[] arguments) { + RequestId = requestId; Language = language; WorkingDirectory = workingDirectory; TempDirectory = tempDirectory; @@ -72,7 +74,7 @@ private bool CheckAnalyzers(string baseDirectory, ImmutableArray? errorMessages)) { + Logger.Log($"Rejected: {request.RequestId}: for analyer load issues {string.Join(";", errorMessages)}"); return new AnalyzerInconsistencyBuildResponse(new ReadOnlyCollection(errorMessages)); } - Logger.Log($"Begin {request.Language} compiler run"); - TextWriter output = new StringWriter(CultureInfo.InvariantCulture); - int returnCode = compiler.Run(output, cancellationToken); - var outputString = output.ToString(); - Logger.Log(@$" -End {request.Language} Compilation complete. + Logger.Log($"Begin {request.RequestId} {request.Language} compiler run"); + try + { + TextWriter output = new StringWriter(CultureInfo.InvariantCulture); + int returnCode = compiler.Run(output, cancellationToken); + var outputString = output.ToString(); + Logger.Log(@$"End {request.RequestId} {request.Language} compiler run Return code: {returnCode} Output: {outputString}"); - return new CompletedBuildResponse(returnCode, utf8output, outputString); + return new CompletedBuildResponse(returnCode, utf8output, outputString); + } + catch (Exception ex) + { + Logger.LogException(ex, $"Running compilation for {request.RequestId}"); + throw; + } } } } diff --git a/src/Compilers/Server/VBCSCompiler/IClientConnection.cs b/src/Compilers/Server/VBCSCompiler/IClientConnection.cs index 1df9ef1c26456..42e2129d211a3 100644 --- a/src/Compilers/Server/VBCSCompiler/IClientConnection.cs +++ b/src/Compilers/Server/VBCSCompiler/IClientConnection.cs @@ -14,12 +14,6 @@ namespace Microsoft.CodeAnalysis.CompilerServer /// internal interface IClientConnection : IDisposable { - /// - /// A value which can be used to identify this connection for logging purposes only. It has - /// no guarantee of uniqueness. - /// - string LoggingIdentifier { get; } - /// /// This task resolves if the client disconnects from the server. /// diff --git a/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs b/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs index da1f26b49c850..f8509a3452058 100644 --- a/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs +++ b/src/Compilers/Server/VBCSCompiler/ICompilerServerHost.cs @@ -17,6 +17,6 @@ internal interface ICompilerServerHost { ICompilerServerLogger Logger { get; } - BuildResponse RunCompilation(RunRequest request, CancellationToken cancellationToken); + BuildResponse RunCompilation(in RunRequest request, CancellationToken cancellationToken); } } diff --git a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs index fdb3cf20e0670..5df1c170ab14c 100644 --- a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs +++ b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnection.cs @@ -22,15 +22,13 @@ internal sealed class NamedPipeClientConnection : IClientConnection public NamedPipeServerStream Stream { get; } public ICompilerServerLogger Logger { get; } - public string LoggingIdentifier { get; } public bool IsDisposed { get; private set; } public Task DisconnectTask => DisconnectTaskCompletionSource.Task; - internal NamedPipeClientConnection(NamedPipeServerStream stream, string loggingIdentifier, ICompilerServerLogger logger) + internal NamedPipeClientConnection(NamedPipeServerStream stream, ICompilerServerLogger logger) { Stream = stream; - LoggingIdentifier = loggingIdentifier; Logger = logger; } @@ -46,7 +44,7 @@ public void Dispose() } catch (Exception ex) { - Logger.LogException(ex, $"Error closing client connection {LoggingIdentifier}"); + Logger.LogException(ex, $"Error closing client connection"); } IsDisposed = true; @@ -73,11 +71,11 @@ async Task MonitorDisconnect() { try { - await BuildServerConnection.MonitorDisconnectAsync(Stream, LoggingIdentifier, Logger, DisconnectCancellationTokenSource.Token).ConfigureAwait(false); + await BuildServerConnection.MonitorDisconnectAsync(Stream, request.RequestId, Logger, DisconnectCancellationTokenSource.Token).ConfigureAwait(false); } catch (Exception ex) { - Logger.LogException(ex, $"Error monitoring disconnect {LoggingIdentifier}"); + Logger.LogException(ex, $"Error monitoring disconnect {request.RequestId}"); } finally { diff --git a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs index 2419128053043..1b860f8856f19 100644 --- a/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs +++ b/src/Compilers/Server/VBCSCompiler/NamedPipeClientConnectionHost.cs @@ -68,18 +68,11 @@ public void BeginListening() // large builds such as dotnet/roslyn or dotnet/runtime var listenCount = Math.Min(4, Environment.ProcessorCount); _listenTasks = new Task[listenCount]; - int clientLoggingIdentifier = 0; for (int i = 0; i < listenCount; i++) { - var task = Task.Run(() => ListenCoreAsync(PipeName, Logger, _queue, GetNextClientLoggingIdentifier, _cancellationTokenSource.Token)); + var task = Task.Run(() => ListenCoreAsync(PipeName, Logger, _queue, _cancellationTokenSource.Token)); _listenTasks[i] = task; } - - string GetNextClientLoggingIdentifier() - { - var count = Interlocked.Increment(ref clientLoggingIdentifier); - return $"Client{count}"; - } } public void EndListening() @@ -175,7 +168,6 @@ private static async Task ListenCoreAsync( string pipeName, ICompilerServerLogger logger, AsyncQueue queue, - Func getClientLoggingIdentifier, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) @@ -213,7 +205,7 @@ private static async Task ListenCoreAsync( await connectTask.ConfigureAwait(false); logger.Log("Pipe connection established."); - var connection = new NamedPipeClientConnection(pipeStream, getClientLoggingIdentifier(), logger); + var connection = new NamedPipeClientConnection(pipeStream, logger); queue.Enqueue(new ListenResult(connection: connection)); } catch (OperationCanceledException) diff --git a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs index 007fe40de92b6..e0c4c4dc7c508 100644 --- a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs +++ b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs @@ -13,7 +13,7 @@ internal static class VBCSCompiler { public static int Main(string[] args) { - using var logger = new CompilerServerLogger(); + using var logger = new CompilerServerLogger("VBCSCompiler"); NameValueCollection appSettings; try diff --git a/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs b/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs index 98e04464dff83..9228dfb6a34d9 100644 --- a/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs +++ b/src/Compilers/Server/VBCSCompilerTests/BuildProtocolTest.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Collections.Immutable; using System.IO; using System.Threading; @@ -44,7 +45,6 @@ public async Task ReadWriteCompleted() public async Task ReadWriteRequest() { var request = new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.VisualBasicCompile, "HashValue", ImmutableArray.Create( @@ -55,7 +55,6 @@ public async Task ReadWriteRequest() Assert.True(memoryStream.Position > 0); memoryStream.Position = 0; var read = await BuildRequest.ReadAsync(memoryStream, default(CancellationToken)); - Assert.Equal(BuildProtocolConstants.ProtocolVersion, read.ProtocolVersion); Assert.Equal(RequestLanguage.VisualBasicCompile, read.Language); Assert.Equal("HashValue", read.CompilerHash); Assert.Equal(2, read.Arguments.Count); diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs index 15d22527d653e..0dbd1b2e14379 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs @@ -70,7 +70,6 @@ private async Task CreateBuildRequest(string sourceText, TimeSpan? builder.Add(new BuildRequest.Argument(BuildProtocolConstants.ArgumentId.CommandLineArgument, argumentIndex: 0, value: file.Path)); return new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, BuildProtocolConstants.GetCommitHash(), builder.ToImmutable()); @@ -182,19 +181,11 @@ public async Task RejectEmptyTempPath() Assert.Equal(ResponseType.Rejected, response.Type); } - [Fact] - public async Task IncorrectProtocolReturnsMismatchedVersionResponse() - { - using var serverData = await ServerUtil.CreateServer(Logger); - var buildResponse = await serverData.SendAsync(new BuildRequest(1, RequestLanguage.CSharpCompile, "abc", new List { })); - Assert.Equal(BuildResponse.ResponseType.MismatchedVersion, buildResponse.Type); - } - [Fact] public async Task IncorrectServerHashReturnsIncorrectHashResponse() { using var serverData = await ServerUtil.CreateServer(Logger); - var buildResponse = await serverData.SendAsync(new BuildRequest(BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, "abc", new List { })); + var buildResponse = await serverData.SendAsync(new BuildRequest(RequestLanguage.CSharpCompile, "abc", new List { })); Assert.Equal(BuildResponse.ResponseType.IncorrectHash, buildResponse.Type); } diff --git a/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs b/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs index 5e056293beba8..5822aa4d989e6 100644 --- a/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs +++ b/src/Compilers/Server/VBCSCompilerTests/CompilerServerTests.cs @@ -1495,7 +1495,7 @@ public async Task MissingCompilerAssembly_CompilerServerHost() }); using var serverData = await ServerUtil.CreateServer(_logger, compilerServerHost: host); - var request = new BuildRequest(1, RequestLanguage.CSharpCompile, string.Empty, new BuildRequest.Argument[0]); + var request = new BuildRequest(RequestLanguage.CSharpCompile, string.Empty, new BuildRequest.Argument[0]); var compileTask = serverData.SendAsync(request); var response = await compileTask; Assert.Equal(BuildResponse.ResponseType.Completed, response.Type); diff --git a/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs b/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs index c2dccbddb76ac..3969e10dec9ff 100644 --- a/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs +++ b/src/Compilers/Server/VBCSCompilerTests/ServerUtil.cs @@ -24,13 +24,11 @@ namespace Microsoft.CodeAnalysis.CompilerServer.UnitTests internal static class ProtocolUtil { internal static readonly BuildRequest EmptyCSharpBuildRequest = new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.CSharpCompile, BuildProtocolConstants.GetCommitHash(), ImmutableArray.Empty); internal static readonly BuildRequest EmptyBasicBuildRequest = new BuildRequest( - BuildProtocolConstants.ProtocolVersion, RequestLanguage.VisualBasicCompile, BuildProtocolConstants.GetCommitHash(), ImmutableArray.Empty); @@ -158,8 +156,8 @@ internal static BuildClient CreateBuildClient( TextWriter textWriter = null, int? timeoutOverride = null) { - compileFunc = compileFunc ?? GetCompileFunc(language); - textWriter = textWriter ?? new StringWriter(); + compileFunc ??= GetCompileFunc(language); + textWriter ??= new StringWriter(); return new BuildClient(language, compileFunc, logger, timeoutOverride: timeoutOverride); } diff --git a/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs b/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs index 06f7dfd63cc85..c8297f1cbf0c4 100644 --- a/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs +++ b/src/Compilers/Server/VBCSCompilerTests/TestableCompilerServerHost.cs @@ -21,7 +21,7 @@ internal TestableCompilerServerHost(Func arguments, RequestLanguage language, CompileFunc compileFunc, ICompilerServerLogger logger) + internal static int Run(IEnumerable arguments, RequestLanguage language, CompileFunc compileFunc, ICompilerServerLogger logger, Guid? requestId = null) { var sdkDir = GetSystemSdkDirectory(); if (RuntimeHostInfo.IsCoreClrRuntime) @@ -93,7 +93,7 @@ internal static int Run(IEnumerable arguments, RequestLanguage language, var tempDir = BuildServerConnection.GetTempPath(workingDir); var buildPaths = new BuildPaths(clientDir: clientDir, workingDir: workingDir, sdkDir: sdkDir, tempDir: tempDir); var originalArguments = GetCommandLineArgs(arguments); - return client.RunCompilation(originalArguments, buildPaths).ExitCode; + return client.RunCompilation(originalArguments, buildPaths, requestId: requestId).ExitCode; } @@ -102,7 +102,7 @@ internal static int Run(IEnumerable arguments, RequestLanguage language, /// to the console. If the compiler server fails, run the fallback /// compiler. /// - internal RunCompilationResult RunCompilation(IEnumerable originalArguments, BuildPaths buildPaths, TextWriter textWriter = null, string pipeName = null) + internal RunCompilationResult RunCompilation(IEnumerable originalArguments, BuildPaths buildPaths, TextWriter textWriter = null, string pipeName = null, Guid? requestId = null) { textWriter = textWriter ?? Console.Out; @@ -132,7 +132,7 @@ internal RunCompilationResult RunCompilation(IEnumerable originalArgumen { pipeName = pipeName ?? GetPipeName(buildPaths); var libDirectory = Environment.GetEnvironmentVariable("LIB"); - var serverResult = RunServerCompilation(textWriter, parsedArgs, buildPaths, libDirectory, pipeName, keepAliveOpt); + var serverResult = RunServerCompilation(textWriter, parsedArgs, buildPaths, libDirectory, pipeName, keepAliveOpt, requestId); if (serverResult.HasValue) { Debug.Assert(serverResult.Value.RanOnServer); @@ -209,7 +209,7 @@ private int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextW /// Runs the provided compilation on the server. If the compilation cannot be completed on the server then null /// will be returned. /// - private RunCompilationResult? RunServerCompilation(TextWriter textWriter, List arguments, BuildPaths buildPaths, string libDirectory, string sessionName, string keepAlive) + private RunCompilationResult? RunServerCompilation(TextWriter textWriter, List arguments, BuildPaths buildPaths, string libDirectory, string pipeName, string keepAlive, Guid? requestId) { BuildResponse buildResponse; @@ -220,13 +220,25 @@ private int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextW try { - var buildResponseTask = RunServerCompilationAsync( + var alt = new BuildPathsAlt( + buildPaths.ClientDirectory, + buildPaths.WorkingDirectory, + buildPaths.SdkDirectory, + buildPaths.TempDirectory); + + var buildResponseTask = BuildServerConnection.RunServerCompilationCoreAsync( + requestId ?? Guid.NewGuid(), + _language, arguments, - buildPaths, - sessionName, + alt, + pipeName, keepAlive, libDirectory, + _timeoutOverride, + _createServerFunc, + _logger, CancellationToken.None); + buildResponse = buildResponseTask.Result; Debug.Assert(buildResponse != null); @@ -266,48 +278,6 @@ private int RunLocalCompilation(string[] arguments, BuildPaths buildPaths, TextW } } - private Task RunServerCompilationAsync( - List arguments, - BuildPaths buildPaths, - string sessionKey, - string keepAlive, - string libDirectory, - CancellationToken cancellationToken) - { - return RunServerCompilationCoreAsync(_language, arguments, buildPaths, sessionKey, keepAlive, libDirectory, _timeoutOverride, _createServerFunc, _logger, cancellationToken); - } - - private static Task RunServerCompilationCoreAsync( - RequestLanguage language, - List arguments, - BuildPaths buildPaths, - string pipeName, - string keepAlive, - string libEnvVariable, - int? timeoutOverride, - CreateServerFunc createServerFunc, - ICompilerServerLogger logger, - CancellationToken cancellationToken) - { - var alt = new BuildPathsAlt( - buildPaths.ClientDirectory, - buildPaths.WorkingDirectory, - buildPaths.SdkDirectory, - buildPaths.TempDirectory); - - return BuildServerConnection.RunServerCompilationCoreAsync( - language, - arguments, - alt, - pipeName, - keepAlive, - libEnvVariable, - timeoutOverride, - createServerFunc, - logger, - cancellationToken); - } - /// /// Given the full path to the directory containing the compiler exes, /// retrieves the name of the pipe for client/server communication on diff --git a/src/Compilers/Shared/BuildServerConnection.cs b/src/Compilers/Shared/BuildServerConnection.cs index 043ae5735a0cf..17b78a3ea99cf 100644 --- a/src/Compilers/Shared/BuildServerConnection.cs +++ b/src/Compilers/Shared/BuildServerConnection.cs @@ -78,6 +78,7 @@ internal sealed class BuildServerConnection internal static bool IsCompilerServerSupported => GetPipeNameForPath("") is object; public static Task RunServerCompilationAsync( + Guid requestId, RequestLanguage language, string? sharedCompilationId, List arguments, @@ -90,6 +91,7 @@ public static Task RunServerCompilationAsync( var pipeNameOpt = sharedCompilationId ?? GetPipeNameForPath(buildPaths.ClientDirectory); return RunServerCompilationCoreAsync( + requestId, language, arguments, buildPaths, @@ -103,12 +105,13 @@ public static Task RunServerCompilationAsync( } internal static async Task RunServerCompilationCoreAsync( + Guid requestId, RequestLanguage language, List arguments, BuildPathsAlt buildPaths, string? pipeName, string? keepAlive, - string? libEnvVariable, + string? libDirectory, int? timeoutOverride, CreateServerFunc createServerFunc, ICompilerServerLogger logger, @@ -149,8 +152,9 @@ internal static async Task RunServerCompilationCoreAsync( workingDirectory: buildPaths.WorkingDirectory, tempDirectory: buildPaths.TempDirectory, compilerHash: BuildProtocolConstants.GetCommitHash() ?? "", + requestId: requestId, keepAlive: keepAlive, - libDirectory: libEnvVariable); + libDirectory: libDirectory); return await TryCompileAsync(pipe, request, logger, cancellationToken).ConfigureAwait(false); } @@ -256,26 +260,26 @@ private static async Task TryCompileAsync( // Write the request try { - logger.Log("Begin writing request"); + logger.Log($"Begin writing request for {request.RequestId}"); await request.WriteAsync(pipeStream, cancellationToken).ConfigureAwait(false); - logger.Log("End writing request"); + logger.Log($"End writing request for {request.RequestId}"); } catch (Exception e) { - logger.LogException(e, "Error writing build request."); + logger.LogException(e, $"Error writing build request for {request.RequestId}"); return new RejectedBuildResponse($"Error writing build request: {e.Message}"); } // Wait for the compilation and a monitor to detect if the server disconnects var serverCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - logger.Log("Begin reading response"); + logger.Log($"Begin reading response for {request.RequestId}"); var responseTask = BuildResponse.ReadAsync(pipeStream, serverCts.Token); - var monitorTask = MonitorDisconnectAsync(pipeStream, "client", logger, serverCts.Token); + var monitorTask = MonitorDisconnectAsync(pipeStream, request.RequestId, logger, serverCts.Token); await Task.WhenAny(responseTask, monitorTask).ConfigureAwait(false); - logger.Log("End reading response"); + logger.Log($"End reading response for {request.RequestId}"); if (responseTask.IsCompleted) { @@ -286,13 +290,13 @@ private static async Task TryCompileAsync( } catch (Exception e) { - logger.LogException(e, "Error reading response"); + logger.LogException(e, $"Reading response for {request.RequestId}"); response = new RejectedBuildResponse($"Error reading response: {e.Message}"); } } else { - logger.LogError("Client disconnect"); + logger.LogError($"Client disconnect for {request.RequestId}"); response = new RejectedBuildResponse($"Client disconnected"); } @@ -310,7 +314,7 @@ private static async Task TryCompileAsync( /// internal static async Task MonitorDisconnectAsync( PipeStream pipeStream, - string identifier, + Guid requestId, ICompilerServerLogger logger, CancellationToken cancellationToken = default) { @@ -332,7 +336,7 @@ internal static async Task MonitorDisconnectAsync( { // It is okay for this call to fail. Errors will be reflected in the // IsConnected property which will be read on the next iteration of the - logger.LogException(e, $"Error poking pipe {identifier}."); + logger.LogException(e, $"Error poking pipe {requestId}."); } } } diff --git a/src/Compilers/VisualBasic/vbc/Program.cs b/src/Compilers/VisualBasic/vbc/Program.cs index 3e1a32b7c113a..ee7f9061dade8 100644 --- a/src/Compilers/VisualBasic/vbc/Program.cs +++ b/src/Compilers/VisualBasic/vbc/Program.cs @@ -33,8 +33,9 @@ private static int MainCore(string[] args) ExitingTraceListener.Install(); #endif - using var logger = new CompilerServerLogger(); - return BuildClient.Run(args, RequestLanguage.VisualBasicCompile, Vbc.Run, logger); + var requestId = Guid.NewGuid(); + using var logger = new CompilerServerLogger($"vbc {requestId}"); + return BuildClient.Run(args, RequestLanguage.VisualBasicCompile, Vbc.Run, logger, requestId); } public static int Run(string[] args, string clientDir, string workingDir, string sdkDir, string tempDir, TextWriter textWriter, IAnalyzerAssemblyLoader analyzerLoader) From 487576327b115cc53278a4a2e96d80b2746f9ed9 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 14 Apr 2021 11:11:52 -0700 Subject: [PATCH 173/220] Enable test and change to use correct method --- .../GenerateFromUsage/GenerateMethodCrossLanguageTests.vb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb b/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb index 0032dd260d0f8..6fbeff7ac1a68 100644 --- a/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/GenerateFromUsage/GenerateMethodCrossLanguageTests.vb @@ -741,6 +741,7 @@ End Module]]> Await TestAsync(input, expected, onAfterWorkspaceCreated:=Sub(w) w.SetTestLogger(AddressOf _outputHelper.WriteLine)) End Function + Public Async Function GenerateMethodUsingTypeConstraint_3BaseTypeConstraints_CommonDerived() As Task Dim input = @@ -750,7 +751,7 @@ End Module]]> Module Program Sub Main(args As String()) Dim list = New List(Of String) - list.AddRange($$goo()) + extensions.AddRange(list, $$goo()) End Sub End Module @@ -817,7 +818,7 @@ public class outer Module Program Sub Main(args As String()) Dim list = New List(Of String) - list.AddRange(goo()) + extensions.AddRange(list, goo()) End Sub Private Function goo() As MyStruct(Of outer.inner.derived3) From a1dd1f901e0e031857e551249547f5ab0fa1952f Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Wed, 14 Apr 2021 12:16:18 -0700 Subject: [PATCH 174/220] Multi-target Microsoft.CodeAnalysis.Workspaces.Desktop Workspaces.Desktop is an increasingly-misnamed part of Roslyn; it's where we put types that we wanted to ship as a public API or public implementation that didn't run on the original "portable" forms of .NET. Since the introduction of netstandard most of that code ended up moving back to the main Workspaces binary, leaving only a few types that use the original form of MEF, and one that depended on the desktop reading of app.config. At this point, that code can now also run on netcoreapp3.1 too, so let's support that. This unblocks some folks who need to create a MEF composition on .NET Core using VS MEF but don't otherwise have an implementation of IMefHostExportProvider that is usable for them. --- .../Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj b/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj index fbe9cb851a771..95006f5728f33 100644 --- a/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj +++ b/src/Workspaces/Core/Desktop/Microsoft.CodeAnalysis.Workspaces.Desktop.csproj @@ -4,11 +4,11 @@ Library Microsoft.CodeAnalysis - net472 + net472;netcoreapp3.1 true - + From 08faabb96c8d9d06e500d965f09e9b818cf506da Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Wed, 14 Apr 2021 12:45:24 -0700 Subject: [PATCH 175/220] Add telemetry for when the infobar is shown from using IErrorReportingService in VS --- .../Workspace/VisualStudioErrorReportingService.cs | 11 ++++++++++- .../Compiler/Core/Log/FunctionId.cs | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs index 68a8f71047bd4..04e6fd8c65ea5 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioErrorReportingService.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Extensions; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation @@ -22,7 +23,15 @@ public VisualStudioErrorReportingService(IInfoBarService infoBarService) public string HostDisplayName => "Visual Studio"; public void ShowGlobalErrorInfo(string message, params InfoBarUI[] items) - => _infoBarService.ShowInfoBar(message, items); + { + _infoBarService.ShowInfoBar(message, items); + + // Have to use KeyValueLogMessage so it gets reported in telemetry + Logger.Log(FunctionId.VS_ErrorReportingService_ShowGlobalErrorInfo, KeyValueLogMessage.Create(m => + { + m["errorMessage"] = message; + })); + } public void ShowDetailedErrorInfo(Exception exception) { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index dc5ce56cbc42c..48081316f19b2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -514,5 +514,7 @@ internal enum FunctionId Intellicode_UnknownIntent = 485, LSP_CompletionListCacheMiss = 486, + + VS_ErrorReportingService_ShowGlobalErrorInfo = 487, } } From 1ee2cb9ec539898358d1eda70963d969d1f81a82 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 15:00:29 -0700 Subject: [PATCH 176/220] Fix doc comment. --- .../Core/Portable/Workspace/Solution/Document.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index c2f6350dd9ac3..997cdd34f4601 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -156,9 +156,10 @@ public bool SupportsSemanticModel /// /// /// The returned syntax tree can be if the returns . This function will return the same value if called multiple times. Specifically, the - /// same Task instance will be returned, which will always return the same underlying - /// instance. + /// langword="false"/>. This function may cause computation to occurr the first time it is called, but will return + /// a cached result every subsequent time. 's can hold onto their roots lazily. So calls + /// to see or may end up causing computation + /// to occur at that point. /// public Task GetSyntaxTreeAsync(CancellationToken cancellationToken = default) { From 8eb9b59bbd35a5840298ef65953ead6ed0b18846 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 14 Apr 2021 15:12:55 -0700 Subject: [PATCH 177/220] Update src/Workspaces/Core/Portable/Workspace/Solution/Document.cs Co-authored-by: David Wengier --- src/Workspaces/Core/Portable/Workspace/Solution/Document.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 997cdd34f4601..8310f1770b190 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -156,7 +156,7 @@ public bool SupportsSemanticModel /// /// /// The returned syntax tree can be if the returns . This function may cause computation to occurr the first time it is called, but will return + /// langword="false"/>. This function may cause computation to occur the first time it is called, but will return /// a cached result every subsequent time. 's can hold onto their roots lazily. So calls /// to see or may end up causing computation /// to occur at that point. From ac5b4cf6ed90899ee260b577786f9281b0047fe4 Mon Sep 17 00:00:00 2001 From: CyrusNajmabadi Date: Wed, 14 Apr 2021 15:13:05 -0700 Subject: [PATCH 178/220] Update src/Workspaces/Core/Portable/Workspace/Solution/Document.cs Co-authored-by: David Wengier --- src/Workspaces/Core/Portable/Workspace/Solution/Document.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs index 8310f1770b190..867713e1ae808 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Document.cs @@ -158,7 +158,7 @@ public bool SupportsSemanticModel /// The returned syntax tree can be if the returns . This function may cause computation to occur the first time it is called, but will return /// a cached result every subsequent time. 's can hold onto their roots lazily. So calls - /// to see or may end up causing computation + /// to or may end up causing computation /// to occur at that point. /// public Task GetSyntaxTreeAsync(CancellationToken cancellationToken = default) From c4e2bf950a4bbcb052577a79a6395989337717cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 15:33:11 -0700 Subject: [PATCH 179/220] Do not cancel classification change notifications --- .../SyntacticClassificationTaggerProvider.TagComputer.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 8054c0b62a4ed..233672a244380 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -62,7 +62,7 @@ internal partial class TagComputer private object _lastProcessedCachedData; private Workspace _workspace; - private CancellationTokenSource _reportChangeCancellationSource; + private readonly CancellationTokenSource _reportChangeCancellationSource = new(); private readonly IAsynchronousOperationListener _listener; private readonly IForegroundNotificationService _notificationService; @@ -196,9 +196,6 @@ private void EnqueueProcessSnapshot(Document newDocument) private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, CancellationToken cancellationToken) { - // we will enqueue new one soon, cancel pending refresh right away - _reportChangeCancellationSource.Cancel(); - var currentText = await currentDocument.GetTextAsync(cancellationToken).ConfigureAwait(false); var currentSnapshot = currentText.FindCorrespondingEditorTextSnapshot(); if (currentSnapshot == null) @@ -235,7 +232,6 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C _lastProcessedCachedData = currentCachedData; } - _reportChangeCancellationSource = new CancellationTokenSource(); _notificationService.RegisterNotification(() => { _workQueue.AssertIsForeground(); From 0f3e31937084efafab9edf3f654a85be3c8ef799 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 15:42:02 -0700 Subject: [PATCH 180/220] Remove superfluous wait --- .../SyntacticClassificationTaggerProvider.TagComputer.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs index 233672a244380..39a5628f840dc 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/SyntacticClassificationTaggerProvider.TagComputer.cs @@ -35,12 +35,6 @@ internal partial class SyntacticClassificationTaggerProvider /// internal partial class TagComputer { - // This is how long we will wait after completing a parse before we tell the editor to - // re-classify the file. We do this, since the edit may have non-local changes, or the user - // may have made a change that introduced text that we didn't classify because we hadn't - // parsed it yet, and we want to get back to a known state. - private const int ReportChangeDelayInMilliseconds = TaggerConstants.ShortDelay; - private readonly ITextBuffer _subjectBuffer; private readonly WorkspaceRegistration _workspaceRegistration; private readonly AsynchronousSerialWorkQueue _workQueue; @@ -237,8 +231,7 @@ private async Task EnqueueProcessSnapshotWorkerAsync(Document currentDocument, C _workQueue.AssertIsForeground(); ReportChangedSpan(changedSpan); }, - ReportChangeDelayInMilliseconds, - _listener.BeginAsyncOperation("ReportEntireFileChanged"), + _listener.BeginAsyncOperation("EnqueueProcessSnapshotWorkerAsync.ReportChangedSpan"), _reportChangeCancellationSource.Token); } From 81a56ec0da53a85c8dd5ae9185b12ba9fbde3a27 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 15 Apr 2021 09:35:50 +1000 Subject: [PATCH 181/220] EnC - Support for records (#51926) --- .../EditAndContinueTestBase.cs | 4 +- .../EditAndContinue/EditAndContinueTests.cs | 156 +- .../EditAndContinue/SymbolMatcherTests.cs | 148 ++ .../CSharp/Test/Emit/PDB/PDBLambdaTests.cs | 278 ++++ .../Core/Portable/PublicAPI.Unshipped.txt | 1 + .../Portable/Symbols/WellKnownMemberNames.cs | 6 +- .../EditAndContinue/ActiveStatementTests.cs | 81 + .../CSharpEditAndContinueAnalyzerTests.cs | 3 + .../EditAndContinue/StatementEditingTests.cs | 56 +- .../EditAndContinue/TopLevelEditingTests.cs | 1307 +++++++++++++++-- .../RudeEditDiagnosticTests.cs | 2 + .../Portable/CSharpFeaturesResources.resx | 3 + .../CSharpEditAndContinueAnalyzer.cs | 137 +- .../EditAndContinue/SyntaxComparer.cs | 4 +- .../xlf/CSharpFeaturesResources.cs.xlf | 5 + .../xlf/CSharpFeaturesResources.de.xlf | 5 + .../xlf/CSharpFeaturesResources.es.xlf | 5 + .../xlf/CSharpFeaturesResources.fr.xlf | 5 + .../xlf/CSharpFeaturesResources.it.xlf | 5 + .../xlf/CSharpFeaturesResources.ja.xlf | 5 + .../xlf/CSharpFeaturesResources.ko.xlf | 5 + .../xlf/CSharpFeaturesResources.pl.xlf | 5 + .../xlf/CSharpFeaturesResources.pt-BR.xlf | 5 + .../xlf/CSharpFeaturesResources.ru.xlf | 5 + .../xlf/CSharpFeaturesResources.tr.xlf | 5 + .../xlf/CSharpFeaturesResources.zh-Hans.xlf | 5 + .../xlf/CSharpFeaturesResources.zh-Hant.xlf | 5 + .../AbstractEditAndContinueAnalyzer.cs | 260 +++- .../EditAndContinueDiagnosticDescriptors.cs | 6 + .../Portable/EditAndContinue/RudeEditKind.cs | 6 + .../EditAndContinue/SemanticEditInfo.cs | 4 + .../Core/Portable/FeaturesResources.resx | 15 + .../Portable/xlf/FeaturesResources.cs.xlf | 25 + .../Portable/xlf/FeaturesResources.de.xlf | 25 + .../Portable/xlf/FeaturesResources.es.xlf | 25 + .../Portable/xlf/FeaturesResources.fr.xlf | 25 + .../Portable/xlf/FeaturesResources.it.xlf | 25 + .../Portable/xlf/FeaturesResources.ja.xlf | 25 + .../Portable/xlf/FeaturesResources.ko.xlf | 25 + .../Portable/xlf/FeaturesResources.pl.xlf | 25 + .../Portable/xlf/FeaturesResources.pt-BR.xlf | 25 + .../Portable/xlf/FeaturesResources.ru.xlf | 25 + .../Portable/xlf/FeaturesResources.tr.xlf | 25 + .../xlf/FeaturesResources.zh-Hans.xlf | 25 + .../xlf/FeaturesResources.zh-Hant.xlf | 25 + .../VisualBasicEditAndContinueAnalyzer.vb | 13 + 46 files changed, 2685 insertions(+), 195 deletions(-) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs index baa930c9177cf..c0bb0bccd09a7 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTestBase.cs @@ -275,9 +275,9 @@ internal static void SaveImages(string outputDirectory, CompilationVerifier base public static class EditAndContinueTestExtensions { - internal static CSharpCompilation WithSource(this CSharpCompilation compilation, string newSource) + internal static CSharpCompilation WithSource(this CSharpCompilation compilation, CSharpTestSource newSource) { - return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(CSharpTestBase.Parse(newSource)); + return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSource.GetSyntaxTrees(TestOptions.Regular)); } internal static CSharpCompilation WithSource(this CSharpCompilation compilation, SyntaxTree newTree) diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 015034f5ef769..a85a32a1d921e 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -324,7 +324,6 @@ static void Main() { } var method1 = compilation1.GetMember("C.F"); - var diff1 = compilation1.EmitDifference( generation0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0, method1))); @@ -5965,7 +5964,6 @@ .locals init (<>f__AnonymousType0 V_0) //x diff1.VerifySynthesizedMembers( "<>f__AnonymousType0<j__TPar, j__TPar>: {Equals, GetHashCode, ToString}"); - var baselineIL = @" { // Code size 24 (0x18) @@ -7570,7 +7568,6 @@ static void F() generation0, ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, method0F, method1F, syntaxMap: s => null, preserveLocalVariables: true))); - var handle = MetadataTokens.BlobHandle(1); byte[] value0 = reader0.GetBlobBytes(handle); Assert.Equal("20-01-01-08", BitConverter.ToString(value0)); @@ -10578,5 +10575,158 @@ .maxstack 1 } "); } + + [Fact] + public void Records_AddWellKnownMember() + { + var source0 = +@" +namespace N +{ + record R(int X) + { + } +} +"; + var source1 = +@" +namespace N +{ + record R(int X) + { + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } + } +} +"; + + var compilation0 = CreateCompilation(new[] { source0, IsExternalInitTypeDefinition }, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1, IsExternalInitTypeDefinition }); + + var printMembers0 = compilation0.GetMember("N.R.PrintMembers"); + var printMembers1 = compilation1.GetMember("N.R.PrintMembers"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + // Verify full metadata contains expected rows. + var reader0 = md0.MetadataReader; + + CheckNames(reader0, reader0.GetTypeDefNames(), "", "EmbeddedAttribute", "NullableAttribute", "NullableContextAttribute", "IsExternalInit", "R"); + CheckNames(reader0, reader0.GetMethodDefNames(), + /* EmbeddedAttribute */".ctor", + /* NullableAttribute */ ".ctor", + /* NullableContextAttribute */".ctor", + /* IsExternalInit */".ctor", + /* R: */ + ".ctor", + "get_EqualityContract", + "get_X", + "set_X", + "ToString", + "PrintMembers", + "op_Inequality", + "op_Equality", + "GetHashCode", + "Equals", + "Equals", + "$", + ".ctor", + "Deconstruct"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, printMembers0, printMembers1))); + + diff1.VerifySynthesizedMembers( + ": {Microsoft}", + "Microsoft: {CodeAnalysis}", + "Microsoft.CodeAnalysis: {EmbeddedAttribute}", + "System.Runtime.CompilerServices: {NullableAttribute, NullableContextAttribute}"); + + // Verify delta metadata contains expected rows. + using var md1 = diff1.GetMetadata(); + var reader1 = md1.Reader; + var readers = new[] { reader0, reader1 }; + + EncValidation.VerifyModuleMvid(1, reader0, reader1); + + CheckNames(readers, reader1.GetTypeDefNames()); + CheckNames(readers, reader1.GetMethodDefNames(), "PrintMembers"); + + CheckEncLog(reader1, + Row(2, TableIndex.AssemblyRef, EditAndContinueOperation.Default), + Row(19, TableIndex.TypeRef, EditAndContinueOperation.Default), + Row(20, TableIndex.TypeRef, EditAndContinueOperation.Default), + Row(21, TableIndex.TypeRef, EditAndContinueOperation.Default), + Row(4, TableIndex.TypeSpec, EditAndContinueOperation.Default), + Row(3, TableIndex.StandAloneSig, EditAndContinueOperation.Default), + Row(10, TableIndex.MethodDef, EditAndContinueOperation.Default)); // R.PrintMembers + + CheckEncMap(reader1, + Handle(19, TableIndex.TypeRef), + Handle(20, TableIndex.TypeRef), + Handle(21, TableIndex.TypeRef), + Handle(10, TableIndex.MethodDef), + Handle(3, TableIndex.StandAloneSig), + Handle(4, TableIndex.TypeSpec), + Handle(2, TableIndex.AssemblyRef)); + } + + [Fact] + public void Records_RemoveWellKnownMember() + { + var source0 = +@" +namespace N +{ + record R(int X) + { + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } + } +} +"; + var source1 = +@" +namespace N +{ + record R(int X) + { + } +} +"; + + var compilation0 = CreateCompilation(new[] { source0, IsExternalInitTypeDefinition }, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1, IsExternalInitTypeDefinition }); + + var method0 = compilation0.GetMember("N.R.PrintMembers"); + var method1 = compilation1.GetMember("N.R.PrintMembers"); + + var v0 = CompileAndVerify(compilation0, verify: Verification.Skipped); + + using var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create( + SemanticEdit.Create(SemanticEditKind.Update, method0, method1))); + + diff1.VerifySynthesizedMembers( + ": {Microsoft}", + "Microsoft: {CodeAnalysis}", + "Microsoft.CodeAnalysis: {EmbeddedAttribute}", + "System.Runtime.CompilerServices: {NullableAttribute, NullableContextAttribute}"); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs index 3d633944d05e1..e3238e95c4e61 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/SymbolMatcherTests.cs @@ -1404,5 +1404,153 @@ static void verify(string source1, string source2) Assert.Null(matcher.MapDefinition(f_1.GetCciAdapter())); } } + + [Fact] + public void Record_ImplementSynthesizedMember_ToString() + { + var source0 = @" +public record R +{ +}"; + var source1 = @" +public record R +{ + public override string ToString() => ""R""; +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member = compilation1.GetMember("R.ToString"); + var other = matcher.MapDefinition(member.GetCciAdapter()); + Assert.NotNull(other); + } + + [Fact] + public void Record_ImplementSynthesizedMember_PrintMembers() + { + var source0 = @" +public record R +{ +}"; + var source1 = @" +public record R +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true; +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member0 = compilation0.GetMember("R.PrintMembers"); + var member1 = compilation1.GetMember("R.PrintMembers"); + + Assert.Equal(member0, (MethodSymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol()); + } + + [Fact] + public void Record_RemoveSynthesizedMember_PrintMembers() + { + var source0 = @" +public record R +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) => true; +}"; + var source1 = @" +public record R +{ +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member0 = compilation0.GetMember("R.PrintMembers"); + var member1 = compilation1.GetMember("R.PrintMembers"); + + Assert.Equal(member0, (MethodSymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol()); + } + + [Fact] + public void Record_ImplementSynthesizedMember_Property() + { + var source0 = @" +public record R(int X);"; + var source1 = @" +public record R(int X) +{ + public int X { get; init; } = this.X; +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var member0 = compilation0.GetMember("R.X"); + var member1 = compilation1.GetMember("R.X"); + + Assert.Equal(member0, (PropertySymbol)matcher.MapDefinition(member1.GetCciAdapter()).GetInternalSymbol()); + } + + [Fact] + public void Record_ImplementSynthesizedMember_Constructor() + { + var source0 = @" +public record R(int X);"; + var source1 = @" +public record R +{ + public R(int X) + { + this.X = X; + } + + public int X { get; init; } +}"; + var compilation0 = CreateCompilation(source0, options: TestOptions.DebugDll); + var compilation1 = compilation0.WithSource(source1); + + var matcher = new CSharpSymbolMatcher( + null, + compilation1.SourceAssembly, + default, + compilation0.SourceAssembly, + default, + null); + + var members = compilation1.GetMembers("R..ctor"); + // There are two, one is the copy constructor + Assert.Equal(2, members.Length); + + var member = (SourceConstructorSymbol)members.Single(m => m.ToString() == "R.R(int)"); + var other = matcher.MapDefinition(member.GetCciAdapter()); + Assert.NotNull(other); + } } } diff --git a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs index 32e09832f4b52..0b00fbbfa991e 100644 --- a/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs +++ b/src/Compilers/CSharp/Test/Emit/PDB/PDBLambdaTests.cs @@ -1381,5 +1381,283 @@ .locals init (bool V_0, //result } ", sequencePoints: "C+<>c.b__0_0"); } + + [Fact] + public void WithExpression() + { + var source = MarkedSource(WithWindowsLineBreaks(@" +using System; +record R(int X); + +class Test +{ + public static void M(int a) + { + var x = new R(1); + var y = x with + { + X = new Func(() => a)() + }; + } +} +"), removeTags: true); // We're validating offsets so need to remove tags entirely + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source.Tree, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdbLambdasAndClosures(source); + } + + [Fact] + public void WithExpression_2() + { + var source = MarkedSource(WithWindowsLineBreaks(@" +using System; +record R(int X, int Y); + +class Test +{ + public static void M(int a) + { + var x = new R(1, 2); + var b = 1; + var y = x with + { + X = new Func(() => a)(), + Y = new Func(() => b)() + }; + } +} +"), removeTags: true); // We're validating offsets so need to remove tags entirely + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source.Tree, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdbLambdasAndClosures(source); + } + + [Fact(Skip = "https://github.com/dotnet/roslyn/issues/52068")] + public void WithExpression_3() + { + var source = MarkedSource(WithWindowsLineBreaks(@" +using System; +record R(int X, int Y); +record Z(int A, R R); + +class Test +{ + public static void M(int a) + { + var r = new R(1, 2); + var x = new Z(1, new R(2, 3)); + var b = 1; + var y = x with + { + A = new Func(() => a)(), + R = r with + { + X = 4, + Y = new Func(() => b)() + } + }; + } +} +"), removeTags: true); // We're validating offsets so need to remove tags entirely + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source.Tree, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdbLambdasAndClosures(source); + } + + [Fact] + public void Record_1() + { + var source = WithWindowsLineBreaks(@" +using System; +record D(int X) +{ + public int Y { get; set; } = new Func(a => a + X).Invoke(1); +} +"); + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + + compilation.VerifyPdb(@" + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", format: CodeAnalysis.Emit.DebugInformationFormat.Pdb); + } + + [Fact] + public void Record_2() + { + var source = WithWindowsLineBreaks(@" +using System; +record C(int X) +{ + public C(int x, Func f) + : this(x) + { + } +} + +record D(int X) : C(F(X, out int z), () => z) +{ + static int F(int x, out int p) + { + p = 1; + return x + 1; + } +} +"); + + // Use NetCoreApp in order to use records + var compilation = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp, options: TestOptions.DebugDll); + compilation.VerifyDiagnostics(); + + compilation.VerifyPdb(@" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ", format: CodeAnalysis.Emit.DebugInformationFormat.Pdb); + } } } diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index e4b56d0250f85..fcfd8f9f24e90 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -1,4 +1,5 @@ *REMOVED*Microsoft.CodeAnalysis.SyntaxNode.IsEquivalentTo(Microsoft.CodeAnalysis.SyntaxNode! other) -> bool +const Microsoft.CodeAnalysis.WellKnownMemberNames.PrintMembersMethodName = "PrintMembers" -> string! Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! Microsoft.CodeAnalysis.IFieldSymbol.IsExplicitlyNamedTupleElement.get -> bool diff --git a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs index 72b9dd19a062a..7975906f8cbdd 100644 --- a/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs +++ b/src/Compilers/Core/Portable/Symbols/WellKnownMemberNames.cs @@ -343,7 +343,11 @@ public static class WellKnownMemberNames // internal until we settle on this long-term internal const string CloneMethodName = "$"; - internal const string PrintMembersMethodName = "PrintMembers"; + + /// + /// The required name for the PrintMembers method that is synthesized in a record. + /// + public const string PrintMembersMethodName = "PrintMembers"; /// /// The name of an entry point method synthesized for top-level statements. diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs index 3f5d01ca493bf..8d24525dc36c6 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/ActiveStatementTests.cs @@ -10588,6 +10588,87 @@ public void InsertDeleteMethod_Active() #endregion + #region Records + + [Fact] + public void Record() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } = 1; +}"; + var src2 = @" +record C(int X) +{ + public int X { get; init; } = 2; +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void Record_Constructor() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } = 1; + + static void Main(string[] args) + { + var x = new C(1); + } +}"; + var src2 = @" +record C(int X) +{ + public int X { get; init; } = 2; + + static void Main(string[] args) + { + var x = new C(1); + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + [Fact] + public void Record_FieldInitializer_Lambda2() + { + var src1 = @" +record C(int X) +{ + Func> a = z => () => z + 1; + + static void Main(string[] args) + { + new C(1).a(1)(); + } +}"; + var src2 = @" +record C(int X) +{ + Func> a = z => () => z + 2; + + static void Main(string[] args) + { + new C(1).a(1)(); + } +}"; + var edits = GetTopEdits(src1, src2); + var active = GetActiveStatements(src1, src2); + + edits.VerifyRudeDiagnostics(active); + } + + #endregion + #region Misc [Fact] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs index a6f68489a40e9..58256706ad272 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/CSharpEditAndContinueAnalyzerTests.cs @@ -123,6 +123,9 @@ public void ErrorSpans_TopLevel() [A, B] /**/public abstract partial class C/**/ { } +[A, B] +/**/public abstract partial record R/**/ { } + /**/interface I/**/ : J, K, L { } [A] diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs index e863592fd1c4e..a68572a77607f 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/StatementEditingTests.cs @@ -10296,6 +10296,60 @@ public void TupleInDelegate() "Update [(int, int) x]@35 -> [(int, int) y]@35"); } - #endregion + #endregion + + #region With Expressions + + [Fact] + public void WithExpression_PropertyAdd() + { + var src1 = @"var x = y with { X = 1 };"; + var src2 = @"var x = y with { X = 1, Y = 2 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1 }]@6 -> [x = y with { X = 1, Y = 2 }]@6"); + } + + [Fact] + public void WithExpression_PropertyDelete() + { + var src1 = @"var x = y with { X = 1, Y = 2 };"; + var src2 = @"var x = y with { X = 1 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1, Y = 2 }]@6 -> [x = y with { X = 1 }]@6"); + } + + [Fact] + public void WithExpression_PropertyChange() + { + var src1 = @"var x = y with { X = 1 };"; + var src2 = @"var x = y with { Y = 1 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1 }]@6 -> [x = y with { Y = 1 }]@6"); + } + + [Fact] + public void WithExpression_PropertyValueChange() + { + var src1 = @"var x = y with { X = 1, Y = 1 };"; + var src2 = @"var x = y with { X = 1, Y = 2 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1, Y = 1 }]@6 -> [x = y with { X = 1, Y = 2 }]@6"); + } + + [Fact] + public void WithExpression_PropertyValueReorder() + { + var src1 = @"var x = y with { X = 1, Y = 1 };"; + var src2 = @"var x = y with { Y = 1, X = 1 };"; + var edits = GetMethodEdits(src1, src2); + + edits.VerifyEdits(@"Update [x = y with { X = 1, Y = 1 }]@6 -> [x = y with { Y = 1, X = 1 }]@6"); + } + + #endregion } } diff --git a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs index 1f189a7c0f65d..f9b904bff620a 100644 --- a/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs +++ b/src/EditorFeatures/CSharpTest/EditAndContinue/TopLevelEditingTests.cs @@ -571,6 +571,22 @@ public void TypeKindUpdate() Diagnostic(RudeEditKind.TypeKindUpdate, "struct C", CSharpFeaturesResources.struct_)); } + [Fact] + public void TypeKindUpdate2() + { + // TODO: Allow this conversion: https://github.com/dotnet/roslyn/issues/51874 + var src1 = "class C { }"; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [class C { }]@0 -> [record C { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.TypeKindUpdate, "record C", CSharpFeaturesResources.record_)); + } + [Fact] public void Class_Modifiers_Update() { @@ -1371,243 +1387,1200 @@ public void PartialType_InsertFirstDeclaration() } [Fact] - public void PartialType_InsertSecondDeclaration() + public void PartialType_InsertSecondDeclaration() + { + var srcA1 = "partial class C { void F() {} }"; + var srcB1 = ""; + var srcA2 = "partial class C { void F() {} }"; + var srcB2 = "partial class C { void G() {} }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("G"), preserveLocalVariables: false) + }), + }); + } + + [Fact] + public void Type_DeleteInsert() + { + var srcA1 = @" +class C { void F() {} } +struct S { void F() {} } +interface I { void F() {} } +"; + var srcB1 = ""; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + }) + }); + } + + [Fact] + public void GenericType_DeleteInsert() + { + var srcA1 = @" +class C { void F() {} } +struct S { void F() {} } +interface I { void F() {} } +"; + var srcB1 = ""; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + diagnostics: new[] + { + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_NonInsertableMembers() + { + var srcA1 = @" +abstract class C +{ + public abstract void AbstractMethod(); + public virtual void VirtualMethod() {} + public override string ToString() => null; + public void I.G() {} +} + +interface I +{ + void G(); + void F() {} +} +"; + var srcB1 = ""; + + var srcA2 = srcB1; + var srcB2 = srcA1; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_DataMembers() + { + var srcA1 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; + public event System.Action E = new System.Action(null); +} +"; + var srcB1 = ""; + + var srcA2 = ""; + var srcB2 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; + public event System.Action E = new System.Action(null); +} +"; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_DataMembers_PartialSplit() + { + var srcA1 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; +} +"; + var srcB1 = ""; + + var srcA2 = @" +partial class C +{ + public int x = 1; + public int y = 2; +} +"; + var srcB2 = @" +partial class C +{ + public int P { get; set; } = 3; +} +"; + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), + }) + }); + } + + [Fact] + public void Type_DeleteInsert_DataMembers_PartialMerge() + { + var srcA1 = @" +partial class C +{ + public int x = 1; + public int y = 2; +} +"; + var srcB1 = @" +partial class C +{ + public int P { get; set; } = 3; +}"; + + var srcA2 = @" +class C +{ + public int x = 1; + public int y = 2; + public int P { get; set; } = 3; +} +"; + + var srcB2 = @" +"; + // note that accessors are not updated since they do not have bodies + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), + }), + + DocumentResults() + }); + } + + #endregion + + #region Records + + [Fact] + public void Record_Partial_MovePrimaryConstructor() + { + var src1 = @" +partial record C { } +partial record C(int X);"; + var src2 = @" +partial record C(int X); +partial record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics(); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_Name_Update() + { + var src1 = "record C { }"; + var src2 = "record D { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record D { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Renamed, "record D", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_NoModifiers_Insert() + { + var src1 = ""; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_NoModifiers_IntoNamespace_Insert() + { + var src1 = "namespace N { }"; + var src2 = "namespace N { record C { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_NoModifiers_IntoType_Insert() + { + var src1 = "struct N { }"; + var src2 = "struct N { record C { } }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_BaseTypeUpdate1() + { + var src1 = "record C { }"; + var src2 = "record C : D { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record C : D { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseTypeUpdate2() + { + var src1 = "record C : D1 { }"; + var src2 = "record C : D2 { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C : D1 { }]@0 -> [record C : D2 { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseInterfaceUpdate1() + { + var src1 = "record C { }"; + var src2 = "record C : IDisposable { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C { }]@0 -> [record C : IDisposable { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseInterfaceUpdate2() + { + var src1 = "record C : IGoo, IBar { }"; + var src2 = "record C : IGoo { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C : IGoo, IBar { }]@0 -> [record C : IGoo { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void Record_BaseInterfaceUpdate3() + { + var src1 = "record C : IGoo, IBar { }"; + var src2 = "record C : IBar, IGoo { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyEdits( + "Update [record C : IGoo, IBar { }]@0 -> [record C : IBar, IGoo { }]@0"); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.BaseTypeOrInterfaceUpdate, "record C", CSharpFeaturesResources.record_)); + } + + [Fact] + public void RecordInsert_AbstractVirtualOverride() + { + var src1 = ""; + var src2 = @" +public abstract record C +{ + public abstract void F(); + public virtual void G() {} + public override void H() {} +}"; + + var edits = GetTopEdits(src1, src2); + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_PrintMembers() + { + var src1 = "record C { }"; + var src2 = @" +record C +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return true; + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_WrongParameterName() + { + // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 + + var src1 = "record C { }"; + var src2 = @" +record C +{ + protected virtual bool PrintMembers(System.Text.StringBuilder sb) + { + return false; + } + + public virtual bool Equals(C rhs) + { + return false; + } + + protected C(C other) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemanticDiagnostics( + Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "protected virtual bool PrintMembers(System.Text.StringBuilder sb)", "PrintMembers(System.Text.StringBuilder builder)"), + Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "public virtual bool Equals(C rhs)", "Equals(C other)"), + Diagnostic(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, "protected C(C other)", "C(C original)")); + } + + [Fact] + public void Record_ImplementSynthesized_ToString() + { + var src1 = "record C { }"; + var src2 = @" +record C +{ + public override string ToString() + { + return ""R""; + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.ToString"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_ToString() + { + var src1 = @" +record C +{ + public override string ToString() + { + return ""R""; + } +}"; + var src2 = "record C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.ToString"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_Primary() + { + var src1 = "record C(int X);"; + var src2 = "record C(int X, int Y);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.AddRecordPositionalParameter, "int Y", FeaturesResources.parameter)); + } + + [Fact] + public void Record_AddProperty_NotPrimary() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_NotPrimary_WithConstructor() + { + var src1 = @" +record C(int X) +{ + public C(string fromAString) + { + } +}"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } + + public C(string fromAString) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_NotPrimary_WithExplicitMembers() + { + var src1 = @" +record C(int X) +{ + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public virtual bool Equals(C other) + { + return false; + } + + public C(C original) + { + } +}"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } + + protected virtual bool PrintMembers(System.Text.StringBuilder builder) + { + return false; + } + + public override int GetHashCode() + { + return 0; + } + + public virtual bool Equals(C other) + { + return false; + } + + public C(C original) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddProperty_NotPrimary_WithInitializer() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int Y { get; set; } = 1; +}"; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C.Y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField() + { + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { private int _y; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithExplicitMembers() + { + var src1 = @" +record C(int X) +{ + public C(C other) + { + } +}"; + var src2 = @" +record C(int X) +{ + private int _y; + + public C(C other) + { + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithInitializer() + { + var src1 = "record C(int X) { }"; + var src2 = "record C(int X) { private int _y = 1; }"; + var syntaxMap = GetSyntaxMap(src1, src2); + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._y")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithExistingInitializer() + { + var src1 = "record C(int X) { private int _y = 1; }"; + var src2 = "record C(int X) { private int _y = 1; private int _z; }"; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), syntaxMap[0]), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_AddField_WithInitializerAndExistingInitializer() + { + var src1 = "record C(int X) { private int _y = 1; }"; + var src2 = "record C(int X) { private int _y = 1; private int _z = 1; }"; + + var edits = GetTopEdits(src1, src2); + var syntaxMap = GetSyntaxMap(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C._z")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), syntaxMap[0]), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_DeleteField() + { + var src1 = "record C(int X) { private int _y; }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.field, "_y"))); + } + + [Fact] + public void Record_DeleteProperty_Primary() + { + var src1 = "record C(int X, int Y) { }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics(Diagnostic(RudeEditKind.DeleteRecordPositionalParameter, "record C", FeaturesResources.parameter)); + } + + [Fact] + public void Record_DeleteProperty_NotPrimary() + { + var src1 = "record C(int X) { public int P { get; set; } }"; + var src2 = "record C(int X) { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.auto_property, "P"))); + } + + [Fact] + public void Record_ImplementSynthesized_Property() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get; init; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_WithBody() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X + { + get + { + return 4; + } + init + { + throw null; + } + } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_WithExpressionBody() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get => 4; init => throw null; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_ImplementSynthesized_Property_InitToSet() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get; set; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ImplementRecordParameterWithSet, "public int X", "X")); + } + + [Fact] + public void Record_ImplementSynthesized_Property_MakeReadOnly() + { + var src1 = "record C(int X);"; + var src2 = @" +record C(int X) +{ + public int X { get; } +}"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.ImplementRecordParameterAsReadOnly, "public int X", "X")); + } + + [Fact] + public void Record_UnImplementSynthesized_Property() + { + var src1 = @" +record C(int X) +{ + public int X { get; init; } +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_WithExpressionBody() + { + var src1 = @" +record C(int X) +{ + public int X { get => 4; init => throw null; } +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_WithBody() { - var srcA1 = "partial class C { void F() {} }"; - var srcB1 = ""; - var srcA2 = "partial class C { void F() {} }"; - var srcB2 = "partial class C { void G() {} }"; + var src1 = @" +record C(int X) +{ + public int X { get { return 4; } init { } } +}"; + var src2 = "record C(int X);"; - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - new[] - { - DocumentResults(), + var edits = GetTopEdits(src1, src2); - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Insert, c => c.GetMember("C").GetMember("G"), preserveLocalVariables: false) - }), - }); + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C"))); + + edits.VerifyRudeDiagnostics(); } [Fact] - public void Type_DeleteInsert() + public void Record_ImplementSynthesized_Property_Partial() { - var srcA1 = @" -class C { void F() {} } -struct S { void F() {} } -interface I { void F() {} } -"; - var srcB1 = ""; - - var srcA2 = srcB1; - var srcB2 = srcA1; + var srcA1 = @"partial record C(int X);"; + var srcB1 = @"partial record C;"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @" +partial record C +{ + public int X { get; init; } +}"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("S").GetMember("F")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType: "C", preserveLocalVariables: true) }) }); } [Fact] - public void GenericType_DeleteInsert() + public void Record_UnImplementSynthesized_Property_Partial() { - var srcA1 = @" -class C { void F() {} } -struct S { void F() {} } -interface I { void F() {} } -"; - var srcB1 = ""; - - var srcA2 = srcB1; - var srcB2 = srcA1; + var srcA1 = @"partial record C(int X);"; + var srcB1 = @" +partial record C +{ + public int X { get; init; } +}"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @"partial record C;"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - DocumentResults( - diagnostics: new[] + semanticEdits: new[] { - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), - Diagnostic(RudeEditKind.GenericTypeTriviaUpdate, "void F()", FeaturesResources.method), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType: "C", preserveLocalVariables: true) }) }); } [Fact] - public void Type_DeleteInsert_NonInsertableMembers() + public void Record_ImplementSynthesized_Property_Partial_WithBody() { - var srcA1 = @" -abstract class C -{ - public abstract void AbstractMethod(); - public virtual void VirtualMethod() {} - public override string ToString() => null; - public void I.G() {} -} - -interface I + var srcA1 = @"partial record C(int X);"; + var srcB1 = @"partial record C;"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @" +partial record C { - void G(); - void F() {} -} -"; - var srcB1 = ""; - - var srcA2 = srcB1; - var srcB2 = srcA1; + public int X + { + get + { + return 4; + } + init + { + throw null; + } + } +}"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("VirtualMethod")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("ToString")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMember("I.G")), - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("I").GetMember("F")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType : "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) }) }); } [Fact] - public void Type_DeleteInsert_DataMembers() + public void Record_UnImplementSynthesized_Property_Partial_WithBody() { - var srcA1 = @" -class C + var srcA1 = @"partial record C(int X);"; + var srcB1 = @" +partial record C { - public int x = 1; - public int y = 2; - public int P { get; set; } = 3; - public event System.Action E = new System.Action(null); -} -"; - var srcB1 = ""; + public int X + { + get + { + return 4; + } + init + { + throw null; + } + } +}"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @"partial record C;"; - var srcA2 = ""; - var srcB2 = @" -class C -{ - public int x = 1; - public int y = 2; - public int P { get; set; } = 3; - public event System.Action E = new System.Action(null); -} -"; EditAndContinueValidation.VerifySemantics( new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - DocumentResults( semanticEdits: new[] { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.PrintMembers")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").GetMembers("Equals").OfType().First(m => SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType))), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.GetHashCode")), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").GetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C.X").SetMethod), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), partialType : "C", preserveLocalVariables: true), + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "C")) }) }); } [Fact] - public void Type_DeleteInsert_DataMembers_PartialSplit() + public void Record_MoveProperty_Partial() { - var srcA1 = @" -class C -{ - public int x = 1; - public int y = 2; - public int P { get; set; } = 3; -} -"; - var srcB1 = ""; - - var srcA2 = @" -partial class C + var srcA1 = @"partial record C(int X) { - public int x = 1; - public int y = 2; -} -"; - var srcB2 = @" -partial class C + public int Y { get; init; } +}"; + var srcB1 = @"partial record C;"; + var srcA2 = @"partial record C(int X);"; + var srcB2 = @"partial record C { - public int P { get; set; } = 3; -} -"; + public int Y { get; init; } +}"; + EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, new[] { DocumentResults(), - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), - }) + DocumentResults(), }); } [Fact] - public void Type_DeleteInsert_DataMembers_PartialMerge() + public void Record_UnImplementSynthesized_Property_WithInitializer() { - var srcA1 = @" -partial class C + var src1 = @" +record C(int X) { - public int x = 1; - public int y = 2; -} -"; - var srcB1 = @" -partial class C + public int X { get; init; } = 1; +}"; + var src2 = "record C(int X);"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_UnImplementSynthesized_Property_WithInitializerMatchingCompilerGenerated() + { + var src1 = @" +record C(int X) { - public int P { get; set; } = 3; + public int X { get; init; } = X; }"; + var src2 = "record C(int X);"; - var srcA2 = @" -class C + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); + + edits.VerifyRudeDiagnostics(); + } + + [Fact] + public void Record_Property_Delete_NotPrimary() + { + var src1 = @" +record C(int X) { - public int x = 1; - public int y = 2; - public int P { get; set; } = 3; -} -"; + public int Y { get; init; } +}"; + var src2 = "record C(int X);"; - var srcB2 = @" -"; - // note that accessors are not updated since they do not have bodies - EditAndContinueValidation.VerifySemantics( - new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, - new[] - { - DocumentResults( - semanticEdits: new[] - { - SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true), - }), + var edits = GetTopEdits(src1, src2); - DocumentResults() - }); + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "record C", DeletedSymbolDisplay(FeaturesResources.auto_property, "Y"))); + } + + [Fact] + public void Record_PropertyInitializer_Update_NotPrimary() + { + var src1 = "record C { int X { get; } = 0; }"; + var src2 = "record C { int X { get; } = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters.Length == 0), preserveLocalVariables: true)); + } + + [Fact] + public void Record_PropertyInitializer_Update_Primary() + { + var src1 = "record C(int X) { int X { get; } = 0; }"; + var src2 = "record C(int X) { int X { get; } = 1; }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifySemantics( + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").Constructors.Single(c => c.Parameters[0].Type.ToDisplayString() == "int"), preserveLocalVariables: true)); } #endregion @@ -8935,6 +9908,28 @@ public void Field_Partial_DeleteInsert_InitializerRemoval() }); } + [Fact] + public void Field_Partial_DeleteInsert_InitializerUpdate() + { + var srcA1 = "partial class C { int F = 1; }"; + var srcB1 = "partial class C { }"; + + var srcA2 = "partial class C { }"; + var srcB2 = "partial class C { int F = 2; }"; + + EditAndContinueValidation.VerifySemantics( + new[] { GetTopEdits(srcA1, srcA2), GetTopEdits(srcB1, srcB2) }, + new[] + { + DocumentResults(), + DocumentResults( + semanticEdits: new[] + { + SemanticEdit(SemanticEditKind.Update, c => c.GetMember("C").InstanceConstructors.Single(), partialType: "C", preserveLocalVariables: true) + }), + }); + } + #endregion #region Fields @@ -9808,6 +10803,18 @@ public void PropertyRename2() Diagnostic(RudeEditKind.Renamed, "int J.P", FeaturesResources.property_)); } + [Fact] + public void PropertyDelete() + { + var src1 = "class C { int P { get { return 1; } } }"; + var src2 = "class C { }"; + + var edits = GetTopEdits(src1, src2); + + edits.VerifyRudeDiagnostics( + Diagnostic(RudeEditKind.Delete, "class C", DeletedSymbolDisplay(FeaturesResources.property_, "P"))); + } + [Fact] public void PropertyReorder1() { diff --git a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs index 4643bb76e6670..63a037312c79f 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RudeEditDiagnosticTests.cs @@ -49,6 +49,8 @@ public void ToDiagnostic() RudeEditKind.UpdatingStateMachineMethodAroundActiveStatement, RudeEditKind.SwitchBetweenLambdaAndLocalFunction, RudeEditKind.InsertMethodWithExplicitInterfaceSpecifier, + RudeEditKind.AddRecordPositionalParameter, + RudeEditKind.DeleteRecordPositionalParameter, }; var arg2 = new HashSet() diff --git a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx index c5b6b2c9b08d2..019ca0f85ca72 100644 --- a/src/Features/CSharp/Portable/CSharpFeaturesResources.resx +++ b/src/Features/CSharp/Portable/CSharpFeaturesResources.resx @@ -618,4 +618,7 @@ Change to cast + + record + \ No newline at end of file diff --git a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs index e0e62cceef9c2..b6d6970728c20 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/CSharpEditAndContinueAnalyzer.cs @@ -648,6 +648,77 @@ private static IEnumerable GetChildNodes(SyntaxNode root, SyntaxNode } } + internal override void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol) + { + // Compiler generated methods of records have a declaring syntax reference to the record declaration itself + // but their explicitly implemented counterparts reference the actual member. Compiler generated properties + // of records reference the parameter that names them. + // + // Since there is no useful "old" syntax node for these members, we can't compute declaration or body edits + // using the standard tree comparison code. + // + // Based on this, we can detect a new explicit implementation of a record member by checking if the + // declaration kind has changed. If it hasn't changed, then our standard code will handle it. + if (oldNode.RawKind == newNode.RawKind) + { + base.ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode, oldSymbol, newSymbol); + return; + } + + // When explicitly implementing a property that is represented by a positional parameter + // what looks like an edit could actually be a rude delete, or something else + if (oldNode is ParameterSyntax && + newNode is PropertyDeclarationSyntax property) + { + if (property.AccessorList!.Accessors.Count == 1) + { + // Explicitly implementing a property with only one accessor is a delete of the init accessor, so a rude edit. + // Not implementing the get accessor would be a compile error + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ImplementRecordParameterAsReadOnly, + GetDiagnosticSpan(newNode, EditKind.Delete), + oldNode, + new[] { + property.Identifier.ToString() + })); + } + else if (property.AccessorList.Accessors.Any(a => a.IsKind(SyntaxKind.SetAccessorDeclaration))) + { + // The compiler implements the properties with an init accessor so explicitly implementing + // it with a set accessor is a rude accessor change edit + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ImplementRecordParameterWithSet, + GetDiagnosticSpan(newNode, EditKind.Delete), + oldNode, + new[] { + property.Identifier.ToString() + })); + } + } + else if (oldNode is RecordDeclarationSyntax && + newNode is MethodDeclarationSyntax && + !oldSymbol.GetParameters().Select(p => p.Name).SequenceEqual(newSymbol.GetParameters().Select(p => p.Name))) + { + // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 + // Explicitly implemented methods must have parameter names that match the compiler generated versions + // exactly otherwise symbol matching won't work for them. + // We don't need to worry about parameter types, because if they were different then we wouldn't get here + // as this wouldn't be the explicit implementation of a known method. + // We don't need to worry about access modifiers because the symbol matching still works, and most of the + // time changing access modifiers for these known methods is a compile error anyway. + + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, + GetDiagnosticSpan(newNode, EditKind.Update), + oldNode, + new[] { + oldSymbol.ToDisplayString(SymbolDisplayFormats.NameFormat) + })); + } + } + protected override void ReportLocalFunctionsDeclarationRudeEdits(ArrayBuilder diagnostics, Match bodyMatch) { var bodyEditsForLambda = bodyMatch.GetTreeEdits(); @@ -1028,6 +1099,9 @@ private static bool AreSimilarActiveStatements(CommonForEachStatementSyntax oldN internal override bool IsInterfaceDeclaration(SyntaxNode node) => node.IsKind(SyntaxKind.InterfaceDeclaration); + internal override bool IsRecordDeclaration(SyntaxNode node) + => node.IsKind(SyntaxKind.RecordDeclaration); + internal override SyntaxNode? TryGetContainingTypeDeclaration(SyntaxNode node) => node.Parent!.FirstAncestorOrSelf(); @@ -1041,6 +1115,42 @@ internal override bool HasBackingField(SyntaxNode propertyOrIndexerDeclaration) internal override bool IsDeclarationWithInitializer(SyntaxNode declaration) => declaration is VariableDeclaratorSyntax { Initializer: not null } || declaration is PropertyDeclarationSyntax { Initializer: not null }; + internal override bool IsRecordPrimaryConstructorParameter(SyntaxNode declaration) + => declaration is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } }; + + private static bool IsPropertyDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType) + { + if (newContainingType.IsRecord && + declaration is PropertyDeclarationSyntax { Identifier: { ValueText: var name } }) + { + // We need to use symbol information to find the primary constructor, because it could be in another file if the type is partial + foreach (var reference in newContainingType.DeclaringSyntaxReferences) + { + // Since users can define as many constructors as they like, going back to syntax to find the parameter list + // in the record declaration is the simplest way to check if there is a matching parameter + if (reference.GetSyntax() is RecordDeclarationSyntax record && + record.ParameterList is not null && + record.ParameterList.Parameters.Any(p => p.Identifier.ValueText.Equals(name))) + { + return true; + } + } + } + return false; + } + + internal override bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType, out bool isFirstAccessor) + { + isFirstAccessor = false; + if (declaration is AccessorDeclarationSyntax { Parent: AccessorListSyntax { Parent: PropertyDeclarationSyntax property } list } && + IsPropertyDeclarationMatchingPrimaryConstructorParameter(property, newContainingType)) + { + isFirstAccessor = list.Accessors[0] == declaration; + return true; + } + return false; + } + internal override bool IsConstructorWithMemberInitializers(SyntaxNode constructorDeclaration) => constructorDeclaration is ConstructorDeclarationSyntax ctor && (ctor.Initializer == null || ctor.Initializer.IsKind(SyntaxKind.BaseConstructorInitializer)); @@ -1315,6 +1425,7 @@ private static bool GroupBySignatureComparer(ImmutableArray ol case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: var typeDeclaration = (TypeDeclarationSyntax)node; return GetDiagnosticSpan(typeDeclaration.Modifiers, typeDeclaration.Keyword, typeDeclaration.TypeParameterList ?? (SyntaxNodeOrToken)typeDeclaration.Identifier); @@ -1663,6 +1774,9 @@ internal override TextSpan GetLambdaParameterDiagnosticSpan(SyntaxNode lambda, i case SyntaxKind.InterfaceDeclaration: return FeaturesResources.interface_; + case SyntaxKind.RecordDeclaration: + return CSharpFeaturesResources.record_; + case SyntaxKind.EnumDeclaration: return FeaturesResources.enum_; @@ -2021,6 +2135,7 @@ private void ClassifyReorder(SyntaxNode newNode) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: case SyntaxKind.VariableDeclaration: @@ -2093,6 +2208,7 @@ private void ClassifyInsert(SyntaxNode node) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.InterfaceDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.DelegateDeclaration: @@ -2121,7 +2237,14 @@ private void ClassifyInsert(SyntaxNode node) case SyntaxKind.Parameter when !_classifyStatementSyntax: // Parameter inserts are allowed for local functions - ReportError(RudeEditKind.Insert); + if (node.Parent?.Parent is RecordDeclarationSyntax) + { + ReportError(RudeEditKind.AddRecordPositionalParameter); + } + else + { + ReportError(RudeEditKind.Insert); + } return; case SyntaxKind.EnumMemberDeclaration: @@ -2175,6 +2298,7 @@ private void ClassifyDelete(SyntaxNode oldNode) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: case SyntaxKind.MethodDeclaration: case SyntaxKind.PropertyDeclaration: case SyntaxKind.IndexerDeclaration: @@ -2216,7 +2340,14 @@ private void ClassifyDelete(SyntaxNode oldNode) case SyntaxKind.Parameter when !_classifyStatementSyntax: case SyntaxKind.ParameterList when !_classifyStatementSyntax: - ReportError(RudeEditKind.Delete); + if (oldNode.Parent?.Parent is RecordDeclarationSyntax) + { + ReportError(RudeEditKind.DeleteRecordPositionalParameter); + } + else + { + ReportError(RudeEditKind.Delete); + } return; } @@ -2255,6 +2386,7 @@ private void ClassifyUpdate(SyntaxNode oldNode, SyntaxNode newNode) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: ClassifyUpdate((TypeDeclarationSyntax)oldNode, (TypeDeclarationSyntax)newNode); return; @@ -3055,6 +3187,7 @@ protected override List GetExceptionHandlingAncestors(SyntaxNode nod // stop at type declaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: + case SyntaxKind.RecordDeclaration: return result; } diff --git a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs index 2517806df62f5..0b60b3031ed77 100644 --- a/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs +++ b/src/Features/CSharp/Portable/EditAndContinue/SyntaxComparer.cs @@ -563,10 +563,10 @@ private static Label ClassifyTopSyntax(SyntaxKind kind, out bool isLeaf) case SyntaxKind.NamespaceDeclaration: return Label.NamespaceDeclaration; - // Need to add support for records (tracked by https://github.com/dotnet/roslyn/issues/44877) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: return Label.TypeDeclaration; case SyntaxKind.MethodDeclaration: @@ -1342,10 +1342,10 @@ private static double CombineOptional( case SyntaxKind.NamespaceDeclaration: return ((NamespaceDeclarationSyntax)node).Name; - // Need to add support for records (tracked by https://github.com/dotnet/roslyn/issues/44877) case SyntaxKind.ClassDeclaration: case SyntaxKind.StructDeclaration: case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.RecordDeclaration: return ((TypeDeclarationSyntax)node).Identifier; case SyntaxKind.EnumDeclaration: diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf index 61f9418132995..c2a819d8bc8f8 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.cs.xlf @@ -412,6 +412,11 @@ Generovat implicitní operátor převodu v {0} + + record + record + + switch statement příkaz switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf index 6adb896ee20b1..4b95649bede12 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.de.xlf @@ -412,6 +412,11 @@ Impliziten Konversionsoperator in '{0}' generieren + + record + record + + switch statement switch-Anweisung diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf index d4679efa92d51..f775b17afcf35 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.es.xlf @@ -412,6 +412,11 @@ Generar operador de conversión implícito en '{0}' + + record + record + + switch statement instrucción switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf index d6eb8d8bdbe4d..4c5916b74e553 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.fr.xlf @@ -412,6 +412,11 @@ Générer l’opérateur de conversion implicite dans « {0} » + + record + record + + switch statement instruction switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf index 96655e64685b7..94688d112ab3a 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.it.xlf @@ -412,6 +412,11 @@ Genera l'operatore di conversione implicito in '{0}' + + record + record + + switch statement istruzione switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf index 0fc18e575d69c..70ac45620b7f1 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ja.xlf @@ -412,6 +412,11 @@ 暗黙的な変換演算子を '{0}' に生成します + + record + record + + switch statement switch ステートメント diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf index 20db7647648fb..f8f5c44cd8f87 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ko.xlf @@ -412,6 +412,11 @@ '{0}'에서 암시적 변환 연산자 생성 + + record + record + + switch statement switch 문 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf index b2cb8529f8703..f98de496503d0 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pl.xlf @@ -412,6 +412,11 @@ Generuj operator niejawnej konwersji w elemencie „{0}” + + record + record + + switch statement instrukcja switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf index 4fe6cad7ddb99..8a1031efa4b6a 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.pt-BR.xlf @@ -412,6 +412,11 @@ Gerar um operador de conversão implícita em '{0}' + + record + record + + switch statement instrução switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf index be7ce19fffb1e..c6846379a30d8 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.ru.xlf @@ -412,6 +412,11 @@ Создать неявный оператор преобразования в "{0}" + + record + record + + switch statement оператор switch diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf index 0c4b40a5a0531..a8901cab24888 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.tr.xlf @@ -412,6 +412,11 @@ '{0}' içinde örtük dönüşüm işleci oluştur + + record + record + + switch statement switch deyimi diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf index 2ea3ba0e72a5e..d0dcc2b43c5da 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hans.xlf @@ -412,6 +412,11 @@ 在“{0}”中生成隐式转换运算符 + + record + record + + switch statement switch 语句 diff --git a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf index c21786df040f9..43be8381fa6dd 100644 --- a/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf +++ b/src/Features/CSharp/Portable/xlf/CSharpFeaturesResources.zh-Hant.xlf @@ -412,6 +412,11 @@ 在 '{0}' 中產生隱含轉換運算子 + + record + record + + switch statement switch 陳述式 diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 30ca572b91487..e36af4dc5caf1 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.InteropServices; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Differencing; @@ -368,6 +369,7 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind internal abstract bool IsLambda(SyntaxNode node); internal abstract bool IsInterfaceDeclaration(SyntaxNode node); + internal abstract bool IsRecordDeclaration(SyntaxNode node); /// /// True if the node represents any form of a function definition nested in another function body (i.e. anonymous function, lambda, local function). @@ -421,6 +423,16 @@ protected virtual string GetSuspensionPointDisplayName(SyntaxNode node, EditKind /// internal abstract bool IsDeclarationWithInitializer(SyntaxNode declaration); + /// + /// Return true if the declaration is a parameter that is part of a records primary constructor. + /// + internal abstract bool IsRecordPrimaryConstructorParameter(SyntaxNode declaration); + + /// + /// Return true if the declaration is a property accessor for a property that represents one of the parameters in a records primary constructor. + /// + internal abstract bool IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(SyntaxNode declaration, INamedTypeSymbol newContainingType, out bool isFirstAccessor); + /// /// Return true if the declaration is a constructor declaration to which field/property initializers are emitted. /// @@ -650,7 +662,7 @@ private void ReportTopLevelSyntacticRudeEdits(ArrayBuilder d /// or . /// The scenarios include moving a type declaration from one file to another and moving a member of a partial type from one partial declaration to another. /// - internal void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode) + internal virtual void ReportDeclarationInsertDeleteRudeEdits(ArrayBuilder diagnostics, SyntaxNode oldNode, SyntaxNode newNode, ISymbol oldSymbol, ISymbol newSymbol) { // Consider replacing following syntax analysis with semantic analysis of the corresponding symbols, // or a combination of semantic and syntax analysis (e.g. primarily analyze symbols but fall back @@ -2297,8 +2309,29 @@ private async Task> AnalyzeSemanticsAsync( if (!newSymbol.IsImplicitlyDeclared) { // Ignore the delete. The new symbol is explicitly declared and thus there will be an insert edit that will issue a semantic update. + // Note that this could also be the case for deleting properties of records, but they will be handled when we see + // their accessors below. continue; } + else if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(edit.OldNode, newSymbol.ContainingType, out var isFirst)) + { + // Defer a constructor edit to cover the property initializer changing + DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newDeclaration: null, syntaxMap, oldSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + + // If there was no body deleted then we are done since the compiler generated property also has no body + if (TryGetDeclarationBody(edit.OldNode) is null) + { + continue; + } + + // If there was a body, then the backing field of the property will be affected so we + // need to issue edits for the synthezied members. + // We only need to do this once though. + if (isFirst) + { + AddEditsForSynthesizedRecordMembers(newCompilation, newSymbol.ContainingType, semanticEdits); + } + } // can't change visibility: if (newSymbol.DeclaredAccessibility != oldSymbol.DeclaredAccessibility) @@ -2307,7 +2340,9 @@ private async Task> AnalyzeSemanticsAsync( continue; } - // If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers. + // If a constructor is deleted and replaced by an implicit one the update needs to aggregate updates to all data member initializers, + // or if a property is deleted that is part of a records primary constructor, which is effectivelly moving from an explicit to implicit + // initializer. if (IsConstructorWithMemberInitializers(edit.OldNode)) { processedSymbols.Remove(oldSymbol); @@ -2385,8 +2420,7 @@ private async Task> AnalyzeSemanticsAsync( if (oldSymbol.IsImplicitlyDeclared) { - // Replace implicit declaration with an explicit one. - + // Replace implicit declaration with an explicit one with a different visibility is a rude edit. if (oldSymbol.DeclaredAccessibility != newSymbol.DeclaredAccessibility) { diagnostics.Add(new RudeEditDiagnostic(RudeEditKind.ChangingVisibility, @@ -2395,6 +2429,34 @@ private async Task> AnalyzeSemanticsAsync( continue; } + + // If a user explicitly implements a member of a record then we want to issue an update, not an insert. + if (oldSymbol.DeclaringSyntaxReferences.Length == 1) + { + var oldNode = GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences[0], cancellationToken); + var newNode = edit.NewNode; + + ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode, oldSymbol, newSymbol); + + if (IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(newNode, newSymbol.ContainingType, out var isFirst)) + { + // If there is no body declared we can skip it entirely because for a property accessor + // it matches what the compiler would have previously implicitly implemented. + if (TryGetDeclarationBody(newNode) is null) + { + continue; + } + + // If there was a body, then the backing field of the property will be affected so we + // need to issue edits for the synthezied members. Only need to do it once. + if (isFirst) + { + AddEditsForSynthesizedRecordMembers(newCompilation, newSymbol.ContainingType, semanticEdits); + } + } + + editKind = SemanticEditKind.Update; + } } else if (newSymbol is IFieldSymbol { ContainingType: { TypeKind: TypeKind.Enum } }) { @@ -2416,21 +2478,29 @@ private async Task> AnalyzeSemanticsAsync( } else if (oldSymbol.DeclaringSyntaxReferences.Length == 1 && newSymbol.DeclaringSyntaxReferences.Length == 1) { + // Handles partial methods and explicitly implemented properties that implement positional parameters of records + // We ignore partial method definition parts when processing edits (GetSymbolForEdit). // The only declaration in compilation without syntax errors that can have multiple declaring references is a type declaration. // We can therefore ignore any symbols that have more than one declaration. ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); - // Compare the old declaration syntax of the symbol with its new declaration and report rude edits - // if it changed in any way that's not allowed. var oldNode = GetSymbolDeclarationSyntax(oldSymbol.DeclaringSyntaxReferences[0], cancellationToken); var newNode = edit.NewNode; - ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode); + // Compare the old declaration syntax of the symbol with its new declaration and report rude edits + // if it changed in any way that's not allowed. + ReportDeclarationInsertDeleteRudeEdits(diagnostics, oldNode, newNode, oldSymbol, newSymbol); + + // If a node has been inserted but neither old nor new has a body, we can stop processing. + // The exception to this is explicitly implemented properties that implement positional parameters of + // records, as even not having an initializer is an "edit", since the compiler generated property would have + // had one. + var isRecordPrimaryConstructorParameter = IsRecordPrimaryConstructorParameter(oldNode); var oldBody = TryGetDeclarationBody(oldNode); var newBody = TryGetDeclarationBody(newNode); - if (oldBody == null && newBody == null) + if (oldBody == null && newBody == null && !isRecordPrimaryConstructorParameter) { continue; } @@ -2468,7 +2538,8 @@ private async Task> AnalyzeSemanticsAsync( // we don't need to aggregate syntax map from all initializers for the constructor update semantic edit. if (IsConstructorWithMemberInitializers(newNode) || IsDeclarationWithInitializer(oldNode) || - IsDeclarationWithInitializer(newNode)) + IsDeclarationWithInitializer(newNode) || + isRecordPrimaryConstructorParameter) { processedSymbols.Remove(newSymbol); DeferConstructorEdit(oldSymbol.ContainingType, newSymbol.ContainingType, newNode, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); @@ -2510,6 +2581,16 @@ private async Task> AnalyzeSemanticsAsync( // We disallow moving a data member of a partial type with explicit layout even when it actually does not change the layout. // We could compare the exact order of the members but the scenario is unlikely to occur. ReportTypeLayoutUpdateRudeEdits(diagnostics, newSymbol, edit.NewNode, newModel, ref lazyLayoutAttribute); + + // If a property or field is added to a record then the implicit constructors change, + // and we need to mark a number of other synthesized members as having changed. + if (newSymbol is IPropertySymbol or IFieldSymbol && newSymbol.ContainingType.IsRecord) + { + processedSymbols.Remove(newSymbol); + DeferConstructorEdit(oldContainingType, newSymbol.ContainingType, edit.NewNode, syntaxMap, newSymbol.IsStatic, ref instanceConstructorEdits, ref staticConstructorEdits); + + AddEditsForSynthesizedRecordMembers(newCompilation, newContainingType, semanticEdits); + } } else { @@ -2754,6 +2835,60 @@ private async Task> AnalyzeSemanticsAsync( return semanticEdits.ToImmutable(); } + private static void AddEditsForSynthesizedRecordMembers(Compilation compilation, INamedTypeSymbol recordType, ArrayBuilder semanticEdits) + { + foreach (var member in GetRecordUpdatedSynthesizedMembers(compilation, recordType)) + { + var symbolKey = SymbolKey.Create(member); + semanticEdits.Add(new SemanticEditInfo(SemanticEditKind.Update, symbolKey, syntaxMap: null, syntaxMapTree: null, partialType: null)); + } + } + + private static IEnumerable GetRecordUpdatedSynthesizedMembers(Compilation compilation, INamedTypeSymbol record) + { + // All methods that are updated have well known names, and calling GetMembers(string) is + // faster than enumerating. + + // When a new field or property is added the PrintMembers, Equals(R) and GetHashCode() methods are updated + // We don't need to worry about Deconstruct because it only changes when a new positional parameter + // is added, and those are rude edits (due to adding a constructor parameter). + // We don't need to worry about the constructors as they are reported elsewhere. + // We have to use SingleOrDefault and check IsImplicitlyDeclared because the user can provide their + // own implementation of these methods, and edits to them are handled by normal processing. + var result = record.GetMembers(WellKnownMemberNames.PrintMembersMethodName) + .OfType() + .SingleOrDefault(m => + m.IsImplicitlyDeclared && + m.Parameters.Length == 1 && + SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, compilation.GetTypeByMetadataName(typeof(StringBuilder).FullName!)) && + SymbolEqualityComparer.Default.Equals(m.ReturnType, compilation.GetTypeByMetadataName(typeof(bool).FullName!))); + if (result is not null) + { + yield return result; + } + + result = record.GetMembers(WellKnownMemberNames.ObjectEquals) + .OfType() + .SingleOrDefault(m => + m.IsImplicitlyDeclared && + m.Parameters.Length == 1 && + SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, m.ContainingType)); + if (result is not null) + { + yield return result; + } + + result = record.GetMembers(WellKnownMemberNames.ObjectGetHashCode) + .OfType() + .SingleOrDefault(m => + m.IsImplicitlyDeclared && + m.Parameters.Length == 0); + if (result is not null) + { + yield return result; + } + } + private void ReportDeletedMemberRudeEdit( ArrayBuilder diagnostics, EditScript editScript, @@ -2946,7 +3081,7 @@ private static void DeferConstructorEdit( constructorEdits.Add(newType, constructorEdit = new ConstructorEdit(oldType)); } - if (newDeclaration != null) + if (newDeclaration != null && !constructorEdit.ChangedDeclarations.ContainsKey(newDeclaration)) { constructorEdit.ChangedDeclarations.Add(newDeclaration, syntaxMap); } @@ -3004,31 +3139,44 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) var newCtorKey = SymbolKey.Create(newCtor, cancellationToken); + var syntaxMapToUse = aggregateSyntaxMap; + ISymbol? oldCtor; if (!newCtor.IsImplicitlyDeclared) { // Constructors have to have a single declaration syntax, they can't be partial var newDeclaration = GetSymbolDeclarationSyntax(newCtor.DeclaringSyntaxReferences.Single(), cancellationToken); - // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously - // or was not edited. In either case we should not produce a semantic edit for it. - if (!IsConstructorWithMemberInitializers(newDeclaration)) + // Compiler generated constructors of records are not implicitly declared, since they + // points to the actual record declaration. We want to skip these checks because we can't + // reason about initializers for them. + if (!IsRecordDeclaration(newDeclaration)) { - continue; - } + // Constructor that doesn't contain initializers had a corresponding semantic edit produced previously + // or was not edited. In either case we should not produce a semantic edit for it. + if (!IsConstructorWithMemberInitializers(newDeclaration)) + { + continue; + } - // If no initializer updates were made in the type we only need to produce semantic edits for constructors - // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. - // If changes were made to initializers or constructors of a partial type in another document they will be merged - // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will - // be reported in the document they were made in. - if (!anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) - { - continue; + // If no initializer updates were made in the type we only need to produce semantic edits for constructors + // whose body has been updated, otherwise we need to produce edits for all constructors that include initializers. + // If changes were made to initializers or constructors of a partial type in another document they will be merged + // when aggregating semantic edits from all changed documents. Rude edits resulting from those changes, if any, will + // be reported in the document they were made in. + if (!anyInitializerUpdatesInCurrentDocument && !updatesInCurrentDocument.ChangedDeclarations.ContainsKey(newDeclaration)) + { + continue; + } } // To avoid costly SymbolKey resolution we first try to match the constructor in the current document // and special case parameter-less constructor. + + // In the case of records, newDeclaration will point to the record declaration, and hence this + // actually finds the old record declaration, but that is actually sufficient for our needs, as all + // we're using it for is detecting an update, and any changes to the standard record constructors must + // be an update by definition. if (topMatch.TryGetOldNode(newDeclaration, out var oldDeclaration)) { Contract.ThrowIfNull(oldModel); @@ -3059,7 +3207,7 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) // Report an error if the updated constructor's declaration is in the current document // and its body edit is disallowed (e.g. contains stackalloc). - if (oldCtor != null && newDeclaration?.SyntaxTree == newSyntaxTree && anyInitializerUpdatesInCurrentDocument) + if (oldCtor != null && newDeclaration.SyntaxTree == newSyntaxTree && anyInitializerUpdatesInCurrentDocument) { // attribute rude edit to one of the modified members var firstSpan = updatesInCurrentDocument.ChangedDeclarations.Keys.Where(IsDeclarationWithInitializer).Aggregate( @@ -3069,18 +3217,62 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) Contract.ThrowIfTrue(firstSpan.IsEmpty); ReportMemberUpdateRudeEdits(diagnostics, newDeclaration, firstSpan); } + + // When explicitly implementing the copy constructor of a record the parameter name must match for symbol matching to work + // TODO: Remove this requirement with https://github.com/dotnet/roslyn/issues/52563 + if (oldCtor != null && + !IsRecordDeclaration(newDeclaration) && + oldCtor.DeclaringSyntaxReferences.Length == 0 && + newCtor.Parameters.Length == 1 && + newType.IsRecord && + oldCtor.GetParameters().First().Name != newCtor.GetParameters().First().Name) + { + diagnostics.Add(new RudeEditDiagnostic( + RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, + GetDiagnosticSpan(newDeclaration, EditKind.Update), + arguments: new[] { + oldCtor.ToDisplayString(SymbolDisplayFormats.NameFormat) + })); + continue; + } } else { - // New constructor is implicitly declared so it must be parameterless. - // - // Instance constructor: - // Its presence indicates there are no other instance constructors in the new type and therefore - // there must be a single parameterless instance constructor in the old type (constructors with parameters can't be removed). - // - // Static constructor: - // Static constructor is always parameterless and not implicitly generated if there are no static initializers. - oldCtor = TryGetParameterlessConstructor(oldType, isStatic); + if (newCtor.Parameters.Length == 1) + { + // New constructor is implicitly declared with a parameter, so its the copy constructor of a record + Debug.Assert(oldType.IsRecord); + Debug.Assert(newType.IsRecord); + + // We only need an edit for this if the number of properties or fields on the record has changed. Changes to + // initializers, or whether the property is part of the primary constructor, will still come through this code + // path because they need an edit to the other constructor, but not the copy construcor. + if (oldType.GetMembers().OfType().Count() == newType.GetMembers().OfType().Count() && + oldType.GetMembers().OfType().Count() == newType.GetMembers().OfType().Count()) + { + continue; + } + + oldCtor = oldType.InstanceConstructors.Single(c => c.Parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(c.Parameters[0].Type, c.ContainingType)); + // The copy constructor does not have a syntax map + syntaxMapToUse = null; + // Since there is no syntax map, we don't need to handle anything special to merge them for partial types. + // The easiest way to do this is just to pretend this isn't a partial edit. + isPartialEdit = false; + } + else + { + // New constructor is implicitly declared so it must be parameterless. + // + // Instance constructor: + // Its presence indicates there are no other instance constructors in the new type and therefore + // there must be a single parameterless instance constructor in the old type (constructors with parameters can't be removed). + // + // Static constructor: + // Static constructor is always parameterless and not implicitly generated if there are no static initializers. + oldCtor = TryGetParameterlessConstructor(oldType, isStatic); + } + Contract.ThrowIfFalse(isStatic || oldCtor != null); } @@ -3089,7 +3281,7 @@ static bool IsNotInDocument(SyntaxReference reference, SyntaxTree syntaxTree) semanticEdits.Add(new SemanticEditInfo( SemanticEditKind.Update, newCtorKey, - aggregateSyntaxMap, + syntaxMapToUse, syntaxMapTree: isPartialEdit ? newSyntaxTree : null, partialType: isPartialEdit ? SymbolKey.Create(newType, cancellationToken) : null)); } diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs index f8758d63b9399..b85a9329191ce 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueDiagnosticDescriptors.cs @@ -145,6 +145,12 @@ void AddGeneralDiagnostic(EditAndContinueErrorCode code, string resourceName, Di AddRudeEdit(RudeEditKind.SourceFileTooBig, nameof(FeaturesResources.Modifying_source_file_will_prevent_the_debug_session_from_continuing_because_the_file_is_too_big)); AddRudeEdit(RudeEditKind.InsertIntoGenericType, nameof(FeaturesResources.Adding_0_into_a_generic_type_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ImplementRecordParameterAsReadOnly, nameof(FeaturesResources.Implementing_a_record_positional_parameter_0_as_read_only_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ImplementRecordParameterWithSet, nameof(FeaturesResources.Implementing_a_record_positional_parameter_0_with_a_set_accessor_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.AddRecordPositionalParameter, nameof(FeaturesResources.Adding_a_positional_parameter_to_a_record_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.DeleteRecordPositionalParameter, nameof(FeaturesResources.Deleting_a_positional_parameter_from_a_record_will_prevent_the_debug_session_from_continuing)); + AddRudeEdit(RudeEditKind.ExplicitRecordMethodParameterNamesMustMatch, nameof(FeaturesResources.Explicitly_implemented_methods_of_records_must_have_parameter_names_that_match_the_compiler_generated_equivalent_0)); + // VB specific AddRudeEdit(RudeEditKind.HandlesClauseUpdate, nameof(FeaturesResources.Updating_the_Handles_clause_of_0_will_prevent_the_debug_session_from_continuing)); AddRudeEdit(RudeEditKind.ImplementsClauseUpdate, nameof(FeaturesResources.Updating_the_Implements_clause_of_a_0_will_prevent_the_debug_session_from_continuing)); diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs index c8a5d18c294ad..297456012e4ab 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditKind.cs @@ -117,5 +117,11 @@ internal enum RudeEditKind : ushort SourceFileTooBig = 89, MemberBodyTooBig = 90, InsertIntoGenericType = 91, + + ImplementRecordParameterAsReadOnly = 92, + ImplementRecordParameterWithSet = 93, + AddRecordPositionalParameter = 94, + DeleteRecordPositionalParameter = 95, + ExplicitRecordMethodParameterNamesMustMatch = 96 } } diff --git a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs index ac50ecc194331..9f7a60a523640 100644 --- a/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs +++ b/src/Features/Core/Portable/EditAndContinue/SemanticEditInfo.cs @@ -24,6 +24,10 @@ internal readonly struct SemanticEditInfo /// public SymbolKey Symbol { get; } + /// + /// The syntax map for nodes in the tree for this edit, which will be merged with other maps from other trees for this type. + /// Only available when is not null. + /// public Func? SyntaxMap { get; } /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 5a8e15f7e424c..15be011d73e46 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2840,4 +2840,19 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Convert to record + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + + Adding a positional parameter to a record will prevent the debug session from continuing. + + + Deleting a positional parameter from a record will prevent the debug session from continuing. + + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + \ No newline at end of file diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 3676f4c6da9db..cc68e91d98834 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -105,6 +105,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Přidání prvku {0} do rozhraní zabrání v pokračování relace ladění. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Přidání metody s explicitním specifikátorem rozhraní zabrání v pokračování relace ladění. @@ -305,6 +310,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Vytvořit a přiřadit zbývající položky jako vlastnosti + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Neměňte tento kód. Kód pro vyčištění vložte do metody {0}. @@ -360,6 +370,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Příklady: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Extrahovat základní třídu... @@ -495,6 +510,16 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Implementovat přes {0} + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Neúplné uvození znaků \p{X} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index 43d654a8f85a7..922bd8a12bbd7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -105,6 +105,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Das Hinzufügen von "{0}" zu einer Schnittstelle verhindert das Fortsetzen der Debugsitzung. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Das Hinzufügen einer Methode mit einem expliziten Schnittstellenbezeichner verhindert das Fortsetzen der Debugsitzung. @@ -305,6 +310,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Verbleibende Elemente als Eigenschaften erstellen und zuweisen + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Ändern Sie diesen Code nicht. Fügen Sie Bereinigungscode in der Methode "{0}" ein. @@ -360,6 +370,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Beispiele: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Basisklasse extrahieren... @@ -495,6 +510,16 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d Über "{0}" implementieren + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Unvollständiges \p{X}-Escapezeichen. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index ccdae4720dfb6..3fb1679bc0cff 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -105,6 +105,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Agregar '{0}' en una interfaz impedirá que continúe la sesión de depuración. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Agregar un método con un especificador de interfaz explícita evitará que la sesión de depuración continúe. @@ -305,6 +310,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Crear y asignar el resto como propiedades + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method No cambie este código. Coloque el código de limpieza en el método "{0}". @@ -360,6 +370,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Ejemplos: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Extraer clase base... @@ -495,6 +510,16 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Implementar a través de "{0}" + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Escape de carácter incompleto \p{X} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index fa306e3947455..b6a370422253d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -105,6 +105,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai L'ajout de '{0}' à une interface empêche la session de débogage de se poursuivre. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. L'ajout d'une méthode avec un spécificateur d'interface explicite empêche la session de débogage de continuer. @@ -305,6 +310,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Créer et affecter ce qui reste en tant que propriétés + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Ne changez pas ce code. Placez le code de nettoyage dans la méthode '{0}' @@ -360,6 +370,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Exemples : Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Extraire la classe de base... @@ -495,6 +510,16 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Implémenter via '{0}' + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Caractère d'échappement \p{X} incomplet diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 0b536a2a25cd9..0665349c39b8d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -105,6 +105,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Se si aggiunge '{0}' in un'interfaccia, la sessione di debug non potrà continuare. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Se si aggiunge un metodo con identificatore di interfaccia esplicita, la sessione di debug non potrà continuare. @@ -305,6 +310,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Crea e assegna rimanenti come proprietà + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Non modificare questo codice. Inserire il codice di pulizia nel metodo '{0}' @@ -360,6 +370,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Esempi: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Estrai classe di base... @@ -495,6 +510,16 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Implementa tramite '{0}' + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Sequenza di caratteri di escape \p{X} incompleta diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 8b5db864298a7..a40344b670a01 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -105,6 +105,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' をインターフェイスに追加すると、デバッグ セッションは続行されません。 + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 明示的なインターフェイス指定子が含まれるメソッドを追加すると、デバッグ セッションを続行できなくなります。 @@ -305,6 +310,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 残りをプロパティとして作成して割り当てる + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method このコードを変更しないでください。クリーンアップ コードを '{0}' メソッドに記述します @@ -360,6 +370,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 例: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... 基底クラスの抽出... @@ -495,6 +510,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}' を通じて実装します + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape 不完全な \p{X} 文字エスケープです diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 8fc98c488b435..0605ebddedcd9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -105,6 +105,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 인터페이스에 '{0}'을(를) 추가하면 디버그 세션을 계속할 수 없습니다. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 명시적 인터페이스 지정자를 사용하여 메서드를 추가하면 디버그 세션을 계속할 수 없습니다. @@ -305,6 +310,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 나머지를 만들고 속성으로 할당합니다. + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method 이 코드를 변경하지 마세요. '{0}' 메서드에 정리 코드를 입력합니다. @@ -360,6 +370,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 예: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... 기본 클래스 추출... @@ -495,6 +510,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) 통해 구현 + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape 불완전한 \p{X} 문자 이스케이프 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index c21b2d32b3247..c37875eacce17 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -105,6 +105,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Dodanie elementu „{0}” do interfejsu uniemożliwi kontynuowanie sesji debugowania. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Dodanie metody z jawnym specyfikatorem interfejsu uniemożliwi kontynuowanie sesji debugowania. @@ -305,6 +310,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Utwórz i przypisz pozostałe jako właściwości + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Nie zmieniaj tego kodu. Umieść kod czyszczący w metodzie „{0}”. @@ -360,6 +370,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Przykłady: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Wyodrębnij klasę bazową... @@ -495,6 +510,16 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Implementuj za pomocą elementu „{0}” + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Niekompletna sekwencja ucieczki znaku \p{X} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index bb8dba590143d..7152bac06dc28 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -105,6 +105,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Adicionar '{0}' a uma interface impedirá que a sessão de depuração continue. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Adicionar um método com um especificador explícito da interface impedirá a sessão de depuração de continuar. @@ -305,6 +310,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Criar e atribuir os restantes como propriedades + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Não altere este código. Coloque o código de limpeza no método '{0}' @@ -360,6 +370,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Exemplos: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Extrair a classe base... @@ -495,6 +510,16 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Implementar por meio de '{0}' + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Escape de caractere incompleto \p{X} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 274ccab78e18b..fd99539c61bb7 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -105,6 +105,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Добавление "{0}" в интерфейс сделает продолжение сеанса отладки невозможным. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. При добавлении метода с явным спецификатором интерфейса вы не сможете продолжить сеанс отладки. @@ -305,6 +310,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Создать и назначить оставшиеся как свойства + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Не изменяйте этот код. Разместите код очистки в методе "{0}". @@ -360,6 +370,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Примеры: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Извлечь базовый класс... @@ -495,6 +510,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Реализовать через "{0}" + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Незавершенная escape-последовательность \p{X} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index a99efc879c47c..52f39dbc7753d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -105,6 +105,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Arabirime '{0}' eklenmesi, hata ayıklama oturumunun devam etmesini engeller. + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. Bir yöntem ile bir açık arabirim belirleyici ekleme hata ayıklama oturumu devam etmesini engeller. @@ -305,6 +310,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Kalanları özellik olarak oluştur ve ata + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method Bu kodu değiştirmeyin. Temizleme kodunu '{0}' metodunun içine yerleştirin. @@ -360,6 +370,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be Örnekler: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... Temel sınıfı ayıkla... @@ -495,6 +510,16 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' aracılığıyla uygula + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape Tamamlanmamış \p{X} karakter kaçış diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index a8f054401a3d2..906c6650eeb49 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -105,6 +105,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 将 "{0}" 添加进接口将阻止调试会话继续。 + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 添加具有显式接口说明符的方法将阻止调试会话继续。 @@ -305,6 +310,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 创建并分配其余属性 + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method 不要更改此代码。请将清理代码放入“{0}”方法中 @@ -360,6 +370,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 示例: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... 提取基类... @@ -495,6 +510,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 通过“{0}”实现 + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape \p{X} 字符转义不完整 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 141787394ee24..7be1353dd9beb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -105,6 +105,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 將 '{0}' 新增至介面,會造成偵錯工作階段無法繼續。 + + Adding a positional parameter to a record will prevent the debug session from continuing. + Adding a positional parameter to a record will prevent the debug session from continuing. + + Adding a method with an explicit interface specifier will prevent the debug session from continuing. 新增具有明確介面指定名稱的方法會導致偵錯工作階段無法繼續。 @@ -305,6 +310,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 建立其餘項目並將其指派為屬性 + + Deleting a positional parameter from a record will prevent the debug session from continuing. + Deleting a positional parameter from a record will prevent the debug session from continuing. + + Do not change this code. Put cleanup code in '{0}' method 請勿變更此程式碼。請將清除程式碼放入 '{0}' 方法 @@ -360,6 +370,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 範例: Plural form when we have multiple examples to show. + + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + Explicitly implemented methods of records must have parameter names that match the compiler generated equivalent '{0}' + + Extract base class... 擷取基底類別... @@ -495,6 +510,16 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 透過 '{0}' 實作 + + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + Implementing a record positional parameter '{0}' as read only will prevent the debug session from continuing, + + + + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + Implementing a record positional parameter '{0}' with a set accessor will prevent the debug session from continuing. + + Incomplete \p{X} character escape 不完整的 \p{X} 字元逸出 diff --git a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb index 265bcbeb60281..e807e4ffe1e0f 100644 --- a/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb +++ b/src/Features/VisualBasic/Portable/EditAndContinue/VisualBasicEditAndContinueAnalyzer.vb @@ -946,6 +946,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue Return node.IsKind(SyntaxKind.InterfaceBlock) End Function + Friend Overrides Function IsRecordDeclaration(node As SyntaxNode) As Boolean + ' No records in VB + Return False + End Function + Friend Overrides Function TryGetContainingTypeDeclaration(node As SyntaxNode) As SyntaxNode Return node.Parent.FirstAncestorOrSelf(Of TypeBlockSyntax)() ' TODO: EnbumBlock? End Function @@ -987,6 +992,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.EditAndContinue End Select End Function + Friend Overrides Function IsRecordPrimaryConstructorParameter(declaration As SyntaxNode) As Boolean + Return False + End Function + + Friend Overrides Function IsPropertyAccessorDeclarationMatchingPrimaryConstructorParameter(declaration As SyntaxNode, newContainingType As INamedTypeSymbol, ByRef isFirstAccessor As Boolean) As Boolean + Return False + End Function + Private Shared Function GetInitializerExpression(equalsValue As EqualsValueSyntax, asClause As AsClauseSyntax) As ExpressionSyntax If equalsValue IsNot Nothing Then Return equalsValue.Value From 767de77bac3f67ea7520f0a1bde05508c7640df9 Mon Sep 17 00:00:00 2001 From: David Barbet Date: Wed, 14 Apr 2021 17:55:35 -0700 Subject: [PATCH 182/220] Make image catalog private --- .../Core/Microsoft.CodeAnalysis.EditorFeatures.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index 57a5ebbdd94bb..eb81b2f68e22e 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -31,7 +31,7 @@ - + From e2afdb6c242d83d2f11d3d3646040b5e55fc18f5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 22:20:07 -0700 Subject: [PATCH 183/220] Remove unnecessary check --- .../NavigationBarController_ModelComputation.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 713ec9a4fd450..8e55c5a05a186 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -164,11 +164,6 @@ private async Task DetermineSelectedItemInfoAsync( await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // After we switch to the main thread, we may discover the previous work on the main thread canceled us and - // enqueued another task to determine the selected item. Bail out and let that task proceed. - if (cancellationToken.IsCancellationRequested) - return; - AssertIsForeground(); // Update the UI to show *just* the type/member that was selected. We don't need it to know about all items From 8f27533a45e48fb9f7220bc8ff3f1f35bc80dd73 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 22:22:12 -0700 Subject: [PATCH 184/220] report errors --- .../NavigationBarController_ModelComputation.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs index 8e55c5a05a186..a79f61b9fef6a 100644 --- a/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs +++ b/src/EditorFeatures/Core/Implementation/NavigationBar/NavigationBarController_ModelComputation.cs @@ -11,7 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; -using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -68,6 +68,9 @@ private static async Task ComputeModelAfterDelayAsync( catch (OperationCanceledException) { } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } } // If we canceled, then just return along whatever we have computed so far. Note: this means the From 9a9bd4b16110f1a389943f837ceb939f1257c793 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 22:23:00 -0700 Subject: [PATCH 185/220] Change obsolete message --- .../Core/Extensibility/NavigationBar/NavigationBarItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs index 068a165972daf..4f94084460cc6 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/NavigationBarItem.cs @@ -25,7 +25,7 @@ internal abstract class NavigationBarItem internal ImmutableArray TrackingSpans { get; set; } = ImmutableArray.Empty; // Legacy constructor for TypeScript. - [Obsolete("Use the constructor that uses ImmutableArray")] + [Obsolete("This is a compatibility shim for TypeScript; please do not use it.")] public NavigationBarItem(string text, Glyph glyph, IList spans, IList? childItems = null, int indent = 0, bool bolded = false, bool grayed = false) : this(text, glyph, spans.ToImmutableArrayOrEmpty(), childItems.ToImmutableArrayOrEmpty(), indent, bolded, grayed) { From 00186ed03adae3b8e24de5932725cd570d5ad1c3 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 22:52:39 -0700 Subject: [PATCH 186/220] get rid of async --- .../AbstractEditorNavigationBarItemService.cs | 21 +++++++++---------- .../MockSymbolNavigationService.vb | 5 +++-- .../MockSymbolNavigationServiceProvider.vb | 9 ++++---- ...NavigationBarItemService_CodeGeneration.vb | 2 +- .../DefaultSymbolNavigationService.cs | 13 ++++++------ .../Navigation/ISymbolNavigationService.cs | 10 ++++----- .../Progression/GraphNavigatorExtension.cs | 14 ++++++------- .../VisualStudioSymbolNavigationService.cs | 13 ++++++------ 8 files changed, 45 insertions(+), 42 deletions(-) diff --git a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs index acb60a4e97bc1..85ce557157f08 100644 --- a/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs +++ b/src/EditorFeatures/Core/Extensibility/NavigationBar/AbstractEditorNavigationBarItemService.cs @@ -16,12 +16,12 @@ namespace Microsoft.CodeAnalysis.Editor.Extensibility.NavigationBar { - internal abstract class AbstractEditorNavigationBarItemService : INavigationBarItemService2 + internal abstract class AbstractEditorNavigationBarItemService : ForegroundThreadAffinitizedObject, INavigationBarItemService2 { - protected readonly IThreadingContext ThreadingContext; - protected AbstractEditorNavigationBarItemService(IThreadingContext threadingContext) - => ThreadingContext = threadingContext; + : base(threadingContext, assertIsForeground: false) + { + } protected abstract Task GetSymbolNavigationPointAsync(Document document, ISymbol symbol, CancellationToken cancellationToken); protected abstract Task NavigateToItemAsync(Document document, WrappedNavigationBarItem item, ITextView textView, CancellationToken cancellationToken); @@ -53,9 +53,9 @@ protected async Task NavigateToSymbolItemAsync( // Do not allow third party navigation to types or constructors if (symbol != null && - !(symbol is ITypeSymbol) && + symbol is not ITypeSymbol && !symbol.IsConstructor() && - symbolNavigationService.TrySymbolNavigationNotify(symbol, document.Project, cancellationToken)) + await symbolNavigationService.TrySymbolNavigationNotifyAsync(symbol, document.Project, cancellationToken).ConfigureAwait(false)) { return; } @@ -63,19 +63,18 @@ protected async Task NavigateToSymbolItemAsync( var navigationPoint = await this.GetSymbolItemNavigationPointAsync(document, item, cancellationToken).ConfigureAwait(false); if (navigationPoint.HasValue) { - await NavigateToVirtualTreePointAsync(document.Project.Solution, navigationPoint.Value, cancellationToken).ConfigureAwait(false); + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + NavigateToVirtualTreePoint(document.Project.Solution, navigationPoint.Value, cancellationToken); } } - protected async Task NavigateToVirtualTreePointAsync(Solution solution, VirtualTreePoint navigationPoint, CancellationToken cancellationToken) + protected void NavigateToVirtualTreePoint(Solution solution, VirtualTreePoint navigationPoint, CancellationToken cancellationToken) { + this.AssertIsForeground(); var documentToNavigate = solution.GetRequiredDocument(navigationPoint.Tree); var workspace = solution.Workspace; var navigationService = workspace.Services.GetRequiredService(); - // Have to move back to UI thread in order to navigate. - await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - if (navigationService.CanNavigateToPosition(workspace, documentToNavigate.Id, navigationPoint.Position, navigationPoint.VirtualSpaces, cancellationToken)) { navigationService.TryNavigateToPosition(workspace, documentToNavigate.Id, navigationPoint.Position, navigationPoint.VirtualSpaces, options: null, cancellationToken); diff --git a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb index cbe98430b3127..392863ffccfb8 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/GoToHelpers/MockSymbolNavigationService.vb @@ -6,6 +6,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.FindUsages Imports Microsoft.CodeAnalysis.Navigation Imports Microsoft.CodeAnalysis.Options +Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Friend Class MockSymbolNavigationService @@ -20,9 +21,9 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers Return True End Function - Public Function TrySymbolNavigationNotify(symbol As ISymbol, project As Project, cancellationToken As CancellationToken) As Boolean Implements ISymbolNavigationService.TrySymbolNavigationNotify + Public Function TrySymbolNavigationNotifyAsync(symbol As ISymbol, project As Project, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TrySymbolNavigationNotifyAsync _triedSymbolNavigationNotify = True - Return True + Return SpecializedTasks.True End Function Public Function WouldNavigateToSymbol(definitionItem As DefinitionItem, solution As Solution, cancellationToken As CancellationToken, ByRef filePath As String, ByRef lineNumber As Integer, ByRef charOffset As Integer) As Boolean Implements ISymbolNavigationService.WouldNavigateToSymbol diff --git a/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb b/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb index ec1aa30fe1fc6..631dfb334c914 100644 --- a/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb +++ b/src/EditorFeatures/TestUtilities2/Utilities/MockSymbolNavigationServiceProvider.vb @@ -53,13 +53,14 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Utilities Return True End Function - Public Function TrySymbolNavigationNotify(symbol As ISymbol, - project As Project, - cancellationToken As CancellationToken) As Boolean Implements ISymbolNavigationService.TrySymbolNavigationNotify + Public Function TrySymbolNavigationNotifyAsync( + symbol As ISymbol, + project As Project, + cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISymbolNavigationService.TrySymbolNavigationNotifyAsync Me.TrySymbolNavigationNotifyProvidedSymbol = symbol Me.TrySymbolNavigationNotifyProvidedProject = project - Return TrySymbolNavigationNotifyReturnValue + Return Task.FromResult(TrySymbolNavigationNotifyReturnValue) End Function Public Function WouldNavigateToSymbol(definitionItem As DefinitionItem, diff --git a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb index f7c2f4b27f487..3eb7869c2e1a1 100644 --- a/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb +++ b/src/EditorFeatures/VisualBasic/NavigationBar/VisualBasicEditorNavigationBarItemService_CodeGeneration.vb @@ -38,7 +38,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.NavigationBar Using transaction = New CaretPreservingEditTransaction(VBEditorResources.Generate_Member, textView, _textUndoHistoryRegistry, _editorOperationsFactoryService) newDocument.Project.Solution.Workspace.ApplyDocumentChanges(newDocument, cancellationToken) - Await NavigateToVirtualTreePointAsync(newDocument.Project.Solution, navigationPoint, cancellationToken).ConfigureAwait(True) + NavigateToVirtualTreePoint(newDocument.Project.Solution, navigationPoint, cancellationToken) transaction.Complete() End Using diff --git a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs index 5e200c64f7bd0..e83f04659ab10 100644 --- a/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/DefaultSymbolNavigationService.cs @@ -2,25 +2,26 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Options; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Navigation { internal class DefaultSymbolNavigationService : ISymbolNavigationService { - public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options = null, CancellationToken cancellationToken = default) + public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet? options = null, CancellationToken cancellationToken = default) => false; - public bool TrySymbolNavigationNotify(ISymbol symbol, Project project, CancellationToken cancellationToken) - => false; + public Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) + => SpecializedTasks.False; public bool WouldNavigateToSymbol( DefinitionItem definitionItem, Solution solution, CancellationToken cancellationToken, - out string filePath, out int lineNumber, out int charOffset) + [NotNullWhen(true)] out string? filePath, out int lineNumber, out int charOffset) { filePath = null; lineNumber = 0; diff --git a/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs b/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs index 17f7b9d427faa..ad30f8cf4da83 100644 --- a/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs +++ b/src/Features/Core/Portable/Navigation/ISymbolNavigationService.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System.Diagnostics.CodeAnalysis; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; @@ -22,15 +22,15 @@ internal interface ISymbolNavigationService : IWorkspaceService /// A set of options. If these options are not supplied the /// current set of options from the project's workspace will be used. /// The token to check for cancellation - bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options = null, CancellationToken cancellationToken = default); + bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet? options = null, CancellationToken cancellationToken = default); /// True if the navigation was handled, indicating that the caller should not /// perform the navigation. - bool TrySymbolNavigationNotify(ISymbol symbol, Project project, CancellationToken cancellationToken); + Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken); /// True if the navigation would be handled. bool WouldNavigateToSymbol( DefinitionItem definitionItem, Solution solution, CancellationToken cancellationToken, - out string filePath, out int lineNumber, out int charOffset); + [NotNullWhen(true)] out string? filePath, out int lineNumber, out int charOffset); } } diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs index 58af6780b309c..d2c1b6a68e294 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs @@ -70,7 +70,7 @@ public void NavigateTo(GraphObject graphObject) { // If we are already on the UI thread, invoke NavigateOnForegroundThread // directly to preserve any existing NewDocumentStateScope. - NavigateOnForegroundThread(sourceLocation, symbolId, project, document); + NavigateOnForegroundThread(sourceLocation, symbolId, project, document, CancellationToken.None); } else { @@ -82,7 +82,7 @@ public void NavigateTo(GraphObject graphObject) async () => { await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - NavigateOnForegroundThread(sourceLocation, symbolId, project, document); + NavigateOnForegroundThread(sourceLocation, symbolId, project, document, CancellationToken.None); }, CancellationToken.None, TaskScheduler.Default); @@ -92,7 +92,7 @@ public void NavigateTo(GraphObject graphObject) } private void NavigateOnForegroundThread( - SourceLocation sourceLocation, SymbolKey? symbolId, Project project, Document document) + SourceLocation sourceLocation, SymbolKey? symbolId, Project project, Document document, CancellationToken cancellationToken) { AssertIsForeground(); @@ -100,13 +100,13 @@ private void NavigateOnForegroundThread( if (symbolId != null) { var symbolNavigationService = _workspace.Services.GetService(); - var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None)).Symbol; + var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken)).Symbol; // Do not allow third party navigation to types or constructors if (symbol != null && - !(symbol is ITypeSymbol) && + symbol is not ITypeSymbol && !symbol.IsConstructor() && - symbolNavigationService.TrySymbolNavigationNotify(symbol, project, CancellationToken.None)) + symbolNavigationService.TrySymbolNavigationNotifyAsync(symbol, project, cancellationToken).WaitAndGetResult(cancellationToken)) { return; } @@ -129,7 +129,7 @@ private void NavigateOnForegroundThread( document.Id, sourceLocation.StartPosition.Line, sourceLocation.StartPosition.Character, - CancellationToken.None); + cancellationToken); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs index 6bc550c09167c..b930ac585e834 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioSymbolNavigationService.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.DecompiledSource; using Microsoft.CodeAnalysis.Editor; @@ -53,7 +54,7 @@ public VisualStudioSymbolNavigationService( _metadataAsSourceFileService = metadataAsSourceFileService; } - public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet options, CancellationToken cancellationToken) + public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet? options, CancellationToken cancellationToken) { if (project == null || symbol == null) { @@ -172,14 +173,14 @@ public bool TryNavigateToSymbol(ISymbol symbol, Project project, OptionSet optio return true; } - public bool TrySymbolNavigationNotify(ISymbol symbol, Project project, CancellationToken cancellationToken) - => TryNotifyForSpecificSymbol(symbol, project.Solution, cancellationToken); - - private bool TryNotifyForSpecificSymbol( - ISymbol symbol, Solution solution, CancellationToken cancellationToken) + public async Task TrySymbolNavigationNotifyAsync(ISymbol symbol, Project project, CancellationToken cancellationToken) { + await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + AssertIsForeground(); + var solution = project.Solution; + var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); From 0a883bbd110d37af2e35419fe639b134697ce293 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 15 Apr 2021 08:24:43 +0200 Subject: [PATCH 187/220] Update src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs --- .../CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index 4597a52cb5d76..c37ebca5f9d6e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -323,9 +323,7 @@ public bool IsMemberAttributeContext(ISet validTypeDeclarations, Can if (token.Kind() == SyntaxKind.OpenBracketToken && token.Parent.IsKind(SyntaxKind.AttributeList)) { - if (token.Parent.IsParentKind(SyntaxKind.Parameter) && - token.Parent.Parent.IsParentKind(SyntaxKind.ParameterList) && - token.Parent.Parent.Parent.IsParentKind(SyntaxKind.RecordDeclaration)) + if (token.Parent.Parent is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } } ) { return true; } From ac65a9d329cf5c815a31fa878b867a70bd7fb958 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 15 Apr 2021 08:39:00 +0200 Subject: [PATCH 188/220] Update src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs Co-authored-by: CyrusNajmabadi --- .../CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index c37ebca5f9d6e..356ea154539da 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -323,7 +323,7 @@ public bool IsMemberAttributeContext(ISet validTypeDeclarations, Can if (token.Kind() == SyntaxKind.OpenBracketToken && token.Parent.IsKind(SyntaxKind.AttributeList)) { - if (token.Parent.Parent is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } } ) + if (token.Parent.Parent is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } } ) { return true; } From 0255d3ed913886ed1dd43c7ff7f1f8bb01c4fe3f Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 15 Apr 2021 08:46:03 +0200 Subject: [PATCH 189/220] Extract helper --- .../NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs | 9 ++++++--- .../AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs | 10 +--------- .../Core/Extensions/IMethodSymbolExtensions.cs | 8 ++++++++ 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs b/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs index 5a65e4795b844..3a6e82d88639d 100644 --- a/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs +++ b/src/Analyzers/Core/Analyzers/NamingStyle/NamingStyleDiagnosticAnalyzerBase.cs @@ -108,9 +108,12 @@ void SyntaxNodeAction(SyntaxNodeAnalysisContext syntaxContext) ConcurrentDictionary> idToCachedResult, CancellationToken cancellationToken) { - if (string.IsNullOrEmpty(symbol.Name) || - // Heuristic for recognizing entry points. - symbol.IsStatic && symbol.Name == WellKnownMemberNames.EntryPointMethodName) + if (string.IsNullOrEmpty(symbol.Name)) + { + return null; + } + + if (symbol is IMethodSymbol methodSymbol && methodSymbol.IsEntryPoint(compilation.TaskType(), compilation.TaskOfTType())) { return null; } diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs index 3d3661ab75e23..791c62728bf4a 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs @@ -633,7 +633,7 @@ private bool IsCandidateSymbol(ISymbol memberSymbol) } // Do not flag unused entry point (Main) method. - if (IsEntryPoint(methodSymbol)) + if (methodSymbol.IsEntryPoint(_taskType, _genericTaskType)) { return false; } @@ -699,14 +699,6 @@ private bool IsCandidateSymbol(ISymbol memberSymbol) return false; } - private bool IsEntryPoint(IMethodSymbol methodSymbol) - => methodSymbol.Name is WellKnownMemberNames.EntryPointMethodName or WellKnownMemberNames.TopLevelStatementsEntryPointMethodName && - methodSymbol.IsStatic && - (methodSymbol.ReturnsVoid || - methodSymbol.ReturnType.SpecialType == SpecialType.System_Int32 || - methodSymbol.ReturnType.OriginalDefinition.Equals(_taskType) || - methodSymbol.ReturnType.OriginalDefinition.Equals(_genericTaskType)); - private bool IsMethodWithSpecialAttribute(IMethodSymbol methodSymbol) => methodSymbol.GetAttributes().Any(a => _attributeSetForMethodsToIgnore.Contains(a.AttributeClass)); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs index 3b0d62642c9a9..34d3c61dd7f1a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/IMethodSymbolExtensions.cs @@ -73,5 +73,13 @@ public static PredefinedOperator GetPredefinedOperator(this IMethodSymbol symbol "op_Subtraction" or "op_UnaryNegation" => PredefinedOperator.Subtraction, _ => PredefinedOperator.None, }; + + public static bool IsEntryPoint(this IMethodSymbol methodSymbol, INamedTypeSymbol? taskType, INamedTypeSymbol? genericTaskType) + => methodSymbol.Name is WellKnownMemberNames.EntryPointMethodName or WellKnownMemberNames.TopLevelStatementsEntryPointMethodName && + methodSymbol.IsStatic && + (methodSymbol.ReturnsVoid || + methodSymbol.ReturnType.SpecialType == SpecialType.System_Int32 || + methodSymbol.ReturnType.OriginalDefinition.Equals(taskType) || + methodSymbol.ReturnType.OriginalDefinition.Equals(genericTaskType)); } } From 81a281d3abe32b3b4e2816df3f64d985f6aefda4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 14 Apr 2021 23:49:36 -0700 Subject: [PATCH 190/220] Add CT --- .../Def/Implementation/Progression/GraphNavigatorExtension.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs index d2c1b6a68e294..4f8626212699e 100644 --- a/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs +++ b/src/VisualStudio/Core/Def/Implementation/Progression/GraphNavigatorExtension.cs @@ -100,7 +100,7 @@ private void NavigateOnForegroundThread( if (symbolId != null) { var symbolNavigationService = _workspace.Services.GetService(); - var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken)).Symbol; + var symbol = symbolId.Value.Resolve(project.GetCompilationAsync(cancellationToken).WaitAndGetResult(cancellationToken), cancellationToken: cancellationToken).Symbol; // Do not allow third party navigation to types or constructors if (symbol != null && @@ -136,7 +136,6 @@ symbol is not ITypeSymbol && public int GetRank(GraphObject graphObject) { - if (graphObject is GraphNode graphNode) { var sourceLocation = graphNode.GetValue(CodeNodeProperties.SourceLocation); From 90ff6db2c2f44a1c2501cd71440d9115439360e6 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 15 Apr 2021 13:36:35 +0200 Subject: [PATCH 191/220] Fix FAR for less than operator and add tests for all operators --- .../FindReferencesTests.OperatorSymbols.vb | 132 ++++++++++++++++++ .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 3 + 2 files changed, 135 insertions(+) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb index be74e04754d36..312d4512224f6 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.OperatorSymbols.vb @@ -107,6 +107,138 @@ class A Await TestAPIAndFeature(input, kind, host) End Function + + + Public Async Function TestCSharpFindReferencesOnEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + throw new System.NotImplementedException(); + public static bool operator !=(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnNotEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + throw new System.NotImplementedException(); + public static bool operator {|Definition:$$!=|}(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnGreaterThanOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + |] new A(); + } + public static bool operator {|Definition:$$>|}(A left, A right) => throw new System.NotImplementedException(); + public static bool operator <(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnLessThanOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + (A left, A right) => throw new System.NotImplementedException(); + public static bool operator {|Definition:$$<|}(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnGreaterThanOrEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + =|] new A(); + } + public static bool operator {|Definition:$$>=|}(A left, A right) => throw new System.NotImplementedException(); + public static bool operator <=(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + + + + Public Async Function TestCSharpFindReferencesOnLessThanOrEqualsOperator(kind As TestKind, host As TestHost) As Task + Dim input = + + + =(A left, A right) => throw new System.NotImplementedException(); + public static bool operator {|Definition:$$<=|}(A left, A right) => throw new System.NotImplementedException(); +}]]> + + + + Await TestAPIAndFeature(input, kind, host) + End Function + Public Async Function TestVisualBasicFindReferencesOnUnaryOperatorOverload(kind As TestKind, host As TestHost) As Task Dim input = diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index abf316d7a1e17..8535d8254b282 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -393,6 +393,9 @@ private static PredefinedOperator GetPredefinedOperator(SyntaxToken token) case SyntaxKind.LessThanLessThanEqualsToken: return PredefinedOperator.LeftShift; + case SyntaxKind.LessThanToken: + return PredefinedOperator.LessThan; + case SyntaxKind.LessThanEqualsToken: return PredefinedOperator.LessThanOrEqual; From a23bc8d45b07da69e8ab630fcdddc24c7ace6d8b Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 15 Apr 2021 14:55:16 +0200 Subject: [PATCH 192/220] Fix failing test --- .../CSharp/Impl/LanguageService/CSharpHelpContextService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs index eb480e1993f1b..580f10ef44911 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs @@ -216,7 +216,7 @@ private static bool TryGetTextForOperator(SyntaxToken token, Document document, } var syntaxFacts = document.GetLanguageService(); - if (syntaxFacts.IsOperator(token) || syntaxFacts.IsPredefinedOperator(token) || SyntaxFacts.IsAssignmentExpressionOperatorToken(token.Kind())) + if (syntaxFacts.IsOperator(token) || SyntaxFacts.IsAssignmentExpressionOperatorToken(token.Kind())) { text = Keyword(syntaxFacts.GetText(token.RawKind)); return true; From e0ebe2884d947dac2b4a2ed71d5e445ff33b4096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 15 Apr 2021 09:17:42 -0700 Subject: [PATCH 193/220] Implement ManagedHotReloadLanguageService (#52640) --- .../EmitSolutionUpdateResultsTests.cs | 91 +++++++++++ .../RemoteEditAndContinueServiceTests.cs | 2 +- .../EditAndContinueWorkspaceService.cs | 2 +- .../EmitSolutionUpdateResults.cs | 98 ++++++++++- .../IManagedHotReloadLanguageService.cs | 82 ++++++++++ .../Remote/IRemoteEditAndContinueService.cs | 4 +- .../RemoteEditAndContinueServiceProxy.cs | 21 ++- .../EditAndContinue/RudeEditDiagnostic.cs | 23 ++- .../ManagedEditAndContinueLanguageService.cs | 3 +- .../ManagedHotReloadLanguageService.cs | 152 ++++++++++++++++++ .../AbstractTableEntriesSnapshot.cs | 28 ---- ...DiagnosticListTable.LiveTableDataSource.cs | 2 +- .../VisualStudioBaseTodoListTable.cs | 3 +- ...iagnosticListTable.BuildTableDataSource.cs | 2 +- .../Diagnostics/DiagnosticDataLocation.cs | 40 +++++ .../RemoteEditAndContinueService.cs | 6 +- 16 files changed, 503 insertions(+), 56 deletions(-) create mode 100644 src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs create mode 100644 src/Features/Core/Portable/EditAndContinue/HotReload/IManagedHotReloadLanguageService.cs create mode 100644 src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs diff --git a/src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs b/src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs new file mode 100644 index 0000000000000..497182e37ea8e --- /dev/null +++ b/src/EditorFeatures/Test/EditAndContinue/EmitSolutionUpdateResultsTests.cs @@ -0,0 +1,91 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests +{ + [UseExportProvider] + public class EmitSolutionUpdateResultsTests + { + [Fact] + public async Task GetHotReloadDiagnostics() + { + using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); + + var sourcePath = Path.Combine(TempRoot.Root, "x", "a.cs"); + var razorPath = Path.Combine(TempRoot.Root, "a.razor"); + + var document = workspace.CurrentSolution. + AddProject("proj", "proj", LanguageNames.CSharp). + WithMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Standard)). + AddDocument(sourcePath, SourceText.From("class C {}", Encoding.UTF8), filePath: Path.Combine(TempRoot.Root, sourcePath)); + + var solution = document.Project.Solution; + + var diagnosticData = ImmutableArray.Create( + new DiagnosticData( + id: "CS0001", + category: "Test", + message: "warning", + enuMessageForBingSearch: "test2 message format", + severity: DiagnosticSeverity.Warning, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + warningLevel: 0, + customTags: ImmutableArray.Create("Test2"), + properties: ImmutableDictionary.Empty, + document.Project.Id, + new DiagnosticDataLocation(document.Id, new TextSpan(1, 2), "a.cs", 0, 0, 0, 5, "a.razor", 10, 10, 10, 15), + language: "C#", + title: "title", + description: "description", + helpLink: "http://link"), + new DiagnosticData( + id: "CS0012", + category: "Test", + message: "error", + enuMessageForBingSearch: "test2 message format", + severity: DiagnosticSeverity.Error, + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true, + warningLevel: 0, + customTags: ImmutableArray.Create("Test2"), + properties: ImmutableDictionary.Empty, + document.Project.Id, + new DiagnosticDataLocation(document.Id, new TextSpan(1, 2), originalFilePath: sourcePath, 0, 0, 0, 5, mappedFilePath: @"..\a.razor", 10, 10, 10, 15), + language: "C#", + title: "title", + description: "description", + helpLink: "http://link")); + + var rudeEdits = ImmutableArray.Create( + (document.Id, ImmutableArray.Create(new RudeEditDiagnostic(RudeEditKind.Insert, TextSpan.FromBounds(1, 10), 123, new[] { "a" }))), + (document.Id, ImmutableArray.Create(new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(1, 10), 123, new[] { "b" })))); + + var actual = await EmitSolutionUpdateResults.GetHotReloadDiagnosticsAsync(solution, diagnosticData, rudeEdits, CancellationToken.None); + + AssertEx.Equal(new[] + { + $@"Error CS0012: {razorPath} (10,10)-(10,15): error", + $@"Error ENC0021: {sourcePath} (0,1)-(0,10): {string.Format(FeaturesResources.Adding_0_will_prevent_the_debug_session_from_continuing, "a")}", + $@"Error ENC0033: {sourcePath} (0,1)-(0,10): {string.Format(FeaturesResources.Deleting_0_will_prevent_the_debug_session_from_continuing, "b")}" + }, actual.Select(d => $"{d.Severity} {d.Id}: {d.FilePath} {d.Span.GetDebuggerDisplay()}: {d.Message}")); + } + } +} diff --git a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 2c4e7342489f7..b5bfa6f24106d 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -229,7 +229,7 @@ await proxy.StartDebuggingSessionAsync( return new(updates, diagnostics, documentsWithRudeEdits); }; - var updates = await proxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, solutionActiveStatementSpanProvider, mockDiagnosticService, diagnosticUpdateSource, CancellationToken.None).ConfigureAwait(false); + var (updates, _, _) = await proxy.EmitSolutionUpdateAsync(localWorkspace.CurrentSolution, solutionActiveStatementSpanProvider, mockDiagnosticService, diagnosticUpdateSource, CancellationToken.None).ConfigureAwait(false); VerifyReanalyzeInvocation(ImmutableArray.Create(document1.Id)); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); diff --git a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs index 0567974a98dc6..9d1fc38fcad36 100644 --- a/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs +++ b/src/Features/Core/Portable/EditAndContinue/EditAndContinueWorkspaceService.cs @@ -196,7 +196,7 @@ public async ValueTask> GetDocumentDiagnosticsAsync(D // track the document, so that we can refresh or clean diagnostics at the end of edit session: editSession.TrackDocumentWithReportedDiagnostics(document.Id); - var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); return analysis.RudeEditErrors.SelectAsArray((e, t) => e.ToDiagnostic(t), tree); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) diff --git a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs index 563e55ee7396e..f26bcd1e77f82 100644 --- a/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs +++ b/src/Features/Core/Portable/EditAndContinue/EmitSolutionUpdateResults.cs @@ -3,18 +3,44 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Globalization; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.EditAndContinue { internal readonly struct EmitSolutionUpdateResults { + [DataContract] + internal readonly struct Data + { + [DataMember(Order = 0)] + public readonly ManagedModuleUpdates ModuleUpdates; + + [DataMember(Order = 1)] + public readonly ImmutableArray Diagnostics; + + [DataMember(Order = 2)] + public readonly ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)> RudeEdits; + + public Data( + ManagedModuleUpdates moduleUpdates, + ImmutableArray diagnostics, + ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)> rudeEdits) + { + ModuleUpdates = moduleUpdates; + Diagnostics = diagnostics; + RudeEdits = rudeEdits; + } + } + public static readonly EmitSolutionUpdateResults Empty = new(new ManagedModuleUpdates(ManagedModuleUpdateStatus.None, ImmutableArray.Empty), ImmutableArray<(ProjectId ProjectId, ImmutableArray Diagnostic)>.Empty, @@ -22,7 +48,7 @@ internal readonly struct EmitSolutionUpdateResults public readonly ManagedModuleUpdates ModuleUpdates; public readonly ImmutableArray<(ProjectId ProjectId, ImmutableArray Diagnostic)> Diagnostics; - public readonly ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)> DocumentsWithRudeEdits; + public readonly ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)> RudeEdits; public EmitSolutionUpdateResults( ManagedModuleUpdates moduleUpdates, @@ -31,9 +57,12 @@ public EmitSolutionUpdateResults( { ModuleUpdates = moduleUpdates; Diagnostics = diagnostics; - DocumentsWithRudeEdits = documentsWithRudeEdits; + RudeEdits = documentsWithRudeEdits; } + public Data Dehydrate(Solution solution) + => new(ModuleUpdates, GetDiagnosticData(solution), RudeEdits); + public ImmutableArray GetDiagnosticData(Solution solution) { using var _ = ArrayBuilder.GetInstance(out var result); @@ -58,7 +87,7 @@ public async Task> GetAllDiagnosticsAsync(Solution so using var _ = ArrayBuilder.GetInstance(out var diagnostics); // add rude edits: - foreach (var (documentId, documentRudeEdits) in DocumentsWithRudeEdits) + foreach (var (documentId, documentRudeEdits) in RudeEdits) { var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(document); @@ -78,5 +107,68 @@ public async Task> GetAllDiagnosticsAsync(Solution so return diagnostics.ToImmutable(); } + + internal static async ValueTask> GetHotReloadDiagnosticsAsync( + Solution solution, + ImmutableArray diagnosticData, ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)> rudeEdits, + CancellationToken cancellationToken) + { + using var _ = ArrayBuilder.GetInstance(out var builder); + + // Add the first compiler emit error. Do not report warnings - they do not block applying the edit. + // It's unnecessary to report more then one error since all the diagnostics are already reported in the Error List + // and this is just messaging to the agent. + + foreach (var data in diagnosticData) + { + if (data.Severity != DiagnosticSeverity.Error) + { + continue; + } + + var fileSpan = data.DataLocation?.GetFileLinePositionSpan(); + + builder.Add(new ManagedHotReloadDiagnostic( + data.Id, + data.Message ?? FeaturesResources.Unknown_error_occurred, + ManagedHotReloadDiagnosticSeverity.Error, + fileSpan?.Path ?? "", + fileSpan?.Span.ToSourceSpan() ?? default)); + + // only report first error + break; + } + + // Report all rude edits. + + foreach (var (documentId, diagnostics) in rudeEdits) + { + var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + + foreach (var diagnostic in diagnostics) + { + var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(diagnostic.Kind); + + var severity = descriptor.DefaultSeverity switch + { + DiagnosticSeverity.Error => ManagedHotReloadDiagnosticSeverity.Error, + DiagnosticSeverity.Warning => ManagedHotReloadDiagnosticSeverity.Warning, + _ => throw ExceptionUtilities.UnexpectedValue(descriptor.DefaultSeverity) + }; + + var fileSpan = tree.GetMappedLineSpan(diagnostic.Span, cancellationToken); + + builder.Add(new ManagedHotReloadDiagnostic( + descriptor.Id, + string.Format(descriptor.MessageFormat.ToString(CultureInfo.CurrentUICulture), diagnostic.Arguments), + severity, + fileSpan.Path ?? "", + fileSpan.Span.ToSourceSpan())); + } + } + + return builder.ToImmutable(); + } } } diff --git a/src/Features/Core/Portable/EditAndContinue/HotReload/IManagedHotReloadLanguageService.cs b/src/Features/Core/Portable/EditAndContinue/HotReload/IManagedHotReloadLanguageService.cs new file mode 100644 index 0000000000000..21a59f41858b9 --- /dev/null +++ b/src/Features/Core/Portable/EditAndContinue/HotReload/IManagedHotReloadLanguageService.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using System.Collections.Immutable; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; + +// These types are available in newer version of Debugger.Contracts package in main-vs-deps. + +namespace Microsoft.VisualStudio.Debugger.Contracts.HotReload +{ + internal interface IManagedHotReloadLanguageService + { + ValueTask StartSessionAsync(CancellationToken cancellationToken); + + ValueTask GetUpdatesAsync(CancellationToken cancellationToken); + + ValueTask CommitUpdatesAsync(CancellationToken cancellationToken); + + ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken); + + ValueTask EndSessionAsync(CancellationToken cancellationToken); + } + + internal interface IManagedHotReloadService + { + ValueTask> GetCapabilitiesAsync(CancellationToken cancellationToken); + } + + internal readonly struct ManagedHotReloadUpdates + { + public ImmutableArray Updates { get; } + public ImmutableArray Diagnostics { get; } + + public ManagedHotReloadUpdates(ImmutableArray updates, ImmutableArray diagnostics) + { + Updates = updates; + Diagnostics = diagnostics; + } + } + + internal readonly struct ManagedHotReloadUpdate + { + public Guid Module { get; } + public ImmutableArray ILDelta { get; } + public ImmutableArray MetadataDelta { get; } + + public ManagedHotReloadUpdate(Guid module, ImmutableArray ilDelta, ImmutableArray metadataDelta) + { + Module = module; + ILDelta = ilDelta; + MetadataDelta = metadataDelta; + } + } + + internal readonly struct ManagedHotReloadDiagnostic + { + public string Id { get; } + public string Message { get; } + public ManagedHotReloadDiagnosticSeverity Severity { get; } + public string FilePath { get; } + public SourceSpan Span { get; } + + public ManagedHotReloadDiagnostic(string id, string message, ManagedHotReloadDiagnosticSeverity severity, string filePath, SourceSpan span) + { + Id = id; + Message = message; + Severity = severity; + FilePath = filePath; + Span = span; + } + } + + internal enum ManagedHotReloadDiagnosticSeverity + { + Warning = 1, + Error + } +} diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs index 464e45bf64cba..e9f3f9c2a6a04 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/IRemoteEditAndContinueService.cs @@ -27,9 +27,7 @@ internal interface ICallback ValueTask> GetDocumentDiagnosticsAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, DocumentId documentId, CancellationToken cancellationToken); ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, string? sourceFilePath, CancellationToken cancellationToken); - - ValueTask<(ManagedModuleUpdates Updates, ImmutableArray Diagnostics, ImmutableArray DocumentsWithRudeEdits)> EmitSolutionUpdateAsync( - PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); + ValueTask EmitSolutionUpdateAsync(PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken); /// /// Returns ids of documents for which diagnostics need to be refreshed in-proc. diff --git a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs index 0262f4a6935f9..c1ccbaf301655 100644 --- a/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs +++ b/src/Features/Core/Portable/EditAndContinue/Remote/RemoteEditAndContinueServiceProxy.cs @@ -273,7 +273,10 @@ public async ValueTask HasChangesAsync(Solution solution, SolutionActiveSt return result.HasValue ? result.Value : true; } - public async ValueTask EmitSolutionUpdateAsync( + public async ValueTask<( + ManagedModuleUpdates updates, + ImmutableArray diagnostics, + ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)>)> EmitSolutionUpdateAsync( Solution solution, SolutionActiveStatementSpanProvider activeStatementSpanProvider, IDiagnosticAnalyzerService diagnosticService, @@ -282,7 +285,7 @@ public async ValueTask EmitSolutionUpdateAsync( { ManagedModuleUpdates moduleUpdates; ImmutableArray diagnosticData; - IEnumerable documentsWithRudeEdits; + ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)> rudeEdits; var client = await RemoteHostClient.TryGetClientAsync(Workspace, cancellationToken).ConfigureAwait(false); if (client == null) @@ -290,11 +293,11 @@ public async ValueTask EmitSolutionUpdateAsync( var results = await GetLocalService().EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, cancellationToken).ConfigureAwait(false); moduleUpdates = results.ModuleUpdates; diagnosticData = results.GetDiagnosticData(solution); - documentsWithRudeEdits = results.DocumentsWithRudeEdits.Select(d => d.DocumentId); + rudeEdits = results.RudeEdits; } else { - var result = await client.TryInvokeAsync, ImmutableArray)>( + var result = await client.TryInvokeAsync( solution, (service, solutionInfo, callbackId, cancellationToken) => service.EmitSolutionUpdateAsync(solutionInfo, callbackId, cancellationToken), callbackTarget: new SolutionActiveStatementSpanProviderCallback(activeStatementSpanProvider), @@ -302,13 +305,15 @@ public async ValueTask EmitSolutionUpdateAsync( if (result.HasValue) { - (moduleUpdates, diagnosticData, documentsWithRudeEdits) = result.Value; + moduleUpdates = result.Value.ModuleUpdates; + diagnosticData = result.Value.Diagnostics; + rudeEdits = result.Value.RudeEdits; } else { moduleUpdates = new ManagedModuleUpdates(ManagedModuleUpdateStatus.Blocked, ImmutableArray.Empty); diagnosticData = ImmutableArray.Empty; - documentsWithRudeEdits = SpecializedCollections.EmptyEnumerable(); + rudeEdits = ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)>.Empty; } } @@ -316,12 +321,12 @@ public async ValueTask EmitSolutionUpdateAsync( diagnosticUpdateSource.ClearDiagnostics(); // clear all reported rude edits: - diagnosticService.Reanalyze(Workspace, documentIds: documentsWithRudeEdits); + diagnosticService.Reanalyze(Workspace, documentIds: rudeEdits.Select(d => d.DocumentId)); // report emit/apply diagnostics: diagnosticUpdateSource.ReportDiagnostics(Workspace, solution, diagnosticData); - return moduleUpdates; + return (moduleUpdates, diagnosticData, rudeEdits); } public async ValueTask CommitSolutionUpdateAsync(IDiagnosticAnalyzerService diagnosticService, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs b/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs index 6d19be1a5ccf5..c86a7b7cd1e50 100644 --- a/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs +++ b/src/Features/Core/Portable/EditAndContinue/RudeEditDiagnostic.cs @@ -2,27 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - +using System; +using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.EditAndContinue { + [DataContract] internal readonly struct RudeEditDiagnostic { + [DataMember(Order = 0)] public readonly RudeEditKind Kind; + + [DataMember(Order = 1)] public readonly TextSpan Span; + + [DataMember(Order = 2)] public readonly ushort SyntaxKind; - public readonly string[] Arguments; - internal RudeEditDiagnostic(RudeEditKind kind, TextSpan span, SyntaxNode node = null, string[] arguments = null) + [DataMember(Order = 3)] + public readonly string?[] Arguments; + + internal RudeEditDiagnostic(RudeEditKind kind, TextSpan span, ushort syntaxKind, string?[] arguments) { Kind = kind; Span = span; - SyntaxKind = (ushort)(node != null ? node.RawKind : 0); + SyntaxKind = syntaxKind; Arguments = arguments; } + internal RudeEditDiagnostic(RudeEditKind kind, TextSpan span, SyntaxNode? node = null, string?[]? arguments = null) + : this(kind, span, (ushort)(node != null ? node.RawKind : 0), arguments ?? Array.Empty()) + { + } + internal Diagnostic ToDiagnostic(SyntaxTree tree) { var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(Kind); diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs index 23438f00e9fbc..42d1d646e0f75 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs @@ -191,7 +191,8 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { var solution = _proxy.Workspace.CurrentSolution; var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); - return await _proxy.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + var (updates, _, _) = await _proxy.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + return updates; } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs new file mode 100644 index 0000000000000..9a66932e1340b --- /dev/null +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedHotReloadLanguageService.cs @@ -0,0 +1,152 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; +using Microsoft.VisualStudio.Debugger.Contracts.HotReload; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.EditAndContinue +{ + [Shared] + [Export(typeof(IManagedEditAndContinueLanguageService))] + [ExportMetadata("UIContext", Guids.EncCapableProjectExistsInWorkspaceUIContextString)] + internal sealed class ManagedHotReloadLanguageService : IManagedHotReloadLanguageService + { + private sealed class DebuggerService : IManagedEditAndContinueDebuggerService + { + private readonly IManagedHotReloadService _hotReloadService; + + public DebuggerService(IManagedHotReloadService hotReloadService) + { + _hotReloadService = hotReloadService; + } + + public Task> GetActiveStatementsAsync(CancellationToken cancellationToken) + => Task.FromResult(ImmutableArray.Empty); + + public Task GetAvailabilityAsync(Guid module, CancellationToken cancellationToken) + => Task.FromResult(new ManagedEditAndContinueAvailability(ManagedEditAndContinueAvailabilityStatus.Available)); + + public Task> GetCapabilitiesAsync(CancellationToken cancellationToken) + => _hotReloadService.GetCapabilitiesAsync(cancellationToken).AsTask(); + + public Task PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) + => Task.CompletedTask; + } + + private static readonly SolutionActiveStatementSpanProvider s_solutionActiveStatementSpanProvider = + (_, _) => ValueTaskFactory.FromResult(ImmutableArray.Empty); + + private readonly RemoteEditAndContinueServiceProxy _proxy; + private readonly IDiagnosticAnalyzerService _diagnosticService; + private readonly EditAndContinueDiagnosticUpdateSource _diagnosticUpdateSource; + private readonly DebuggerService _debuggerService; + + private IDisposable? _debuggingSessionConnection; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public ManagedHotReloadLanguageService( + VisualStudioWorkspace workspace, + IManagedHotReloadService hotReloadService, + IDiagnosticAnalyzerService diagnosticService, + EditAndContinueDiagnosticUpdateSource diagnosticUpdateSource) + { + _proxy = new RemoteEditAndContinueServiceProxy(workspace); + _debuggerService = new DebuggerService(hotReloadService); + _diagnosticService = diagnosticService; + _diagnosticUpdateSource = diagnosticUpdateSource; + } + + public async ValueTask StartSessionAsync(CancellationToken cancellationToken) + { + try + { + var solution = _proxy.Workspace.CurrentSolution; + _debuggingSessionConnection = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + + public async ValueTask GetUpdatesAsync(CancellationToken cancellationToken) + { + try + { + var solution = _proxy.Workspace.CurrentSolution; + var (moduleUpdates, diagnosticData, rudeEdits) = await _proxy.EmitSolutionUpdateAsync(solution, s_solutionActiveStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); + + var updates = moduleUpdates.Updates.SelectAsArray( + update => new ManagedHotReloadUpdate(update.Module, update.ILDelta, update.MetadataDelta)); + + var diagnostics = await EmitSolutionUpdateResults.GetHotReloadDiagnosticsAsync(solution, diagnosticData, rudeEdits, cancellationToken).ConfigureAwait(false); + + return new ManagedHotReloadUpdates(updates, diagnostics); + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + var descriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(RudeEditKind.InternalError); + + // TODO: better error + var diagnostic = new ManagedHotReloadDiagnostic( + descriptor.Id, + string.Format(descriptor.MessageFormat.ToString(), "", e.Message), + ManagedHotReloadDiagnosticSeverity.Error, + filePath: "", + span: default); + + return new ManagedHotReloadUpdates(ImmutableArray.Empty, ImmutableArray.Create(diagnostic)); + } + } + + public async ValueTask CommitUpdatesAsync(CancellationToken cancellationToken) + { + try + { + await _proxy.CommitSolutionUpdateAsync(_diagnosticService, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + public async ValueTask DiscardUpdatesAsync(CancellationToken cancellationToken) + { + try + { + await _proxy.DiscardSolutionUpdateAsync(cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndCatch(e)) + { + } + } + + public async ValueTask EndSessionAsync(CancellationToken cancellationToken) + { + try + { + await _proxy.EndDebuggingSessionAsync(_diagnosticUpdateSource, _diagnosticService, cancellationToken).ConfigureAwait(false); + + Contract.ThrowIfNull(_debuggingSessionConnection); + _debuggingSessionConnection.Dispose(); + _debuggingSessionConnection = null; + } + catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) + { + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs index e7a82bea149e2..571a6c22a2773 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/AbstractTableEntriesSnapshot.cs @@ -194,34 +194,6 @@ protected bool TryNavigateToItem(int index, bool previewTab, bool activate, Canc return TryNavigateTo(workspace, documentId, position, previewTab, activate, cancellationToken); } - protected static string GetFileName(string original, string mapped) - => mapped == null ? original : original == null ? mapped : Combine(original, mapped); - - private static string Combine(string path1, string path2) - { - if (TryCombine(path1, path2, out var result)) - { - return result; - } - - return string.Empty; - } - - public static bool TryCombine(string path1, string path2, out string result) - { - try - { - // don't throw exception when either path1 or path2 contains illegal path char - result = System.IO.Path.Combine(path1, path2); - return true; - } - catch - { - result = null; - return false; - } - } - // we don't use these #pragma warning disable IDE0060 // Remove unused parameter - Implements interface method for sub-type public object Identity(int index) diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs index cc5fb14bac73a..400c299288b9b 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseDiagnosticListTable.LiveTableDataSource.cs @@ -362,7 +362,7 @@ public override bool TryGetValue(int index, string columnName, [NotNullWhen(retu content = data.Message; return content != null; case StandardTableKeyNames.DocumentName: - content = GetFileName(data.DataLocation?.OriginalFilePath, data.DataLocation?.MappedFilePath); + content = data.DataLocation?.GetFilePath(); return content != null; case StandardTableKeyNames.Line: content = data.DataLocation?.MappedStartLine ?? 0; diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs index 827fda729f71b..5ba1a4fe9cb60 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioBaseTodoListTable.cs @@ -12,6 +12,7 @@ using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Common; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.TodoComments; @@ -225,7 +226,7 @@ public override bool TryGetValue(int index, string columnName, out object conten content = data.Value.Message; return content != null; case StandardTableKeyNames.DocumentName: - content = GetFileName(data.Value.OriginalFilePath, data.Value.MappedFilePath); + content = DiagnosticDataLocation.GetFilePath(data.Value.OriginalFilePath, data.Value.MappedFilePath); return content != null; case StandardTableKeyNames.Line: content = GetLineColumn(item).Line; diff --git a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs index 940d4b6a61ee8..bb04f29635839 100644 --- a/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TableDataSource/VisualStudioDiagnosticListTable.BuildTableDataSource.cs @@ -178,7 +178,7 @@ public override bool TryGetValue(int index, string columnName, out object conten content = data.Message; return content != null; case StandardTableKeyNames.DocumentName: - content = GetFileName(data.DataLocation?.OriginalFilePath, data.DataLocation?.MappedFilePath); + content = data.DataLocation?.GetFilePath(); return content != null; case StandardTableKeyNames.Line: content = data.DataLocation?.MappedStartLine ?? 0; diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs index 9440e3069355b..d743bf6688716 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataLocation.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.IO; using System.Runtime.Serialization; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -67,6 +68,9 @@ public DiagnosticDataLocation( int mappedEndLine = 0, int mappedEndColumn = 0) { + // If the original source location path is not available then mapped must be as well. + Contract.ThrowIfFalse(originalFilePath != null || mappedFilePath == null); + DocumentId = documentId; SourceSpan = sourceSpan; MappedFilePath = mappedFilePath; @@ -81,6 +85,8 @@ public DiagnosticDataLocation( OriginalEndColumn = originalEndColumn; } + public bool IsMapped => MappedFilePath != null; + internal DiagnosticDataLocation WithCalculatedSpan(TextSpan newSourceSpan) { Contract.ThrowIfTrue(SourceSpan.HasValue); @@ -92,5 +98,39 @@ internal DiagnosticDataLocation WithCalculatedSpan(TextSpan newSourceSpan) MappedFilePath, MappedStartLine, MappedStartColumn, MappedEndLine, MappedEndColumn); } + + internal FileLinePositionSpan GetFileLinePositionSpan() + { + var filePath = GetFilePath(); + if (filePath == null) + { + return default; + } + + return IsMapped ? + new(filePath, new(MappedStartLine, MappedStartColumn), new(MappedEndLine, MappedEndColumn)) : + new(filePath, new(OriginalStartLine, OriginalStartColumn), new(OriginalEndLine, OriginalEndColumn)); + } + + internal string? GetFilePath() + => GetFilePath(OriginalFilePath, MappedFilePath); + + internal static string? GetFilePath(string? original, string? mapped) + { + if (RoslynString.IsNullOrEmpty(mapped)) + { + return original; + } + + var combined = PathUtilities.CombinePaths(PathUtilities.GetDirectoryName(original), mapped); + try + { + return Path.GetFullPath(combined); + } + catch + { + return combined; + } + } } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs index 6714587735c06..b286729584c40 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/EditAndContinue/RemoteEditAndContinueService.cs @@ -132,7 +132,7 @@ public ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteSe /// /// Remote API. /// - public ValueTask<(ManagedModuleUpdates Updates, ImmutableArray Diagnostics, ImmutableArray DocumentsWithRudeEdits)> EmitSolutionUpdateAsync( + public ValueTask EmitSolutionUpdateAsync( PinnedSolutionInfo solutionInfo, RemoteServiceCallbackId callbackId, CancellationToken cancellationToken) { return RunServiceAsync(async cancellationToken => @@ -143,7 +143,7 @@ public ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteSe try { var results = await service.EmitSolutionUpdateAsync(solution, CreateSolutionActiveStatementSpanProvider(callbackId), cancellationToken).ConfigureAwait(false); - return (results.ModuleUpdates, results.GetDiagnosticData(solution), results.DocumentsWithRudeEdits.SelectAsArray(d => d.DocumentId)); + return results.Dehydrate(solution); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) { @@ -152,7 +152,7 @@ public ValueTask HasChangesAsync(PinnedSolutionInfo solutionInfo, RemoteSe var diagnostic = Diagnostic.Create(descriptor, Location.None, new[] { e.Message }); var diagnostics = ImmutableArray.Create(DiagnosticData.Create(diagnostic, solution.Options)); - return (updates, diagnostics, ImmutableArray.Empty); + return new EmitSolutionUpdateResults.Data(updates, diagnostics, ImmutableArray<(DocumentId DocumentId, ImmutableArray Diagnostics)>.Empty); } }, cancellationToken); } From e7d9cc1d9a7c4906299b7b6cee80b6d6b9b71248 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 15 Apr 2021 19:13:42 +0200 Subject: [PATCH 194/220] Update CSharpHelpContextService.cs --- .../Impl/LanguageService/CSharpHelpContextService.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs index 580f10ef44911..c78ddc0c1c95c 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs @@ -215,8 +215,15 @@ private static bool TryGetTextForOperator(SyntaxToken token, Document document, return true; } + // Workaround IsPredefinedOperator returning true for '<' in generics. + if (token.IsKind(SyntaxKind.LessThanToken) && token.Parent is not BinaryExpressionSyntax) + { + text = null; + return false; + } + var syntaxFacts = document.GetLanguageService(); - if (syntaxFacts.IsOperator(token) || SyntaxFacts.IsAssignmentExpressionOperatorToken(token.Kind())) + if (syntaxFacts.IsOperator(token) || syntaxFacts.IsPredefinedOperator(token) || SyntaxFacts.IsAssignmentExpressionOperatorToken(token.Kind())) { text = Keyword(syntaxFacts.GetText(token.RawKind)); return true; From 04e19198a2635ba11eadca798e7fbc836ad1a4ca Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 15 Apr 2021 17:16:10 -0700 Subject: [PATCH 195/220] Fix syntactic range computation. --- .../SyntacticChangeRangeComputerTests.vb | 50 +++++++++++++++++++ .../SyntacticChangeRangeComputer.cs | 19 +++++-- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb index 86e75d4e298d1..9e6ec6396ec88 100644 --- a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb +++ b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb @@ -281,5 +281,55 @@ public class C } ") End Function + + + Public Async Function TestInsertDuplicateLineBelow() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + throw new NotImplementedException();[||] +{|changed:|} } + + void M3() + { + } +} +", " + throw new NotImplementedException();") + End Function + + + Public Async Function TestInsertDuplicateLineAbove() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + {[||] + throw new NotImplementedException(); +{|changed:|} } + + void M3() + { + } +} +", " + throw new NotImplementedException();") + End Function End Class End Namespace diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index 42895c27b418b..d15ef1921dc6d 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -125,14 +125,14 @@ private static TextChangeRange ComputeSyntacticChangeRange( return default; } + var oldRootWidth = oldRoot.FullWidth(); + var newRootWidth = newRoot.FullWidth(); + // Only compute the right side if we have time for it. Otherwise, assume there is nothing in common there. var commonRightWidth = 0; if (stopwatch.Elapsed < timeout) commonRightWidth = ComputeCommonRightWidth(rightOldStack.Object, rightNewStack.Object); - var oldRootWidth = oldRoot.FullWidth(); - var newRootWidth = newRoot.FullWidth(); - Contract.ThrowIfTrue(commonLeftWidth > oldRootWidth); Contract.ThrowIfTrue(commonLeftWidth > newRootWidth); Contract.ThrowIfTrue(commonRightWidth > oldRootWidth); @@ -206,8 +206,19 @@ int ComputeCommonRightWidth( (newRoot.FullSpan.End - currentNew.FullSpan.End)); // If the two nodes/tokens were the same just skip past them. They're part of the common right width. - if (currentOld.IsIncrementallyIdenticalTo(currentNew)) + // Critically though, we can only skip past if this wasn't already something we consumed when determining + // the common left width. If this was common the left side, we can't consider it common to the right, + // otherwise we could end up with overlapping regions of commonality. + // + // This can occur in incremental settings when the similar tokens are written successsively. + // Because the parser can reuse underlying token data, it may end up with many incrementally + // identical tokens in a row. + if (currentOld.IsIncrementallyIdenticalTo(currentNew) && + currentOld.FullSpan.Start >= commonLeftWidth && + currentNew.FullSpan.Start >= commonLeftWidth) + { continue; + } // if we reached a token for either of these, then we can't break things down any further, and we hit // the furthest point they are common. From c8afc016e1d4f9e2a3acdd5f20b1342b11030991 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 15 Apr 2021 17:24:54 -0700 Subject: [PATCH 196/220] Add tests --- .../SyntacticChangeRangeComputerTests.vb | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb index 9e6ec6396ec88..bbf46affd64f6 100644 --- a/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb +++ b/src/EditorFeatures/Test2/Classification/SyntacticChangeRangeComputerTests.vb @@ -331,5 +331,55 @@ public class C ", " throw new NotImplementedException();") End Function + + + Public Async Function TestDeleteDuplicateLineBelow() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { + throw new NotImplementedException(); +{|changed: [|throw new NotImplementedException();|] + } +|} + void M3() + { + } +} +", "") + End Function + + + Public Async Function TestDeleteDuplicateLineAbove() As Task + Await TestCSharp( +" +using X; + +public class C +{ + void M1() + { + } + + void M2() + { +{|changed: [|throw new NotImplementedException();|] + throw |}new NotImplementedException(); + } + + void M3() + { + } +} +", "") + End Function End Class End Namespace From 91da3970e5c134c87c50fdc73ab60b56727efd8a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 15 Apr 2021 17:26:06 -0700 Subject: [PATCH 197/220] REvert --- .../SyntaxClassification/SyntacticChangeRangeComputer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs index d15ef1921dc6d..a39fc2758df6f 100644 --- a/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs +++ b/src/Workspaces/Core/Portable/Classification/SyntaxClassification/SyntacticChangeRangeComputer.cs @@ -125,14 +125,14 @@ private static TextChangeRange ComputeSyntacticChangeRange( return default; } - var oldRootWidth = oldRoot.FullWidth(); - var newRootWidth = newRoot.FullWidth(); - // Only compute the right side if we have time for it. Otherwise, assume there is nothing in common there. var commonRightWidth = 0; if (stopwatch.Elapsed < timeout) commonRightWidth = ComputeCommonRightWidth(rightOldStack.Object, rightNewStack.Object); + var oldRootWidth = oldRoot.FullWidth(); + var newRootWidth = newRoot.FullWidth(); + Contract.ThrowIfTrue(commonLeftWidth > oldRootWidth); Contract.ThrowIfTrue(commonLeftWidth > newRootWidth); Contract.ThrowIfTrue(commonRightWidth > oldRootWidth); From b5d5a2c8fac695a8462eb71627ec92799271d68d Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Thu, 15 Apr 2021 18:17:09 -0700 Subject: [PATCH 198/220] Update status of record features (#52575) --- docs/Language Feature Status.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Language Feature Status.md b/docs/Language Feature Status.md index 5416425dc3d70..086bd1e0adfd2 100644 --- a/docs/Language Feature Status.md +++ b/docs/Language Feature Status.md @@ -28,7 +28,7 @@ efforts behind them. | [Mix declarations and variables in deconstruction](https://github.com/dotnet/csharplang/issues/125) | main | [Merged into 16.10](https://github.com/dotnet/roslyn/issues/47746) | [YairHalberstadt ](https://github.com/YairHalberstadt) | [jcouv](https://github.com/jcouv) | [MadsTorgersen](https://github.com/MadsTorgersen) | | [List patterns](https://github.com/dotnet/csharplang/issues/3435) | [list-patterns](https://github.com/dotnet/roslyn/tree/features/list-patterns) | [In Progress](https://github.com/dotnet/roslyn/issues/51289) | [alrz](https://github.com/alrz) | [jcouv](https://github.com/jcouv), [333fred](https://github.com/333fred) | [333fred](https://github.com/333fred) | | [Extended property patterns](https://github.com/dotnet/csharplang/issues/4394) | [extended-property-patterns](https://github.com/dotnet/roslyn/tree/features/extended-property-patterns) | In Progress | [alrz](https://github.com/alrz) | [333fred](https://github.com/333fred) (Tentative) | [333fred](https://github.com/333fred) | -| [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174) | main | [In Progress](https://github.com/dotnet/roslyn/issues/52031) | [thomaslevesque](https://github.com/thomaslevesque/) | [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | +| [Sealed record ToString](https://github.com/dotnet/csharplang/issues/4174) | main | [Merged](https://github.com/dotnet/roslyn/issues/52031) | [thomaslevesque](https://github.com/thomaslevesque/) | [jcouv](https://github.com/jcouv) | [333fred](https://github.com/333fred) | | [Source Generator V2 APIs](https://github.com/dotnet/roslyn/issues/51257) | [features/source-generators](https://github.com/dotnet/roslyn/tree/features/source-generators) | [In Progress](https://github.com/dotnet/roslyn/issues/51257) | [chsienki](https://github.com/chsienki/) | [rikkigibson](https://github.com/rikkigibson), [jaredpar](https://github.com/jaredpar), [cston](https://github.com/cston) | N/A | # VB 16.9 From bed18ddf2c053d892adf93a4667ddb0d378f3fec Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 16 Apr 2021 08:47:39 +0200 Subject: [PATCH 199/220] Update src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs Co-authored-by: CyrusNajmabadi --- .../CSharp/Impl/LanguageService/CSharpHelpContextService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs index c78ddc0c1c95c..f985b4c3608be 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs @@ -216,7 +216,7 @@ private static bool TryGetTextForOperator(SyntaxToken token, Document document, } // Workaround IsPredefinedOperator returning true for '<' in generics. - if (token.IsKind(SyntaxKind.LessThanToken) && token.Parent is not BinaryExpressionSyntax) + if (token is { Kind: (int)SyntaxKind.LessThanToken, Parent: not BinaryExpressionSyntax }) { text = null; return false; From 79eb35b51301cedbe95fdce6d429314c55b13666 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 16 Apr 2021 11:38:20 +0200 Subject: [PATCH 200/220] Update src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs --- .../CSharp/Impl/LanguageService/CSharpHelpContextService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs index f985b4c3608be..600982810868c 100644 --- a/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs +++ b/src/VisualStudio/CSharp/Impl/LanguageService/CSharpHelpContextService.cs @@ -216,7 +216,7 @@ private static bool TryGetTextForOperator(SyntaxToken token, Document document, } // Workaround IsPredefinedOperator returning true for '<' in generics. - if (token is { Kind: (int)SyntaxKind.LessThanToken, Parent: not BinaryExpressionSyntax }) + if (token is { RawKind: (int)SyntaxKind.LessThanToken, Parent: not BinaryExpressionSyntax }) { text = null; return false; From 883ec07e7b0f0fe5639541cdad74c4ba753f1c97 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 17 Apr 2021 00:02:13 +0200 Subject: [PATCH 201/220] Update DocumentationMode docs (#52675) * Update DocumentationMode docs --- src/Compilers/Core/Portable/Compilation/ParseOptions.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compilers/Core/Portable/Compilation/ParseOptions.cs b/src/Compilers/Core/Portable/Compilation/ParseOptions.cs index e56f948a52dc7..51aa13c819159 100644 --- a/src/Compilers/Core/Portable/Compilation/ParseOptions.cs +++ b/src/Compilers/Core/Portable/Compilation/ParseOptions.cs @@ -32,9 +32,8 @@ public abstract class ParseOptions public SourceCodeKind SpecifiedKind { get; protected set; } /// - /// Gets a value indicating whether the documentation comments are parsed. + /// Gets a value indicating whether the documentation comments are parsed and analyzed. /// - /// true if documentation comments are parsed, false otherwise. public DocumentationMode DocumentationMode { get; protected set; } internal ParseOptions(SourceCodeKind kind, DocumentationMode documentationMode) From c31d8af3c2d39d1ab1129a6572af0a16385cf624 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Fri, 16 Apr 2021 23:49:54 +0000 Subject: [PATCH 202/220] [main] Update dependencies from dotnet/arcade (#52683) [main] Update dependencies from dotnet/arcade - Downgrade to maintain NET 6 Preview 1 SDK --- eng/Version.Details.xml | 8 +- eng/common/cross/arm64/tizen-fetch.sh | 2 +- eng/common/cross/build-android-rootfs.sh | 1 + eng/common/cross/build-rootfs.sh | 4 + eng/common/generate-locproject.ps1 | 8 +- eng/common/performance/blazor_perf.proj | 30 -- eng/common/performance/crossgen_perf.proj | 110 ------- eng/common/performance/microbenchmarks.proj | 144 --------- eng/common/performance/performance-setup.ps1 | 139 -------- eng/common/performance/performance-setup.sh | 297 ------------------ eng/common/templates/job/onelocbuild.yml | 10 +- eng/common/templates/job/performance.yml | 95 ------ .../templates/steps/perf-send-to-helix.yml | 50 --- eng/common/templates/steps/source-build.yml | 2 +- global.json | 6 +- 15 files changed, 26 insertions(+), 880 deletions(-) delete mode 100644 eng/common/performance/blazor_perf.proj delete mode 100644 eng/common/performance/crossgen_perf.proj delete mode 100644 eng/common/performance/microbenchmarks.proj delete mode 100644 eng/common/performance/performance-setup.ps1 delete mode 100755 eng/common/performance/performance-setup.sh delete mode 100644 eng/common/templates/job/performance.yml delete mode 100644 eng/common/templates/steps/perf-send-to-helix.yml diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 6f1f9e5855ea3..32aeb91d1c8bc 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -3,17 +3,17 @@ - + https://github.com/dotnet/arcade - 0ca849f0b71866b007fedaaa938cee63f8d056a6 + 53fe29e220fc0db05eafd5c6bc6c8fb9ee7cec7c https://github.com/dotnet/roslyn 31a3e4904f5a02ee51dbe15d7a68daaac9b5c6a8 - + https://github.com/dotnet/arcade - 0ca849f0b71866b007fedaaa938cee63f8d056a6 + 53fe29e220fc0db05eafd5c6bc6c8fb9ee7cec7c diff --git a/eng/common/cross/arm64/tizen-fetch.sh b/eng/common/cross/arm64/tizen-fetch.sh index a48a6f51c49d0..16d1301f21e4c 100644 --- a/eng/common/cross/arm64/tizen-fetch.sh +++ b/eng/common/cross/arm64/tizen-fetch.sh @@ -157,7 +157,7 @@ fetch_tizen_pkgs() Inform "Initialize arm base" fetch_tizen_pkgs_init standard base Inform "fetch common packages" -fetch_tizen_pkgs aarch64 gcc glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel +fetch_tizen_pkgs aarch64 gcc glibc glibc-devel libicu libicu-devel libatomic linux-glibc-devel keyutils keyutils-devel libkeyutils Inform "fetch coreclr packages" fetch_tizen_pkgs aarch64 lldb lldb-devel libgcc libstdc++ libstdc++-devel libunwind libunwind-devel lttng-ust-devel lttng-ust userspace-rcu-devel userspace-rcu Inform "fetch corefx packages" diff --git a/eng/common/cross/build-android-rootfs.sh b/eng/common/cross/build-android-rootfs.sh index 42516bbeebc3f..c29c8267e7a4c 100755 --- a/eng/common/cross/build-android-rootfs.sh +++ b/eng/common/cross/build-android-rootfs.sh @@ -106,6 +106,7 @@ __AndroidPackages+=" libandroid-glob" __AndroidPackages+=" liblzma" __AndroidPackages+=" krb5" __AndroidPackages+=" openssl" +__AndroidPackages+=" openldap" for path in $(wget -qO- http://termux.net/dists/stable/main/binary-$__AndroidArch/Packages |\ grep -A15 "Package: \(${__AndroidPackages// /\\|}\)" | grep -v "static\|tool" | grep Filename); do diff --git a/eng/common/cross/build-rootfs.sh b/eng/common/cross/build-rootfs.sh index b26622444f5ba..81e641a57b53d 100755 --- a/eng/common/cross/build-rootfs.sh +++ b/eng/common/cross/build-rootfs.sh @@ -55,11 +55,13 @@ __UbuntuPackages+=" libcurl4-openssl-dev" __UbuntuPackages+=" libkrb5-dev" __UbuntuPackages+=" libssl-dev" __UbuntuPackages+=" zlib1g-dev" +__UbuntuPackages+=" libldap2-dev" __AlpinePackages+=" curl-dev" __AlpinePackages+=" krb5-dev" __AlpinePackages+=" openssl-dev" __AlpinePackages+=" zlib-dev" +__AlpinePackages+=" openldap-dev" __FreeBSDBase="12.1-RELEASE" __FreeBSDPkg="1.12.0" @@ -68,11 +70,13 @@ __FreeBSDPackages+=" icu" __FreeBSDPackages+=" libinotify" __FreeBSDPackages+=" lttng-ust" __FreeBSDPackages+=" krb5" +__FreeBSDPackages+=" libslapi-2.4" __IllumosPackages="icu-64.2nb2" __IllumosPackages+=" mit-krb5-1.16.2nb4" __IllumosPackages+=" openssl-1.1.1e" __IllumosPackages+=" zlib-1.2.11" +__IllumosPackages+=" openldap-client-2.4.49" __UseMirror=0 diff --git a/eng/common/generate-locproject.ps1 b/eng/common/generate-locproject.ps1 index 9e2f7ad98bf0b..7225ddc666906 100644 --- a/eng/common/generate-locproject.ps1 +++ b/eng/common/generate-locproject.ps1 @@ -38,7 +38,7 @@ if ($allXlfFiles) { $langXlfFiles = Get-ChildItem -Recurse -Path "$SourcesDirectory\*\*.$firstLangCode.xlf" } $langXlfFiles | ForEach-Object { - $null = $_.Name -Match "([^.]+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf' + $null = $_.Name -Match "(.+)\.[\w-]+\.xlf" # matches '[filename].[langcode].xlf $destinationFile = "$($_.Directory.FullName)\$($Matches.1).xlf" $xlfFiles += Copy-Item "$($_.FullName)" -Destination $destinationFile -PassThru @@ -52,7 +52,7 @@ $locJson = @{ LanguageSet = $LanguageSet LocItems = @( $locFiles | ForEach-Object { - $outputPath = "Localize\$(($_.DirectoryName | Resolve-Path -Relative) + "\")" + $outputPath = "$(($_.DirectoryName | Resolve-Path -Relative) + "\")" $continue = $true foreach ($exclusion in $exclusions.Exclusions) { if ($outputPath.Contains($exclusion)) @@ -79,7 +79,7 @@ $locJson = @{ } $json = ConvertTo-Json $locJson -Depth 5 -Write-Host "(NETCORE_ENGINEERING_TELEMETRY=Build) LocProject.json generated:`n`n$json`n`n" +Write-Host "LocProject.json generated:`n`n$json`n`n" Pop-Location if (!$UseCheckedInLocProjectJson) { @@ -91,7 +91,7 @@ else { Set-Content "$SourcesDirectory\Localize\LocProject-generated.json" $json if ((Get-FileHash "$SourcesDirectory\Localize\LocProject-generated.json").Hash -ne (Get-FileHash "$SourcesDirectory\Localize\LocProject.json").Hash) { - Write-PipelineTaskError -Type "warning" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." + Write-PipelineTelemetryError -Category "OneLocBuild" -Message "Existing LocProject.json differs from generated LocProject.json. Download LocProject-generated.json and compare them." exit 1 } diff --git a/eng/common/performance/blazor_perf.proj b/eng/common/performance/blazor_perf.proj deleted file mode 100644 index 3b25359c43807..0000000000000 --- a/eng/common/performance/blazor_perf.proj +++ /dev/null @@ -1,30 +0,0 @@ - - - python3 - $(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk - - - - - %(Identity) - - - - - %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\ - $(ScenarioDirectory)blazor\ - - - $HELIX_CORRELATION_PAYLOAD/performance/src/scenarios/ - $(ScenarioDirectory)blazor/ - - - - - $(WorkItemDirectory) - cd $(BlazorDirectory);$(Python) pre.py publish --msbuild %27/p:_TrimmerDumpDependencies=true%27 --msbuild-static AdditionalMonoLinkerOptions=%27"%24(AdditionalMonoLinkerOptions) --dump-dependencies"%27 --binlog %27./traces/blazor_publish.binlog%27 - $(Python) test.py sod --scenario-name "%(Identity)" - $(Python) post.py - - - \ No newline at end of file diff --git a/eng/common/performance/crossgen_perf.proj b/eng/common/performance/crossgen_perf.proj deleted file mode 100644 index eb8bdd9c440cd..0000000000000 --- a/eng/common/performance/crossgen_perf.proj +++ /dev/null @@ -1,110 +0,0 @@ - - - - - %(Identity) - - - - - - py -3 - $(HelixPreCommands) - %HELIX_CORRELATION_PAYLOAD%\Core_Root - %HELIX_CORRELATION_PAYLOAD%\performance\src\scenarios\ - $(ScenarioDirectory)crossgen\ - $(ScenarioDirectory)crossgen2\ - - - python3 - $(HelixPreCommands);chmod +x $HELIX_WORKITEM_PAYLOAD/startup/Startup;chmod +x $HELIX_WORKITEM_PAYLOAD/startup/perfcollect;sudo apt update;chmod +x $HELIX_WORKITEM_PAYLOAD/SOD/SizeOnDisk - $HELIX_CORRELATION_PAYLOAD/Core_Root - $HELIX_CORRELATION_PAYLOAD/performance/src/scenarios/ - $(ScenarioDirectory)crossgen/ - $(ScenarioDirectory)crossgen2/ - - - - - - - - - - - - - - - - - - - - - - - $(WorkItemDirectory) - $(Python) $(CrossgenDirectory)test.py crossgen --core-root $(CoreRoot) --test-name %(Identity) - - - - - - $(WorkItemDirectory) - $(Python) $(Crossgen2Directory)test.py crossgen2 --core-root $(CoreRoot) --single %(Identity) - - - - - - $(WorkItemDirectory) - $(Python) $(Crossgen2Directory)test.py crossgen2 --core-root $(CoreRoot) --single %(Identity) --singlethreaded True - - - - - - $(WorkItemDirectory) - $(Python) $(CrossgenDirectory)pre.py crossgen --core-root $(CoreRoot) --single %(Identity) - $(Python) $(CrossgenDirectory)test.py sod --scenario-name "Crossgen %(Identity) Size" --dirs ./crossgen.out/ - $(Python) $(CrossgenDirectory)post.py - - - - - - $(WorkItemDirectory) - $(Python) $(Crossgen2Directory)pre.py crossgen2 --core-root $(CoreRoot) --single %(Identity) - $(Python) $(Crossgen2Directory)test.py sod --scenario-name "Crossgen2 %(Identity) Size" --dirs ./crossgen.out/ - $(Python) $(Crossgen2Directory)post.py - - - - - - - 4:00 - - - - 4:00 - - - 4:00 - - - $(WorkItemDirectory) - $(Python) $(Crossgen2Directory)test.py crossgen2 --core-root $(CoreRoot) --composite $(Crossgen2Directory)framework-r2r.dll.rsp - 1:00 - - - 4:00 - - - 4:00 - - - \ No newline at end of file diff --git a/eng/common/performance/microbenchmarks.proj b/eng/common/performance/microbenchmarks.proj deleted file mode 100644 index 318ca5f1b8d4c..0000000000000 --- a/eng/common/performance/microbenchmarks.proj +++ /dev/null @@ -1,144 +0,0 @@ - - - - %HELIX_CORRELATION_PAYLOAD%\performance\scripts\benchmarks_ci.py --csproj %HELIX_CORRELATION_PAYLOAD%\performance\$(TargetCsproj) - --dotnet-versions %DOTNET_VERSION% --cli-source-info args --cli-branch %PERFLAB_BRANCH% --cli-commit-sha %PERFLAB_HASH% --cli-repository https://github.com/%PERFLAB_REPO% --cli-source-timestamp %PERFLAB_BUILDTIMESTAMP% - py -3 - %HELIX_CORRELATION_PAYLOAD%\Core_Root\CoreRun.exe - %HELIX_CORRELATION_PAYLOAD%\Baseline_Core_Root\CoreRun.exe - - $(HelixPreCommands);call %HELIX_CORRELATION_PAYLOAD%\performance\tools\machine-setup.cmd;set PYTHONPATH=%HELIX_WORKITEM_PAYLOAD%\scripts%3B%HELIX_WORKITEM_PAYLOAD% - %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts - %HELIX_CORRELATION_PAYLOAD%\artifacts\BenchmarkDotNet.Artifacts_Baseline - %HELIX_CORRELATION_PAYLOAD%\performance\src\tools\ResultsComparer\ResultsComparer.csproj - %HELIX_CORRELATION_PAYLOAD%\performance\tools\dotnet\$(Architecture)\dotnet.exe - %25%25 - %HELIX_WORKITEM_ROOT%\testResults.xml - - - - $HELIX_CORRELATION_PAYLOAD - $(BaseDirectory)/performance - - - - $HELIX_WORKITEM_PAYLOAD - $(BaseDirectory) - - - - $(PerformanceDirectory)/scripts/benchmarks_ci.py --csproj $(PerformanceDirectory)/$(TargetCsproj) - --dotnet-versions $DOTNET_VERSION --cli-source-info args --cli-branch $PERFLAB_BRANCH --cli-commit-sha $PERFLAB_HASH --cli-repository https://github.com/$PERFLAB_REPO --cli-source-timestamp $PERFLAB_BUILDTIMESTAMP - python3 - $(BaseDirectory)/Core_Root/corerun - $(BaseDirectory)/Baseline_Core_Root/corerun - $(HelixPreCommands);chmod +x $(PerformanceDirectory)/tools/machine-setup.sh;. $(PerformanceDirectory)/tools/machine-setup.sh - $(BaseDirectory)/artifacts/BenchmarkDotNet.Artifacts - $(BaseDirectory)/artifacts/BenchmarkDotNet.Artifacts_Baseline - $(PerformanceDirectory)/src/tools/ResultsComparer/ResultsComparer.csproj - $(PerformanceDirectory)/tools/dotnet/$(Architecture)/dotnet - %25 - $HELIX_WORKITEM_ROOT/testResults.xml - - - - $(CliArguments) --wasm - - - - --corerun %HELIX_CORRELATION_PAYLOAD%\dotnet-mono\shared\Microsoft.NETCore.App\6.0.0\corerun.exe - - - --corerun $(BaseDirectory)/dotnet-mono/shared/Microsoft.NETCore.App/6.0.0/corerun - - - - --corerun $(CoreRun) - - - - --corerun $(BaselineCoreRun) - - - - $(Python) $(WorkItemCommand) --incremental no --architecture $(Architecture) -f $(_Framework) $(PerfLabArguments) - - - - $(WorkItemCommand) $(CliArguments) - - - - 2:30 - 0:15 - - - - - %(Identity) - - - - - 30 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - false - - - - - - $(WorkItemDirectory) - $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument) --partition-count $(PartitionCount) --partition-index %(HelixWorkItem.Index)" - $(DotnetExe) run -f $(_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults);$(FinalCommand) - $(WorkItemTimeout) - - - - - - $(WorkItemDirectory) - $(WorkItemCommand) --bdn-artifacts $(BaselineArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(BaselineCoreRunArgument)" - $(WorkItemCommand) --bdn-artifacts $(ArtifactsDirectory) --bdn-arguments="--anyCategories $(BDNCategories) $(ExtraBenchmarkDotNetArguments) $(CoreRunArgument)" - $(DotnetExe) run -f $(_Framework) -p $(ResultsComparer) --base $(BaselineArtifactsDirectory) --diff $(ArtifactsDirectory) --threshold 2$(Percent) --xml $(XMLResults) - 4:00 - - - diff --git a/eng/common/performance/performance-setup.ps1 b/eng/common/performance/performance-setup.ps1 deleted file mode 100644 index 9a64b07e692f6..0000000000000 --- a/eng/common/performance/performance-setup.ps1 +++ /dev/null @@ -1,139 +0,0 @@ -Param( - [string] $SourceDirectory=$env:BUILD_SOURCESDIRECTORY, - [string] $CoreRootDirectory, - [string] $BaselineCoreRootDirectory, - [string] $Architecture="x64", - [string] $Framework="net5.0", - [string] $CompilationMode="Tiered", - [string] $Repository=$env:BUILD_REPOSITORY_NAME, - [string] $Branch=$env:BUILD_SOURCEBRANCH, - [string] $CommitSha=$env:BUILD_SOURCEVERSION, - [string] $BuildNumber=$env:BUILD_BUILDNUMBER, - [string] $RunCategories="Libraries Runtime", - [string] $Csproj="src\benchmarks\micro\MicroBenchmarks.csproj", - [string] $Kind="micro", - [switch] $LLVM, - [switch] $MonoInterpreter, - [switch] $MonoAOT, - [switch] $Internal, - [switch] $Compare, - [string] $MonoDotnet="", - [string] $Configurations="CompilationMode=$CompilationMode RunKind=$Kind", - [string] $LogicalMachine="" -) - -$RunFromPerformanceRepo = ($Repository -eq "dotnet/performance") -or ($Repository -eq "dotnet-performance") -$UseCoreRun = ($CoreRootDirectory -ne [string]::Empty) -$UseBaselineCoreRun = ($BaselineCoreRootDirectory -ne [string]::Empty) - -$PayloadDirectory = (Join-Path $SourceDirectory "Payload") -$PerformanceDirectory = (Join-Path $PayloadDirectory "performance") -$WorkItemDirectory = (Join-Path $SourceDirectory "workitem") -$ExtraBenchmarkDotNetArguments = "--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true" -$Creator = $env:BUILD_DEFINITIONNAME -$PerfLabArguments = "" -$HelixSourcePrefix = "pr" - -$Queue = "" - -if ($Internal) { - switch ($LogicalMachine) { - "perftiger" { $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" } - "perfowl" { $Queue = "Windows.10.Amd64.20H2.Owl.Perf" } - "perfsurf" { $Queue = "Windows.10.Arm64.Perf.Surf" } - Default { $Queue = "Windows.10.Amd64.19H1.Tiger.Perf" } - } - $PerfLabArguments = "--upload-to-perflab-container" - $ExtraBenchmarkDotNetArguments = "" - $Creator = "" - $HelixSourcePrefix = "official" -} -else { - $Queue = "Windows.10.Amd64.ClientRS4.DevEx.15.8.Open" -} - -if($MonoInterpreter) -{ - $ExtraBenchmarkDotNetArguments = "--category-exclusion-filter NoInterpreter" -} - -if($MonoDotnet -ne "") -{ - $Configurations += " LLVM=$LLVM MonoInterpreter=$MonoInterpreter MonoAOT=$MonoAOT" - if($ExtraBenchmarkDotNetArguments -eq "") - { - #FIX ME: We need to block these tests as they don't run on mono for now - $ExtraBenchmarkDotNetArguments = "--exclusion-filter *Perf_Image* *Perf_NamedPipeStream*" - } - else - { - #FIX ME: We need to block these tests as they don't run on mono for now - $ExtraBenchmarkDotNetArguments += " --exclusion-filter *Perf_Image* *Perf_NamedPipeStream*" - } -} - -# FIX ME: This is a workaround until we get this from the actual pipeline -$CommonSetupArguments="--channel master --queue $Queue --build-number $BuildNumber --build-configs $Configurations --architecture $Architecture" -$SetupArguments = "--repository https://github.com/$Repository --branch $Branch --get-perf-hash --commit-sha $CommitSha $CommonSetupArguments" - - -if ($RunFromPerformanceRepo) { - $SetupArguments = "--perf-hash $CommitSha $CommonSetupArguments" - - robocopy $SourceDirectory $PerformanceDirectory /E /XD $PayloadDirectory $SourceDirectory\artifacts $SourceDirectory\.git -} -else { - git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $PerformanceDirectory -} - -if($MonoDotnet -ne "") -{ - $UsingMono = "true" - $MonoDotnetPath = (Join-Path $PayloadDirectory "dotnet-mono") - Move-Item -Path $MonoDotnet -Destination $MonoDotnetPath -} - -if ($UseCoreRun) { - $NewCoreRoot = (Join-Path $PayloadDirectory "Core_Root") - Move-Item -Path $CoreRootDirectory -Destination $NewCoreRoot -} -if ($UseBaselineCoreRun) { - $NewBaselineCoreRoot = (Join-Path $PayloadDirectory "Baseline_Core_Root") - Move-Item -Path $BaselineCoreRootDirectory -Destination $NewBaselineCoreRoot -} - -$DocsDir = (Join-Path $PerformanceDirectory "docs") -robocopy $DocsDir $WorkItemDirectory - -# Set variables that we will need to have in future steps -$ci = $true - -. "$PSScriptRoot\..\pipeline-logging-functions.ps1" - -# Directories -Write-PipelineSetVariable -Name 'PayloadDirectory' -Value "$PayloadDirectory" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'PerformanceDirectory' -Value "$PerformanceDirectory" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'WorkItemDirectory' -Value "$WorkItemDirectory" -IsMultiJobVariable $false - -# Script Arguments -Write-PipelineSetVariable -Name 'Python' -Value "py -3" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'ExtraBenchmarkDotNetArguments' -Value "$ExtraBenchmarkDotNetArguments" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'SetupArguments' -Value "$SetupArguments" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'PerfLabArguments' -Value "$PerfLabArguments" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'BDNCategories' -Value "$RunCategories" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'TargetCsproj' -Value "$Csproj" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Kind' -Value "$Kind" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Architecture' -Value "$Architecture" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'UseCoreRun' -Value "$UseCoreRun" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'UseBaselineCoreRun' -Value "$UseBaselineCoreRun" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'RunFromPerfRepo' -Value "$RunFromPerformanceRepo" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Compare' -Value "$Compare" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'MonoDotnet' -Value "$UsingMono" -IsMultiJobVariable $false - -# Helix Arguments -Write-PipelineSetVariable -Name 'Creator' -Value "$Creator" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'Queue' -Value "$Queue" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name 'HelixSourcePrefix' -Value "$HelixSourcePrefix" -IsMultiJobVariable $false -Write-PipelineSetVariable -Name '_BuildConfig' -Value "$Architecture.$Kind.$Framework" -IsMultiJobVariable $false - -exit 0 \ No newline at end of file diff --git a/eng/common/performance/performance-setup.sh b/eng/common/performance/performance-setup.sh deleted file mode 100755 index 33b60b5033742..0000000000000 --- a/eng/common/performance/performance-setup.sh +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env bash - -source_directory=$BUILD_SOURCESDIRECTORY -core_root_directory= -baseline_core_root_directory= -architecture=x64 -framework=net5.0 -compilation_mode=tiered -repository=$BUILD_REPOSITORY_NAME -branch=$BUILD_SOURCEBRANCH -commit_sha=$BUILD_SOURCEVERSION -build_number=$BUILD_BUILDNUMBER -internal=false -compare=false -mono_dotnet= -kind="micro" -llvm=false -monointerpreter=false -monoaot=false -run_categories="Libraries Runtime" -csproj="src\benchmarks\micro\MicroBenchmarks.csproj" -configurations="CompliationMode=$compilation_mode RunKind=$kind" -run_from_perf_repo=false -use_core_run=true -use_baseline_core_run=true -using_mono=false -wasm_runtime_loc= -using_wasm=false -use_latest_dotnet=false -logical_machine= - -while (($# > 0)); do - lowerI="$(echo $1 | tr "[:upper:]" "[:lower:]")" - case $lowerI in - --sourcedirectory) - source_directory=$2 - shift 2 - ;; - --corerootdirectory) - core_root_directory=$2 - shift 2 - ;; - --baselinecorerootdirectory) - baseline_core_root_directory=$2 - shift 2 - ;; - --architecture) - architecture=$2 - shift 2 - ;; - --framework) - framework=$2 - shift 2 - ;; - --compilationmode) - compilation_mode=$2 - shift 2 - ;; - --logicalmachine) - logical_machine=$2 - shift 2 - ;; - --repository) - repository=$2 - shift 2 - ;; - --branch) - branch=$2 - shift 2 - ;; - --commitsha) - commit_sha=$2 - shift 2 - ;; - --buildnumber) - build_number=$2 - shift 2 - ;; - --kind) - kind=$2 - configurations="CompilationMode=$compilation_mode RunKind=$kind" - shift 2 - ;; - --runcategories) - run_categories=$2 - shift 2 - ;; - --csproj) - csproj=$2 - shift 2 - ;; - --internal) - internal=true - shift 1 - ;; - --alpine) - alpine=true - shift 1 - ;; - --llvm) - llvm=true - shift 1 - ;; - --monointerpreter) - monointerpreter=true - shift 1 - ;; - --monoaot) - monoaot=true - shift 1 - ;; - --monodotnet) - mono_dotnet=$2 - shift 2 - ;; - --wasm) - wasm_runtime_loc=$2 - shift 2 - ;; - --compare) - compare=true - shift 1 - ;; - --configurations) - configurations=$2 - shift 2 - ;; - --latestdotnet) - use_latest_dotnet=true - shift 1 - ;; - *) - echo "Common settings:" - echo " --corerootdirectory Directory where Core_Root exists, if running perf testing with --corerun" - echo " --architecture Architecture of the testing being run" - echo " --configurations List of key=value pairs that will be passed to perf testing infrastructure." - echo " ex: --configurations \"CompilationMode=Tiered OptimzationLevel=PGO\"" - echo " --help Print help and exit" - echo "" - echo "Advanced settings:" - echo " --framework The framework to run, if not running in master" - echo " --compliationmode The compilation mode if not passing --configurations" - echo " --sourcedirectory The directory of the sources. Defaults to env:BUILD_SOURCESDIRECTORY" - echo " --repository The name of the repository in the / format. Defaults to env:BUILD_REPOSITORY_NAME" - echo " --branch The name of the branch. Defaults to env:BUILD_SOURCEBRANCH" - echo " --commitsha The commit sha1 to run against. Defaults to env:BUILD_SOURCEVERSION" - echo " --buildnumber The build number currently running. Defaults to env:BUILD_BUILDNUMBER" - echo " --csproj The relative path to the benchmark csproj whose tests should be run. Defaults to src\benchmarks\micro\MicroBenchmarks.csproj" - echo " --kind Related to csproj. The kind of benchmarks that should be run. Defaults to micro" - echo " --runcategories Related to csproj. Categories of benchmarks to run. Defaults to \"coreclr corefx\"" - echo " --internal If the benchmarks are running as an official job." - echo " --monodotnet Pass the path to the mono dotnet for mono performance testing." - echo " --wasm Path to the unpacked wasm runtime pack." - echo " --latestdotnet --dotnet-versions will not be specified. --dotnet-versions defaults to LKG version in global.json " - echo " --alpine Set for runs on Alpine" - echo "" - exit 0 - ;; - esac -done - -if [ "$repository" == "dotnet/performance" ] || [ "$repository" == "dotnet-performance" ]; then - run_from_perf_repo=true -fi - -if [ -z "$configurations" ]; then - configurations="CompilationMode=$compilation_mode" -fi - -if [ -z "$core_root_directory" ]; then - use_core_run=false -fi - -if [ -z "$baseline_core_root_directory" ]; then - use_baseline_core_run=false -fi - -payload_directory=$source_directory/Payload -performance_directory=$payload_directory/performance -workitem_directory=$source_directory/workitem -extra_benchmark_dotnet_arguments="--iterationCount 1 --warmupCount 0 --invocationCount 1 --unrollFactor 1 --strategy ColdStart --stopOnFirstError true" -perflab_arguments= -queue=Ubuntu.1804.Amd64.Open -creator=$BUILD_DEFINITIONNAME -helix_source_prefix="pr" - -if [[ "$internal" == true ]]; then - perflab_arguments="--upload-to-perflab-container" - helix_source_prefix="official" - creator= - extra_benchmark_dotnet_arguments= - - if [[ "$architecture" = "arm64" ]]; then - queue=Ubuntu.1804.Arm64.Perf - else - if [[ "$logical_machine" = "perfowl" ]]; then - queue=Ubuntu.1804.Amd64.Owl.Perf - else - queue=Ubuntu.1804.Amd64.Tiger.Perf - fi - fi - - if [[ "$alpine" = "true" ]]; then - queue=alpine.amd64.tiger.perf - fi -else - if [[ "$architecture" = "arm64" ]]; then - queue=ubuntu.1804.armarch.open - else - queue=Ubuntu.1804.Amd64.Open - fi - - if [[ "$alpine" = "true" ]]; then - queue=alpine.amd64.tiger.perf - fi -fi - -if [[ "$mono_dotnet" != "" ]] && [[ "$monointerpreter" == "false" ]]; then - configurations="$configurations LLVM=$llvm MonoInterpreter=$monointerpreter MonoAOT=$monoaot" - extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --category-exclusion-filter NoMono" -fi - -if [[ "$wasm_runtime_loc" != "" ]]; then - configurations="CompilationMode=wasm RunKind=$kind" - extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --category-exclusion-filter NoInterpreter NoWASM NoMono" -fi - -if [[ "$mono_dotnet" != "" ]] && [[ "$monointerpreter" == "true" ]]; then - configurations="$configurations LLVM=$llvm MonoInterpreter=$monointerpreter MonoAOT=$monoaot" - extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --category-exclusion-filter NoInterpreter NoMono" -fi - -common_setup_arguments="--channel master --queue $queue --build-number $build_number --build-configs $configurations --architecture $architecture" -setup_arguments="--repository https://github.com/$repository --branch $branch --get-perf-hash --commit-sha $commit_sha $common_setup_arguments" - -if [[ "$run_from_perf_repo" = true ]]; then - payload_directory= - workitem_directory=$source_directory - performance_directory=$workitem_directory - setup_arguments="--perf-hash $commit_sha $common_setup_arguments" -else - git clone --branch master --depth 1 --quiet https://github.com/dotnet/performance $performance_directory - - docs_directory=$performance_directory/docs - mv $docs_directory $workitem_directory -fi - -if [[ "$wasm_runtime_loc" != "" ]]; then - using_wasm=true - wasm_dotnet_path=$payload_directory/dotnet-wasm - mv $wasm_runtime_loc $wasm_dotnet_path - extra_benchmark_dotnet_arguments="$extra_benchmark_dotnet_arguments --wasmMainJS \$HELIX_CORRELATION_PAYLOAD/dotnet-wasm/runtime-test.js --wasmEngine /home/helixbot/.jsvu/v8 --customRuntimePack \$HELIX_CORRELATION_PAYLOAD/dotnet-wasm" -fi - -if [[ "$mono_dotnet" != "" ]]; then - using_mono=true - mono_dotnet_path=$payload_directory/dotnet-mono - mv $mono_dotnet $mono_dotnet_path -fi - -if [[ "$use_core_run" = true ]]; then - new_core_root=$payload_directory/Core_Root - mv $core_root_directory $new_core_root -fi - -if [[ "$use_baseline_core_run" = true ]]; then - new_baseline_core_root=$payload_directory/Baseline_Core_Root - mv $baseline_core_root_directory $new_baseline_core_root -fi - -ci=true - -_script_dir=$(pwd)/eng/common -. "$_script_dir/pipeline-logging-functions.sh" - -# Make sure all of our variables are available for future steps -Write-PipelineSetVariable -name "UseCoreRun" -value "$use_core_run" -is_multi_job_variable false -Write-PipelineSetVariable -name "UseBaselineCoreRun" -value "$use_baseline_core_run" -is_multi_job_variable false -Write-PipelineSetVariable -name "Architecture" -value "$architecture" -is_multi_job_variable false -Write-PipelineSetVariable -name "PayloadDirectory" -value "$payload_directory" -is_multi_job_variable false -Write-PipelineSetVariable -name "PerformanceDirectory" -value "$performance_directory" -is_multi_job_variable false -Write-PipelineSetVariable -name "WorkItemDirectory" -value "$workitem_directory" -is_multi_job_variable false -Write-PipelineSetVariable -name "Queue" -value "$queue" -is_multi_job_variable false -Write-PipelineSetVariable -name "SetupArguments" -value "$setup_arguments" -is_multi_job_variable false -Write-PipelineSetVariable -name "Python" -value "python3" -is_multi_job_variable false -Write-PipelineSetVariable -name "PerfLabArguments" -value "$perflab_arguments" -is_multi_job_variable false -Write-PipelineSetVariable -name "ExtraBenchmarkDotNetArguments" -value "$extra_benchmark_dotnet_arguments" -is_multi_job_variable false -Write-PipelineSetVariable -name "BDNCategories" -value "$run_categories" -is_multi_job_variable false -Write-PipelineSetVariable -name "TargetCsproj" -value "$csproj" -is_multi_job_variable false -Write-PipelineSetVariable -name "RunFromPerfRepo" -value "$run_from_perf_repo" -is_multi_job_variable false -Write-PipelineSetVariable -name "Creator" -value "$creator" -is_multi_job_variable false -Write-PipelineSetVariable -name "HelixSourcePrefix" -value "$helix_source_prefix" -is_multi_job_variable false -Write-PipelineSetVariable -name "Kind" -value "$kind" -is_multi_job_variable false -Write-PipelineSetVariable -name "_BuildConfig" -value "$architecture.$kind.$framework" -is_multi_job_variable false -Write-PipelineSetVariable -name "Compare" -value "$compare" -is_multi_job_variable false -Write-PipelineSetVariable -name "MonoDotnet" -value "$using_mono" -is_multi_job_variable false -Write-PipelineSetVariable -name "WasmDotnet" -value "$using_wasm" -is_multi_job_variable false diff --git a/eng/common/templates/job/onelocbuild.yml b/eng/common/templates/job/onelocbuild.yml index 6abc041cd1c84..d2b271ec1ac13 100644 --- a/eng/common/templates/job/onelocbuild.yml +++ b/eng/common/templates/job/onelocbuild.yml @@ -11,6 +11,7 @@ parameters: SourcesDirectory: $(Build.SourcesDirectory) CreatePr: true + AutoCompletePr: false UseCheckedInLocProjectJson: false LanguageSet: VS_Main_Languages LclSource: lclFilesInRepo @@ -46,16 +47,21 @@ jobs: - task: OneLocBuild@2 displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) inputs: locProj: Localize/LocProject.json outDir: $(Build.ArtifactStagingDirectory) lclSource: ${{ parameters.LclSource }} lclPackageId: ${{ parameters.LclPackageId }} isCreatePrSelected: ${{ parameters.CreatePr }} - repoType: ${{ parameters.RepoType }} - gitHubPatVariable: "${{ parameters.GithubPat }}" + ${{ if eq(parameters.CreatePr, true) }}: + isAutoCompletePrSelected: ${{ parameters.AutoCompletePr }} packageSourceAuth: patAuth patVariable: ${{ parameters.CeapexPat }} + ${{ if eq(parameters.RepoType, 'gitHub') }}: + repoType: ${{ parameters.RepoType }} + gitHubPatVariable: "${{ parameters.GithubPat }}" condition: always() - task: PublishBuildArtifacts@1 diff --git a/eng/common/templates/job/performance.yml b/eng/common/templates/job/performance.yml deleted file mode 100644 index f877fd7a89800..0000000000000 --- a/eng/common/templates/job/performance.yml +++ /dev/null @@ -1,95 +0,0 @@ -parameters: - steps: [] # optional -- any additional steps that need to happen before pulling down the performance repo and sending the performance benchmarks to helix (ie building your repo) - variables: [] # optional -- list of additional variables to send to the template - jobName: '' # required -- job name - displayName: '' # optional -- display name for the job. Will use jobName if not passed - pool: '' # required -- name of the Build pool - container: '' # required -- name of the container - osGroup: '' # required -- operating system for the job - extraSetupParameters: '' # optional -- extra arguments to pass to the setup script - frameworks: ['netcoreapp3.0'] # optional -- list of frameworks to run against - continueOnError: 'false' # optional -- determines whether to continue the build if the step errors - dependsOn: '' # optional -- dependencies of the job - timeoutInMinutes: 320 # optional -- timeout for the job - enableTelemetry: false # optional -- enable for telemetry - -jobs: -- template: ../jobs/jobs.yml - parameters: - dependsOn: ${{ parameters.dependsOn }} - enableTelemetry: ${{ parameters.enableTelemetry }} - enablePublishBuildArtifacts: true - continueOnError: ${{ parameters.continueOnError }} - - jobs: - - job: '${{ parameters.jobName }}' - - ${{ if ne(parameters.displayName, '') }}: - displayName: '${{ parameters.displayName }}' - ${{ if eq(parameters.displayName, '') }}: - displayName: '${{ parameters.jobName }}' - - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - - variables: - - - ${{ each variable in parameters.variables }}: - - ${{ if ne(variable.name, '') }}: - - name: ${{ variable.name }} - value: ${{ variable.value }} - - ${{ if ne(variable.group, '') }}: - - group: ${{ variable.group }} - - - IsInternal: '' - - HelixApiAccessToken: '' - - HelixPreCommand: '' - - - ${{ if and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}: - - ${{ if eq( parameters.osGroup, 'Windows_NT') }}: - - HelixPreCommand: 'set "PERFLAB_UPLOAD_TOKEN=$(PerfCommandUploadToken)"' - - IsInternal: -Internal - - ${{ if ne(parameters.osGroup, 'Windows_NT') }}: - - HelixPreCommand: 'export PERFLAB_UPLOAD_TOKEN="$(PerfCommandUploadTokenLinux)"' - - IsInternal: --internal - - - group: DotNet-HelixApi-Access - - group: dotnet-benchview - - workspace: - clean: all - pool: - ${{ parameters.pool }} - container: ${{ parameters.container }} - strategy: - matrix: - ${{ each framework in parameters.frameworks }}: - ${{ framework }}: - _Framework: ${{ framework }} - steps: - - checkout: self - clean: true - # Run all of the steps to setup repo - - ${{ each step in parameters.steps }}: - - ${{ step }} - - powershell: $(Build.SourcesDirectory)\eng\common\performance\performance-setup.ps1 $(IsInternal) -Framework $(_Framework) ${{ parameters.extraSetupParameters }} - displayName: Performance Setup (Windows) - condition: and(succeeded(), eq(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - script: $(Build.SourcesDirectory)/eng/common/performance/performance-setup.sh $(IsInternal) --framework $(_Framework) ${{ parameters.extraSetupParameters }} - displayName: Performance Setup (Unix) - condition: and(succeeded(), ne(variables['Agent.Os'], 'Windows_NT')) - continueOnError: ${{ parameters.continueOnError }} - - script: $(Python) $(PerformanceDirectory)/scripts/ci_setup.py $(SetupArguments) - displayName: Run ci setup script - # Run perf testing in helix - - template: /eng/common/templates/steps/perf-send-to-helix.yml - parameters: - HelixSource: '$(HelixSourcePrefix)/$(Build.Repository.Name)/$(Build.SourceBranch)' # sources must start with pr/, official/, prodcon/, or agent/ - HelixType: 'test/performance/$(Kind)/$(_Framework)/$(Architecture)' - HelixAccessToken: $(HelixApiAccessToken) - HelixTargetQueues: $(Queue) - HelixPreCommands: $(HelixPreCommand) - Creator: $(Creator) - WorkItemTimeout: 4:00 # 4 hours - WorkItemDirectory: '$(WorkItemDirectory)' # WorkItemDirectory can not be empty, so we send it some docs to keep it happy - CorrelationPayloadDirectory: '$(PayloadDirectory)' # it gets checked out to a folder with shorter path than WorkItemDirectory so we can avoid file name too long exceptions \ No newline at end of file diff --git a/eng/common/templates/steps/perf-send-to-helix.yml b/eng/common/templates/steps/perf-send-to-helix.yml deleted file mode 100644 index 0fb786fabf7e3..0000000000000 --- a/eng/common/templates/steps/perf-send-to-helix.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Please remember to update the documentation if you make changes to these parameters! -parameters: - ProjectFile: '' # required -- project file that specifies the helix workitems - HelixSource: 'pr/default' # required -- sources must start with pr/, official/, prodcon/, or agent/ - HelixType: 'tests/default/' # required -- Helix telemetry which identifies what type of data this is; should include "test" for clarity and must end in '/' - HelixBuild: $(Build.BuildNumber) # required -- the build number Helix will use to identify this -- automatically set to the AzDO build number - HelixTargetQueues: '' # required -- semicolon delimited list of Helix queues to test on; see https://helix.dot.net/ for a list of queues - HelixAccessToken: '' # required -- access token to make Helix API requests; should be provided by the appropriate variable group - HelixPreCommands: '' # optional -- commands to run before Helix work item execution - HelixPostCommands: '' # optional -- commands to run after Helix work item execution - WorkItemDirectory: '' # optional -- a payload directory to zip up and send to Helix; requires WorkItemCommand; incompatible with XUnitProjects - CorrelationPayloadDirectory: '' # optional -- a directory to zip up and send to Helix as a correlation payload - IncludeDotNetCli: false # optional -- true will download a version of the .NET CLI onto the Helix machine as a correlation payload; requires DotNetCliPackageType and DotNetCliVersion - DotNetCliPackageType: '' # optional -- either 'sdk', 'runtime' or 'aspnetcore-runtime'; determines whether the sdk or runtime will be sent to Helix; see https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases.json - DotNetCliVersion: '' # optional -- version of the CLI to send to Helix; based on this: https://raw.githubusercontent.com/dotnet/core/main/release-notes/releases.json - EnableXUnitReporter: false # optional -- true enables XUnit result reporting to Mission Control - WaitForWorkItemCompletion: true # optional -- true will make the task wait until work items have been completed and fail the build if work items fail. False is "fire and forget." - Creator: '' # optional -- if the build is external, use this to specify who is sending the job - DisplayNamePrefix: 'Send job to Helix' # optional -- rename the beginning of the displayName of the steps in AzDO - condition: succeeded() # optional -- condition for step to execute; defaults to succeeded() - continueOnError: false # optional -- determines whether to continue the build if the step errors; defaults to false - osGroup: '' # required -- operating system for the job - - -steps: -- template: /eng/pipelines/common/templates/runtimes/send-to-helix-inner-step.yml - parameters: - osGroup: ${{ parameters.osGroup }} - sendParams: $(Build.SourcesDirectory)/eng/common/performance/${{ parameters.ProjectFile }} /restore /t:Test /bl:$(Build.SourcesDirectory)/artifacts/log/$(_BuildConfig)/SendToHelix.binlog - displayName: ${{ parameters.DisplayNamePrefix }} - condition: ${{ parameters.condition }} - shouldContinueOnError: ${{ parameters.continueOnError }} - environment: - BuildConfig: $(_BuildConfig) - HelixSource: ${{ parameters.HelixSource }} - HelixType: ${{ parameters.HelixType }} - HelixBuild: ${{ parameters.HelixBuild }} - HelixTargetQueues: ${{ parameters.HelixTargetQueues }} - HelixAccessToken: ${{ parameters.HelixAccessToken }} - HelixPreCommands: ${{ parameters.HelixPreCommands }} - HelixPostCommands: ${{ parameters.HelixPostCommands }} - WorkItemDirectory: ${{ parameters.WorkItemDirectory }} - CorrelationPayloadDirectory: ${{ parameters.CorrelationPayloadDirectory }} - IncludeDotNetCli: ${{ parameters.IncludeDotNetCli }} - DotNetCliPackageType: ${{ parameters.DotNetCliPackageType }} - DotNetCliVersion: ${{ parameters.DotNetCliVersion }} - EnableXUnitReporter: ${{ parameters.EnableXUnitReporter }} - WaitForWorkItemCompletion: ${{ parameters.WaitForWorkItemCompletion }} - Creator: ${{ parameters.Creator }} - SYSTEM_ACCESSTOKEN: $(System.AccessToken) diff --git a/eng/common/templates/steps/source-build.yml b/eng/common/templates/steps/source-build.yml index 8e336b7d16b34..65ee5992bf460 100644 --- a/eng/common/templates/steps/source-build.yml +++ b/eng/common/templates/steps/source-build.yml @@ -36,7 +36,7 @@ steps: ${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \ --configuration $buildConfig \ - --restore --build --pack --publish \ + --restore --build --pack --publish -bl \ $officialBuildArgs \ $targetRidArgs \ /p:SourceBuildNonPortable=${{ parameters.platform.nonPortable }} \ diff --git a/global.json b/global.json index d1c3de1fe0ed7..ef32b3909eaab 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "16.8.0-preview2.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21167.3", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21167.3" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21216.2", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21216.2" } -} +} \ No newline at end of file From 78cbf230b86bf9275edb62490badcd30cde59cf8 Mon Sep 17 00:00:00 2001 From: Jason Malinowski Date: Fri, 16 Apr 2021 15:46:53 -0700 Subject: [PATCH 203/220] Fix build of our .NET 2.0-targeting projects For reasons we don't entirely understand yet, on some machines that don't have .NET 2.0 installed, these projects no longer build successfuly after some recent updates to something. But the problems go away if we use the official package which has extra MSBuild support that properly tells MSBuild where the framework binaries are. --- eng/Versions.props | 2 +- .../ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj | 2 +- .../Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj | 2 +- .../ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index d0fdfc39d2bad..520c633f6606a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -110,7 +110,7 @@ 1.0.1 1.1.0 1.0.3 - 1.0.0 + 1.0.0 2.0.0-alpha-20170405-2 0.1.0 0.1.2-dev diff --git a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj index e34d41ffff1cc..fa4d315968f61 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj +++ b/src/ExpressionEvaluator/CSharp/Source/ResultProvider/NetFX20/CSharpResultProvider.NetFX20.csproj @@ -33,9 +33,9 @@ + - diff --git a/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj b/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj index 775f4d87a28f3..cc964c299a8a5 100644 --- a/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj +++ b/src/ExpressionEvaluator/Core/Source/ResultProvider/NetFX20/ResultProvider.NetFX20.csproj @@ -74,9 +74,9 @@ + - diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj index 88d58005035d8..bb240cea6c5e2 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj +++ b/src/ExpressionEvaluator/VisualBasic/Source/ResultProvider/NetFX20/BasicResultProvider.NetFX20.vbproj @@ -32,9 +32,9 @@ + - From ec92c2490b3d0b8c8612514a2d23dbe16e07b3a3 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 17 Apr 2021 13:51:11 +0200 Subject: [PATCH 204/220] Simplify code style option check --- ...pIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs index 483e5fc247533..3e069446b169b 100644 --- a/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs +++ b/src/Features/CSharp/Portable/UsePatternMatching/CSharpIsAndCastCheckWithoutNameDiagnosticAnalyzer.cs @@ -69,23 +69,15 @@ private void SyntaxNodeAction(SyntaxNodeAnalysisContext context) return; } - var isExpression = (BinaryExpressionSyntax)context.Node; - - var options = context.Options as WorkspaceAnalyzerOptions; - var workspace = options?.Services.Workspace; - if (workspace == null) - { - return; - } - - var optionSet = options.GetAnalyzerOptionSet(syntaxTree, cancellationToken); - var styleOption = optionSet.GetOption(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck); + var styleOption = context.GetOption(CSharpCodeStyleOptions.PreferPatternMatchingOverIsWithCastCheck); if (!styleOption.Value) { // User has disabled this feature. return; } + var isExpression = (BinaryExpressionSyntax)context.Node; + // See if this is an 'is' expression that would be handled by the analyzer. If so // we don't need to do anything. if (CSharpIsAndCastCheckDiagnosticAnalyzer.TryGetPatternPieces( From e9ad1dbc0616c93c7f916eb496887e0e7d80aa8f Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 17 Apr 2021 19:08:47 +0200 Subject: [PATCH 205/220] Fix formatting --- .../CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs index 356ea154539da..1d1a6eca5decd 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ContextQuery/CSharpSyntaxContext.cs @@ -323,7 +323,7 @@ public bool IsMemberAttributeContext(ISet validTypeDeclarations, Can if (token.Kind() == SyntaxKind.OpenBracketToken && token.Parent.IsKind(SyntaxKind.AttributeList)) { - if (token.Parent.Parent is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } } ) + if (token.Parent.Parent is ParameterSyntax { Parent: ParameterListSyntax { Parent: RecordDeclarationSyntax } }) { return true; } From 19ca04b634128177a381074340b06daa72b373ac Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 17 Apr 2021 15:43:59 -0700 Subject: [PATCH 206/220] Remove virtual functionality that was never overridden --- ...actAsynchronousTaggerProvider.TagSource.cs | 77 ------------------- ...erProvider.TagSource_IEqualityComparer.cs} | 19 +---- ...ousTaggerProvider.TagSource_ProduceTags.cs | 77 ++++++++++++++++++- .../AbstractAsynchronousTaggerProvider.cs | 9 --- 4 files changed, 78 insertions(+), 104 deletions(-) rename src/EditorFeatures/Core/Tagging/{AbstractAsynchronousTaggerProvider.TagSpanComparer.cs => AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs} (52%) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index b0fcd709f7094..afc4b090a4b35 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -39,8 +39,6 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject private readonly AbstractAsynchronousTaggerProvider _dataSource; - private readonly IEqualityComparer> _tagSpanComparer; - /// /// async operation notifier /// @@ -112,7 +110,6 @@ public TagSource( _dataSource = dataSource; _asyncListener = asyncListener; _notificationService = notificationService; - _tagSpanComparer = new TagSpanComparer(_dataSource.TagComparer); DebugRecordInitialStackTrace(); @@ -302,80 +299,6 @@ private void RaiseResumed() private static T NextOrDefault(IEnumerator enumerator) => enumerator.MoveNext() ? enumerator.Current : default; - - /// - /// Return all the spans that appear in only one of "latestSpans" or "previousSpans". - /// - private static DiffResult Difference(IEnumerable> latestSpans, IEnumerable> previousSpans, IEqualityComparer comparer) - where T : ITag - { - using var addedPool = SharedPools.Default>().GetPooledObject(); - using var removedPool = SharedPools.Default>().GetPooledObject(); - using var latestEnumerator = latestSpans.GetEnumerator(); - using var previousEnumerator = previousSpans.GetEnumerator(); - - var added = addedPool.Object; - var removed = removedPool.Object; - - var latest = NextOrDefault(latestEnumerator); - var previous = NextOrDefault(previousEnumerator); - - while (latest != null && previous != null) - { - var latestSpan = latest.Span; - var previousSpan = previous.Span; - - if (latestSpan.Start < previousSpan.Start) - { - added.Add(latestSpan); - latest = NextOrDefault(latestEnumerator); - } - else if (previousSpan.Start < latestSpan.Start) - { - removed.Add(previousSpan); - previous = NextOrDefault(previousEnumerator); - } - else - { - // If the starts are the same, but the ends are different, report the larger - // region to be conservative. - if (previousSpan.End > latestSpan.End) - { - removed.Add(previousSpan); - latest = NextOrDefault(latestEnumerator); - } - else if (latestSpan.End > previousSpan.End) - { - added.Add(latestSpan); - previous = NextOrDefault(previousEnumerator); - } - else - { - if (!comparer.Equals(latest.Tag, previous.Tag)) - { - added.Add(latestSpan); - } - - latest = NextOrDefault(latestEnumerator); - previous = NextOrDefault(previousEnumerator); - } - } - } - - while (latest != null) - { - added.Add(latest.Span); - latest = NextOrDefault(latestEnumerator); - } - - while (previous != null) - { - removed.Add(previous.Span); - previous = NextOrDefault(previousEnumerator); - } - - return new DiffResult(added, removed); - } } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSpanComparer.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs similarity index 52% rename from src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSpanComparer.cs rename to src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs index 243a2f1ebc6df..d46ec6f98deb9 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSpanComparer.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_IEqualityComparer.cs @@ -6,30 +6,19 @@ using System.Collections.Generic; using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.Tagging { internal abstract partial class AbstractAsynchronousTaggerProvider { - private class TagSpanComparer : IEqualityComparer> + private partial class TagSource : IEqualityComparer> { - private readonly IEqualityComparer _tagComparer; - - public TagSpanComparer(IEqualityComparer tagComparer) - => _tagComparer = tagComparer; - public bool Equals(ITagSpan x, ITagSpan y) - { - if (x.Span != y.Span) - { - return false; - } - - return _tagComparer.Equals(x.Tag, y.Tag); - } + => x.Span == y.Span && EqualityComparer.Default.Equals(x.Tag, y.Tag); public int GetHashCode(ITagSpan obj) - => obj.Span.GetHashCode() ^ _tagComparer.GetHashCode(obj.Tag); + => Hash.Combine(obj.Span.GetHashCode(), EqualityComparer.Default.GetHashCode(obj.Tag)); } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index bc682bd43152b..94ecb518a3e45 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -196,7 +196,7 @@ private void RemoveTagsThatIntersectEdit(TextContentChangedEventArgs e) var newTagTree = new TagSpanIntervalTree( buffer, treeForBuffer.SpanTrackingMode, - allTags.Except(tagsToRemove, _tagSpanComparer)); + allTags.Except(tagsToRemove, comparer: this)); var snapshot = e.After; @@ -443,7 +443,7 @@ private IEnumerable> GetNonIntersectingTagSpans(IEnumerable>( spansToInvalidate.SelectMany(ss => oldTagTree.GetIntersectingSpans(ss))); - return oldTagTree.GetSpans(snapshot).Except(tagSpansToInvalidate, _tagSpanComparer); + return oldTagTree.GetSpans(snapshot).Except(tagSpansToInvalidate, comparer: this); } private async Task RecomputeTagsAsync( @@ -623,7 +623,78 @@ private DiffResult ComputeDifference( TagSpanIntervalTree latestSpans, TagSpanIntervalTree previousSpans) { - return Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot), _dataSource.TagComparer); + return Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot)); + } + + /// + /// Return all the spans that appear in only one of "latestSpans" or "previousSpans". + /// + private static DiffResult Difference(IEnumerable> latestSpans, IEnumerable> previousSpans) + { + using var addedPool = SharedPools.Default>().GetPooledObject(); + using var removedPool = SharedPools.Default>().GetPooledObject(); + using var latestEnumerator = latestSpans.GetEnumerator(); + using var previousEnumerator = previousSpans.GetEnumerator(); + + var added = addedPool.Object; + var removed = removedPool.Object; + + var latest = NextOrDefault(latestEnumerator); + var previous = NextOrDefault(previousEnumerator); + + while (latest != null && previous != null) + { + var latestSpan = latest.Span; + var previousSpan = previous.Span; + + if (latestSpan.Start < previousSpan.Start) + { + added.Add(latestSpan); + latest = NextOrDefault(latestEnumerator); + } + else if (previousSpan.Start < latestSpan.Start) + { + removed.Add(previousSpan); + previous = NextOrDefault(previousEnumerator); + } + else + { + // If the starts are the same, but the ends are different, report the larger + // region to be conservative. + if (previousSpan.End > latestSpan.End) + { + removed.Add(previousSpan); + latest = NextOrDefault(latestEnumerator); + } + else if (latestSpan.End > previousSpan.End) + { + added.Add(latestSpan); + previous = NextOrDefault(previousEnumerator); + } + else + { + if (!EqualityComparer.Default.Equals(latest.Tag, previous.Tag)) + added.Add(latestSpan); + + latest = NextOrDefault(latestEnumerator); + previous = NextOrDefault(previousEnumerator); + } + } + } + + while (latest != null) + { + added.Add(latest.Span); + latest = NextOrDefault(latestEnumerator); + } + + while (previous != null) + { + removed.Add(previous.Span); + previous = NextOrDefault(previousEnumerator); + } + + return new DiffResult(added, removed); } /// diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 5ed3a6f193719..47e5d1a89d83b 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -55,15 +55,6 @@ internal abstract partial class AbstractAsynchronousTaggerProvider : Foreg /// protected virtual SpanTrackingMode SpanTrackingMode => SpanTrackingMode.EdgeExclusive; - /// - /// Comparer used to determine if two s are the same. This is used by - /// the to determine if a previous set of - /// computed tags and a current set of computed tags should be considered the same or not. - /// If they are the same, then the UI will not be updated. If they are different then - /// the UI will be updated for sets of tags that have been removed or added. - /// - protected virtual IEqualityComparer TagComparer => EqualityComparer.Default; - /// /// Options controlling this tagger. The tagger infrastructure will check this option /// against the buffer it is associated with to see if it should tag or not. From 4e2de2cc27d76d387f6d1630be50e8d534d8b7e1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 17 Apr 2021 15:47:45 -0700 Subject: [PATCH 207/220] Fix build caused by independent merges that conflicted --- .../CSharp/CompleteStatement/CompleteStatementCommandHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs index 2e57a2c394ba9..2f25d2f6fbb28 100644 --- a/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs +++ b/src/EditorFeatures/CSharp/CompleteStatement/CompleteStatementCommandHandler.cs @@ -524,7 +524,7 @@ private static bool StatementClosingDelimiterIsMissing(SyntaxNode currentNode) /// private static bool RequiredDelimiterIsMissing(SyntaxNode currentNode) { - return currentNode.GetBrackets().closeBrace.IsMissing || + return currentNode.GetBrackets().closeBracket.IsMissing || currentNode.GetParentheses().closeParen.IsMissing; } } From 58eed4e856b3436935ff03d200b30c2d88046fdd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 17 Apr 2021 15:53:53 -0700 Subject: [PATCH 208/220] Remove virtual functionality that was never overridden --- .../Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs | 1 - .../Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs | 4 ++-- .../Core/Tagging/AbstractAsynchronousTaggerProvider.cs | 5 ----- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index afc4b090a4b35..cb82461248c0c 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -90,7 +90,6 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject private readonly CancellationTokenSource _initialComputationCancellationTokenSource = new(); public TaggerDelay AddedTagNotificationDelay => _dataSource.AddedTagNotificationDelay; - public TaggerDelay RemovedTagNotificationDelay => _dataSource.RemovedTagNotificationDelay; public TagSource( ITextView textViewOpt, diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs index 83acf6f63d9d5..c9477c35ce724 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs @@ -144,8 +144,8 @@ private void OnTagsChangedForBuffer( // Now report them back to the UI on the main thread. - // We ask to update UI immediately for removed tags - NotifyEditors(change.Value.Removed, initialTags ? TaggerDelay.NearImmediate : _tagSource.RemovedTagNotificationDelay); + // We ask to update UI immediately for removed tags, or for the very first set of tags created. + NotifyEditors(change.Value.Removed, TaggerDelay.NearImmediate); NotifyEditors(change.Value.Added, initialTags ? TaggerDelay.NearImmediate : _tagSource.AddedTagNotificationDelay); } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs index 47e5d1a89d83b..c6d713d5308ba 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.cs @@ -70,11 +70,6 @@ internal abstract partial class AbstractAsynchronousTaggerProvider : Foreg /// protected virtual TaggerDelay AddedTagNotificationDelay => TaggerDelay.NearImmediate; - /// - /// This controls what delay tagger will use to let editor know about just deleted tags. - /// - protected virtual TaggerDelay RemovedTagNotificationDelay => TaggerDelay.NearImmediate; - #if DEBUG public readonly string StackTrace; #endif From 1eca3c6370729ff3cd91e1c0ccfb950b05e96e52 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 17 Apr 2021 16:00:52 -0700 Subject: [PATCH 209/220] Simplify --- ...ousTaggerProvider.TagSource_ProduceTags.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index 94ecb518a3e45..9f3fded6019e6 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -562,7 +562,7 @@ private void ProcessNewTagTrees( // Do this in a fire and forget manner, but ensure we notify the test harness of this so that it // doesn't try to acquire tag results prior to this work finishing. var asyncToken = this._asyncListener.BeginAsyncOperation(nameof(ProcessNewTagTrees)); - Task.Run(async () => + _ = Task.Run(async () => { await this.ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); UpdateStateAndReportChanges(newTagTrees, bufferToChanges, newState, initialTags); @@ -618,19 +618,17 @@ private void UpdateStateAndReportChanges( RaiseTagsChanged(bufferToChanges, initialTags); } - private DiffResult ComputeDifference( - ITextSnapshot snapshot, - TagSpanIntervalTree latestSpans, - TagSpanIntervalTree previousSpans) - { - return Difference(latestSpans.GetSpans(snapshot), previousSpans.GetSpans(snapshot)); - } - /// - /// Return all the spans that appear in only one of "latestSpans" or "previousSpans". + /// Return all the spans that appear in only one of or . /// - private static DiffResult Difference(IEnumerable> latestSpans, IEnumerable> previousSpans) + private static DiffResult ComputeDifference( + ITextSnapshot snapshot, + TagSpanIntervalTree latestTree, + TagSpanIntervalTree previousTree) { + var latestSpans = latestTree.GetSpans(snapshot); + var previousSpans = previousTree.GetSpans(snapshot); + using var addedPool = SharedPools.Default>().GetPooledObject(); using var removedPool = SharedPools.Default>().GetPooledObject(); using var latestEnumerator = latestSpans.GetEnumerator(); From 24d034e2b679a6687f6e3b3639c45ebf01a95db4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 17 Apr 2021 16:17:32 -0700 Subject: [PATCH 210/220] Remove tagger functionality related to pausing/resumign tagging. --- .../CompilationAvailableTaggerEventSource.cs | 12 ----- .../EventSources/AbstractTaggerEventSource.cs | 8 --- ...ggerEventSources.CompositionEventSource.cs | 37 +------------- ...onousTaggerProvider.BatchChangeNotifier.cs | 50 +------------------ ...actAsynchronousTaggerProvider.TagSource.cs | 18 ------- ...ousTaggerProvider.TagSource_ProduceTags.cs | 22 +------- ...stractAsynchronousTaggerProvider.Tagger.cs | 10 ---- .../Core/Tagging/ITaggerEventSource.cs | 10 ---- 8 files changed, 5 insertions(+), 162 deletions(-) diff --git a/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs b/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs index 5b9a2c7f42e2f..94f43fe50bfa1 100644 --- a/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs +++ b/src/EditorFeatures/Core/Implementation/Classification/CompilationAvailableTaggerEventSource.cs @@ -67,18 +67,6 @@ public void Disconnect() _workQueue.CancelCurrentWork(); } - public event EventHandler UIUpdatesPaused - { - add { _underlyingSource.UIUpdatesPaused += value; } - remove { _underlyingSource.UIUpdatesPaused -= value; } - } - - public event EventHandler UIUpdatesResumed - { - add { _underlyingSource.UIUpdatesResumed += value; } - remove { _underlyingSource.UIUpdatesResumed -= value; } - } - private void OnEventSourceChanged(object? sender, TaggerEventArgs args) { // First, notify anyone listening to us that something definitely changed. diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs index 756c399850226..ad934664b6514 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/AbstractTaggerEventSource.cs @@ -18,16 +18,8 @@ protected AbstractTaggerEventSource(TaggerDelay delay) public abstract void Disconnect(); public event EventHandler? Changed; - public event EventHandler? UIUpdatesPaused; - public event EventHandler? UIUpdatesResumed; protected virtual void RaiseChanged() => this.Changed?.Invoke(this, new TaggerEventArgs(_delay)); - - protected virtual void RaiseUIUpdatesPaused() - => this.UIUpdatesPaused?.Invoke(this, EventArgs.Empty); - - protected virtual void RaiseUIUpdatesResumed() - => this.UIUpdatesResumed?.Invoke(this, EventArgs.Empty); } } diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs index 1de988ab25d25..8bbba9286191a 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.CompositionEventSource.cs @@ -29,41 +29,8 @@ public void Disconnect() public event EventHandler Changed { - add - { - _providers.Do(p => p.Changed += value); - } - - remove - { - _providers.Do(p => p.Changed -= value); - } - } - - public event EventHandler UIUpdatesPaused - { - add - { - _providers.Do(p => p.UIUpdatesPaused += value); - } - - remove - { - _providers.Do(p => p.UIUpdatesPaused -= value); - } - } - - public event EventHandler UIUpdatesResumed - { - add - { - _providers.Do(p => p.UIUpdatesResumed += value); - } - - remove - { - _providers.Do(p => p.UIUpdatesResumed -= value); - } + add => _providers.Do(p => p.Changed += value); + remove => _providers.Do(p => p.Changed -= value); } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs index f3ea8f0c36ee4..3c26655501f9b 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.BatchChangeNotifier.cs @@ -21,9 +21,7 @@ internal abstract partial class AbstractAsynchronousTaggerProvider { /// /// Handles the job of batching up change notifications so that don't spam the editor with too - /// many update requests at a time. Updating the editor can even be paused and resumed at a - /// later point if some feature doesn't want the editor changing while it performs some bit of - /// work. + /// many update requests at a time. /// private class BatchChangeNotifier : ForegroundThreadAffinitizedObject { @@ -49,18 +47,7 @@ private class BatchChangeNotifier : ForegroundThreadAffinitizedObject // instead create a timer that will report the changes and we enqueue any pending updates to // a list that will be updated all at once the timer actually runs. private bool _notificationRequestEnqueued; - private readonly SortedDictionary _snapshotVersionToSpansMap = - new(); - - /// - /// True if we are currently suppressing UI updates. While suppressed we still continue - /// doing everything as normal, except we do not update the UI. Then, when we are no longer - /// suppressed we will issue all pending UI notifications to the editor. During the time - /// that we're suppressed we will respond to all GetTags requests with the tags we had - /// before we were paused. - /// - public bool IsPaused { get; private set; } - private int _lastPausedTime; + private readonly SortedDictionary _snapshotVersionToSpansMap = new(); private readonly Action _notifyEditorNow; @@ -81,21 +68,6 @@ public BatchChangeNotifier( _notifyEditorNow = notifyEditorNow; } - public void Pause() - { - AssertIsForeground(); - - _lastPausedTime = Environment.TickCount; - this.IsPaused = true; - } - - public void Resume() - { - AssertIsForeground(); - _lastPausedTime = Environment.TickCount; - this.IsPaused = false; - } - private static readonly Func s_addFunction = _ => new NormalizedSnapshotSpanCollection(); @@ -169,21 +141,6 @@ private void NotifyEditor() { AssertIsForeground(); - // If we're currently suppressed, then just re-enqueue a request to update in the - // future. - if (this.IsPaused) - { - // TODO(cyrusn): Do we need to make this delay customizable? I don't think we do. - // Pausing is only used for features we don't want to spam the user with (like - // squiggles while the completion list is up. It's ok to have them appear 1.5 - // seconds later once we become un-paused. - if ((Environment.TickCount - _lastPausedTime) < TaggerConstants.IdleDelay) - { - EnqueueNotificationRequest(TaggerDelay.OnIdle); - return; - } - } - using (Logger.LogBlock(FunctionId.Tagger_BatchChangeNotifier_NotifyEditor, CancellationToken.None)) { // Go through and report the snapshots from oldest to newest. @@ -199,9 +156,6 @@ private void NotifyEditor() // Finally, clear out the collection so that we don't re-report spans. _snapshotVersionToSpansMap.Clear(); _lastReportTick = Environment.TickCount; - - // reset paused time - _lastPausedTime = Environment.TickCount; } } } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index b0fcd709f7094..dbe84b89e548c 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -64,11 +64,6 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject /// private readonly ITaggerEventSource _eventSource; - /// - /// During the time that we are paused from updating the UI, we will use these tags instead. - /// - private ImmutableDictionary> _previousCachedTagTrees; - /// /// accumulated text changes since last tag calculation /// @@ -81,9 +76,6 @@ private sealed partial class TagSource : ForegroundThreadAffinitizedObject public event Action>, bool> TagsChangedForBuffer; - public event EventHandler Paused; - public event EventHandler Resumed; - /// /// A cancellation source we use for the initial tagging computation. We only cancel /// if our ref count actually reaches 0. Otherwise, we always try to compute the initial @@ -228,8 +220,6 @@ private void Connect() _workQueue.AssertIsForeground(); _eventSource.Changed += OnEventSourceChanged; - _eventSource.UIUpdatesResumed += OnUIUpdatesResumed; - _eventSource.UIUpdatesPaused += OnUIUpdatesPaused; if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges)) { @@ -269,8 +259,6 @@ public void Disconnect() _subjectBuffer.Changed -= OnSubjectBufferChanged; } - _eventSource.UIUpdatesPaused -= OnUIUpdatesPaused; - _eventSource.UIUpdatesResumed -= OnUIUpdatesResumed; _eventSource.Changed -= OnEventSourceChanged; } @@ -294,12 +282,6 @@ private void RaiseTagsChanged( TagsChangedForBuffer?.Invoke(collection, initialTags); } - private void RaisePaused() - => this.Paused?.Invoke(this, EventArgs.Empty); - - private void RaiseResumed() - => this.Resumed?.Invoke(this, EventArgs.Empty); - private static T NextOrDefault(IEnumerator enumerator) => enumerator.MoveNext() ? enumerator.Current : default; diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs index bc682bd43152b..4ddfbfcb2d407 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource_ProduceTags.cs @@ -44,22 +44,6 @@ internal partial class AbstractAsynchronousTaggerProvider /// private partial class TagSource { - private void OnUIUpdatesPaused(object sender, EventArgs e) - { - _workQueue.AssertIsForeground(); - _previousCachedTagTrees = CachedTagTrees; - - RaisePaused(); - } - - private void OnUIUpdatesResumed(object sender, EventArgs e) - { - _workQueue.AssertIsForeground(); - _previousCachedTagTrees = null; - - RaiseResumed(); - } - private void OnEventSourceChanged(object sender, TaggerEventArgs e) { // First, cancel any previous requests (either still queued, or started). We no longer @@ -634,12 +618,8 @@ public TagSpanIntervalTree TryGetTagIntervalTreeForBuffer(ITextBuffer buff { _workQueue.AssertIsForeground(); - // If we're currently pausing updates to the UI, then just use the tags we had before we - // were paused so that nothing changes. - // // We're on the UI thread, so it's safe to access these variables. - var map = _previousCachedTagTrees ?? this.CachedTagTrees; - map.TryGetValue(buffer, out var tags); + this.CachedTagTrees.TryGetValue(buffer, out var tags); return tags; } diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs index 83acf6f63d9d5..b8637ca74f9e6 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.Tagger.cs @@ -72,8 +72,6 @@ public Tagger( _tagSource.OnTaggerAdded(this); _tagSource.TagsChangedForBuffer += OnTagsChangedForBuffer; - _tagSource.Paused += OnPaused; - _tagSource.Resumed += OnResumed; // There is a many-to-one relationship between Taggers and TagSources. i.e. one // tag-source can be used by many Taggers. As such, we may be a tagger that is @@ -113,18 +111,10 @@ public void Dispose() _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); - _tagSource.Resumed -= OnResumed; - _tagSource.Paused -= OnPaused; _tagSource.TagsChangedForBuffer -= OnTagsChangedForBuffer; _tagSource.OnTaggerDisposed(this); } - private void OnPaused(object sender, EventArgs e) - => _batchChangeNotifier.Pause(); - - private void OnResumed(object sender, EventArgs e) - => _batchChangeNotifier.Resume(); - private void OnTagsChangedForBuffer( ICollection> changes, bool initialTags) { diff --git a/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs b/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs index 5675089f5c344..213693081c93c 100644 --- a/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs +++ b/src/EditorFeatures/Core/Tagging/ITaggerEventSource.cs @@ -34,15 +34,5 @@ internal interface ITaggerEventSource /// recompute tags. /// event EventHandler Changed; - - /// - /// The tagger should stop updating the UI with the tags it's produced. - /// - event EventHandler UIUpdatesPaused; - - /// - /// The tagger can start notifying the UI about its tags again. - /// - event EventHandler UIUpdatesResumed; } } From 3238600de194a3a419b9c04c8a97a60b294f8926 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Sat, 17 Apr 2021 23:10:00 -0700 Subject: [PATCH 211/220] Remove --- .../TaggerEventSources.ViewSpanChangedEventSource.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs index 9bc675e8b7cc6..b4190b4c9909a 100644 --- a/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs +++ b/src/EditorFeatures/Core/Shared/Tagging/EventSources/TaggerEventSources.ViewSpanChangedEventSource.cs @@ -26,8 +26,6 @@ private class ViewSpanChangedEventSource : ITaggerEventSource private ITextSnapshot? _viewVisualSnapshot; public event EventHandler? Changed; - public event EventHandler UIUpdatesPaused { add { } remove { } } - public event EventHandler UIUpdatesResumed { add { } remove { } } public ViewSpanChangedEventSource(IThreadingContext threadingContext, ITextView textView, TaggerDelay textChangeDelay, TaggerDelay scrollChangeDelay) { From 4b1cd2124ccc13703aa90d847da8c0d9c47cb06f Mon Sep 17 00:00:00 2001 From: Jared Parsons Date: Mon, 19 Apr 2021 00:21:47 -0700 Subject: [PATCH 212/220] Make ExitingTraceListener log visible (#52719) --- src/Compilers/CSharp/csc/Program.cs | 7 ++--- .../Core/CommandLine/CompilerServerLogger.cs | 2 +- .../Server/VBCSCompiler/VBCSCompiler.cs | 2 +- src/Compilers/Shared/ExitingTraceListener.cs | 27 ++++++++----------- src/Compilers/VisualBasic/vbc/Program.cs | 7 ++--- 5 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Compilers/CSharp/csc/Program.cs b/src/Compilers/CSharp/csc/Program.cs index 0b6bb4b6a2784..9028512d5428b 100644 --- a/src/Compilers/CSharp/csc/Program.cs +++ b/src/Compilers/CSharp/csc/Program.cs @@ -29,12 +29,13 @@ public static int Main(string[] args) private static int MainCore(string[] args) { + var requestId = Guid.NewGuid(); + using var logger = new CompilerServerLogger($"csc {requestId}"); + #if BOOTSTRAP - ExitingTraceListener.Install(); + ExitingTraceListener.Install(logger); #endif - var requestId = Guid.NewGuid(); - using var logger = new CompilerServerLogger($"csc {requestId}"); return BuildClient.Run(args, RequestLanguage.CSharpCompile, Csc.Run, logger, requestId); } diff --git a/src/Compilers/Core/CommandLine/CompilerServerLogger.cs b/src/Compilers/Core/CommandLine/CompilerServerLogger.cs index 75a261b22c195..1deb60a84c9e3 100644 --- a/src/Compilers/Core/CommandLine/CompilerServerLogger.cs +++ b/src/Compilers/Core/CommandLine/CompilerServerLogger.cs @@ -112,7 +112,6 @@ internal sealed class CompilerServerLogger : ICompilerServerLogger, IDisposable public CompilerServerLogger(string identifier) { _identifier = identifier; - var processId = Process.GetCurrentProcess().Id; try { @@ -124,6 +123,7 @@ public CompilerServerLogger(string identifier) // Otherwise, assume that the environment variable specifies the name of the log file. if (Directory.Exists(loggingFileName)) { + var processId = Process.GetCurrentProcess().Id; loggingFileName = Path.Combine(loggingFileName, $"server.{processId}.log"); } diff --git a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs index e0c4c4dc7c508..a67d4e8d767eb 100644 --- a/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs +++ b/src/Compilers/Server/VBCSCompiler/VBCSCompiler.cs @@ -19,7 +19,7 @@ public static int Main(string[] args) try { #if BOOTSTRAP - ExitingTraceListener.Install(); + ExitingTraceListener.Install(logger); #endif #if NET472 diff --git a/src/Compilers/Shared/ExitingTraceListener.cs b/src/Compilers/Shared/ExitingTraceListener.cs index 4c4acc178c93e..5c783c0384c1c 100644 --- a/src/Compilers/Shared/ExitingTraceListener.cs +++ b/src/Compilers/Shared/ExitingTraceListener.cs @@ -18,6 +18,13 @@ namespace Microsoft.CodeAnalysis.CommandLine /// internal sealed class ExitingTraceListener : TraceListener { + internal ICompilerServerLogger Logger { get; } + + internal ExitingTraceListener(ICompilerServerLogger logger) + { + Logger = logger; + } + public override void Write(string message) { Exit(message); @@ -28,13 +35,13 @@ public override void WriteLine(string message) Exit(message); } - internal static void Install() + internal static void Install(ICompilerServerLogger logger) { Trace.Listeners.Clear(); - Trace.Listeners.Add(new ExitingTraceListener()); + Trace.Listeners.Add(new ExitingTraceListener(logger)); } - private static void Exit(string originalMessage) + private void Exit(string originalMessage) { var builder = new StringBuilder(); builder.AppendLine($"Debug.Assert failed with message: {originalMessage}"); @@ -43,21 +50,9 @@ private static void Exit(string originalMessage) builder.AppendLine(stackTrace.ToString()); var message = builder.ToString(); - var logFullName = GetLogFileFullName(); - File.WriteAllText(logFullName, message); - - Console.WriteLine(message); - Console.WriteLine($"Log at: {logFullName}"); + Logger.Log(message); Environment.Exit(1); } - - private static string GetLogFileFullName() - { - var assembly = typeof(ExitingTraceListener).Assembly; - var name = $"{Path.GetFileName(assembly.Location)}.tracelog"; - var path = Path.GetDirectoryName(assembly.Location); - return Path.Combine(path, name); - } } } diff --git a/src/Compilers/VisualBasic/vbc/Program.cs b/src/Compilers/VisualBasic/vbc/Program.cs index ee7f9061dade8..0e9131c13210b 100644 --- a/src/Compilers/VisualBasic/vbc/Program.cs +++ b/src/Compilers/VisualBasic/vbc/Program.cs @@ -29,12 +29,13 @@ public static int Main(string[] args) private static int MainCore(string[] args) { + var requestId = Guid.NewGuid(); + using var logger = new CompilerServerLogger($"vbc {requestId}"); + #if BOOTSTRAP - ExitingTraceListener.Install(); + ExitingTraceListener.Install(logger); #endif - var requestId = Guid.NewGuid(); - using var logger = new CompilerServerLogger($"vbc {requestId}"); return BuildClient.Run(args, RequestLanguage.VisualBasicCompile, Vbc.Run, logger, requestId); } From 500735cc955b55d9b0bba0753c48089b56fd9233 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 19 Apr 2021 01:52:03 -0700 Subject: [PATCH 213/220] Ensure that we register to analyzer generated code for MakeFieldReadOnly analyzer Fixes #50925 Note that we only pass in the `Analyze` flag, not the `ReportDiagnostics` flag, so that fields defined in generated code are not flagged. --- .../MakeFieldReadonlyTests.cs | 33 +++++++++++++++++++ ...BuiltInCodeStyleDiagnosticAnalyzer_Core.cs | 6 ++-- .../MakeFieldReadonlyDiagnosticAnalyzer.cs | 3 ++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs b/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs index 58a279a732dec..811f51493f746 100644 --- a/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs +++ b/src/Analyzers/CSharp/Tests/MakeFieldReadonly/MakeFieldReadonlyTests.cs @@ -1746,5 +1746,38 @@ class Program private static object [|t_obj|]; }"); } + + [WorkItem(50925, "https://github.com/dotnet/roslyn/issues/50925")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsMakeFieldReadonly)] + public async Task Test_MemberUsedInGeneratedCode() + { + await TestMissingInRegularAndScriptAsync( +@" + + +public sealed partial class Test +{ + private int [|_value|]; + + public static void M() + => _ = new Test { Value = 1 }; +} + + +using System.CodeDom.Compiler; + +[GeneratedCode(null, null)] +public sealed partial class Test +{ + public int Value + { + get => _value; + set => _value = value; + } +} + + +"); + } } } diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs index 5793dc9e32a43..c34f2c550297a 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs @@ -65,10 +65,12 @@ protected static DiagnosticDescriptor CreateDescriptorWithId( customTags: DiagnosticCustomTags.Create(isUnnecessary, isConfigurable, enforceOnBuild)); #pragma warning restore RS0030 // Do not used banned APIs + // Code style analyzers should not run on generated code, unless explicitly required by the analyzer. + protected virtual GeneratedCodeAnalysisFlags GeneratedCodeAnalysisFlags => GeneratedCodeAnalysisFlags.None; + public sealed override void Initialize(AnalysisContext context) { - // Code style analyzers should not run on generated code. - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags); context.EnableConcurrentExecution(); InitializeWorker(context); diff --git a/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs index 273404db77b94..ccf362a7ab32d 100644 --- a/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs @@ -31,6 +31,9 @@ public MakeFieldReadonlyDiagnosticAnalyzer() public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; + // We need to analyze generated code to get callbacks for read/writes to non-generated members in generated code. + protected override GeneratedCodeAnalysisFlags GeneratedCodeAnalysisFlags => GeneratedCodeAnalysisFlags.Analyze; + protected override void InitializeWorker(AnalysisContext context) { context.RegisterCompilationStartAction(compilationStartContext => From de17f29dd427abc78bfd6ec295a820948d88ef28 Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Mon, 19 Apr 2021 03:56:18 -0700 Subject: [PATCH 214/220] Address feedback --- .../AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs | 10 +++++++--- .../MakeFieldReadonlyDiagnosticAnalyzer.cs | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs index c34f2c550297a..8d0a0b3688a4f 100644 --- a/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs +++ b/src/Analyzers/Core/Analyzers/AbstractBuiltInCodeStyleDiagnosticAnalyzer_Core.cs @@ -65,12 +65,16 @@ protected static DiagnosticDescriptor CreateDescriptorWithId( customTags: DiagnosticCustomTags.Create(isUnnecessary, isConfigurable, enforceOnBuild)); #pragma warning restore RS0030 // Do not used banned APIs - // Code style analyzers should not run on generated code, unless explicitly required by the analyzer. - protected virtual GeneratedCodeAnalysisFlags GeneratedCodeAnalysisFlags => GeneratedCodeAnalysisFlags.None; + /// + /// Flag indicating whether or not analyzer should receive analysis callbacks for generated code. + /// By default, code style analyzers should not run on generated code, so the value is false. + /// + protected virtual bool ReceiveAnalysisCallbacksForGeneratedCode => false; public sealed override void Initialize(AnalysisContext context) { - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags); + var flags = ReceiveAnalysisCallbacksForGeneratedCode ? GeneratedCodeAnalysisFlags.Analyze : GeneratedCodeAnalysisFlags.None; + context.ConfigureGeneratedCodeAnalysis(flags); context.EnableConcurrentExecution(); InitializeWorker(context); diff --git a/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs index ccf362a7ab32d..af266f53e39ad 100644 --- a/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/MakeFieldReadonly/MakeFieldReadonlyDiagnosticAnalyzer.cs @@ -32,7 +32,7 @@ public MakeFieldReadonlyDiagnosticAnalyzer() public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis; // We need to analyze generated code to get callbacks for read/writes to non-generated members in generated code. - protected override GeneratedCodeAnalysisFlags GeneratedCodeAnalysisFlags => GeneratedCodeAnalysisFlags.Analyze; + protected override bool ReceiveAnalysisCallbacksForGeneratedCode => true; protected override void InitializeWorker(AnalysisContext context) { From 13a4a8ff5fb9d265afc16180cbd31938d2b6e3c7 Mon Sep 17 00:00:00 2001 From: "dotnet-maestro[bot]" <42748379+dotnet-maestro[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 13:55:10 +0000 Subject: [PATCH 215/220] [main] Update dependencies from dotnet/roslyn (#52570) [main] Update dependencies from dotnet/roslyn --- eng/Version.Details.xml | 4 ++-- eng/Versions.props | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 32aeb91d1c8bc..d27b92ebb2d71 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -7,9 +7,9 @@ https://github.com/dotnet/arcade 53fe29e220fc0db05eafd5c6bc6c8fb9ee7cec7c - + https://github.com/dotnet/roslyn - 31a3e4904f5a02ee51dbe15d7a68daaac9b5c6a8 + 9bfe989ad46e3f457d33ed800c3be012cec5afc6 https://github.com/dotnet/arcade diff --git a/eng/Versions.props b/eng/Versions.props index d0fdfc39d2bad..40f68cd6127fa 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -23,7 +23,7 @@ - 3.10.0-3.21202.20 + 3.10.0-3.21216.10 From 16121b16d09eca62cc541683cb2132903a135547 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 19 Apr 2021 17:00:44 +0200 Subject: [PATCH 216/220] Cleanup --- .../DocumentationCommentCommandHandler.cs | 2 +- .../AbstractDocumentationCommentCommandHandler.cs | 4 +--- .../DocumentationCommentCommandHandler.vb | 10 ++++------ 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs b/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs index 839a690ee1583..14f93489cfd0b 100644 --- a/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs +++ b/src/EditorFeatures/CSharp/DocumentationComments/DocumentationCommentCommandHandler.cs @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.DocumentationComments [Order(After = PredefinedCommandHandlerNames.Rename)] [Order(After = PredefinedCompletionNames.CompletionCommandHandler)] internal class DocumentationCommentCommandHandler - : AbstractDocumentationCommentCommandHandler + : AbstractDocumentationCommentCommandHandler { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] diff --git a/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs b/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs index 3bb7d4069025c..01e36699d5b73 100644 --- a/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs +++ b/src/EditorFeatures/Core/Implementation/DocumentationComments/AbstractDocumentationCommentCommandHandler.cs @@ -20,14 +20,12 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.DocumentationComments { - internal abstract class AbstractDocumentationCommentCommandHandler : + internal abstract class AbstractDocumentationCommentCommandHandler : IChainedCommandHandler, ICommandHandler, ICommandHandler, IChainedCommandHandler, IChainedCommandHandler - where TDocumentationComment : SyntaxNode, IStructuredTriviaSyntax - where TMemberNode : SyntaxNode { private readonly IWaitIndicator _waitIndicator; private readonly ITextUndoHistoryRegistry _undoHistoryRegistry; diff --git a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb index 81f3d9f4fd8e6..2ec934a5d469a 100644 --- a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb @@ -3,10 +3,8 @@ ' See the LICENSE file in the project root for more information. Imports System.ComponentModel.Composition -Imports System.Diagnostics.CodeAnalysis -Imports Microsoft.CodeAnalysis.Editor.Host +Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Editor.Implementation.DocumentationComments -Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Commanding Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion Imports Microsoft.VisualStudio.Text.Operations @@ -19,10 +17,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.DocumentationComments Friend Class DocumentationCommentCommandHandler - Inherits AbstractDocumentationCommentCommandHandler(Of DocumentationCommentTriviaSyntax, DeclarationStatementSyntax) + Inherits AbstractDocumentationCommentCommandHandler - - + + Public Sub New( waitIndicator As IWaitIndicator, undoHistoryRegistry As ITextUndoHistoryRegistry, From 6f0ab27f3a42059ac87df55bacdfa4aa36bd8ac4 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 19 Apr 2021 17:23:50 +0200 Subject: [PATCH 217/220] Fix --- .../DocumentationComments/DocumentationCommentCommandHandler.vb | 1 + 1 file changed, 1 insertion(+) diff --git a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb index 2ec934a5d469a..fcc6d3ba56b0c 100644 --- a/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/DocumentationComments/DocumentationCommentCommandHandler.vb @@ -4,6 +4,7 @@ Imports System.ComponentModel.Composition Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.Editor.Host Imports Microsoft.CodeAnalysis.Editor.Implementation.DocumentationComments Imports Microsoft.VisualStudio.Commanding Imports Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion From 9898e1cc82f209eb2a57a9484fab5a6a2a7bae43 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 19 Apr 2021 12:01:12 -0700 Subject: [PATCH 218/220] Make method private --- .../Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs index a881008b10b50..fbfeb1e146d35 100644 --- a/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs +++ b/src/EditorFeatures/Core/Tagging/AbstractAsynchronousTaggerProvider.TagSource.cs @@ -237,7 +237,7 @@ private void Connect() _eventSource.Connect(); } - public void Disconnect() + private void Disconnect() { _workQueue.AssertIsForeground(); _workQueue.CancelCurrentWork(remainCancelled: true); From d7236c63b90e1eef6ad089779ad5a61f2ebca272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Mon, 19 Apr 2021 12:51:43 -0700 Subject: [PATCH 219/220] Use compile-time solution for solution crawler and EnC (#52537) * Use compile-time solution for solution crawler and EnC * Only remove design-time docs when enc config file is present * Only enable when Razor LSP editor is enabled --- .../SolutionCrawlerRegistrationService.cs | 4 +- .../WorkCoordinator.HighPriorityProcessor.cs | 2 +- ...oordinator.IncrementalAnalyzerProcessor.cs | 3 +- .../WorkCoordinator.LowPriorityProcessor.cs | 2 +- ...WorkCoordinator.NormalPriorityProcessor.cs | 11 +- ...WorkCoordinator.SemanticChangeProcessor.cs | 4 +- .../SolutionCrawler/WorkCoordinator.cs | 14 +- .../Workspace/CompileTimeSolutionProvider.cs | 125 ++++++++++++++++++ .../Workspace/ICompileTimeSolutionProvider.cs | 16 +++ .../ManagedEditAndContinueLanguageService.cs | 16 ++- .../Experiments/IExperimentationService.cs | 1 + 11 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs create mode 100644 src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs diff --git a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs index aa51f79e56732..a976f6b0d22f8 100644 --- a/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs +++ b/src/Features/Core/Portable/SolutionCrawler/SolutionCrawlerRegistrationService.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -306,7 +307,8 @@ public Registration(int correlationId, Workspace workspace, SolutionCrawlerProgr ProgressReporter = progressReporter; } - public Solution CurrentSolution => Workspace.CurrentSolution; + public Solution GetSolutionToAnalyze() + => Workspace.Services.GetRequiredService().GetCurrentCompileTimeSolution(); } } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index edcb55e313bd0..78fc49ca54d23 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -135,7 +135,7 @@ protected override async Task ExecuteAsync() // see whether we have work item for the document Contract.ThrowIfFalse(GetNextWorkItem(out var workItem, out var documentCancellation)); - var solution = _processor.CurrentSolution; + var solution = _processor._registration.GetSolutionToAnalyze(); // okay now we have work to do await ProcessDocumentAsync(solution, Analyzers, workItem, documentCancellation).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index a8380d1682143..fd82c0bbab9b8 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -204,8 +204,7 @@ public void Shutdown() public ImmutableArray Analyzers => _normalPriorityProcessor.Analyzers; - private Solution CurrentSolution => _registration.CurrentSolution; - private ProjectDependencyGraph DependencyGraph => CurrentSolution.GetProjectDependencyGraph(); + private ProjectDependencyGraph DependencyGraph => _registration.GetSolutionToAnalyze().GetProjectDependencyGraph(); private IDiagnosticAnalyzerService? DiagnosticAnalyzerService => _lazyDiagnosticAnalyzerService?.Value; public Task AsyncProcessorTask diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index 3c4eb2fca1e26..4d85e49dd4577 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -126,7 +126,7 @@ private async Task ProcessProjectAsync(ImmutableArray anal // we do have work item for this project var projectId = workItem.ProjectId; var processedEverything = false; - var processingSolution = Processor.CurrentSolution; + var processingSolution = Processor._registration.GetSolutionToAnalyze(); try { diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 6794c08832399..ca88e5392bb8d 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -327,7 +327,7 @@ private async Task ProcessDocumentAsync(ImmutableArray ana // using later version of solution is always fine since, as long as there is new work item in the queue, // solution crawler will eventually call the last workitem with the lastest solution // making everything to catch up - var solution = Processor.CurrentSolution; + var solution = Processor._registration.GetSolutionToAnalyze(); try { using (Logger.LogBlock(FunctionId.WorkCoordinator_ProcessDocumentAsync, w => w.ToString(), workItem, cancellationToken)) @@ -522,7 +522,10 @@ private async Task ResetStatesAsync() return; } - await Processor.RunAnalyzersAsync(Analyzers, Processor.CurrentSolution, workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); + await Processor.RunAnalyzersAsync( + Analyzers, + Processor._registration.GetSolutionToAnalyze(), + workItem: new WorkItem(), (a, s, c) => a.NewSolutionSnapshotAsync(s, c), CancellationToken).ConfigureAwait(false); foreach (var id in Processor.GetOpenDocumentIds()) { @@ -538,7 +541,7 @@ private async Task ResetStatesAsync() bool IsSolutionChanged() { - var currentSolution = Processor.CurrentSolution; + var currentSolution = Processor._registration.GetSolutionToAnalyze(); var oldSolution = _lastSolution; if (currentSolution == oldSolution) @@ -577,7 +580,7 @@ public override void Shutdown() { base.Shutdown(); - SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor.CurrentSolution, Processor._logAggregator, Analyzers); + SolutionCrawlerLogger.LogIncrementalAnalyzerProcessorStatistics(Processor._registration.CorrelationId, Processor._registration.GetSolutionToAnalyze(), Processor._logAggregator, Analyzers); _workItemQueue.Dispose(); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs index 2940db02c61bb..cc1ab544caf6e 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs @@ -417,7 +417,7 @@ protected override async Task ExecuteAsync() using (data.AsyncToken) { - var project = _registration.CurrentSolution.GetProject(data.ProjectId); + var project = _registration.GetSolutionToAnalyze().GetProject(data.ProjectId); if (project == null) { return; @@ -430,7 +430,7 @@ protected override async Task ExecuteAsync() } // do dependency tracking here with current solution - var solution = _registration.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); foreach (var projectId in GetProjectsToAnalyze(solution, data.ProjectId)) { project = solution.GetProject(projectId); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 670561dfe1c0c..cb37a85992512 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -87,7 +87,7 @@ public WorkCoordinator( // subscribe to active document changed event for active file background analysis scope. if (_documentTrackingService != null) { - _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.Workspace.CurrentSolution); + _lastActiveDocument = _documentTrackingService.GetActiveDocument(_registration.GetSolutionToAnalyze()); _documentTrackingService.ActiveDocumentChanged += OnActiveDocumentChanged; } } @@ -100,7 +100,7 @@ public void AddAnalyzer(IIncrementalAnalyzer analyzer, bool highPriorityForActiv _documentAndProjectWorkerProcessor.AddAnalyzer(analyzer, highPriorityForActiveFile); // and ask to re-analyze whole solution for the given analyzer - var scope = new ReanalyzeScope(_registration.CurrentSolution.Id); + var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); } @@ -196,7 +196,7 @@ private void ReanalyzeOnOptionChange(object? sender, OptionChangedEventArgs e) { if (forceAnalyze || analyzer.NeedsReanalysisOnOptionChanged(sender, e)) { - var scope = new ReanalyzeScope(_registration.CurrentSolution.Id); + var scope = new ReanalyzeScope(_registration.GetSolutionToAnalyze().Id); Reanalyze(analyzer, scope); } } @@ -212,7 +212,7 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool { // log big reanalysis request from things like fix all, suppress all or option changes // we are not interested in 1 file re-analysis request which can happen from like venus typing - var solution = _registration.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); SolutionCrawlerLogger.LogReanalyze( CorrelationId, analyzer, scope.GetDocumentCount(solution), scope.GetLanguagesStringForTelemetry(solution), highPriority); } @@ -220,7 +220,7 @@ public void Reanalyze(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool private void OnActiveDocumentChanged(object? sender, DocumentId activeDocumentId) { - var solution = _registration.Workspace.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); // Check if we are only performing backgroung analysis for active file. if (activeDocumentId != null) @@ -509,7 +509,7 @@ private async Task EnqueueWorkItemAsync(Project project, InvocationReasons invoc private async Task EnqueueWorkItemAsync(IIncrementalAnalyzer analyzer, ReanalyzeScope scope, bool highPriority) { - var solution = _registration.CurrentSolution; + var solution = _registration.GetSolutionToAnalyze(); var invocationReasons = highPriority ? InvocationReasons.ReanalyzeHighPriority : InvocationReasons.Reanalyze; foreach (var document in scope.GetDocuments(solution)) @@ -683,7 +683,7 @@ internal TestAccessor(WorkCoordinator workCoordinator) internal void WaitUntilCompletion(ImmutableArray workers) { - var solution = _workCoordinator._registration.CurrentSolution; + var solution = _workCoordinator._registration.GetSolutionToAnalyze(); var list = new List(); foreach (var project in solution.Projects) diff --git a/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs new file mode 100644 index 0000000000000..387a15328a824 --- /dev/null +++ b/src/Features/Core/Portable/Workspace/CompileTimeSolutionProvider.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Experiments; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices +{ + /// + /// Provides a compile-time view of the current workspace solution. + /// Workaround for Razor projects which generate both design-time and compile-time source files. + /// TODO: remove https://github.com/dotnet/roslyn/issues/51678 + /// + internal sealed class CompileTimeSolutionProvider : ICompileTimeSolutionProvider + { + [ExportWorkspaceServiceFactory(typeof(ICompileTimeSolutionProvider), WorkspaceKind.Host), Shared] + private sealed class Factory : IWorkspaceServiceFactory + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory() + { + } + + [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] + public IWorkspaceService? CreateService(HostWorkspaceServices workspaceServices) + => new CompileTimeSolutionProvider(workspaceServices.Workspace); + } + + private const string RazorEncConfigFileName = "RazorSourceGenerator.razorencconfig"; + + private readonly Workspace _workspace; + private readonly object _gate = new(); + + private Solution? _lazyCompileTimeSolution; + private int? _correspondingDesignTimeSolutionVersion; + private readonly bool _enabled; + + public CompileTimeSolutionProvider(Workspace workspace) + { + _workspace = workspace; + _enabled = workspace.Services.GetRequiredService().IsExperimentEnabled(WellKnownExperimentNames.RazorLspEditorFeatureFlag); + + workspace.WorkspaceChanged += (s, e) => + { + if (e.Kind is WorkspaceChangeKind.SolutionCleared or WorkspaceChangeKind.SolutionRemoved) + { + lock (_gate) + { + _lazyCompileTimeSolution = null; + _correspondingDesignTimeSolutionVersion = null; + } + } + }; + } + + private static bool IsRazorAnalyzerConfig(TextDocumentState documentState) + => documentState.FilePath != null && documentState.FilePath.EndsWith(RazorEncConfigFileName, StringComparison.OrdinalIgnoreCase); + + public Solution GetCurrentCompileTimeSolution() + { + if (!_enabled) + { + return _workspace.CurrentSolution; + } + + lock (_gate) + { + var currentDesignTimeSolution = _workspace.CurrentSolution; + + // Design time solution hasn't changed since we calculated the last compile-time solution: + if (currentDesignTimeSolution.WorkspaceVersion == _correspondingDesignTimeSolutionVersion) + { + Contract.ThrowIfNull(_lazyCompileTimeSolution); + return _lazyCompileTimeSolution; + } + + using var _1 = ArrayBuilder.GetInstance(out var configIdsToRemove); + using var _2 = ArrayBuilder.GetInstance(out var documentIdsToRemove); + + var compileTimeSolution = currentDesignTimeSolution; + + foreach (var (_, projectState) in currentDesignTimeSolution.State.ProjectStates) + { + var anyConfigs = false; + + foreach (var configState in projectState.AnalyzerConfigDocumentStates.States) + { + if (IsRazorAnalyzerConfig(configState)) + { + configIdsToRemove.Add(configState.Id); + anyConfigs = true; + } + } + + // only remove design-time only documents when source-generated ones replace them + if (anyConfigs) + { + foreach (var documentState in projectState.DocumentStates.States) + { + if (documentState.Attributes.DesignTimeOnly) + { + documentIdsToRemove.Add(documentState.Id); + } + } + } + } + + _lazyCompileTimeSolution = currentDesignTimeSolution + .RemoveAnalyzerConfigDocuments(configIdsToRemove.ToImmutable()) + .RemoveDocuments(documentIdsToRemove.ToImmutable()); + + _correspondingDesignTimeSolutionVersion = currentDesignTimeSolution.WorkspaceVersion; + return _lazyCompileTimeSolution; + } + } + } +} diff --git a/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs new file mode 100644 index 0000000000000..8f70c0af00a45 --- /dev/null +++ b/src/Features/Core/Portable/Workspace/ICompileTimeSolutionProvider.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.Host +{ + /// + /// Provides a compile-time view of the current workspace solution. + /// Workaround for Razor projects which generate both design-time and compile-time source files. + /// TODO: remove https://github.com/dotnet/roslyn/issues/51678 + /// + internal interface ICompileTimeSolutionProvider : IWorkspaceService + { + Solution GetCurrentCompileTimeSolution(); + } +} diff --git a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs index 42d1d646e0f75..c518cc6f64dfb 100644 --- a/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs +++ b/src/VisualStudio/Core/Def/Implementation/EditAndContinue/ManagedEditAndContinueLanguageService.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.Editor.Implementation.EditAndContinue; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; @@ -52,6 +53,9 @@ public ManagedEditAndContinueLanguageService( _diagnosticUpdateSource = diagnosticUpdateSource; } + private Solution GetCurrentCompileTimeSolution() + => _proxy.Workspace.Services.GetRequiredService().GetCurrentCompileTimeSolution(); + /// /// Called by the debugger when a debugging session starts and managed debugging is being used. /// @@ -67,7 +71,7 @@ public async Task StartDebuggingAsync(DebugSessionFlags flags, CancellationToken try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); _debuggingSessionConnection = await _proxy.StartDebuggingSessionAsync(solution, _debuggerService, captureMatchingDocuments: false, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) @@ -85,7 +89,7 @@ public async Task EnterBreakStateAsync(CancellationToken cancellationToken) return; } - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); try { @@ -175,7 +179,7 @@ public async Task HasChangesAsync(string? sourceFilePath, CancellationToke { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); return await _proxy.HasChangesAsync(solution, activeStatementSpanProvider, sourceFilePath, cancellationToken).ConfigureAwait(false); } @@ -189,7 +193,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = GetActiveStatementSpanProvider(solution); var (updates, _, _) = await _proxy.EmitSolutionUpdateAsync(solution, activeStatementSpanProvider, _diagnosticService, _diagnosticUpdateSource, cancellationToken).ConfigureAwait(false); return updates; @@ -204,7 +208,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); var activeStatementSpanProvider = new SolutionActiveStatementSpanProvider(async (documentId, cancellationToken) => { @@ -225,7 +229,7 @@ public async Task GetManagedModuleUpdatesAsync(Cancellatio { try { - var solution = _proxy.Workspace.CurrentSolution; + var solution = GetCurrentCompileTimeSolution(); return await _proxy.IsActiveStatementInExceptionRegionAsync(solution, instruction, cancellationToken).ConfigureAwait(false); } catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, cancellationToken)) diff --git a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs index bd8a5eff7d1d8..b4a32ecc1d2d4 100644 --- a/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs +++ b/src/Workspaces/Core/Portable/Experiments/IExperimentationService.cs @@ -38,5 +38,6 @@ internal static class WellKnownExperimentNames public const string RemoveUnusedReferences = "Roslyn.RemoveUnusedReferences"; public const string LSPCompletion = "Roslyn.LSP.Completion"; public const string UnnamedSymbolCompletionDisabled = "Roslyn.UnnamedSymbolCompletionDisabled"; + public const string RazorLspEditorFeatureFlag = "Razor.LSP.Editor"; } } From 3abea95fa70ae426e50397515f665f80bb33d5a9 Mon Sep 17 00:00:00 2001 From: Julien Couvreur Date: Mon, 19 Apr 2021 14:54:52 -0700 Subject: [PATCH 220/220] Error for using positional member that is hidden (#52660) --- ...mpiler Breaking Changes - post DotNet 5.md | 14 + .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 3 +- .../Source/SourceMemberContainerSymbol.cs | 16 +- .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Test/Semantic/Semantics/RecordTests.cs | 352 +++++++++++++++++- 18 files changed, 449 insertions(+), 4 deletions(-) diff --git a/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md b/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md index 852599b639911..bc0f753f5e143 100644 --- a/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md +++ b/docs/compilers/CSharp/Compiler Breaking Changes - post DotNet 5.md @@ -34,3 +34,17 @@ return a ?? (b ? 1 : 2); } ``` + +3. https://github.com/dotnet/roslyn/issues/52630 In C# 9 (.NET 5, Visual Studio 16.9), it is possible that a record uses a hidden member from a base type as a positional member. In Visual Studio 16.10, this is now an error: +```csharp +record Base +{ + public int I { get; init; } +} +record Derived(int I) // The positional member 'Base.I' found corresponding to this parameter is hidden. + : Base +{ + public int I() { return 0; } +} +``` + diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 539c4c0801cd7..c6c2d21453b20 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6606,4 +6606,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Inheriting from a record with a sealed 'Object.ToString' is not supported in C# {0}. Please use language version '{1}' or greater. + + The positional member '{0}' found corresponding to this parameter is hidden. + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index a0856446e23a4..c0d15cec19080 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1934,7 +1934,8 @@ internal enum ErrorCode #region diagnostics introduced for C# 10.0 - ERR_InheritingFromRecordWithSealedToString = 8912 + ERR_InheritingFromRecordWithSealedToString = 8912, + ERR_HiddenPositionalMember = 8913, #endregion diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index bf44e77a5e295..e3914e321208b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -3410,8 +3410,11 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde var memberSignatures = s_duplicateRecordMemberSignatureDictionary.Allocate(); var membersSoFar = builder.GetNonTypeMembers(declaredMembersAndInitializers); var members = ArrayBuilder.GetInstance(membersSoFar.Count + 1); + var memberNames = PooledHashSet.GetInstance(); foreach (var member in membersSoFar) { + memberNames.Add(member.Name); + switch (member) { case FieldSymbol: @@ -3467,6 +3470,7 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde addToStringMethod(printMembers); memberSignatures.Free(); + memberNames.Free(); // We put synthesized record members first so that errors about conflicts show up on user-defined members rather than all // going to the record declaration @@ -3731,7 +3735,7 @@ ImmutableArray addProperties(ImmutableArray rec { addProperty(new SynthesizedRecordPropertySymbol(this, syntax, param, isOverride: true, diagnostics)); } - else + else if (!isInherited || checkMemberNotHidden(prop, param)) { // Deconstruct() is specified to simply assign from this property to the corresponding out parameter. existingOrAddedMembers.Add(prop); @@ -3762,6 +3766,16 @@ void addProperty(SynthesizedRecordPropertySymbol property) } return existingOrAddedMembers.ToImmutableAndFree(); + + bool checkMemberNotHidden(Symbol symbol, ParameterSymbol param) + { + if (memberNames.Contains(symbol.Name) || this.GetTypeMembersDictionary().ContainsKey(symbol.Name)) + { + diagnostics.Add(ErrorCode.ERR_HiddenPositionalMember, param.Locations[0], symbol); + return false; + } + return true; + } } void addObjectEquals(MethodSymbol thisEquals) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index ac1a9531bd044..f5990e8748d8c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -447,6 +447,11 @@ Příkaz goto nemůže přejít na místo za deklarací using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context Operátor potlačení není v tomto kontextu povolený. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index c13857bef8189..74c48737848cb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -447,6 +447,11 @@ Mit "goto" kann nicht an eine Position hinter einer using-Deklaration gesprungen werden. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context Ein Unterdrückungsoperator ist in diesem Kontext unzulässig. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index e77b6c0174b94..534a76879d5ca 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -447,6 +447,11 @@ Una instrucción goto no puede saltar a una ubicación después de una declaración using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context No se permite el operador de supresión en este contexto. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 09aa035471de8..820966f5244b8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -447,6 +447,11 @@ Un goto ne peut pas accéder à un emplacement après une déclaration using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context L'opérateur de suppression n'est pas autorisé dans ce contexte diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 23ba661cefe46..f9582662f277f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -447,6 +447,11 @@ Un'istruzione goto non può passare a una posizione successiva a una dichiarazione using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context L'operatore di eliminazione non è consentito in questo contesto diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index b514b4403a277..d97f45b1dc944 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -447,6 +447,11 @@ goto は using 宣言より後の位置にはジャンプできません。 + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context このコンテキストでは抑制演算子が許可されていません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 4d1e5edcf9666..b966a42221dac 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -447,6 +447,11 @@ goto는 using 선언 뒤 위치로 이동할 수 없습니다. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context 이 컨텍스트에서는 비표시 오류(Suppression) 연산자를 사용할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 761e1ae2d9251..9c5826f148d4a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -447,6 +447,11 @@ Instrukcja goto nie może przechodzić do lokalizacji występującej po deklaracji using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context Operator pominięcia jest niedozwolony w tym kontekście diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 349c28c45bc43..00b3e8edc4e1f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -447,6 +447,11 @@ Um goto não pode saltar para um local antes de uma declaração using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context O operador de supressão não é permitido neste contexto diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 6bcc64b24402b..f2362670e87bb 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -447,6 +447,11 @@ Оператор goto не может переходить к расположению после объявления using. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context Оператор подавления недопустим в данном контексте. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index fcbbf5ccca853..8944397ad2cd7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -447,6 +447,11 @@ Bir goto, using bildiriminden sonraki bir konuma atlayamaz. + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context Gizleme işlecine bu bağlamda izin verilmez diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 2b3e67c909fc6..e6fb53489494c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -447,6 +447,11 @@ goto 无法跳转到 using 声明后的某个位置。 + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context 此上下文中不允许使用抑制运算符 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 2c811ba28ee0e..07b685fba0568 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -447,6 +447,11 @@ goto 不可跳到 using 宣告後的位置。 + + The positional member '{0}' found corresponding to this parameter is hidden. + The positional member '{0}' found corresponding to this parameter is hidden. + + The suppression operator is not allowed in this context 此內容不允許隱藏項目運算子 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs index 07ccbfca40513..afc35b3eaaba2 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordTests.cs @@ -24,7 +24,7 @@ public class RecordTests : CompilingTestBase { private static CSharpCompilation CreateCompilation(CSharpTestSource source) => CSharpTestBase.CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, - parseOptions: TestOptions.Regular9); + parseOptions: TestOptions.RegularPreview); private CompilationVerifier CompileAndVerify( CSharpTestSource src, @@ -33,7 +33,7 @@ private CompilationVerifier CompileAndVerify( => base.CompileAndVerify( new[] { src, IsExternalInitTypeDefinition }, expectedOutput: expectedOutput, - parseOptions: TestOptions.Regular9, + parseOptions: TestOptions.RegularPreview, references: references, // init-only is unverifiable verify: Verification.Skipped); @@ -28680,5 +28680,353 @@ public void SealedIncomplete() Diagnostic(ErrorCode.ERR_RbraceExpected, "").WithLocation(2, 22) ); } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithZeroArityMethod() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } +} +"; + var expected = new[] + { + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(9, 17) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics(expected); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithZeroArityMethod_DeconstructInSource() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } + public void Deconstruct(out int i) { i = 0; } +} +"; + var expected = new[] + { + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(9, 17) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyEmitDiagnostics(expected); + + comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithZeroArityMethod_WithNew() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) // 1 +{ + public new void I() { } +} +"; + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) // 1 + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithGenericMethod() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (11,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(11, 21), + // (13,17): warning CS0108: 'C.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "Base.I").WithLocation(13, 17) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_FromGrandBase() + { + var source = @" +public record GrandBase +{ + public int I { get; set; } = 42; +} +public record Base : GrandBase +{ + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public void I() { } +} +"; + var expected = new[] + { + // (10,21): error CS8913: The positional member 'GrandBase.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("GrandBase.I").WithLocation(10, 21), + // (12,17): warning CS0108: 'C.I()' hides inherited member 'GrandBase.I'. Use the new keyword if hiding was intended. + // public void I() { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I()", "GrandBase.I").WithLocation(12, 17) + }; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(expected); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_NotHiddenByIndexer() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int Item { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int Item) : Base(Item) +{ + public int this[int x] { get => throw null; } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""int Base.Item.get"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_NotHiddenByIndexer_WithIndexerName() + { + var source = @" +var c = new C(0); +c.Deconstruct(out int i); +System.Console.Write(i); + +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + [System.Runtime.CompilerServices.IndexerName(""I"")] + public int this[int x] { get => throw null; } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics(); + + var verifier = CompileAndVerify(comp, expectedOutput: "42"); + verifier.VerifyIL("C.Deconstruct", @" +{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.1 + IL_0001: ldarg.0 + IL_0002: call ""int Base.I.get"" + IL_0007: stind.i4 + IL_0008: ret +} +"); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithType() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public class I { } +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,18): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public class I { } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 18) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithEvent() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public event System.Action I; +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,32): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public event System.Action I; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 32), + // (9,32): warning CS0067: The event 'C.I' is never used + // public event System.Action I; + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "I").WithArguments("C.I").WithLocation(9, 32) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_HiddenWithConstant() + { + var source = @" +public record Base +{ + public int I { get; set; } = 42; + public Base(int ignored) { } +} +public record C(int I) : Base(I) +{ + public const string I = null; +} +"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (7,21): error CS8913: The positional member 'Base.I' found corresponding to this parameter is hidden. + // public record C(int I) : Base(I) + Diagnostic(ErrorCode.ERR_HiddenPositionalMember, "I").WithArguments("Base.I").WithLocation(7, 21), + // (9,25): warning CS0108: 'C.I' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public const string I = null; + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("C.I", "Base.I").WithLocation(9, 25) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_AbstractInBase() + { + var source = @" +abstract record Base +{ + public abstract int I { get; init; } +} +record Derived(int I) : Base +{ + public int I() { return 0; } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (8,16): warning CS0108: 'Derived.I()' hides inherited member 'Base.I'. Use the new keyword if hiding was intended. + // public int I() { return 0; } + Diagnostic(ErrorCode.WRN_NewRequired, "I").WithArguments("Derived.I()", "Base.I").WithLocation(8, 16), + // (8,16): error CS0102: The type 'Derived' already contains a definition for 'I' + // public int I() { return 0; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "I").WithArguments("Derived", "I").WithLocation(8, 16) + ); + } + + [Fact, WorkItem(52630, "https://github.com/dotnet/roslyn/issues/52630")] + public void HiddenPositionalMember_Property_AbstractInBase_AbstractInDerived() + { + var source = @" +abstract record Base +{ + public abstract int I { get; init; } +} +abstract record Derived(int I) : Base +{ + public int I() { return 0; } +} +"; + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.RegularPreview); + comp.VerifyEmitDiagnostics( + // (8,16): error CS0533: 'Derived.I()' hides inherited abstract member 'Base.I' + // public int I() { return 0; } + Diagnostic(ErrorCode.ERR_HidingAbstractMethod, "I").WithArguments("Derived.I()", "Base.I").WithLocation(8, 16), + // (8,16): error CS0102: The type 'Derived' already contains a definition for 'I' + // public int I() { return 0; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "I").WithArguments("Derived", "I").WithLocation(8, 16) + ); + } } }