/// <summary> /// Return all diagnostics for the given project stored in this state /// </summary> public async Task <AnalysisResult> GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); if (lastResult.IsDefault) { return(await LoadInitialAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false)); } // PERF: avoid loading data if version is not right one. // avoid loading data flag is there as a strictly perf optimization. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); if (avoidLoadingData && lastResult.Version != version) { return(lastResult); } // if given project doesnt have any diagnostics, return empty. if (lastResult.IsEmpty) { return(new AnalysisResult(lastResult.ProjectId, lastResult.Version)); } // loading data can be cancelled any time. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); var builder = new Builder(project.Id, lastResult.Version, lastResult.DocumentIds); foreach (var documentId in lastResult.DocumentIds) { cancellationToken.ThrowIfCancellationRequested(); var document = project.GetDocument(documentId); if (document == null) { continue; } if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) { Contract.Requires(false, "How this can happen?"); continue; } } if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) { Contract.Requires(false, "How this can happen?"); } return(builder.ToResult()); }
private async Task <bool> TryDeserializeDocumentAsync(DiagnosticDataSerializer serializer, Document document, Builder builder, CancellationToken cancellationToken) { var result = true; result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, s_addSyntaxLocals, builder, cancellationToken).ConfigureAwait(false); result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.SemanticStateName, s_addSemanticLocals, builder, cancellationToken).ConfigureAwait(false); result &= await TryDeserializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, s_addNonLocals, builder, cancellationToken).ConfigureAwait(false); return(result); }
private async Task SerializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, ImmutableArray <DiagnosticData> diagnostics) { // try to serialize it if (await serializer.SerializeAsync(documentOrProject, stateKey, diagnostics, CancellationToken.None).ConfigureAwait(false)) { // we succeeded saving it to persistent storage. remove it from in memory cache if it exists RemoveInMemoryCacheEntry(key, stateKey); return; } // if serialization fail, hold it in the memory InMemoryStorage.Cache(_owner.Analyzer, ValueTuple.Create(key, stateKey), new CacheEntry(serializer.Version, diagnostics)); }
private async Task <ImmutableArray <DiagnosticData> > DeserializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, CancellationToken cancellationToken) { // check cache first CacheEntry entry; if (InMemoryStorage.TryGetValue(_owner.Analyzer, ValueTuple.Create(key, stateKey), out entry) && serializer.Version == entry.Version) { return(entry.Diagnostics); } // try to deserialize it return(await serializer.DeserializeAsync(documentOrProject, stateKey, cancellationToken).ConfigureAwait(false)); }
private async Task <DiagnosticAnalysisResult> LoadInitialProjectAnalysisDataAsync(Project project, CancellationToken cancellationToken) { // loading data can be cancelled any time. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); var builder = new Builder(project.Id, version); if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, s_addOthers, builder, cancellationToken).ConfigureAwait(false)) { return(new DiagnosticAnalysisResult(project.Id, VersionStamp.Default, ImmutableHashSet <DocumentId> .Empty, isEmpty: true, fromBuild: false)); } return(builder.ToResult()); }
private async Task <DiagnosticAnalysisResult> LoadInitialProjectAnalysisDataAsync(IPersistentStorageService persistentService, Project project, CancellationToken cancellationToken) { // loading data can be cancelled any time. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); var builder = new Builder(project, version); if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) { return(DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default)); } return(builder.ToResult()); }
private async Task <bool> TryDeserializeAsync <T>( DiagnosticDataSerializer serializer, object documentOrProject, T key, string stateKey, Action <T, ImmutableArray <DiagnosticData> > add, CancellationToken cancellationToken) where T : class { var diagnostics = await DeserializeAsync(serializer, documentOrProject, key, stateKey, cancellationToken).ConfigureAwait(false); if (diagnostics.IsDefault) { return(false); } add(key, diagnostics); return(true); }
public async Task MergeAsync(ActiveFileState state, Document document) { Contract.ThrowIfFalse(state.DocumentId == document.Id); // merge active file state to project state var lastResult = _lastResult; var syntax = state.GetAnalysisData(AnalysisKind.Syntax); var semantic = state.GetAnalysisData(AnalysisKind.Semantic); AnalyzerABTestLogger.LogDocumentDiagnostics(document, _owner.StateName, syntax.Items, semantic.Items); var project = document.Project; // if project didn't successfully loaded, then it is same as FSA off var fullAnalysis = ServiceFeatureOnOffOptions.IsClosedFileDiagnosticsEnabled(project) && await project.HasSuccessfullyLoadedAsync(CancellationToken.None).ConfigureAwait(false); // keep from build flag if full analysis is off var fromBuild = fullAnalysis ? false : lastResult.FromBuild; var openFileOnlyAnalyzer = _owner.Analyzer.IsOpenFileOnly(document.Project.Solution.Workspace); // if it is allowed to keep project state, check versions and if they are same, bail out. // if full solution analysis is off or we are asked to reset document state, we always merge. if (fullAnalysis && !openFileOnlyAnalyzer && syntax.Version != VersionStamp.Default && syntax.Version == semantic.Version && syntax.Version == lastResult.Version) { // all data is in sync already. return; } // we have mixed versions or full analysis is off, set it to default so that it can be re-calculated next time so data can be in sync. var version = VersionStamp.Default; // serialization can't be cancelled. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); // save active file diagnostics back to project state await SerializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, syntax.Items).ConfigureAwait(false); await SerializeAsync(serializer, document, document.Id, _owner.SemanticStateName, semantic.Items).ConfigureAwait(false); // save last aggregated form of analysis result _lastResult = _lastResult.UpdateAggregatedResult(version, state.DocumentId, fromBuild); }
private async Task <bool> TryDeserializeAsync <TKey, TArg>( DiagnosticDataSerializer serializer, object documentOrProject, TKey key, string stateKey, Action <TArg, TKey, ImmutableArray <DiagnosticData> > add, TArg arg, CancellationToken cancellationToken) where TKey : class { var diagnostics = await DeserializeAsync(serializer, documentOrProject, key, stateKey, cancellationToken).ConfigureAwait(false); if (diagnostics == null) { return(false); } add(arg, key, diagnostics.Value); return(true); }
private async Task <DiagnosticAnalysisResult> LoadInitialAnalysisDataAsync(Document document, CancellationToken cancellationToken) { // loading data can be cancelled any time. var project = document.Project; var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); var builder = new Builder(project, version); if (!await TryDeserializeDocumentAsync(serializer, document, builder, cancellationToken).ConfigureAwait(false)) { return(DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default)); } return(builder.ToResult()); }
public async Task SerializationTest_Document() { using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic, workspaceKind: "DiagnosticDataSerializerTest")) { var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); var diagnostics = new[] { new DiagnosticData( "test1", "Test", "test1 message", "test1 message format", DiagnosticSeverity.Info, DiagnosticSeverity.Info, false, 1, ImmutableArray<string>.Empty, ImmutableDictionary<string, string>.Empty, workspace, document.Project.Id, new DiagnosticDataLocation(document.Id, new TextSpan(10, 20), "originalFile1", 30, 30, 40, 40, "mappedFile1", 10, 10, 20, 20)), new DiagnosticData( "test2", "Test", "test2 message", "test2 message format", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 0, ImmutableArray.Create<string>("Test2"), ImmutableDictionary<string, string>.Empty.Add("propertyKey", "propertyValue"), workspace, document.Project.Id, new DiagnosticDataLocation(document.Id, new TextSpan(30, 40), "originalFile2", 70, 70, 80, 80, "mappedFile2", 50, 50, 60, 60), title: "test2 title", description: "test2 description", helpLink: "http://test2link"), new DiagnosticData( "test3", "Test", "test3 message", "test3 message format", DiagnosticSeverity.Error, DiagnosticSeverity.Warning, true, 2, ImmutableArray.Create<string>("Test3", "Test3_2"), ImmutableDictionary<string, string>.Empty.Add("p1Key", "p1Value").Add("p2Key", "p2Value"), workspace, document.Project.Id, new DiagnosticDataLocation(document.Id, new TextSpan(50, 60), "originalFile3", 110, 110, 120, 120, "mappedFile3", 90, 90, 100, 100), title: "test3 title", description: "test3 description", helpLink: "http://test3link"), }.ToImmutableArray(); var utcTime = DateTime.UtcNow; var analyzerVersion = VersionStamp.Create(utcTime); var version = VersionStamp.Create(utcTime.AddDays(1)); var key = "document"; var serializer = new DiagnosticDataSerializer(analyzerVersion, version); Assert.True(await serializer.SerializeAsync(document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); var recovered = await serializer.DeserializeAsync(document, key, CancellationToken.None); AssertDiagnostics(diagnostics, recovered); } }
public async Task MergeAsync(ActiveFileState state, Document document) { Contract.ThrowIfFalse(state.DocumentId == document.Id); // merge active file state to project state var lastResult = _lastResult; var syntax = state.GetAnalysisData(AnalysisKind.Syntax); var semantic = state.GetAnalysisData(AnalysisKind.Semantic); var project = document.Project; var fullAnalysis = ServiceFeatureOnOffOptions.IsClosedFileDiagnosticsEnabled(project); // keep from build flag if full analysis is off var fromBuild = fullAnalysis ? false : lastResult.FromBuild; // if it is allowed to keep project state, check versions and if they are same, bail out // if full solution analysis is off or we are asked to reset document state, we always merge. if (fullAnalysis && syntax.Version != VersionStamp.Default && syntax.Version == semantic.Version && syntax.Version == lastResult.Version) { // all data is in sync already. return; } // we have mixed versions or full analysis is off, set it to default so that it can be re-calculated next time so data can be in sync. var version = VersionStamp.Default; // serialization can't be cancelled. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); // save active file diagnostics back to project state await SerializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, syntax.Items).ConfigureAwait(false); await SerializeAsync(serializer, document, document.Id, _owner.SemanticStateName, semantic.Items).ConfigureAwait(false); // save last aggregated form of analysis result _lastResult = new DiagnosticAnalysisResult(_lastResult.ProjectId, version, _lastResult.DocumentIdsOrEmpty.Add(state.DocumentId), isEmpty: false, fromBuild: fromBuild); }
public async Task <DiagnosticAnalysisResult> GetAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; Contract.ThrowIfFalse(lastResult.ProjectId == document.Project.Id); if (lastResult.IsDefault) { return(await LoadInitialAnalysisDataAsync(persistentService, document, cancellationToken).ConfigureAwait(false)); } var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); if (avoidLoadingData && lastResult.Version != version) { return(lastResult); } // if given document doesnt have any diagnostics, return empty. if (IsEmpty(lastResult, document.Id)) { return(DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version)); } // loading data can be cancelled any time. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); var builder = new Builder(document.Project, lastResult.Version); if (!await TryDeserializeDocumentDiagnosticsAsync(persistentService, serializer, document, builder, cancellationToken).ConfigureAwait(false)) { Debug.Assert(lastResult.Version == VersionStamp.Default); // this can happen if we merged back active file diagnostics back to project state but // project state didn't have diagnostics for the file yet. (since project state was staled) } return(builder.ToResult()); }
private async Task SerializeAsync( IPersistentStorageService?persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument?document, object key, string stateKey, ImmutableArray <DiagnosticData> diagnostics ) { Contract.ThrowIfFalse(document == null || document.Project == project); // try to serialize it if ( persistentService != null && await serializer .SerializeAsync( persistentService, project, document, stateKey, diagnostics, CancellationToken.None ) .ConfigureAwait(false) ) { // we succeeded saving it to persistent storage. remove it from in memory cache if it exists RemoveInMemoryCacheEntry(key, stateKey); return; } // if serialization fail, hold it in the memory InMemoryStorage.Cache( _owner.Analyzer, (key, stateKey), new CacheEntry(serializer.Version, diagnostics) ); }
public async Task <DiagnosticAnalysisResult> GetProjectAnalysisDataAsync(IPersistentStorageService persistentService, Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); if (lastResult.IsDefault) { return(await LoadInitialProjectAnalysisDataAsync(persistentService, project, cancellationToken).ConfigureAwait(false)); } var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); if (avoidLoadingData && lastResult.Version != version) { return(lastResult); } // if given document doesnt have any diagnostics, return empty. if (lastResult.IsEmpty) { return(DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version)); } // loading data can be cancelled any time. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); var builder = new Builder(project, lastResult.Version); if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) { // this can happen if SaveAsync is not yet called but active file merge happened. one of case is if user did build before the very first // analysis happened. } return(builder.ToResult()); }
/// <summary> /// Return all no location diagnostics for the given project stored in this state /// </summary> public async Task <AnalysisResult> GetProjectAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); if (lastResult.IsDefault) { return(await LoadInitialProjectAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false)); } var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); if (avoidLoadingData && lastResult.Version != version) { return(lastResult); } // if given document doesnt have any diagnostics, return empty. if (lastResult.IsEmpty) { return(new AnalysisResult(lastResult.ProjectId, lastResult.Version)); } // loading data can be cancelled any time. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); var builder = new Builder(project.Id, lastResult.Version); if (!await TryDeserializeAsync(serializer, project, project.Id, _owner.NonLocalStateName, builder.AddOthers, cancellationToken).ConfigureAwait(false)) { Contract.Requires(false, "How this can happen?"); } return(builder.ToResult()); }
public async Task SaveAsync(Project project, AnalysisResult result) { Contract.ThrowIfTrue(result.IsAggregatedForm); RemoveInMemoryCache(_lastResult); // save last aggregated form of analysis result _lastResult = result.ToAggregatedForm(); // serialization can't be cancelled. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); foreach (var documentId in result.DocumentIds) { var document = project.GetDocument(documentId); Contract.ThrowIfNull(document); await SerializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, GetResult(result, AnalysisKind.Syntax, document.Id)).ConfigureAwait(false); await SerializeAsync(serializer, document, document.Id, _owner.SemanticStateName, GetResult(result, AnalysisKind.Semantic, document.Id)).ConfigureAwait(false); await SerializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, GetResult(result, AnalysisKind.NonLocal, document.Id)).ConfigureAwait(false); } await SerializeAsync(serializer, project, result.ProjectId, _owner.NonLocalStateName, result.Others).ConfigureAwait(false); }
public async Task SaveAsync(Project project, DiagnosticAnalysisResult result) { Contract.ThrowIfTrue(result.IsAggregatedForm); RemoveInMemoryCache(_lastResult); // save last aggregated form of analysis result _lastResult = result.ToAggregatedForm(); // serialization can't be cancelled. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); foreach (var documentId in result.DocumentIds) { var document = project.GetDocument(documentId); if (document == null) { // it can happen with build synchronization since, in build case, // we don't have actual snapshot (we have no idea what sources out of proc build has picked up) // so we might be out of sync. // example of such cases will be changing anything about solution while building is going on. // it can be user explict actions such as unloading project, deleting a file, but also it can be // something project system or roslyn workspace does such as populating workspace right after // solution is loaded. continue; } await SerializeAsync(serializer, document, document.Id, _owner.SyntaxStateName, GetResult(result, AnalysisKind.Syntax, document.Id)).ConfigureAwait(false); await SerializeAsync(serializer, document, document.Id, _owner.SemanticStateName, GetResult(result, AnalysisKind.Semantic, document.Id)).ConfigureAwait(false); await SerializeAsync(serializer, document, document.Id, _owner.NonLocalStateName, GetResult(result, AnalysisKind.NonLocal, document.Id)).ConfigureAwait(false); } await SerializeAsync(serializer, project, result.ProjectId, _owner.NonLocalStateName, result.Others).ConfigureAwait(false); AnalyzerABTestLogger.LogProjectDiagnostics(project, _owner.StateName, result); }
public async Task <DiagnosticAnalysisResult> GetAnalysisDataAsync(IPersistentStorageService persistentService, Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; Contract.ThrowIfFalse(lastResult.ProjectId == project.Id); if (lastResult.IsDefault) { return(await LoadInitialAnalysisDataAsync(persistentService, project, cancellationToken).ConfigureAwait(false)); } RoslynDebug.Assert(lastResult.DocumentIds != null); // PERF: avoid loading data if version is not right one. // avoid loading data flag is there as a strictly perf optimization. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); if (avoidLoadingData && lastResult.Version != version) { return(lastResult); } // if given project doesnt have any diagnostics, return empty. if (lastResult.IsEmpty) { return(DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version)); } // loading data can be cancelled any time. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); var builder = new Builder(project, lastResult.Version, lastResult.DocumentIds); foreach (var documentId in lastResult.DocumentIds) { cancellationToken.ThrowIfCancellationRequested(); var document = project.GetDocument(documentId); if (document == null) { continue; } if (!await TryDeserializeDocumentDiagnosticsAsync(persistentService, serializer, document, builder, cancellationToken).ConfigureAwait(false)) { Debug.Assert(lastResult.Version == VersionStamp.Default); // this can happen if we merged back active file diagnostics back to project state but // project state didn't have diagnostics for the file yet. (since project state was staled) continue; } } if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) { // this can happen if SaveAsync is not yet called but active file merge happened. one of case is if user did build before the very first // analysis happened. } return(builder.ToResult()); }
private ValueTask <ImmutableArray <DiagnosticData> > DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument?document, object key, string stateKey, CancellationToken cancellationToken) { Contract.ThrowIfFalse(document == null || document.Project == project); if (InMemoryStorage.TryGetValue(_owner.Analyzer, (key, stateKey), out var entry) && serializer.Version == entry.Version) { return(new ValueTask <ImmutableArray <DiagnosticData> >(entry.Diagnostics)); } return(serializer.DeserializeAsync(persistentService, project, document, stateKey, cancellationToken)); }
private async Task <bool> TryDeserializeProjectDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Builder builder, CancellationToken cancellationToken) { var diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document : null, project.Id, _owner.NonLocalStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddOthers(diagnostics); return(true); } return(false); }
private async Task <bool> TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, TextDocument document, Builder builder, CancellationToken cancellationToken) { var success = true; var project = document.Project; var documentId = document.Id; var diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document, documentId, _owner.SyntaxStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddSyntaxLocals(documentId, diagnostics); } else { success = false; } diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document, documentId, _owner.SemanticStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddSemanticLocals(documentId, diagnostics); } else { success = false; } diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document, documentId, _owner.NonLocalStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddNonLocals(documentId, diagnostics); } else { success = false; } return(success); }
private Task <StrongBox <ImmutableArray <DiagnosticData> > > DeserializeAsync(DiagnosticDataSerializer serializer, object documentOrProject, object key, string stateKey, CancellationToken cancellationToken) { // when VS is loading new solution, we will try to find out all diagnostics persisted from previous VS session. // in that situation, it is possible that we have a lot of deserialization returning empty result. previously we used to // return default(ImmutableArray) for such case, but it turns out async/await framework has allocation issues with returning // default(value type), so we are using StrongBox to return no data as null. async/await has optimization where it will return // cached empty task if given value is null for reference type. (see AsyncMethodBuilder.GetTaskForResult) // // right now, we can't use Nullable either, since it is not one of value type the async/await will reuse cached task. in future, // if they do, we can change it to return Nullable<ImmutableArray> // // after initial deserialization, we track actual document/project that actually have diagnostics so no data won't be a common // case. // check cache first if (InMemoryStorage.TryGetValue(_owner.Analyzer, (key, stateKey), out var entry) && serializer.Version == entry.Version) { if (entry.Diagnostics.Length == 0) { // if there is no result, use cached task return(s_emptyResultTaskCache); } return(Task.FromResult(new StrongBox <ImmutableArray <DiagnosticData> >(entry.Diagnostics))); } // try to deserialize it return(serializer.DeserializeAsync(documentOrProject, stateKey, cancellationToken)); }