diff --git a/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundAssignmentCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundAssignmentCodeFixProvider.cs index 7734fba6a5bb6..a9b93d04d5c2e 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundAssignmentCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCompoundAssignment/CSharpUseCompoundAssignmentCodeFixProvider.cs @@ -6,6 +6,7 @@ using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.UseCompoundAssignment; namespace Microsoft.CodeAnalysis.CSharp.UseCompoundAssignment @@ -30,14 +31,32 @@ protected override AssignmentExpressionSyntax Assignment( return SyntaxFactory.AssignmentExpression(assignmentOpKind, left, syntaxToken, right); } - protected override ExpressionSyntax Increment(ExpressionSyntax left) - { - return SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostIncrementExpression, left); - } + protected override ExpressionSyntax Increment(ExpressionSyntax left, bool postfix) + => postfix + ? Postfix(SyntaxKind.PostIncrementExpression, left) + : Prefix(SyntaxKind.PreIncrementExpression, left); + + protected override ExpressionSyntax Decrement(ExpressionSyntax left, bool postfix) + => postfix + ? Postfix(SyntaxKind.PostDecrementExpression, left) + : Prefix(SyntaxKind.PreDecrementExpression, left); - protected override ExpressionSyntax Decrement(ExpressionSyntax left) + private static ExpressionSyntax Postfix(SyntaxKind kind, ExpressionSyntax operand) + => SyntaxFactory.PostfixUnaryExpression(kind, operand); + + private static ExpressionSyntax Prefix(SyntaxKind kind, ExpressionSyntax operand) + => SyntaxFactory.PrefixUnaryExpression(kind, operand); + + protected override bool PreferPostfix(ISyntaxFactsService syntaxFacts, AssignmentExpressionSyntax currentAssignment) { - return SyntaxFactory.PostfixUnaryExpression(SyntaxKind.PostDecrementExpression, left); + // in `for (...; x = x + 1)` we prefer to translate that idiomatically as `for (...; x++)` + if (currentAssignment.Parent is ForStatementSyntax forStatement && + forStatement.Incrementors.Contains(currentAssignment)) + { + return true; + } + + return base.PreferPostfix(syntaxFacts, currentAssignment); } } } diff --git a/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundAssignmentTests.cs b/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundAssignmentTests.cs index 65769a72b907f..e428fd8b5da3a 100644 --- a/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundAssignmentTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCompoundAssignment/UseCompoundAssignmentTests.cs @@ -1093,5 +1093,109 @@ void M() } }"); } + + [WorkItem(38054, "https://github.com/dotnet/roslyn/issues/53969")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCompoundAssignment)] + public async Task TestIncrementInExpressionContext() + { + await TestInRegularAndScript1Async( +@"public class C +{ + void M(int i) + { + M(i [||]= i + 1); + } +}", +@"public class C +{ + void M(int i) + { + M(++i); + } +}"); + } + + [WorkItem(38054, "https://github.com/dotnet/roslyn/issues/53969")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseCompoundAssignment)] + [InlineData("switch($$) { }")] + [InlineData("while(($$) > 0) { }")] + [InlineData("_ = true ? $$ : 0;")] + [InlineData("_ = ($$);")] + public async Task TestPrefixIncrement1(string expressionContext) + { + var before = expressionContext.Replace("$$", "i [||]= i + 1"); + var after = expressionContext.Replace("$$", "++i"); + await TestInRegularAndScript1Async( +@$"public class C +{{ + void M(int i) + {{ + {before} + }} +}}", +@$"public class C +{{ + void M(int i) + {{ + {after} + }} +}}"); + } + + [WorkItem(38054, "https://github.com/dotnet/roslyn/issues/53969")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseCompoundAssignment)] + [InlineData("return $$;")] + [InlineData("return true ? $$ : 0;")] + [InlineData("return ($$);")] + public async Task TestPrefixIncrement2(string expressionContext) + { + var before = expressionContext.Replace("$$", "i [||]= i + 1"); + var after = expressionContext.Replace("$$", "++i"); + await TestInRegularAndScript1Async( +@$"public class C +{{ + int M(int i) + {{ + {before} + }} +}}", +@$"public class C +{{ + int M(int i) + {{ + {after} + }} +}}"); + } + + [WorkItem(38054, "https://github.com/dotnet/roslyn/issues/53969")] + [Theory, Trait(Traits.Feature, Traits.Features.CodeActionsUseCompoundAssignment)] + [InlineData( + "/* Before */ i [||]= i + 1; /* After */", + "/* Before */ i++; /* After */")] + [InlineData( + "M( /* Before */ i [||]= i + 1 /* After */ );", + "M( /* Before */ ++i /* After */ );")] + [InlineData( + "M( /* Before */ i [||]= i - 1 /* After */ );", + "M( /* Before */ --i /* After */ );")] + public async Task TestTriviaPreserved(string before, string after) + { + await TestInRegularAndScript1Async( +@$"public class C +{{ + int M(int i) + {{ + {before} + }} +}}", +@$"public class C +{{ + int M(int i) + {{ + {after} + }} +}}"); + } } } diff --git a/src/Analyzers/Core/CodeFixes/UseCompoundAssignment/AbstractUseCompoundAssignmentCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseCompoundAssignment/AbstractUseCompoundAssignmentCodeFixProvider.cs index e3609df5b8128..9ef813bac5d2d 100644 --- a/src/Analyzers/Core/CodeFixes/UseCompoundAssignment/AbstractUseCompoundAssignmentCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseCompoundAssignment/AbstractUseCompoundAssignmentCodeFixProvider.cs @@ -41,8 +41,8 @@ protected AbstractUseCompoundAssignmentCodeFixProvider( protected abstract SyntaxToken Token(TSyntaxKind kind); protected abstract TAssignmentSyntax Assignment( TSyntaxKind assignmentOpKind, TExpressionSyntax left, SyntaxToken syntaxToken, TExpressionSyntax right); - protected abstract TExpressionSyntax Increment(TExpressionSyntax left); - protected abstract TExpressionSyntax Decrement(TExpressionSyntax left); + protected abstract TExpressionSyntax Increment(TExpressionSyntax left, bool postfix); + protected abstract TExpressionSyntax Decrement(TExpressionSyntax left, bool postfix); public override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -68,8 +68,11 @@ protected override Task FixAllAsync( var assignment = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken); editor.ReplaceNode(assignment, - (currentAssignment, generator) => + (current, generator) => { + if (current is not TAssignmentSyntax currentAssignment) + return current; + syntaxFacts.GetPartsOfAssignmentExpressionOrStatement(currentAssignment, out var leftOfAssign, out var equalsToken, out var rightOfAssign); @@ -80,14 +83,10 @@ protected override Task FixAllAsync( out _, out var opToken, out var rightExpr); if (diagnostic.Properties.ContainsKey(UseCompoundAssignmentUtilities.Increment)) - { - return Increment((TExpressionSyntax)leftOfAssign); - } + return Increment((TExpressionSyntax)leftOfAssign, PreferPostfix(syntaxFacts, currentAssignment)).WithTriviaFrom(currentAssignment); if (diagnostic.Properties.ContainsKey(UseCompoundAssignmentUtilities.Decrement)) - { - return Decrement((TExpressionSyntax)leftOfAssign); - } + return Decrement((TExpressionSyntax)leftOfAssign, PreferPostfix(syntaxFacts, currentAssignment)).WithTriviaFrom(currentAssignment); var assignmentOpKind = _binaryToAssignmentMap[syntaxKinds.Convert(rightOfAssign.RawKind)]; var compoundOperator = Token(_assignmentToTokenMap[assignmentOpKind]); @@ -102,6 +101,17 @@ protected override Task FixAllAsync( return Task.CompletedTask; } + protected virtual bool PreferPostfix(ISyntaxFactsService syntaxFacts, TAssignmentSyntax currentAssignment) + { + // If we have `x = x + 1;` on it's own, then we prefer `x++` as idiomatic. + if (syntaxFacts.IsSimpleAssignmentStatement(currentAssignment.Parent)) + return true; + + // In any other circumstance, the value of the assignment might be read, so we need to transform to + // ++x to ensure that we preserve semantics. + return false; + } + private class MyCodeAction : CustomCodeActions.DocumentChangeAction { public MyCodeAction(Func> createChangedDocument) diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseCompoundAssignment/VisualBasicUseCompoundAssignmentCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseCompoundAssignment/VisualBasicUseCompoundAssignmentCodeFixProvider.vb index 4848d738e4558..578a8bbd85a35 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseCompoundAssignment/VisualBasicUseCompoundAssignmentCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseCompoundAssignment/VisualBasicUseCompoundAssignmentCodeFixProvider.vb @@ -30,11 +30,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCompoundAssignment Return SyntaxFactory.AssignmentStatement(assignmentOpKind, left, syntaxToken, right) End Function - Protected Overrides Function Increment(left As ExpressionSyntax) As ExpressionSyntax + Protected Overrides Function Increment(left As ExpressionSyntax, postfix As Boolean) As ExpressionSyntax Throw ExceptionUtilities.Unreachable End Function - Protected Overrides Function Decrement(left As ExpressionSyntax) As ExpressionSyntax + Protected Overrides Function Decrement(left As ExpressionSyntax, postfix As Boolean) As ExpressionSyntax Throw ExceptionUtilities.Unreachable End Function End Class diff --git a/src/Compilers/CSharp/Portable/SourceGeneration/CSharpGeneratorDriver.cs b/src/Compilers/CSharp/Portable/SourceGeneration/CSharpGeneratorDriver.cs index 5ddbb2bd85b3a..e9858fb922c4e 100644 --- a/src/Compilers/CSharp/Portable/SourceGeneration/CSharpGeneratorDriver.cs +++ b/src/Compilers/CSharp/Portable/SourceGeneration/CSharpGeneratorDriver.cs @@ -46,7 +46,7 @@ public static CSharpGeneratorDriver Create(params ISourceGenerator[] generators) /// The incremental generators to create this driver with /// A new instance. public static CSharpGeneratorDriver Create(params IIncrementalGenerator[] incrementalGenerators) - => Create(incrementalGenerators.Select(WrapGenerator), additionalTexts: null); + => Create(incrementalGenerators.Select(GeneratorExtensions.AsSourceGenerator), additionalTexts: null); /// /// Creates a new instance of with the specified s and the provided options or default. diff --git a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs index 5c41caf45b250..6e25a7ef485ad 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerFileReferenceTests.cs @@ -253,7 +253,7 @@ public void TestLoadGenerators() { AnalyzerFileReference reference = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location); var generators = reference.GetGeneratorsForAllLanguages(); - var typeNames = generators.Select(g => GeneratorDriver.GetGeneratorType(g).FullName); + var typeNames = generators.Select(g => g.GetGeneratorType().FullName); AssertEx.SetEqual(new[] { @@ -291,7 +291,7 @@ public void TestLoadCSharpGenerators() AnalyzerFileReference reference = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location); var generators = reference.GetGenerators(LanguageNames.CSharp); - var typeNames = generators.Select(g => GeneratorDriver.GetGeneratorType(g).FullName); + var typeNames = generators.Select(g => g.GetGeneratorType().FullName); AssertEx.SetEqual(new[] { "Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestGenerator", @@ -313,7 +313,7 @@ public void TestLoadVisualBasicGenerators() AnalyzerFileReference reference = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location); var generators = reference.GetGenerators(LanguageNames.VisualBasic); - var typeNames = generators.Select(g => GeneratorDriver.GetGeneratorType(g).FullName); + var typeNames = generators.Select(g => g.GetGeneratorType().FullName); AssertEx.SetEqual(new[] { "Microsoft.CodeAnalysis.UnitTests.VisualBasicOnlyGenerator", @@ -436,7 +436,7 @@ public void TestLoadedGeneratorOrderIsDeterministic() { AnalyzerFileReference reference = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location); - var csharpGenerators = reference.GetGenerators(LanguageNames.CSharp).Select(g => GeneratorDriver.GetGeneratorType(g).FullName).ToArray(); + var csharpGenerators = reference.GetGenerators(LanguageNames.CSharp).Select(g => g.GetGeneratorType().FullName).ToArray(); Assert.Equal(10, csharpGenerators.Length); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedGenerator", csharpGenerators[0]); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestGenerator", csharpGenerators[1]); @@ -449,7 +449,7 @@ public void TestLoadedGeneratorOrderIsDeterministic() Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestSourceAndIncrementalGenerator", csharpGenerators[8]); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicAndCSharpGenerator", csharpGenerators[9]); - var vbGenerators = reference.GetGenerators(LanguageNames.VisualBasic).Select(g => GeneratorDriver.GetGeneratorType(g).FullName).ToArray(); + var vbGenerators = reference.GetGenerators(LanguageNames.VisualBasic).Select(g => g.GetGeneratorType().FullName).ToArray(); Assert.Equal(5, vbGenerators.Length); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.CSharpAndVisualBasicGenerator", vbGenerators[0]); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.TestIncrementalGenerator", vbGenerators[1]); @@ -458,7 +458,7 @@ public void TestLoadedGeneratorOrderIsDeterministic() Assert.Equal("Microsoft.CodeAnalysis.UnitTests.VisualBasicOnlyGenerator", vbGenerators[4]); // generators load in language order (C#, F#, VB), and *do not* include duplicates - var allGenerators = reference.GetGeneratorsForAllLanguages().Select(g => GeneratorDriver.GetGeneratorType(g).FullName).ToArray(); + var allGenerators = reference.GetGeneratorsForAllLanguages().Select(g => g.GetGeneratorType().FullName).ToArray(); Assert.Equal(12, allGenerators.Length); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+SomeType+NestedGenerator", allGenerators[0]); Assert.Equal("Microsoft.CodeAnalysis.UnitTests.AnalyzerFileReferenceTests+TestGenerator", allGenerators[1]); diff --git a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt index 248e4ebb4f600..18d1e21ab84a1 100644 --- a/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/Core/Portable/PublicAPI.Unshipped.txt @@ -6,6 +6,7 @@ Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedMethods.get -> System.Co Microsoft.CodeAnalysis.Emit.EmitDifferenceResult.UpdatedTypes.get -> System.Collections.Immutable.ImmutableArray Microsoft.CodeAnalysis.GeneratorAttribute.GeneratorAttribute(string! firstLanguage, params string![]! additionalLanguages) -> void Microsoft.CodeAnalysis.GeneratorAttribute.Languages.get -> string![]! +Microsoft.CodeAnalysis.GeneratorExtensions Microsoft.CodeAnalysis.IFieldSymbol.IsExplicitlyNamedTupleElement.get -> bool Microsoft.CodeAnalysis.GeneratorExecutionContext.SyntaxContextReceiver.get -> Microsoft.CodeAnalysis.ISyntaxContextReceiver? Microsoft.CodeAnalysis.GeneratorInitializationContext.RegisterForSyntaxNotifications(Microsoft.CodeAnalysis.SyntaxContextReceiverCreator! receiverCreator) -> void @@ -86,10 +87,10 @@ override Microsoft.CodeAnalysis.Operations.OperationWalker.DefaultVis override Microsoft.CodeAnalysis.Operations.OperationWalker.Visit(Microsoft.CodeAnalysis.IOperation? operation, TArgument argument) -> object? static Microsoft.CodeAnalysis.FileLinePositionSpan.operator !=(Microsoft.CodeAnalysis.FileLinePositionSpan left, Microsoft.CodeAnalysis.FileLinePositionSpan right) -> bool static Microsoft.CodeAnalysis.FileLinePositionSpan.operator ==(Microsoft.CodeAnalysis.FileLinePositionSpan left, Microsoft.CodeAnalysis.FileLinePositionSpan right) -> bool +static Microsoft.CodeAnalysis.GeneratorExtensions.AsSourceGenerator(this Microsoft.CodeAnalysis.IIncrementalGenerator! incrementalGenerator) -> Microsoft.CodeAnalysis.ISourceGenerator! +static Microsoft.CodeAnalysis.GeneratorExtensions.GetGeneratorType(this Microsoft.CodeAnalysis.ISourceGenerator! generator) -> System.Type! static Microsoft.CodeAnalysis.LineMapping.operator !=(Microsoft.CodeAnalysis.LineMapping left, Microsoft.CodeAnalysis.LineMapping right) -> bool static Microsoft.CodeAnalysis.LineMapping.operator ==(Microsoft.CodeAnalysis.LineMapping left, Microsoft.CodeAnalysis.LineMapping right) -> bool -static Microsoft.CodeAnalysis.GeneratorDriver.GetGeneratorType(Microsoft.CodeAnalysis.ISourceGenerator! generator) -> System.Type! -static Microsoft.CodeAnalysis.GeneratorDriver.WrapGenerator(Microsoft.CodeAnalysis.IIncrementalGenerator! incrementalGenerator) -> Microsoft.CodeAnalysis.ISourceGenerator! static Microsoft.CodeAnalysis.IncrementalValueProviderExtensions.Collect(this Microsoft.CodeAnalysis.IncrementalValuesProvider source) -> Microsoft.CodeAnalysis.IncrementalValueProvider> static Microsoft.CodeAnalysis.IncrementalValueProviderExtensions.Combine(this Microsoft.CodeAnalysis.IncrementalValueProvider provider1, Microsoft.CodeAnalysis.IncrementalValueProvider provider2) -> Microsoft.CodeAnalysis.IncrementalValueProvider<(TLeft Left, TRight Right)> static Microsoft.CodeAnalysis.IncrementalValueProviderExtensions.Combine(this Microsoft.CodeAnalysis.IncrementalValuesProvider provider1, Microsoft.CodeAnalysis.IncrementalValueProvider provider2) -> Microsoft.CodeAnalysis.IncrementalValuesProvider<(TLeft Left, TRight Right)> diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs index 357540bdbf78b..13abd36b98473 100644 --- a/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs @@ -29,7 +29,7 @@ public abstract class GeneratorDriver internal GeneratorDriver(GeneratorDriverState state) { - Debug.Assert(state.Generators.GroupBy(s => GetGeneratorType(s)).Count() == state.Generators.Length); // ensure we don't have duplicate generator types + Debug.Assert(state.Generators.GroupBy(s => s.GetGeneratorType()).Count() == state.Generators.Length); // ensure we don't have duplicate generator types _state = state; } @@ -130,32 +130,6 @@ static ImmutableArray getGeneratorSources(GeneratorState } } - /// - /// Returns the underlying type of a given generator - /// - /// - /// For s we create a wrapper type that also implements - /// . This method will unwrap and return the underlying type - /// in those cases. - /// - /// The generator to get the type of - /// The underlying generator type - public static Type GetGeneratorType(ISourceGenerator generator) - { - if (generator is IncrementalGeneratorWrapper igw) - { - return igw.Generator.GetType(); - } - return generator.GetType(); - } - - /// - /// Wraps an in an object that can be used to construct a - /// - /// The incremental generator to wrap - /// A wrapped generator that can be passed to a generator driver - public static ISourceGenerator WrapGenerator(IIncrementalGenerator incrementalGenerator) => new IncrementalGeneratorWrapper(incrementalGenerator); - internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, DiagnosticBag? diagnosticsBag, CancellationToken cancellationToken = default) { // with no generators, there is no work to do @@ -281,7 +255,7 @@ private IncrementalExecutionContext UpdateOutputs(ImmutableArray ParseAdditionalSources(ISourceGenerator generator, ImmutableArray generatedSources, CancellationToken cancellationToken) { var trees = ArrayBuilder.GetInstance(generatedSources.Length); - var type = GetGeneratorType(generator); + var type = generator.GetGeneratorType(); var prefix = GetFilePathPrefixForGenerator(generator); foreach (var source in generatedSources) { @@ -311,7 +285,7 @@ private static GeneratorState SetGeneratorException(CommonMessageProvider provid isEnabledByDefault: true, customTags: WellKnownDiagnosticTags.AnalyzerException); - var diagnostic = Diagnostic.Create(descriptor, Location.None, GetGeneratorType(generator).Name, e.GetType().Name, e.Message); + var diagnostic = Diagnostic.Create(descriptor, Location.None, generator.GetGeneratorType().Name, e.GetType().Name, e.Message); diagnosticBag?.Add(diagnostic); return new GeneratorState(generatorState.Info, e, diagnostic); @@ -334,7 +308,7 @@ private static ImmutableArray FilterDiagnostics(Compilation compilat internal static string GetFilePathPrefixForGenerator(ISourceGenerator generator) { - var type = GetGeneratorType(generator); + var type = generator.GetGeneratorType(); return Path.Combine(type.Assembly.GetName().Name ?? string.Empty, type.FullName!); } diff --git a/src/Compilers/Core/Portable/SourceGeneration/GeneratorExtensions.cs b/src/Compilers/Core/Portable/SourceGeneration/GeneratorExtensions.cs new file mode 100644 index 0000000000000..dd6dea79380f0 --- /dev/null +++ b/src/Compilers/Core/Portable/SourceGeneration/GeneratorExtensions.cs @@ -0,0 +1,39 @@ +// 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.Text; + +namespace Microsoft.CodeAnalysis +{ + public static class GeneratorExtensions + { + /// + /// Returns the underlying type of a given generator + /// + /// + /// For s a wrapper is created that also implements + /// . This method will unwrap and return the underlying type + /// in those cases. + /// + /// The generator to get the type of + /// The underlying generator type + public static Type GetGeneratorType(this ISourceGenerator generator) + { + if (generator is IncrementalGeneratorWrapper igw) + { + return igw.Generator.GetType(); + } + return generator.GetType(); + } + + /// + /// Converts an in an object that can be used when constructing a + /// + /// The incremental generator to wrap + /// A wrapped generator that can be passed to a generator driver + public static ISourceGenerator AsSourceGenerator(this IIncrementalGenerator incrementalGenerator) => new IncrementalGeneratorWrapper(incrementalGenerator); + } +} diff --git a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs index aeff2305ff6a4..dfce03777c761 100644 --- a/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs +++ b/src/EditorFeatures/Core/Implementation/Workspaces/ProjectCacheServiceFactory.cs @@ -15,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces [Shared] internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory { - private const int ImplicitCacheTimeoutInMS = 10000; + private static readonly TimeSpan s_implicitCacheTimeout = TimeSpan.FromMilliseconds(10000); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -30,7 +30,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) return new ProjectCacheService(workspaceServices.Workspace); } - var service = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + var service = new ProjectCacheService(workspaceServices.Workspace, s_implicitCacheTimeout); // Also clear the cache when the solution is cleared or removed. workspaceServices.Workspace.WorkspaceChanged += (s, e) => diff --git a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs index 0b5cf858ab7e5..ec124733a96ec 100644 --- a/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs +++ b/src/EditorFeatures/Core/Shared/Preview/PreviewSolutionCrawlerRegistrationService.cs @@ -72,7 +72,7 @@ public void Register(Workspace workspace) private async Task AnalyzeAsync() { - var workerBackOffTimeSpanInMS = _workspace.Options.GetOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); + var workerBackOffTimeSpan = InternalSolutionCrawlerOptions.PreviewBackOffTimeSpan; var incrementalAnalyzer = _owner._analyzerService.CreateIncrementalAnalyzer(_workspace); var solution = _workspace.CurrentSolution; @@ -89,7 +89,7 @@ private async Task AnalyzeAsync() } // delay analyzing - await Task.Delay(workerBackOffTimeSpanInMS, _source.Token).ConfigureAwait(false); + await _owner._listener.Delay(workerBackOffTimeSpan, _source.Token).ConfigureAwait(false); // do actual analysis if (textDocument is Document document) diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 37813f634cb99..598254ecab1a9 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -1905,6 +1905,7 @@ void ValidateDelta(ManagedModuleUpdate delta) Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(0x06000001, delta.UpdatedMethods.Single()); + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); Assert.Equal(moduleId, delta.Module); Assert.Empty(delta.ExceptionRegions); Assert.Empty(delta.SequencePoints); @@ -2042,6 +2043,7 @@ public async Task BreakMode_ValidSignificantChange_EmitSuccessful_UpdateDeferred Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(0x06000002, delta.UpdatedMethods.Single()); + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); Assert.Equal(moduleId, delta.Module); Assert.Empty(delta.ExceptionRegions); Assert.Empty(delta.SequencePoints); @@ -2157,6 +2159,7 @@ partial class C { int Y = 2; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(6, delta.UpdatedMethods.Length); // F, C.C(), D.D(), E.E(int), E.E(int, int), lambda + AssertEx.SetEqual(new[] { 0x02000002, 0x02000003, 0x02000004, 0x02000005 }, delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X")); EndDebuggingSession(debuggingSession); } @@ -2271,6 +2274,7 @@ class C { int Y => 2; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(2, delta.UpdatedMethods.Length); + AssertEx.Equal(new[] { 0x02000002, 0x02000003 }, delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X")); EndDebuggingSession(debuggingSession); } @@ -2332,6 +2336,7 @@ int M() Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Empty(delta.UpdatedMethods); + Assert.Empty(delta.UpdatedTypes); EndDebuggingSession(debuggingSession); } @@ -2375,6 +2380,7 @@ partial class C { int X = 1; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); // constructor update + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -2421,6 +2427,7 @@ class C { int Y => 1; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); + Assert.Equal(0x02000003, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -2463,6 +2470,7 @@ class C { int Y => 1; } Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); + Assert.Equal(0x02000003, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -2502,6 +2510,7 @@ public async Task BreakMode_ValidSignificantChange_SourceGenerators_DocumentRemo Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Equal(1, delta.UpdatedMethods.Length); + Assert.Equal(0x02000002, delta.UpdatedTypes.Single()); EndDebuggingSession(debuggingSession); } @@ -3191,6 +3200,7 @@ void F() Assert.NotEmpty(delta.MetadataDelta); Assert.NotEmpty(delta.PdbDelta); Assert.Empty(delta.UpdatedMethods); + Assert.Empty(delta.UpdatedTypes); AssertEx.Equal(new[] { @@ -3260,6 +3270,7 @@ static void F() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3278,6 +3289,7 @@ static void F() (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3305,6 +3317,7 @@ static void F() (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3431,6 +3444,7 @@ static void F() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3489,6 +3503,7 @@ static void F() var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution); Assert.Empty(emitDiagnostics); Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single()); + Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single()); Assert.Equal(ManagedModuleUpdateStatus.Ready, updates.Status); CommitSolutionUpdate(debuggingSession); @@ -3625,6 +3640,7 @@ public async Task WatchHotReloadServiceTest() var result = await hotReload.EmitSolutionUpdateAsync(solution, CancellationToken.None); Assert.Empty(result.diagnostics); Assert.Equal(1, result.updates.Length); + AssertEx.Equal(new[] { 0x02000002 }, result.updates[0].UpdatedTypes); solution = solution.WithDocumentText(documentIdA, SourceText.From(source3, Encoding.UTF8)); diff --git a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs index 14eb9c357849b..3ecd69d10d544 100644 --- a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs +++ b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs @@ -788,14 +788,23 @@ internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, var expectedDocumentSyntaxEvents = 1; var expectedDocumentSemanticEvents = 5; + var listenerProvider = GetListenerProvider(workspace.ExportProvider); + + // start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test + var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation"); + var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync(); + workspace.ChangeDocument(document.Id, SourceText.From("//")); if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0) { analyzer.RunningEvent.Wait(); } + token.Dispose(); + workspace.ChangeDocument(document.Id, SourceText.From("// ")); await WaitAsync(service, workspace); + await expeditedWait; service.Unregister(workspace); @@ -833,6 +842,12 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope service.Register(workspace); + var listenerProvider = GetListenerProvider(workspace.ExportProvider); + + // start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test + var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation"); + var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync(); + workspace.ChangeDocument(document.Id, SourceText.From("//")); if (expectedDocumentSyntaxEvents > 0 || expectedDocumentSemanticEvents > 0) { @@ -846,8 +861,11 @@ internal async Task Document_Cancellation_MultipleTimes(BackgroundAnalysisScope analyzer.RunningEvent.Wait(); } + token.Dispose(); + workspace.ChangeDocument(document.Id, SourceText.From("// ")); await WaitAsync(service, workspace); + await expeditedWait; service.Unregister(workspace); @@ -1273,6 +1291,12 @@ public async Task FileFromSameProjectTogetherTest() await WaitWaiterAsync(workspace.ExportProvider); + var listenerProvider = GetListenerProvider(workspace.ExportProvider); + + // start an operation that allows an expedited wait to cover the remainder of the delayed operations in the test + var token = listenerProvider.GetListener(FeatureAttribute.SolutionCrawler).BeginAsyncOperation("Test operation"); + var expeditedWait = listenerProvider.GetWaiter(FeatureAttribute.SolutionCrawler).ExpeditedWaitAsync(); + // we want to test order items processed by solution crawler. // but since everything async, lazy and cancellable, order is not 100% deterministic. an item might // start to be processed, and get cancelled due to newly enqueued item requiring current work to be re-processed @@ -1319,8 +1343,11 @@ await crawlerListener.WaitUntilConditionIsMetAsync( operation.Done(); } + token.Dispose(); + // wait analyzers to finish process await WaitAsync(service, workspace); + await expeditedWait; Assert.Equal(1, worker.DocumentIds.Take(5).Select(d => d.ProjectId).Distinct().Count()); Assert.Equal(1, worker.DocumentIds.Skip(5).Take(5).Select(d => d.ProjectId).Distinct().Count()); @@ -1339,7 +1366,6 @@ private static async Task InsertText(string code, string text, bool expectDocume composition: EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(IIncrementalAnalyzerProvider)).AddParts(typeof(AnalyzerProviderNoWaitNoBlock)), workspaceKind: SolutionCrawlerWorkspaceKind); - SetOptions(workspace); var testDocument = workspace.Documents.First(); var textBuffer = testDocument.GetTextBuffer(); @@ -1476,18 +1502,6 @@ private static IEnumerable GetDocuments(ProjectId projectId, int c private static AsynchronousOperationListenerProvider GetListenerProvider(ExportProvider provider) => provider.GetExportedValue(); - private static void SetOptions(Workspace workspace) - { - // override default timespan to make test run faster - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options - .WithChangedOption(InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS, 0) - .WithChangedOption(InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS, 100))); - } - private static void MakeFirstDocumentActive(Project project) => MakeDocumentActive(project.Documents.First()); @@ -1518,8 +1532,6 @@ public WorkCoordinatorWorkspace(string workspaceKind = null, bool disablePartial Assert.False(_workspaceWaiter.HasPendingWork); Assert.False(_solutionCrawlerWaiter.HasPendingWork); - - WorkCoordinatorTests.SetOptions(this); } public static WorkCoordinatorWorkspace CreateWithAnalysisScope(BackgroundAnalysisScope analysisScope, string workspaceKind = null, bool disablePartialSolutions = true, Type incrementalAnalyzer = null) diff --git a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs index a01bff35edaa1..1f55e9d7d2332 100644 --- a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs @@ -30,7 +30,7 @@ private static void Test(Action new object()); @@ -113,7 +113,7 @@ public void TestCacheDoesNotKeepObjectsAliveAfterOwnerIsCollected2() public void TestImplicitCacheKeepsObjectAlive1() { var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cacheService = new ProjectCacheService(workspace, int.MaxValue); + var cacheService = new ProjectCacheService(workspace, TimeSpan.MaxValue); var reference = ObjectReference.CreateFromFactory(() => new object()); reference.UseReference(r => cacheService.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, r)); reference.AssertHeld(); @@ -125,7 +125,7 @@ public void TestImplicitCacheKeepsObjectAlive1() public void TestImplicitCacheMonitoring() { var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cacheService = new ProjectCacheService(workspace, 10); + var cacheService = new ProjectCacheService(workspace, TimeSpan.FromMilliseconds(10)); var weak = PutObjectInImplicitCache(cacheService); weak.AssertReleased(); @@ -156,7 +156,7 @@ public void TestP2PReference() var instanceTracker = ObjectReference.CreateFromFactory(() => new object()); - var cacheService = new ProjectCacheService(workspace, int.MaxValue); + var cacheService = new ProjectCacheService(workspace, TimeSpan.MaxValue); using (var cache = cacheService.EnableCaching(project2.Id)) { instanceTracker.UseReference(r => cacheService.CacheObjectIfCachingEnabledForKey(project1.Id, (object)null, r)); @@ -184,7 +184,7 @@ public void TestEjectFromImplicitCache() var weakLast = ObjectReference.Create(compilations[compilations.Count - 1]); var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cache = new ProjectCacheService(workspace, int.MaxValue); + var cache = new ProjectCacheService(workspace, TimeSpan.MaxValue); for (var i = 0; i < ProjectCacheService.ImplicitCacheSize + 1; i++) { cache.CacheObjectIfCachingEnabledForKey(ProjectId.CreateNewId(), (object)null, compilations[i]); @@ -212,7 +212,7 @@ public void TestCacheCompilationTwice() var weak1 = ObjectReference.Create(comp1); var workspace = new AdhocWorkspace(MockHostServices.Instance, workspaceKind: WorkspaceKind.Host); - var cache = new ProjectCacheService(workspace, int.MaxValue); + var cache = new ProjectCacheService(workspace, TimeSpan.MaxValue); var key = ProjectId.CreateNewId(); var owner = new object(); cache.CacheObjectIfCachingEnabledForKey(key, owner, comp1); diff --git a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs index fb1651592fd8a..866d1187855c4 100644 --- a/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs +++ b/src/Features/Core/Portable/ExternalAccess/Watch/Api/WatchHotReloadService.cs @@ -41,13 +41,15 @@ public readonly struct Update public readonly ImmutableArray ILDelta; public readonly ImmutableArray MetadataDelta; public readonly ImmutableArray PdbDelta; + public readonly ImmutableArray UpdatedTypes; - public Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta) + public Update(Guid moduleId, ImmutableArray ilDelta, ImmutableArray metadataDelta, ImmutableArray pdbDelta, ImmutableArray updatedTypes) { ModuleId = moduleId; ILDelta = ilDelta; MetadataDelta = metadataDelta; PdbDelta = pdbDelta; + UpdatedTypes = updatedTypes; } } @@ -95,7 +97,7 @@ public async Task StartSessionAsync(Solution solution, CancellationToken cancell } var updates = results.ModuleUpdates.Updates.SelectAsArray( - update => new Update(update.Module, update.ILDelta, update.MetadataDelta, update.PdbDelta)); + update => new Update(update.Module, update.ILDelta, update.MetadataDelta, update.PdbDelta, update.UpdatedTypes)); var diagnostics = await results.GetAllDiagnosticsAsync(solution, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs index 99a212faeb0a3..a87224e638ec3 100644 --- a/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/GlobalOperationAwareIdleProcessor.cs @@ -21,9 +21,9 @@ internal abstract class GlobalOperationAwareIdleProcessor : IdleProcessor public GlobalOperationAwareIdleProcessor( IAsynchronousOperationListener listener, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, backOffTimeSpanInMs, shutdownToken) + : base(listener, backOffTimeSpan, shutdownToken) { _globalOperation = null; _globalOperationTask = Task.CompletedTask; diff --git a/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs index 5eb3bc29f5447..84618890b7bef 100644 --- a/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/IdleProcessor.cs @@ -12,28 +12,28 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler { internal abstract class IdleProcessor { - private const int MinimumDelayInMS = 50; + private static readonly TimeSpan s_minimumDelay = TimeSpan.FromMilliseconds(50); protected readonly IAsynchronousOperationListener Listener; protected readonly CancellationToken CancellationToken; - protected readonly int BackOffTimeSpanInMS; + protected readonly TimeSpan BackOffTimeSpan; // points to processor task private Task? _processorTask; // there is one thread that writes to it and one thread reads from it - private int _lastAccessTimeInMS; + private SharedStopwatch _timeSinceLastAccess; public IdleProcessor( IAsynchronousOperationListener listener, - int backOffTimeSpanInMS, + TimeSpan backOffTimeSpan, CancellationToken cancellationToken) { Listener = listener; CancellationToken = cancellationToken; - BackOffTimeSpanInMS = backOffTimeSpanInMS; - _lastAccessTimeInMS = Environment.TickCount; + BackOffTimeSpan = backOffTimeSpan; + _timeSinceLastAccess = SharedStopwatch.StartNew(); } protected abstract Task WaitAsync(CancellationToken cancellationToken); @@ -46,7 +46,7 @@ protected void Start() } protected void UpdateLastAccessTime() - => _lastAccessTimeInMS = Environment.TickCount; + => _timeSinceLastAccess = SharedStopwatch.StartNew(); protected async Task WaitForIdleAsync(IExpeditableDelaySource expeditableDelaySource) { @@ -57,22 +57,22 @@ protected async Task WaitForIdleAsync(IExpeditableDelaySource expeditableDelaySo return; } - var diffInMS = Environment.TickCount - _lastAccessTimeInMS; - if (diffInMS >= BackOffTimeSpanInMS) + var diff = _timeSinceLastAccess.Elapsed; + if (diff >= BackOffTimeSpan) { return; } // TODO: will safestart/unwarp capture cancellation exception? - var timeLeft = BackOffTimeSpanInMS - diffInMS; - if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(Math.Max(MinimumDelayInMS, timeLeft)), CancellationToken).ConfigureAwait(false)) + var timeLeft = BackOffTimeSpan - diff; + if (!await expeditableDelaySource.Delay(TimeSpan.FromMilliseconds(Math.Max(s_minimumDelay.TotalMilliseconds, timeLeft.TotalMilliseconds)), CancellationToken).ConfigureAwait(false)) { - // The delay terminated early to accommodate a blocking operation. Make sure to delay long - // enough that low priority (on idle) operations get a chance to be triggered. + // The delay terminated early to accommodate a blocking operation. Make sure to yield so low + // priority (on idle) operations get a chance to be triggered. // - // 📝 At the time this was discovered, it was not clear exactly why the delay was needed in order - // to avoid live-lock scenarios. - await Task.Delay(TimeSpan.FromMilliseconds(10), CancellationToken).ConfigureAwait(false); + // 📝 At the time this was discovered, it was not clear exactly why the yield (previously delay) + // was needed in order to avoid live-lock scenarios. + await Task.Yield().ConfigureAwait(false); return; } } diff --git a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs index b6eb290a7754d..58529ebc46875 100644 --- a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.cs +++ b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptions.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 Microsoft.CodeAnalysis.Options; namespace Microsoft.CodeAnalysis.SolutionCrawler @@ -16,22 +17,11 @@ internal static class InternalSolutionCrawlerOptions public static readonly Option2 DirectDependencyPropagationOnly = new(nameof(InternalSolutionCrawlerOptions), "Project propagation only on direct dependency", defaultValue: true, storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Project propagation only on direct dependency")); - public static readonly Option2 ActiveFileWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Active file worker backoff timespan in ms", defaultValue: 100, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Active file worker backoff timespan in ms")); - - public static readonly Option2 AllFilesWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "All files worker backoff timespan in ms", defaultValue: 1500, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "All files worker backoff timespan in ms")); - - public static readonly Option2 EntireProjectWorkerBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Entire project analysis worker backoff timespan in ms", defaultValue: 5000, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Entire project analysis worker backoff timespan in ms")); - - public static readonly Option2 SemanticChangeBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Semantic change backoff timespan in ms", defaultValue: 100, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Semantic change backoff timespan in ms")); - - public static readonly Option2 ProjectPropagationBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Project propagation backoff timespan in ms", defaultValue: 500, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Project propagation backoff timespan in ms")); - - public static readonly Option2 PreviewBackOffTimeSpanInMS = new(nameof(InternalSolutionCrawlerOptions), "Preview backoff timespan in ms", defaultValue: 500, - storageLocations: new LocalUserProfileStorageLocation(LocalRegistryPath + "Preview backoff timespan in ms")); + public static readonly TimeSpan ActiveFileWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(100); + public static readonly TimeSpan AllFilesWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(1500); + public static readonly TimeSpan EntireProjectWorkerBackOffTimeSpan = TimeSpan.FromMilliseconds(5000); + public static readonly TimeSpan SemanticChangeBackOffTimeSpan = TimeSpan.FromMilliseconds(100); + public static readonly TimeSpan ProjectPropagationBackOffTimeSpan = TimeSpan.FromMilliseconds(500); + public static readonly TimeSpan PreviewBackOffTimeSpan = TimeSpan.FromMilliseconds(500); } } diff --git a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs index 37ae86b2c73bc..2a5e8c14adad1 100644 --- a/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs +++ b/src/Features/Core/Portable/SolutionCrawler/InternalSolutionCrawlerOptionsProvider.cs @@ -22,11 +22,6 @@ public InternalSolutionCrawlerOptionsProvider() public ImmutableArray Options { get; } = ImmutableArray.Create( InternalSolutionCrawlerOptions.SolutionCrawler, - InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS, - InternalSolutionCrawlerOptions.PreviewBackOffTimeSpanInMS); + InternalSolutionCrawlerOptions.DirectDependencyPropagationOnly); } } diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs index 10f16c389c9cd..d84446da3ba92 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.AbstractPriorityProcessor.cs @@ -30,9 +30,9 @@ public AbstractPriorityProcessor( IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) + : base(listener, globalOperationNotificationService, backOffTimeSpan, shutdownToken) { _gate = new object(); _lazyAnalyzers = lazyAnalyzers; diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs index b857367086186..33fe20c27b821 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.HighPriorityProcessor.cs @@ -35,9 +35,9 @@ public HighPriorityProcessor( IAsynchronousOperationListener listener, IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, backOffTimeSpanInMs, shutdownToken) + : base(listener, backOffTimeSpan, shutdownToken) { _processor = processor; _lazyAnalyzers = lazyAnalyzers; diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs index 0b5f7d08d5f4a..bb5892d87acfb 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.IncrementalAnalyzerProcessor.cs @@ -50,9 +50,9 @@ public IncrementalAnalyzerProcessor( IEnumerable> analyzerProviders, bool initializeLazily, Registration registration, - int highBackOffTimeSpanInMs, - int normalBackOffTimeSpanInMs, - int lowBackOffTimeSpanInMs, + TimeSpan highBackOffTimeSpan, + TimeSpan normalBackOffTimeSpan, + TimeSpan lowBackOffTimeSpan, CancellationToken shutdownToken) { _logAggregator = new LogAggregator(); @@ -81,9 +81,9 @@ public IncrementalAnalyzerProcessor( var globalNotificationService = _registration.Workspace.Services.GetRequiredService(); - _highPriorityProcessor = new HighPriorityProcessor(listener, this, lazyActiveFileAnalyzers, highBackOffTimeSpanInMs, shutdownToken); - _normalPriorityProcessor = new NormalPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, normalBackOffTimeSpanInMs, shutdownToken); - _lowPriorityProcessor = new LowPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, lowBackOffTimeSpanInMs, shutdownToken); + _highPriorityProcessor = new HighPriorityProcessor(listener, this, lazyActiveFileAnalyzers, highBackOffTimeSpan, shutdownToken); + _normalPriorityProcessor = new NormalPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, normalBackOffTimeSpan, shutdownToken); + _lowPriorityProcessor = new LowPriorityProcessor(listener, this, lazyAllAnalyzers, globalNotificationService, lowBackOffTimeSpan, shutdownToken); } private static IDiagnosticAnalyzerService? GetDiagnosticAnalyzerService(IEnumerable> analyzerProviders) diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs index 19b00ffb7a35b..de3db50989511 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.LowPriorityProcessor.cs @@ -30,9 +30,9 @@ public LowPriorityProcessor( IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) + : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpan, shutdownToken) { _workItemQueue = new AsyncProjectWorkItemQueue(processor._registration.ProgressReporter, processor._registration.Workspace); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs index 213ac6b540b6f..681e3ae8c5d49 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.NormalPriorityProcessor.cs @@ -50,9 +50,9 @@ public NormalPriorityProcessor( IncrementalAnalyzerProcessor processor, Lazy> lazyAnalyzers, IGlobalOperationNotificationService globalOperationNotificationService, - int backOffTimeSpanInMs, + TimeSpan backOffTimeSpan, CancellationToken shutdownToken) - : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpanInMs, shutdownToken) + : base(listener, processor, lazyAnalyzers, globalOperationNotificationService, backOffTimeSpan, shutdownToken) { _running = Task.CompletedTask; _workItemQueue = new AsyncDocumentWorkItemQueue(processor._registration.ProgressReporter, processor._registration.Workspace); diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs index b5cf17f1660fd..25bdd62a21fe3 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.SemanticChangeProcessor.cs @@ -41,16 +41,16 @@ public SemanticChangeProcessor( IAsynchronousOperationListener listener, Registration registration, IncrementalAnalyzerProcessor documentWorkerProcessor, - int backOffTimeSpanInMS, - int projectBackOffTimeSpanInMS, + TimeSpan backOffTimeSpan, + TimeSpan projectBackOffTimeSpan, CancellationToken cancellationToken) - : base(listener, backOffTimeSpanInMS, cancellationToken) + : base(listener, backOffTimeSpan, cancellationToken) { _gate = new SemaphoreSlim(initialCount: 0); _registration = registration; - _processor = new ProjectProcessor(listener, registration, documentWorkerProcessor, projectBackOffTimeSpanInMS, cancellationToken); + _processor = new ProjectProcessor(listener, registration, documentWorkerProcessor, projectBackOffTimeSpan, cancellationToken); _workGate = new NonReentrantLock(); _pendingWork = new Dictionary(); @@ -352,9 +352,9 @@ public ProjectProcessor( IAsynchronousOperationListener listener, Registration registration, IncrementalAnalyzerProcessor processor, - int backOffTimeSpanInMS, + TimeSpan backOffTimeSpan, CancellationToken cancellationToken) - : base(listener, backOffTimeSpanInMS, cancellationToken) + : base(listener, backOffTimeSpan, cancellationToken) { _registration = registration; _processor = processor; diff --git a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs index 7cf786abe40fe..430d5e1f23180 100644 --- a/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs +++ b/src/Features/Core/Portable/SolutionCrawler/WorkCoordinator.cs @@ -57,18 +57,18 @@ public WorkCoordinator( _eventProcessingQueue = new TaskQueue(listener, TaskScheduler.Default); - var activeFileBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpanInMS); - var allFilesWorkerBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpanInMS); - var entireProjectWorkerBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpanInMS); + var activeFileBackOffTimeSpan = InternalSolutionCrawlerOptions.ActiveFileWorkerBackOffTimeSpan; + var allFilesWorkerBackOffTimeSpan = InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpan; + var entireProjectWorkerBackOffTimeSpan = InternalSolutionCrawlerOptions.EntireProjectWorkerBackOffTimeSpan; _documentAndProjectWorkerProcessor = new IncrementalAnalyzerProcessor( listener, analyzerProviders, initializeLazily, _registration, - activeFileBackOffTimeSpanInMS, allFilesWorkerBackOffTimeSpanInMS, entireProjectWorkerBackOffTimeSpanInMS, _shutdownToken); + activeFileBackOffTimeSpan, allFilesWorkerBackOffTimeSpan, entireProjectWorkerBackOffTimeSpan, _shutdownToken); - var semanticBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpanInMS); - var projectBackOffTimeSpanInMS = _optionService.GetOption(InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpanInMS); + var semanticBackOffTimeSpan = InternalSolutionCrawlerOptions.SemanticChangeBackOffTimeSpan; + var projectBackOffTimeSpan = InternalSolutionCrawlerOptions.ProjectPropagationBackOffTimeSpan; - _semanticChangeProcessor = new SemanticChangeProcessor(listener, _registration, _documentAndProjectWorkerProcessor, semanticBackOffTimeSpanInMS, projectBackOffTimeSpanInMS, _shutdownToken); + _semanticChangeProcessor = new SemanticChangeProcessor(listener, _registration, _documentAndProjectWorkerProcessor, semanticBackOffTimeSpan, projectBackOffTimeSpan, _shutdownToken); // if option is on if (_optionService.GetOption(InternalSolutionCrawlerOptions.SolutionCrawler)) diff --git a/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs b/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs index 0121f4f09e907..ed90a6a80e861 100644 --- a/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs +++ b/src/Features/Core/Portable/Workspace/ProjectCacheService.SimpleMRUCache.cs @@ -93,9 +93,9 @@ private class ImplicitCacheMonitor : IdleProcessor private readonly ProjectCacheService _owner; private readonly SemaphoreSlim _gate; - public ImplicitCacheMonitor(ProjectCacheService owner, int backOffTimeSpanInMS) + public ImplicitCacheMonitor(ProjectCacheService owner, TimeSpan backOffTimeSpan) : base(AsynchronousOperationListenerProvider.NullListener, - backOffTimeSpanInMS, + backOffTimeSpan, CancellationToken.None) { _owner = owner; @@ -106,7 +106,7 @@ public ImplicitCacheMonitor(ProjectCacheService owner, int backOffTimeSpanInMS) protected override Task ExecuteAsync() { - _owner.ClearExpiredImplicitCache(DateTime.UtcNow - TimeSpan.FromMilliseconds(BackOffTimeSpanInMS)); + _owner.ClearExpiredImplicitCache(DateTime.UtcNow - BackOffTimeSpan); return Task.CompletedTask; } diff --git a/src/Features/Core/Portable/Workspace/ProjectCacheService.cs b/src/Features/Core/Portable/Workspace/ProjectCacheService.cs index 555fffd75022e..dffbc15cc5c51 100644 --- a/src/Features/Core/Portable/Workspace/ProjectCacheService.cs +++ b/src/Features/Core/Portable/Workspace/ProjectCacheService.cs @@ -34,7 +34,7 @@ internal partial class ProjectCacheService : IProjectCacheHostService public ProjectCacheService(Workspace workspace) => _workspace = workspace; - public ProjectCacheService(Workspace workspace, int implicitCacheTimeout) + public ProjectCacheService(Workspace workspace, TimeSpan implicitCacheTimeout) { _workspace = workspace; diff --git a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs index 23b3b6b1a8f8e..3d9a0808a469d 100644 --- a/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/Workspace/VisualStudioProjectCacheHostServiceFactory.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Workspaces [Shared] internal partial class VisualStudioProjectCacheHostServiceFactory : IWorkspaceServiceFactory { - private const int ImplicitCacheTimeoutInMS = 10000; + private static readonly TimeSpan ImplicitCacheTimeout = TimeSpan.FromMilliseconds(10000); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -42,7 +42,7 @@ private static IWorkspaceService GetMiscProjectCache(HostWorkspaceServices works return new ProjectCacheService(workspaceServices.Workspace); } - var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + var projectCacheService = new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeout); // Also clear the cache when the solution is cleared or removed. workspaceServices.Workspace.WorkspaceChanged += (s, e) => @@ -59,7 +59,7 @@ private static IWorkspaceService GetMiscProjectCache(HostWorkspaceServices works private static IWorkspaceService GetVisualStudioProjectCache(HostWorkspaceServices workspaceServices) { // We will finish setting this up in VisualStudioWorkspaceImpl.DeferredInitializationState - return new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + return new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeout); } internal static void ConnectProjectCacheServiceToDocumentTracking(HostWorkspaceServices workspaceServices, ProjectCacheService projectCacheService) diff --git a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs index 77abdc8dd3ea8..06a6f7fc6a383 100644 --- a/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs +++ b/src/VisualStudio/Core/Impl/CodeModel/ProjectCodeModelFactory.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; @@ -27,8 +28,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel [Export(typeof(ProjectCodeModelFactory))] internal sealed class ProjectCodeModelFactory : ForegroundThreadAffinitizedObject, IProjectCodeModelFactory { - private static readonly TimeSpan s_documentBatchProcessingCadence = TimeSpan.FromMilliseconds(1500); - private readonly ConcurrentDictionary _projectCodeModels = new ConcurrentDictionary(); private readonly VisualStudioWorkspace _visualStudioWorkspace; @@ -57,7 +56,7 @@ public ProjectCodeModelFactory( // for the same documents. Once enough time has passed, take the documents that were changed and run // through them, firing their latest events. _documentsToFireEventsFor = new AsyncBatchingWorkQueue( - s_documentBatchProcessingCadence, + InternalSolutionCrawlerOptions.AllFilesWorkerBackOffTimeSpan, ProcessNextDocumentBatchAsync, // We only care about unique doc-ids, so pass in this comparer to collapse streams of changes for a // single document down to one notification. diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs index c2d03ef939c66..72ea5f15aa772 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs @@ -36,7 +36,7 @@ public SourceGeneratedDocumentIdentity(DocumentId documentId, string hintName, s public static string GetGeneratorTypeName(ISourceGenerator generator) { - return GeneratorDriver.GetGeneratorType(generator).FullName!; + return generator.GetGeneratorType().FullName!; } public static string GetGeneratorAssemblyName(ISourceGenerator generator) diff --git a/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs b/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs index 6650c252fe590..fff413c2ac07c 100644 --- a/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs +++ b/src/Workspaces/CoreTestUtilities/TestGeneratorReference.cs @@ -34,7 +34,7 @@ public TestGeneratorReference(ISourceGenerator generator) } public TestGeneratorReference(IIncrementalGenerator generator) - : this(GeneratorDriver.WrapGenerator(generator)) + : this(generator.AsSourceGenerator()) { } diff --git a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs b/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs index 0878c25167aac..447b49f1803e2 100644 --- a/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs +++ b/src/Workspaces/Remote/Core/SolutionChecksumUpdater.cs @@ -4,6 +4,7 @@ #nullable disable +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; @@ -34,7 +35,7 @@ internal sealed class SolutionChecksumUpdater : GlobalOperationAwareIdleProcesso public SolutionChecksumUpdater(Workspace workspace, IAsynchronousOperationListenerProvider listenerProvider, CancellationToken shutdownToken) : base(listenerProvider.GetListener(FeatureAttribute.SolutionChecksumUpdater), workspace.Services.GetService(), - workspace.Options.GetOption(RemoteHostOptions.SolutionChecksumMonitorBackOffTimeSpanInMS), shutdownToken) + TimeSpan.FromMilliseconds(workspace.Options.GetOption(RemoteHostOptions.SolutionChecksumMonitorBackOffTimeSpanInMS)), shutdownToken) { _workspace = workspace; _textChangeQueue = new TaskQueue(Listener, TaskScheduler.Default); diff --git a/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs b/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs index c53e0538403c1..0d58c936114b2 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/ProjectCacheHostServiceFactory.cs @@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Remote [Shared] internal partial class ProjectCacheHostServiceFactory : IWorkspaceServiceFactory { - private const int ImplicitCacheTimeoutInMS = 10000; + private static readonly TimeSpan s_implicitCacheTimeout = TimeSpan.FromMilliseconds(10000); [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -22,6 +22,6 @@ public ProjectCacheHostServiceFactory() } public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new ProjectCacheService(workspaceServices.Workspace, ImplicitCacheTimeoutInMS); + => new ProjectCacheService(workspaceServices.Workspace, s_implicitCacheTimeout); } } diff --git a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs index deaa218aec9c3..e6e77c694d198 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/Host/RemoteHostService.PerformanceReporter.cs @@ -43,7 +43,7 @@ public PerformanceReporter( : base( AsynchronousOperationListenerProvider.NullListener, globalOperationNotificationService, - (int)reportingInterval.TotalMilliseconds, + reportingInterval, shutdownToken) { _event = new SemaphoreSlim(initialCount: 0);