From b19ba09076d975ea10433233bff4ab69463de858 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Thu, 14 Sep 2023 11:51:17 -0700 Subject: [PATCH 1/4] Include a bit in DocumentId to indicate if it corresponds to a SG document or not Docs REorder mistype --- .../Shared/AbstractSyntaxIndex_Persistence.cs | 2 +- .../Shared/Extensions/ProjectExtensions.cs | 17 +- .../Portable/Workspace/Solution/DocumentId.cs | 33 ++-- .../Portable/Workspace/Solution/Project.cs | 35 ++-- .../SourceGeneratedDocumentIdentity.cs | 181 ++++++++++-------- 5 files changed, 134 insertions(+), 134 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs index 22d86e27cb15f..5d23b20ea675f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Shared/AbstractSyntaxIndex_Persistence.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols internal partial class AbstractSyntaxIndex : IObjectWritable { private static readonly string s_persistenceName = typeof(TIndex).Name; - private static readonly Checksum s_serializationFormatChecksum = Checksum.Create("37"); + private static readonly Checksum s_serializationFormatChecksum = Checksum.Create("38"); /// /// Cache of ParseOptions to a checksum for the contained diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/ProjectExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/ProjectExtensions.cs index 8488743d5d473..769477242f8c2 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/ProjectExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/ProjectExtensions.cs @@ -44,25 +44,12 @@ public static TextDocument GetRequiredAnalyzerConfigDocument(this Project projec => project.GetAnalyzerConfigDocument(documentId) ?? throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); public static TextDocument GetRequiredTextDocument(this Project project, DocumentId documentId) - { - var document = project.GetTextDocument(documentId); - if (document == null) - { - throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); - } - - return document; - } + => project.GetTextDocument(documentId) ?? throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); public static async ValueTask GetRequiredSourceGeneratedDocumentAsync(this Project project, DocumentId documentId, CancellationToken cancellationToken) { var document = await project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); - if (document is null) - { - throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); - } - - return document; + return document ?? throw new InvalidOperationException(WorkspaceExtensionsResources.The_solution_does_not_contain_the_specified_document); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentId.cs b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentId.cs index 1c063cc6d1f05..31d3e7cec7da9 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/DocumentId.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/DocumentId.cs @@ -15,8 +15,8 @@ namespace Microsoft.CodeAnalysis /// An identifier that can be used to retrieve the same across versions of the /// workspace. /// - [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] [DataContract] + [DebuggerDisplay("{GetDebuggerDisplay(),nq}")] public sealed class DocumentId : IEquatable, IObjectWritable { [DataMember(Order = 0)] @@ -24,12 +24,15 @@ public sealed class DocumentId : IEquatable, IObjectWritable [DataMember(Order = 1)] public Guid Id { get; } [DataMember(Order = 2)] + internal bool IsSourceGenerated { get; } + [DataMember(Order = 3)] private readonly string? _debugName; - private DocumentId(ProjectId projectId, Guid guid, string? debugName) + private DocumentId(ProjectId projectId, Guid guid, bool isSourceGenerated, string? debugName) { this.ProjectId = projectId; this.Id = guid; + this.IsSourceGenerated = isSourceGenerated; _debugName = debugName; } @@ -39,28 +42,20 @@ private DocumentId(ProjectId projectId, Guid guid, string? debugName) /// The project id this document id is relative to. /// An optional name to make this id easier to recognize while debugging. public static DocumentId CreateNewId(ProjectId projectId, string? debugName = null) - { - if (projectId == null) - { - throw new ArgumentNullException(nameof(projectId)); - } - - return new DocumentId(projectId, Guid.NewGuid(), debugName); - } + => CreateFromSerialized(projectId, Guid.NewGuid(), isSourceGenerated: false, debugName); public static DocumentId CreateFromSerialized(ProjectId projectId, Guid id, string? debugName = null) + => CreateFromSerialized(projectId, id, isSourceGenerated: false, debugName); + + internal static DocumentId CreateFromSerialized(ProjectId projectId, Guid id, bool isSourceGenerated, string? debugName) { if (projectId == null) - { throw new ArgumentNullException(nameof(projectId)); - } if (id == Guid.Empty) - { throw new ArgumentException(nameof(id)); - } - return new DocumentId(projectId, id, debugName); + return new DocumentId(projectId, id, isSourceGenerated, debugName); } internal string? DebugName => _debugName; @@ -78,7 +73,7 @@ public bool Equals(DocumentId? other) { // Technically, we don't need to check project id. return - other is object && + other is not null && this.Id == other.Id && this.ProjectId == other.ProjectId; } @@ -97,19 +92,19 @@ public override int GetHashCode() void IObjectWritable.WriteTo(ObjectWriter writer) { ProjectId.WriteTo(writer); - writer.WriteGuid(Id); + writer.WriteBoolean(IsSourceGenerated); writer.WriteString(DebugName); } internal static DocumentId ReadFrom(ObjectReader reader) { var projectId = ProjectId.ReadFrom(reader); - var guid = reader.ReadGuid(); + var isSourceGenerated = reader.ReadBoolean(); var debugName = reader.ReadString(); - return CreateFromSerialized(projectId, guid, debugName); + return CreateFromSerialized(projectId, guid, isSourceGenerated, debugName); } } } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs index a235517e2f99c..ca5b0afdba85e 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs @@ -271,9 +271,7 @@ public bool ContainsAnalyzerConfigDocument(DocumentId documentId) { var document = GetDocument(documentId) ?? GetAdditionalDocument(documentId) ?? GetAnalyzerConfigDocument(documentId); if (document != null) - { return document; - } return await GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); } @@ -297,21 +295,26 @@ internal async ValueTask> GetAllRegularAndSourceGeneratedD public async ValueTask GetSourceGeneratedDocumentAsync(DocumentId documentId, CancellationToken cancellationToken = default) { + // Immediately shortcircuit out if we know this is not a doc-id corresponding to an SG document. + if (!documentId.IsSourceGenerated) + return null; + + // User incorrect called into us with a doc id for a different project. Ideally we'd throw here, but we've + // always been resilient to this misuse since the start of roslyn, so we just quick-bail instead. + if (this.Id != documentId.ProjectId) + return null; + // Quick check first: if we already have created a SourceGeneratedDocument wrapper, we're good if (_idToSourceGeneratedDocumentMap.TryGetValue(documentId, out var sourceGeneratedDocument)) - { return sourceGeneratedDocument; - } // We'll have to run generators if we haven't already and now try to find it. var generatedDocumentStates = await _solution.State.GetSourceGeneratedDocumentStatesAsync(State, cancellationToken).ConfigureAwait(false); var generatedDocumentState = generatedDocumentStates.GetState(documentId); - if (generatedDocumentState != null) - { - return GetOrCreateSourceGeneratedDocument(generatedDocumentState); - } + if (generatedDocumentState is null) + return null; - return null; + return GetOrCreateSourceGeneratedDocument(generatedDocumentState); } internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGeneratedDocumentState state) @@ -327,20 +330,24 @@ internal SourceGeneratedDocument GetOrCreateSourceGeneratedDocument(SourceGenera /// internal SourceGeneratedDocument? TryGetSourceGeneratedDocumentForAlreadyGeneratedId(DocumentId documentId) { + // Immediately shortcircuit out if we know this is not a doc-id corresponding to an SG document. + if (!documentId.IsSourceGenerated) + return null; + + // User incorrect called into us with a doc id for a different project. Ideally we'd throw here, but we've + // always been resilient to this misuse since the start of roslyn, so we just quick-bail instead. + if (this.Id != documentId.ProjectId) + return null; + // Easy case: do we already have the SourceGeneratedDocument created? if (_idToSourceGeneratedDocumentMap.TryGetValue(documentId, out var document)) - { return document; - } // Trickier case now: it's possible we generated this, but we don't actually have the SourceGeneratedDocument for it, so let's go // try to fetch the state. var documentState = _solution.State.TryGetSourceGeneratedDocumentStateForAlreadyGeneratedId(documentId); - if (documentState == null) - { return null; - } return ImmutableHashMapExtensions.GetOrAdd(ref _idToSourceGeneratedDocumentMap, documentId, s_createSourceGeneratedDocumentFunction, (documentState, this)); } diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs index 4c2f9baef98ec..0af640d481159 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SourceGeneratedDocumentIdentity.cs @@ -3,98 +3,109 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Text; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis +namespace Microsoft.CodeAnalysis; + +/// +/// A small struct that holds the values that define the identity of a source generated document, and don't change +/// as new generations happen. This is mostly for convenience as we are reguarly working with this combination of values. +/// +internal readonly record struct SourceGeneratedDocumentIdentity + : IObjectWritable, IEquatable { - /// - /// A small struct that holds the values that define the identity of a source generated document, and don't change - /// as new generations happen. This is mostly for convenience as we are reguarly working with this combination of values. - /// - internal readonly record struct SourceGeneratedDocumentIdentity - (DocumentId DocumentId, string HintName, SourceGeneratorIdentity Generator, string FilePath) - : IObjectWritable, IEquatable + public bool ShouldReuseInSerialization => true; + + public readonly DocumentId DocumentId; + public readonly string HintName; + public readonly SourceGeneratorIdentity Generator; + public readonly string FilePath; + + public SourceGeneratedDocumentIdentity(DocumentId documentId, string hintName, SourceGeneratorIdentity generator, string filePath) + { + Contract.ThrowIfFalse(documentId.IsSourceGenerated); + DocumentId = documentId; + HintName = hintName; + Generator = generator; + FilePath = filePath; + } + + public static SourceGeneratedDocumentIdentity Generate(ProjectId projectId, string hintName, ISourceGenerator generator, string filePath, AnalyzerReference analyzerReference) + { + // We want the DocumentId generated for a generated output to be stable between Compilations; this is so features that track + // a document by DocumentId can find it after some change has happened that requires generators to run again. + // To achieve this we'll just do a crytographic hash of the generator name and hint name; the choice of a cryptographic hash + // as opposed to a more generic string hash is we actually want to ensure we don't have collisions. + var generatorIdentity = new SourceGeneratorIdentity(generator, analyzerReference); + + // Combine the strings together; we'll use Encoding.Unicode since that'll match the underlying format; this can be made much + // faster once we're on .NET Core since we could directly treat the strings as ReadOnlySpan. + var projectIdBytes = projectId.Id.ToByteArray(); + + // The assembly path should exist in any normal scenario; the hashing of the name only would apply if the user loaded a + // dynamic assembly they produced at runtime and passed us that via a custom AnalyzerReference. + var assemblyNameToHash = generatorIdentity.AssemblyPath ?? generatorIdentity.AssemblyName; + + using var _ = ArrayBuilder.GetInstance(capacity: (assemblyNameToHash.Length + 1 + generatorIdentity.TypeName.Length + 1 + hintName.Length) * 2 + projectIdBytes.Length, out var hashInput); + hashInput.AddRange(projectIdBytes); + + // Add a null to separate the generator name and hint name; since this is effectively a joining of UTF-16 bytes + // we'll use a UTF-16 null just to make sure there's absolutely no risk of collision. + hashInput.AddRange(Encoding.Unicode.GetBytes(assemblyNameToHash)); + hashInput.AddRange(0, 0); + hashInput.AddRange(Encoding.Unicode.GetBytes(generatorIdentity.TypeName)); + hashInput.AddRange(0, 0); + hashInput.AddRange(Encoding.Unicode.GetBytes(hintName)); + + // The particular choice of crypto algorithm here is arbitrary and can be always changed as necessary. The only requirement + // is it must be collision resistant, and provide enough bits to fill a GUID. + using var crytpoAlgorithm = System.Security.Cryptography.SHA256.Create(); + var hash = crytpoAlgorithm.ComputeHash(hashInput.ToArray()); + Array.Resize(ref hash, 16); + var guid = new Guid(hash); + + var documentId = DocumentId.CreateFromSerialized(projectId, guid, isSourceGenerated: true, hintName); + + return new SourceGeneratedDocumentIdentity(documentId, hintName, generatorIdentity, filePath); + } + + public void WriteTo(ObjectWriter writer) { - public bool ShouldReuseInSerialization => true; - - public static SourceGeneratedDocumentIdentity Generate(ProjectId projectId, string hintName, ISourceGenerator generator, string filePath, AnalyzerReference analyzerReference) - { - // We want the DocumentId generated for a generated output to be stable between Compilations; this is so features that track - // a document by DocumentId can find it after some change has happened that requires generators to run again. - // To achieve this we'll just do a crytographic hash of the generator name and hint name; the choice of a cryptographic hash - // as opposed to a more generic string hash is we actually want to ensure we don't have collisions. - var generatorIdentity = new SourceGeneratorIdentity(generator, analyzerReference); - - // Combine the strings together; we'll use Encoding.Unicode since that'll match the underlying format; this can be made much - // faster once we're on .NET Core since we could directly treat the strings as ReadOnlySpan. - var projectIdBytes = projectId.Id.ToByteArray(); - - // The assembly path should exist in any normal scenario; the hashing of the name only would apply if the user loaded a - // dynamic assembly they produced at runtime and passed us that via a custom AnalyzerReference. - var assemblyNameToHash = generatorIdentity.AssemblyPath ?? generatorIdentity.AssemblyName; - - using var _ = ArrayBuilder.GetInstance(capacity: (assemblyNameToHash.Length + 1 + generatorIdentity.TypeName.Length + 1 + hintName.Length) * 2 + projectIdBytes.Length, out var hashInput); - hashInput.AddRange(projectIdBytes); - - // Add a null to separate the generator name and hint name; since this is effectively a joining of UTF-16 bytes - // we'll use a UTF-16 null just to make sure there's absolutely no risk of collision. - hashInput.AddRange(Encoding.Unicode.GetBytes(assemblyNameToHash)); - hashInput.AddRange(0, 0); - hashInput.AddRange(Encoding.Unicode.GetBytes(generatorIdentity.TypeName)); - hashInput.AddRange(0, 0); - hashInput.AddRange(Encoding.Unicode.GetBytes(hintName)); - - // The particular choice of crypto algorithm here is arbitrary and can be always changed as necessary. The only requirement - // is it must be collision resistant, and provide enough bits to fill a GUID. - using var crytpoAlgorithm = System.Security.Cryptography.SHA256.Create(); - var hash = crytpoAlgorithm.ComputeHash(hashInput.ToArray()); - Array.Resize(ref hash, 16); - var guid = new Guid(hash); - - var documentId = DocumentId.CreateFromSerialized(projectId, guid, hintName); - - return new SourceGeneratedDocumentIdentity(documentId, hintName, generatorIdentity, filePath); - } - - public void WriteTo(ObjectWriter writer) - { - DocumentId.WriteTo(writer); - - writer.WriteString(HintName); - writer.WriteString(Generator.AssemblyName); - writer.WriteString(Generator.AssemblyPath); - writer.WriteString(Generator.AssemblyVersion.ToString()); - writer.WriteString(Generator.TypeName); - writer.WriteString(FilePath); - } - - internal static SourceGeneratedDocumentIdentity ReadFrom(ObjectReader reader) - { - var documentId = DocumentId.ReadFrom(reader); - - var hintName = reader.ReadString(); - var generatorAssemblyName = reader.ReadString(); - var generatorAssemblyPath = reader.ReadString(); - var generatorAssemblyVersion = Version.Parse(reader.ReadString()); - var generatorTypeName = reader.ReadString(); - var filePath = reader.ReadString(); - - return new SourceGeneratedDocumentIdentity( - documentId, - hintName, - new SourceGeneratorIdentity - { - AssemblyName = generatorAssemblyName, - AssemblyPath = generatorAssemblyPath, - AssemblyVersion = generatorAssemblyVersion, - TypeName = generatorTypeName - }, - filePath); - } + DocumentId.WriteTo(writer); + + writer.WriteString(HintName); + writer.WriteString(Generator.AssemblyName); + writer.WriteString(Generator.AssemblyPath); + writer.WriteString(Generator.AssemblyVersion.ToString()); + writer.WriteString(Generator.TypeName); + writer.WriteString(FilePath); + } + + internal static SourceGeneratedDocumentIdentity ReadFrom(ObjectReader reader) + { + var documentId = DocumentId.ReadFrom(reader); + + var hintName = reader.ReadString(); + var generatorAssemblyName = reader.ReadString(); + var generatorAssemblyPath = reader.ReadString(); + var generatorAssemblyVersion = Version.Parse(reader.ReadString()); + var generatorTypeName = reader.ReadString(); + var filePath = reader.ReadString(); + + return new SourceGeneratedDocumentIdentity( + documentId, + hintName, + new SourceGeneratorIdentity + { + AssemblyName = generatorAssemblyName, + AssemblyPath = generatorAssemblyPath, + AssemblyVersion = generatorAssemblyVersion, + TypeName = generatorTypeName + }, + filePath); } } From f110af5930c7bc700eec60766fc004fee8a8c7cc Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 15 Sep 2023 09:42:35 -0700 Subject: [PATCH 2/4] Add mitigations for SG documents not being present --- .../Core.Wpf/Peek/PeekableItemSource.cs | 3 ++ .../DefinitionContextTracker.cs | 3 ++ .../AbstractDocumentHighlightsService.cs | 3 +- .../IRemoteDocumentHighlightsService.cs | 13 +++++- .../NavigateTo/RoslynNavigateToItem.cs | 10 +++- .../Portable/Navigation/INavigableItem.cs | 18 ++++++-- .../AbstractGoToDefinitionHandler.cs | 6 ++- .../Symbols/WorkspaceSymbolsHandler.cs | 2 + .../OmniSharpFindDefinitionService.cs | 13 +++++- .../OmniSharpNavigateToSearchService.cs | 3 ++ .../Core/Def/Progression/GraphBuilder.cs | 3 ++ .../Definitions/GoToDefinitionHandler.cs | 3 ++ .../FindReferences/DependentTypeFinder.cs | 19 +++++++- ...mbolFinder.FindReferencesServerCallback.cs | 25 ++++++++-- .../Core/Portable/Remote/RemoteArguments.cs | 11 ++++- .../Portable/Rename/IRemoteRenamerService.cs | 2 +- .../Rename/LightweightRenameLocations.cs | 2 +- .../RemoteDocumentHighlightsService.cs | 9 +++- .../RemoteInheritanceMarginService.cs | 9 +++- .../RemoteNavigationBarItemService.cs | 8 +++- .../TaskList/RemoteTaskListService.cs | 9 +++- .../Extensions/ImmutableArrayExtensions.cs | 15 ++++++ .../Core/Extensions/ISolutionExtensions.cs | 46 ++++++++++++++++++- 23 files changed, 206 insertions(+), 29 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs index 51480e02a54fb..346b43151edaa 100644 --- a/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs +++ b/src/EditorFeatures/Core.Wpf/Peek/PeekableItemSource.cs @@ -137,6 +137,9 @@ private static async IAsyncEnumerable GetPeekableItemsForNavigabl workspace, document.Id, item.SourceSpan.Start, cancellationToken).ConfigureAwait(false)) { var text = await document.GetTextAsync(project.Solution, cancellationToken).ConfigureAwait(false); + if (text is null) + continue; + var linePositionSpan = text.Lines.GetLinePositionSpan(item.SourceSpan); if (document.FilePath != null) { diff --git a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs index 42e07c40d24f0..4209bf8223a07 100644 --- a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs +++ b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs @@ -171,6 +171,9 @@ internal async Task> GetContextFrom if (await navigationService.CanNavigateToSpanAsync(workspace, item.Document.Id, item.SourceSpan, cancellationToken).ConfigureAwait(false)) { var text = await item.Document.GetTextAsync(document.Project.Solution, cancellationToken).ConfigureAwait(false); + if (text is null) + continue; + var linePositionSpan = text.Lines.GetLinePositionSpan(item.SourceSpan); if (item.Document.FilePath != null) diff --git a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs index ec788ed67068c..b30eedb952205 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/AbstractDocumentHighlightsService.cs @@ -53,7 +53,8 @@ public async Task> GetDocumentHighlightsAsync return ImmutableArray.Empty; } - return await result.Value.SelectAsArrayAsync(h => h.RehydrateAsync(solution)).ConfigureAwait(false); + var highlights = await result.Value.SelectAsArrayAsync(h => h.RehydrateAsync(solution, cancellationToken)).ConfigureAwait(false); + return highlights.WhereNotNull(); } return await GetDocumentHighlightsInCurrentProcessAsync( diff --git a/src/Features/Core/Portable/DocumentHighlighting/IRemoteDocumentHighlightsService.cs b/src/Features/Core/Portable/DocumentHighlighting/IRemoteDocumentHighlightsService.cs index 891e7f03ad674..2d4bc58bf9f50 100644 --- a/src/Features/Core/Portable/DocumentHighlighting/IRemoteDocumentHighlightsService.cs +++ b/src/Features/Core/Portable/DocumentHighlighting/IRemoteDocumentHighlightsService.cs @@ -31,8 +31,17 @@ public SerializableDocumentHighlights(DocumentId documentId, ImmutableArray RehydrateAsync(Solution solution) - => new(await solution.GetRequiredDocumentAsync(DocumentId, includeSourceGenerated: true).ConfigureAwait(false), HighlightSpans); + public async ValueTask RehydrateAsync(Solution solution, CancellationToken cancellationToken) + { + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync(DocumentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return null; + + return new(document, HighlightSpans); + } public static SerializableDocumentHighlights Dehydrate(DocumentHighlights highlights) => new(highlights.Document.Id, highlights.HighlightSpans); diff --git a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs index 25215ea1b1093..9940b4f286a5a 100644 --- a/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs +++ b/src/Features/Core/Portable/NavigateTo/RoslynNavigateToItem.cs @@ -91,8 +91,14 @@ public RoslynNavigateToItem( } else { - var document = await solution.GetRequiredDocumentAsync( - DocumentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + DocumentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document == null) + return null; + return new NavigateToSearchResult(this, document, activeDocument); } } diff --git a/src/Features/Core/Portable/Navigation/INavigableItem.cs b/src/Features/Core/Portable/Navigation/INavigableItem.cs index cc4d24811b6de..9ad5270716a94 100644 --- a/src/Features/Core/Portable/Navigation/INavigableItem.cs +++ b/src/Features/Core/Portable/Navigation/INavigableItem.cs @@ -65,8 +65,17 @@ public static NavigableDocument FromDocument(Document document) /// this navigable item. The document is required to exist within the solution, e.g. a case where the /// navigable item was constructed during a Find Symbols operation on the same solution instance. /// - internal ValueTask GetRequiredDocumentAsync(Solution solution, CancellationToken cancellationToken) - => solution.GetRequiredDocumentAsync(Id, includeSourceGenerated: IsSourceGeneratedDocument, cancellationToken); + internal async ValueTask GetRequiredDocumentAsync(Solution solution, CancellationToken cancellationToken) + { + if (!IsSourceGeneratedDocument) + return solution.GetRequiredDocument(Id); + + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + return await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + Id, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + } /// /// Get the of the within @@ -74,9 +83,12 @@ internal ValueTask GetRequiredDocumentAsync(Solution solution, Cancell /// exist within the solution, e.g. a case where the navigable item was constructed during a Find Symbols /// operation on the same solution instance. /// - internal async ValueTask GetTextAsync(Solution solution, CancellationToken cancellationToken) + internal async ValueTask GetTextAsync(Solution solution, CancellationToken cancellationToken) { var document = await GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); + if (document is null) + return null; + return await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs index 7e4c51e8713cb..e32655d832f17 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Definitions/AbstractGoToDefinitionHandler.cs @@ -58,8 +58,12 @@ public AbstractGoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSour continue; } + var definitionDoc = await definition.Document.GetRequiredDocumentAsync(document.Project.Solution, cancellationToken).ConfigureAwait(false); + if (definitionDoc is null) + continue; + var location = await ProtocolConversions.TextSpanToLocationAsync( - await definition.Document.GetRequiredDocumentAsync(document.Project.Solution, cancellationToken).ConfigureAwait(false), + definitionDoc, definition.SourceSpan, definition.IsStale, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs index 108cb201a8283..a8e828a36533d 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Symbols/WorkspaceSymbolsHandler.cs @@ -86,6 +86,8 @@ public LSPNavigateToCallback( public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) { var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + if (document is null) + return; var location = await ProtocolConversions.TextSpanToLocationAsync( document, result.NavigableItem.SourceSpan, result.NavigableItem.IsStale, _context, cancellationToken).ConfigureAwait(false); diff --git a/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs b/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs index 43e2d9cd7492a..8fa71a05bcf98 100644 --- a/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/GoToDefinition/OmniSharpFindDefinitionService.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.Navigation; using Microsoft.CodeAnalysis.GoToDefinition; using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.ExternalAccess.OmniSharp.GoToDefinition { @@ -17,10 +18,18 @@ internal static async Task> FindDefinitio { var service = document.GetRequiredLanguageService(); var result = await service.FindDefinitionsAsync(document, position, cancellationToken).ConfigureAwait(false); - return await result.NullToEmpty().SelectAsArrayAsync( - async (original, solution, cancellationToken) => new OmniSharpNavigableItem(original.DisplayTaggedParts, await original.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false), original.SourceSpan), + var items = await result.NullToEmpty().SelectAsArrayAsync( + async (original, solution, cancellationToken) => + { + var document = await original.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); + if (document is null) + return (OmniSharpNavigableItem?)null; + + return new OmniSharpNavigableItem(original.DisplayTaggedParts, document, original.SourceSpan); + }, document.Project.Solution, cancellationToken).ConfigureAwait(false); + return items.WhereNotNull(); } } } diff --git a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs index bc68564d8777f..2f0bfa32f190a 100644 --- a/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs +++ b/src/Tools/ExternalAccess/OmniSharp/NavigateTo/OmniSharpNavigateToSearchService.cs @@ -45,6 +45,9 @@ public OmniSharpNavigateToCallbackImpl(OmniSharpNavigateToCallback callback) public async Task AddItemAsync(Project project, INavigateToSearchResult result, CancellationToken cancellationToken) { var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(project.Solution, cancellationToken).ConfigureAwait(false); + if (document is null) + return; + var omniSharpResult = new OmniSharpNavigateToSearchResult( result.AdditionalInformation, result.Kind, diff --git a/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs b/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs index 2b343d00754a4..3b5ffceac5e14 100644 --- a/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs +++ b/src/VisualStudio/Core/Def/Progression/GraphBuilder.cs @@ -700,6 +700,9 @@ public void AddLink(GraphNode from, GraphCategory category, GraphNode to, Cancel public async Task CreateNodeAsync(Solution solution, INavigateToSearchResult result, CancellationToken cancellationToken) { var document = await result.NavigableItem.Document.GetRequiredDocumentAsync(solution, cancellationToken).ConfigureAwait(false); + if (document is null) + return null; + var project = document.Project; // If it doesn't belong to a document or project we can navigate to, then ignore entirely. diff --git a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs index c2fab79dbeec5..892a28b243f55 100644 --- a/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs +++ b/src/VisualStudio/Xaml/Impl/Implementation/LanguageServer/Handler/Definitions/GoToDefinitionHandler.cs @@ -163,6 +163,9 @@ public GoToDefinitionHandler(IMetadataAsSourceFileService metadataAsSourceFileSe foreach (var item in items) { var document = await item.Document.GetRequiredDocumentAsync(context.Solution, cancellationToken).ConfigureAwait(false); + if (document is null) + continue; + var location = await ProtocolConversions.TextSpanToLocationAsync( document, item.SourceSpan, item.IsStale, cancellationToken).ConfigureAwait(false); locations.AddIfNotNull(location); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index b56aa39b6b6b8..dbd78ba933fc3 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -249,7 +249,15 @@ async Task AddMatchingTypesAsync( cancellationToken.ThrowIfCancellationRequested(); Debug.Assert(infos.Count > 0); - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + continue; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); cachedModels.Add(semanticModel); @@ -276,7 +284,14 @@ async Task AddSourceTypesThatDeriveFromNameAsync(SymbolSet result, string name) { cancellationToken.ThrowIfCancellationRequested(); - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + continue; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); cachedModels.Add(semanticModel); diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 5bb143b0512ac..59013b3914e46 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -42,13 +42,27 @@ public ValueTask OnCompletedAsync(CancellationToken cancellationToken) public async ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return; + await progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); } public async ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return; + await progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); } @@ -102,10 +116,11 @@ public async ValueTask OnReferenceFoundAsync( } } - var referenceLocation = await reference.RehydrateAsync( - solution, cancellationToken).ConfigureAwait(false); + var referenceLocation = await reference.RehydrateAsync(solution, cancellationToken).ConfigureAwait(false); + if (referenceLocation is null) + return; - await progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation, cancellationToken).ConfigureAwait(false); + await progress.OnReferenceFoundAsync(symbolGroup, symbol, referenceLocation.Value, cancellationToken).ConfigureAwait(false); } } } diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index de37b47ad66e8..69ea8f9755986 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -169,10 +169,17 @@ public static SerializableReferenceLocation Dehydrate( referenceLocation.CandidateReason); } - public async ValueTask RehydrateAsync( + public async ValueTask RehydrateAsync( Solution solution, CancellationToken cancellationToken) { - var document = await solution.GetRequiredDocumentAsync(this.Document, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + this.Document, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return null; + var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var aliasSymbol = await RehydrateAliasAsync(solution, cancellationToken).ConfigureAwait(false); var additionalProperties = this.AdditionalProperties; diff --git a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs index 2efb2693e5883..ee2b50dfec673 100644 --- a/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs +++ b/src/Workspaces/Core/Portable/Rename/IRemoteRenamerService.cs @@ -162,7 +162,7 @@ internal partial class SymbolicRenameLocations serializableLocations.Options, fallbackOptions, locations, - implicitLocations, + implicitLocations.WhereNotNull(), referencedSymbols); } } diff --git a/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs b/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs index 371c530dfcea2..0a4deb94b274b 100644 --- a/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs +++ b/src/Workspaces/Core/Portable/Rename/LightweightRenameLocations.cs @@ -67,7 +67,7 @@ private LightweightRenameLocations( Options, FallbackOptions, Locations, - implicitLocations, + implicitLocations.WhereNotNull(), referencedSymbols); } diff --git a/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs b/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs index 9a630c45e737d..e66774cf3fff7 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DocumentHighlights/RemoteDocumentHighlightsService.cs @@ -35,7 +35,14 @@ public ValueTask> GetDocumentHigh // need to be revisited if we someday support FAR between these languages. return RunServiceAsync(solutionChecksum, async solution => { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return ImmutableArray.Empty; + var documentsToSearch = await documentIdsToSearch.SelectAsArrayAsync(id => solution.GetDocumentAsync(id, includeSourceGenerated: true, cancellationToken)).ConfigureAwait(false); var documentsToSearchSet = ImmutableHashSet.CreateRange(documentsToSearch.WhereNotNull()); diff --git a/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs b/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs index a6990e52752e6..babfd4ec1d4d2 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/InheritanceMargin/RemoteInheritanceMarginService.cs @@ -42,7 +42,14 @@ public ValueTask> GetInheritanceMarginItem // // Tracked by https://github.com/dotnet/roslyn/issues/67065. frozenPartialSemantics = false; - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync(documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return ImmutableArray.Empty; + var service = document.GetRequiredLanguageService(); return await service.GetInheritanceMemberItemsAsync(document, spanToSearch, includeGlobalImports, frozenPartialSemantics, cancellationToken).ConfigureAwait(false); }, cancellationToken); diff --git a/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs b/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs index 0839972677023..3d4f508bc3f20 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/NavigationBar/RemoteNavigationBarItemService.cs @@ -29,8 +29,12 @@ public ValueTask> GetItemsAsync( { return RunServiceAsync(solutionChecksum, async solution => { - var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(document); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync(documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return ImmutableArray.Empty; if (forceFrozenPartialSemanticsForCrossProcessOperations) { diff --git a/src/Workspaces/Remote/ServiceHub/Services/TaskList/RemoteTaskListService.cs b/src/Workspaces/Remote/ServiceHub/Services/TaskList/RemoteTaskListService.cs index ba8fe1b0de46e..b891e557f0cb4 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/TaskList/RemoteTaskListService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/TaskList/RemoteTaskListService.cs @@ -30,7 +30,14 @@ public ValueTask> GetTaskListItemsAsync( { return RunServiceAsync(solutionChecksum, async solution => { - var document = await solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + var document = await solution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return ImmutableArray.Empty; + var service = document.GetRequiredLanguageService(); return await service.GetTaskListItemsAsync(document, descriptors, cancellationToken).ConfigureAwait(false); }, cancellationToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs index 7628dc6ed5898..b8e1d0993e5fa 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs @@ -35,5 +35,20 @@ public static ImmutableArray TakeAsArray(this ImmutableArray array, int return result.ToImmutableAndClear(); } + + public static ImmutableArray WhereNotNull(this ImmutableArray array) where T : struct + { + var count = array.Count(static t => t != null); + + using var _ = ArrayBuilder.GetInstance(count, out var result); + + foreach (var value in array) + { + if (value != null) + result.Add(value.Value); + } + + return result.ToImmutableAndClear(); + } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs index 63763320a91fc..2ffafca7045f6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; namespace Microsoft.CodeAnalysis.Shared.Extensions { @@ -48,8 +50,48 @@ public static Document GetRequiredDocument(this Solution solution, DocumentId do => solution.GetDocument(documentId) ?? throw CreateDocumentNotFoundException(); #if !CODE_STYLE - public static async ValueTask GetRequiredDocumentAsync(this Solution solution, DocumentId documentId, bool includeSourceGenerated = false, CancellationToken cancellationToken = default) - => (await solution.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken).ConfigureAwait(false)) ?? throw CreateDocumentNotFoundException(); + public static async ValueTask GetRequiredDocumentIncludingSourceGeneratedAsync( + this Solution solution, + DocumentId documentId, + bool throwForMissingSourceGenerated = true, + CancellationToken cancellationToken = default) + { + var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + + // https://github.com/dotnet/roslyn/issues/69964 + // + // Remove this once we solve root cause issue of the hosts disagreeing on source generated documents. + if (document is null) + { + if (documentId.IsSourceGenerated && !throwForMissingSourceGenerated) + { + // Create a crash report so we can better hunt this down. + try + { + throw CreateDocumentNotFoundException(); + } + catch (Exception ex) when (FatalError.ReportAndCatch(ex, ErrorSeverity.Critical)) + { + } + + return null; + } + + throw CreateDocumentNotFoundException(); + } + + return document; + } + + public static async ValueTask GetRequiredDocumentAsync( + this Solution solution, + DocumentId documentId, + bool includeSourceGenerated = false, + CancellationToken cancellationToken = default) + { + return await solution.GetDocumentAsync(documentId, includeSourceGenerated, cancellationToken).ConfigureAwait(false) ?? + throw CreateDocumentNotFoundException(); + } public static async ValueTask GetRequiredTextDocumentAsync(this Solution solution, DocumentId documentId, CancellationToken cancellationToken = default) => (await solution.GetTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false)) ?? throw CreateDocumentNotFoundException(); From c1a03b999a0d19300ace90bfaf6a24955cc27ab1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 18 Sep 2023 12:59:34 -0700 Subject: [PATCH 3/4] Add another resiliency location --- .../Def/Workspace/VisualStudioDocumentNavigationService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs index 047510b797f0c..d6a534042be7b 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs @@ -103,7 +103,11 @@ public async Task CanNavigateToPositionAsync(Workspace workspace, Document return true; } - var document = workspace.CurrentSolution.GetRequiredDocument(documentId); + var document = await workspace.CurrentSolution.GetRequiredDocumentIncludingSourceGeneratedAsync( + documentId, throwForMissingSourceGenerated: false, cancellationToken).ConfigureAwait(false); + if (document is null) + return false; + var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); var boundedPosition = GetPositionWithinDocumentBounds(position, text.Length); From 5c86967dbf08604aaee0edea4616707315de6f09 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 18 Sep 2023 13:03:50 -0700 Subject: [PATCH 4/4] make parameters non-optional --- .../Workspace/Core/Extensions/ISolutionExtensions.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs index 2ffafca7045f6..a9d1b9081d7b7 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Extensions/ISolutionExtensions.cs @@ -50,11 +50,18 @@ public static Document GetRequiredDocument(this Solution solution, DocumentId do => solution.GetDocument(documentId) ?? throw CreateDocumentNotFoundException(); #if !CODE_STYLE + + public static ValueTask GetRequiredDocumentIncludingSourceGeneratedAsync( + this Solution solution, + DocumentId documentId, + CancellationToken cancellationToken) + => GetRequiredDocumentIncludingSourceGeneratedAsync(solution, documentId, throwForMissingSourceGenerated: true, cancellationToken); + public static async ValueTask GetRequiredDocumentIncludingSourceGeneratedAsync( this Solution solution, DocumentId documentId, - bool throwForMissingSourceGenerated = true, - CancellationToken cancellationToken = default) + bool throwForMissingSourceGenerated, + CancellationToken cancellationToken) { var document = await solution.GetDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false);