未验证 提交 5551fad9 编写于 作者: J Jason Malinowski 提交者: GitHub

Merge pull request #38064 from jasonmalinowski/fix-metadata-as-source-caching

Fix Metadata as Source caching stale generated files
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
......@@ -36,14 +38,14 @@ internal class MetadataAsSourceFileService : IMetadataAsSourceFileService
private readonly Dictionary<string, MetadataAsSourceGeneratedFileInfo> _generatedFilenameToInformation = new Dictionary<string, MetadataAsSourceGeneratedFileInfo>(StringComparer.OrdinalIgnoreCase);
private IBidirectionalMap<MetadataAsSourceGeneratedFileInfo, DocumentId> _openedDocumentIds = BidirectionalMap<MetadataAsSourceGeneratedFileInfo, DocumentId>.Empty;
private MetadataAsSourceWorkspace _workspace;
private MetadataAsSourceWorkspace? _workspace;
/// <summary>
/// We create a mutex so other processes can see if our directory is still alive. We destroy the mutex when
/// we purge our generated files.
/// </summary>
private Mutex _mutex;
private string _rootTemporaryPathWithGuid;
private Mutex? _mutex;
private string? _rootTemporaryPathWithGuid;
private readonly string _rootTemporaryPath;
[ImportingConstructor]
......@@ -89,7 +91,7 @@ public async Task<MetadataAsSourceFile> GetGeneratedFileAsync(Project project, I
symbol = symbol.GetOriginalUnreducedDefinition();
MetadataAsSourceGeneratedFileInfo fileInfo;
Location navigateLocation = null;
Location? navigateLocation = null;
var topLevelNamedType = MetadataAsSourceHelpers.GetTopLevelContainingNamedType(symbol);
var symbolId = SymbolKey.Create(symbol, cancellationToken);
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
......@@ -97,6 +99,7 @@ public async Task<MetadataAsSourceFile> GetGeneratedFileAsync(Project project, I
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
InitializeWorkspace(project);
Contract.ThrowIfNull(_workspace);
var infoKey = await GetUniqueDocumentKey(project, topLevelNamedType, allowDecompilation, cancellationToken).ConfigureAwait(false);
fileInfo = _keyToInformation.GetOrAdd(infoKey, _ => new MetadataAsSourceGeneratedFileInfo(GetRootPathWithGuid_NoLock(), project, topLevelNamedType, allowDecompilation));
......@@ -111,6 +114,8 @@ public async Task<MetadataAsSourceFile> GetGeneratedFileAsync(Project project, I
var temporaryDocument = _workspace.CurrentSolution.AddProject(temporaryProjectInfoAndDocumentId.Item1)
.GetDocument(temporaryProjectInfoAndDocumentId.Item2);
Contract.ThrowIfNull(temporaryDocument, "The temporary ProjectInfo didn't contain the document it said it would.");
var useDecompiler = allowDecompilation;
if (useDecompiler)
{
......@@ -140,7 +145,7 @@ public async Task<MetadataAsSourceFile> GetGeneratedFileAsync(Project project, I
if (!useDecompiler)
{
var sourceFromMetadataService = temporaryDocument.Project.LanguageServices.GetService<IMetadataAsSourceService>();
var sourceFromMetadataService = temporaryDocument.Project.LanguageServices.GetRequiredService<IMetadataAsSourceService>();
temporaryDocument = await sourceFromMetadataService.AddSourceToAsync(temporaryDocument, compilation, symbol, cancellationToken).ConfigureAwait(false);
}
......@@ -195,6 +200,8 @@ public async Task<MetadataAsSourceFile> GetGeneratedFileAsync(Project project, I
private async Task<Location> RelocateSymbol_NoLock(MetadataAsSourceGeneratedFileInfo fileInfo, SymbolKey symbolId, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(_workspace);
// We need to relocate the symbol in the already existing file. If the file is open, we can just
// reuse that workspace. Otherwise, we have to go spin up a temporary project to do the binding.
if (_openedDocumentIds.TryGetValue(fileInfo, out var openDocumentId))
......@@ -217,6 +224,8 @@ public bool TryAddDocumentToWorkspace(string filePath, ITextBuffer buffer)
{
using (_gate.DisposableWait())
{
Contract.ThrowIfNull(_workspace);
if (_generatedFilenameToInformation.TryGetValue(filePath, out var fileInfo))
{
Contract.ThrowIfTrue(_openedDocumentIds.ContainsKey(fileInfo));
......@@ -258,6 +267,7 @@ private void RemoveDocumentFromWorkspace_NoLock(MetadataAsSourceGeneratedFileInf
{
var documentId = _openedDocumentIds.GetValueOrDefault(fileInfo);
Contract.ThrowIfNull(documentId);
Contract.ThrowIfNull(_workspace);
_workspace.OnDocumentClosed(documentId, new FileTextLoader(fileInfo.TemporaryFilePath, fileInfo.Encoding));
_workspace.OnProjectRemoved(documentId.ProjectId);
......@@ -268,15 +278,18 @@ private void RemoveDocumentFromWorkspace_NoLock(MetadataAsSourceGeneratedFileInf
private async Task<UniqueDocumentKey> GetUniqueDocumentKey(Project project, INamedTypeSymbol topLevelNamedType, bool allowDecompilation, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(compilation, "We are trying to produce a key for a language that doesn't support compilations.");
var peMetadataReference = compilation.GetMetadataReference(topLevelNamedType.ContainingAssembly) as PortableExecutableReference;
if (peMetadataReference.FilePath != null)
if (peMetadataReference?.FilePath != null)
{
return new UniqueDocumentKey(peMetadataReference.FilePath, project.Language, SymbolKey.Create(topLevelNamedType, cancellationToken), allowDecompilation);
return new UniqueDocumentKey(peMetadataReference.FilePath, peMetadataReference.GetMetadataId(), project.Language, SymbolKey.Create(topLevelNamedType, cancellationToken), allowDecompilation);
}
else
{
return new UniqueDocumentKey(topLevelNamedType.ContainingAssembly.Identity, project.Language, SymbolKey.Create(topLevelNamedType, cancellationToken), allowDecompilation);
var containingAssembly = topLevelNamedType.ContainingAssembly;
return new UniqueDocumentKey(containingAssembly.Identity, containingAssembly.GetMetadata()?.Id, project.Language, SymbolKey.Create(topLevelNamedType, cancellationToken), allowDecompilation);
}
}
......@@ -288,7 +301,7 @@ private void InitializeWorkspace(Project project)
}
}
internal async Task<SymbolMappingResult> MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken)
internal async Task<SymbolMappingResult?> MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken)
{
MetadataAsSourceGeneratedFileInfo fileInfo;
......@@ -417,37 +430,41 @@ private class UniqueDocumentKey : IEquatable<UniqueDocumentKey>
/// <summary>
/// The path to the assembly. Null in the case of in-memory assemblies, where we then use assembly identity.
/// </summary>
private readonly string _filePath;
private readonly string? _filePath;
/// <summary>
/// Assembly identity. Only non-null if filePath is null, where it's an in-memory assembly.
/// Assembly identity. Only non-null if <see cref="_filePath"/> is null, where it's an in-memory assembly.
/// </summary>
private readonly AssemblyIdentity _assemblyIdentity;
private readonly AssemblyIdentity? _assemblyIdentity;
private readonly MetadataId? _metadataId;
private readonly string _language;
private readonly SymbolKey _symbolId;
private readonly bool _allowDecompilation;
public UniqueDocumentKey(string filePath, string language, SymbolKey symbolId, bool allowDecompilation)
public UniqueDocumentKey(string filePath, MetadataId? metadataId, string language, SymbolKey symbolId, bool allowDecompilation)
{
Contract.ThrowIfNull(filePath);
_filePath = filePath;
_metadataId = metadataId;
_language = language;
_symbolId = symbolId;
_allowDecompilation = allowDecompilation;
}
public UniqueDocumentKey(AssemblyIdentity assemblyIdentity, string language, SymbolKey symbolId, bool allowDecompilation)
public UniqueDocumentKey(AssemblyIdentity assemblyIdentity, MetadataId? metadataId, string language, SymbolKey symbolId, bool allowDecompilation)
{
Contract.ThrowIfNull(assemblyIdentity);
_assemblyIdentity = assemblyIdentity;
_metadataId = metadataId;
_language = language;
_symbolId = symbolId;
_allowDecompilation = allowDecompilation;
}
public bool Equals(UniqueDocumentKey other)
public bool Equals(UniqueDocumentKey? other)
{
if (other == null)
{
......@@ -456,12 +473,13 @@ public bool Equals(UniqueDocumentKey other)
return StringComparer.OrdinalIgnoreCase.Equals(_filePath, other._filePath) &&
object.Equals(_assemblyIdentity, other._assemblyIdentity) &&
object.Equals(_metadataId, other._metadataId) &&
_language == other._language &&
s_symbolIdComparer.Equals(_symbolId, other._symbolId) &&
_allowDecompilation == other._allowDecompilation;
}
public override bool Equals(object obj)
public override bool Equals(object? obj)
{
return Equals(obj as UniqueDocumentKey);
}
......@@ -470,10 +488,11 @@ public override int GetHashCode()
{
return
Hash.Combine(StringComparer.OrdinalIgnoreCase.GetHashCode(_filePath ?? string.Empty),
Hash.Combine(_assemblyIdentity != null ? _assemblyIdentity.GetHashCode() : 0,
Hash.Combine(_language.GetHashCode(),
Hash.Combine(s_symbolIdComparer.GetHashCode(_symbolId),
_allowDecompilation.GetHashCode()))));
Hash.Combine(_assemblyIdentity?.GetHashCode() ?? 0,
Hash.Combine(_metadataId?.GetHashCode() ?? 0,
Hash.Combine(_language.GetHashCode(),
Hash.Combine(s_symbolIdComparer.GetHashCode(_symbolId),
_allowDecompilation.GetHashCode())))));
}
}
}
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Collections.Immutable;
using System.IO;
......@@ -20,7 +22,7 @@ internal sealed class MetadataAsSourceGeneratedFileInfo
public readonly string TemporaryFilePath;
private readonly ParseOptions _parseOptions;
private readonly ParseOptions? _parseOptions;
public MetadataAsSourceGeneratedFileInfo(string rootPath, Project sourceProject, INamedTypeSymbol topLevelNamedType, bool allowDecompilation)
{
......@@ -59,7 +61,7 @@ public MetadataAsSourceGeneratedFileInfo(string rootPath, Project sourceProject,
var projectId = ProjectId.CreateNewId();
// Just say it's always a DLL since we probably won't have a Main method
var compilationOptions = workspace.Services.GetLanguageServices(LanguageName).CompilationFactory.GetDefaultCompilationOptions().WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var compilationOptions = workspace.Services.GetLanguageServices(LanguageName).CompilationFactory!.GetDefaultCompilationOptions().WithOutputKind(OutputKind.DynamicallyLinkedLibrary);
var extension = LanguageName == LanguageNames.CSharp ? ".cs" : ".vb";
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Editor.Implementation.MetadataAsSource
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System;
using System.Composition;
using System.Threading;
......@@ -26,7 +28,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
private sealed class SymbolMappingService : ISymbolMappingService
{
public Task<SymbolMappingResult> MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken)
public Task<SymbolMappingResult?> MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken)
{
var workspace = document.Project.Solution.Workspace as MetadataAsSourceWorkspace;
if (workspace == null)
......@@ -37,7 +39,7 @@ public Task<SymbolMappingResult> MapSymbolAsync(Document document, SymbolKey sym
return workspace.FileService.MapSymbolAsync(document, symbolId, cancellationToken);
}
public async Task<SymbolMappingResult> MapSymbolAsync(Document document, ISymbol symbol, CancellationToken cancellationToken)
public async Task<SymbolMappingResult?> MapSymbolAsync(Document document, ISymbol symbol, CancellationToken cancellationToken)
{
var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return await MapSymbolAsync(document, SymbolKey.Create(symbol, cancellationToken), cancellationToken).ConfigureAwait(false);
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
......@@ -17,7 +19,7 @@ internal interface ISymbolMappingService : IWorkspaceService
/// <param name="symbolId">The id of the symbol to map</param>
/// <param name="cancellationToken">To cancel symbol resolution</param>
/// <returns>The matching symbol from the correct solution or null</returns>
Task<SymbolMappingResult> MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken = default);
Task<SymbolMappingResult?> MapSymbolAsync(Document document, SymbolKey symbolId, CancellationToken cancellationToken = default);
/// <summary>
/// Given an <cref see="ISymbol"/> and the document whence the corresponding <cref see="ISymbol"/>
......@@ -28,6 +30,6 @@ internal interface ISymbolMappingService : IWorkspaceService
/// <param name="symbol">The symbol to map</param>
/// <param name="cancellationToken">To cancel symbol resolution</param>
/// <returns>The matching symbol from the correct solution or null</returns>
Task<SymbolMappingResult> MapSymbolAsync(Document document, ISymbol symbol, CancellationToken cancellationToken = default);
Task<SymbolMappingResult?> MapSymbolAsync(Document document, ISymbol symbol, CancellationToken cancellationToken = default);
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#nullable enable
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.SymbolMapping
{
internal class SymbolMappingResult
......@@ -9,6 +13,9 @@ internal class SymbolMappingResult
internal SymbolMappingResult(Project project, ISymbol symbol)
{
Contract.ThrowIfNull(project);
Contract.ThrowIfNull(symbol);
Project = project;
Symbol = symbol;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册