未验证 提交 7a202289 编写于 作者: T Tomáš Matoušek 提交者: GitHub

Fix nullable annotations for ValueSource (#40222)

* Fix nullable annotations for ValueSource

* Refactor WeakValueSource

* Replace OptionalValueSource<T> with ValueSource<Optional<T>>
上级 191bd22f
......@@ -138,7 +138,7 @@ private IEnumerable<AbstractFormattingRule> GetFormattingRules(Document document
}
Task<IList<TextChange>?> IEditorFormattingService.GetFormattingChangesOnReturnAsync(Document document, int caretPosition, CancellationToken cancellationToken)
=> SpecializedTasks.Default<IList<TextChange>?>();
=> SpecializedTasks.Null<IList<TextChange>>();
private static async Task<bool> TokenShouldNotFormatOnTypeCharAsync(
SyntaxToken token, CancellationToken cancellationToken)
......
......@@ -593,7 +593,7 @@ public Task<object> CreateChangedAnalyzerConfigDocumentPreviewViewAsync(TextDocu
// This can happen in cases where the user has already applied the fix and light bulb has already been dismissed,
// but platform hasn't cancelled the preview operation yet. Since the light bulb has already been dismissed at
// this point, the preview that we return will never be displayed to the user. So returning null here is harmless.
return SpecializedTasks.Default<object>();
return SpecializedTasks.Null<object>();
}
var originalBuffer = _projectionBufferFactoryService.CreateProjectionBufferWithoutIndentation(
......
......@@ -73,7 +73,7 @@ public void Dispose()
return Task.FromResult<IBlockContext>(result);
}
return SpecializedTasks.Default<IBlockContext>();
return SpecializedTasks.Null<IBlockContext>();
}
}
......
......@@ -224,7 +224,7 @@ protected async Task<SolutionPreviewResult> GetPreviewResultAsync(CancellationTo
public virtual bool HasPreview => false;
public virtual Task<object> GetPreviewAsync(CancellationToken cancellationToken)
=> SpecializedTasks.Default<object>();
=> SpecializedTasks.Null<object>();
public virtual bool HasActionSets => false;
......
......@@ -1111,7 +1111,7 @@ private void OnSuggestedActionsChanged(Workspace currentWorkspace, DocumentId? c
var selection = await GetSpanAsync(range, linkedToken).ConfigureAwait(false);
var refactoringTask = SpecializedTasks.Default<string?>();
var refactoringTask = SpecializedTasks.Null<string>();
if (selection != null && requestedActionCategories.Contains(PredefinedSuggestedActionCategoryNames.Refactoring))
{
refactoringTask = Task.Run(
......
......@@ -12,6 +12,7 @@
namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense
{
internal class ModelComputation<TModel> : ForegroundThreadAffinitizedObject
where TModel : class
{
#region Fields that can be accessed from either thread
......@@ -60,7 +61,7 @@ public ModelComputation(IThreadingContext threadingContext, IController<TModel>
_stopCancellationToken = _stopTokenSource.Token;
// Dummy up a new task so we don't need to check for null.
_notifyControllerTask = _lastTask = SpecializedTasks.Default<TModel>();
_notifyControllerTask = _lastTask = SpecializedTasks.Null<TModel>();
}
public TModel InitialUnfilteredModel
......@@ -108,7 +109,7 @@ public virtual void Stop()
_stopTokenSource.Cancel();
// reset task so that it doesn't hold onto things like WpfTextView
_notifyControllerTask = _lastTask = SpecializedTasks.Default<TModel>();
_notifyControllerTask = _lastTask = SpecializedTasks.Null<TModel>();
}
public void ChainTaskAndNotifyControllerWhenFinished(
......
......@@ -9,6 +9,7 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense
internal class Session<TController, TModel, TPresenterSession> : ForegroundThreadAffinitizedObject, ISession<TModel>
where TPresenterSession : IIntelliSensePresenterSession
where TController : IController<TModel>
where TModel : class
{
public TController Controller { get; }
public ModelComputation<TModel> Computation { get; }
......
......@@ -46,7 +46,7 @@ public Task<RemoteHostClient> CreateAsync(Workspace workspace, CancellationToken
return InProcRemoteHostClient.CreateAsync(workspace, runCacheCleanup: false);
}
return SpecializedTasks.Default<RemoteHostClient>();
return SpecializedTasks.Null<RemoteHostClient>();
}
}
}
......@@ -65,7 +65,7 @@ public CSharpAddAwaitCodeFixProvider()
{
if (!(oldNode is ExpressionSyntax expression))
{
return SpecializedTasks.Default<SyntaxNode>();
return SpecializedTasks.Null<SyntaxNode>();
}
switch (diagnostic.Id)
......@@ -76,7 +76,7 @@ public CSharpAddAwaitCodeFixProvider()
case CS4016:
if (!DoesExpressionReturnTask(expression, semanticModel))
{
return SpecializedTasks.Default<SyntaxNode>();
return SpecializedTasks.Null<SyntaxNode>();
}
return Task.FromResult(root.ReplaceNode(oldNode, ConvertToAwaitExpression(expression)));
......@@ -84,13 +84,13 @@ public CSharpAddAwaitCodeFixProvider()
case CS0029:
if (!DoesExpressionReturnGenericTaskWhoseArgumentsMatchLeftSide(expression, semanticModel, document.Project, cancellationToken))
{
return SpecializedTasks.Default<SyntaxNode>();
return SpecializedTasks.Null<SyntaxNode>();
}
return Task.FromResult(root.ReplaceNode(oldNode, ConvertToAwaitExpression(expression)));
default:
return SpecializedTasks.Default<SyntaxNode>();
return SpecializedTasks.Null<SyntaxNode>();
}
}
......
......@@ -477,7 +477,7 @@ public override Task<CompletionDescription> GetDescriptionAsync(Document documen
{
if (!item.Properties.TryGetValue(DescriptionKey, out var description))
{
return SpecializedTasks.Default<CompletionDescription>();
return SpecializedTasks.Null<CompletionDescription>();
}
return Task.FromResult(CompletionDescription.Create(
......
......@@ -27,7 +27,7 @@ public static QuickInfoService GetService(Document document)
int position,
CancellationToken cancellationToken = default)
{
return SpecializedTasks.Default<QuickInfoItem>();
return SpecializedTasks.Null<QuickInfoItem>();
}
}
}
// 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.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Host
{
......@@ -24,8 +28,8 @@ internal partial class ProjectCacheService : IProjectCacheHostService
private readonly Workspace _workspace;
private readonly Dictionary<ProjectId, Cache> _activeCaches = new Dictionary<ProjectId, Cache>();
private readonly SimpleMRUCache _implicitCache;
private readonly ImplicitCacheMonitor _implicitCacheMonitor;
private readonly SimpleMRUCache? _implicitCache;
private readonly ImplicitCacheMonitor? _implicitCacheMonitor;
public ProjectCacheService(Workspace workspace)
{
......@@ -82,7 +86,8 @@ public IDisposable EnableCaching(ProjectId key)
}
}
public T CacheObjectIfCachingEnabledForKey<T>(ProjectId key, object owner, T instance) where T : class
[return: NotNullIfNotNull("instance")]
public T? CacheObjectIfCachingEnabledForKey<T>(ProjectId key, object owner, T? instance) where T : class
{
lock (_gate)
{
......@@ -90,8 +95,9 @@ public IDisposable EnableCaching(ProjectId key)
{
cache.CreateStrongReference(owner, instance);
}
else if ((_implicitCache != null) && !PartOfP2PReferences(key))
else if (_implicitCache != null && !PartOfP2PReferences(key))
{
RoslynDebug.Assert(_implicitCacheMonitor != null);
_implicitCache.Touch(instance);
_implicitCacheMonitor.Touch();
}
......@@ -123,7 +129,8 @@ private bool PartOfP2PReferences(ProjectId key)
return false;
}
public T CacheObjectIfCachingEnabledForKey<T>(ProjectId key, ICachedObjectOwner owner, T instance) where T : class
[return: NotNullIfNotNull("instance")]
public T? CacheObjectIfCachingEnabledForKey<T>(ProjectId key, ICachedObjectOwner owner, T? instance) where T : class
{
lock (_gate)
{
......@@ -150,12 +157,12 @@ private void DisableCaching(ProjectId key, Cache cache)
}
}
private class Cache : IDisposable
private sealed class Cache : IDisposable
{
internal int Count;
private readonly ProjectCacheService _cacheService;
private readonly ProjectId _key;
private ConditionalWeakTable<object, object> _cache = new ConditionalWeakTable<object, object>();
private ConditionalWeakTable<object, object?>? _cache = new ConditionalWeakTable<object, object?>();
private readonly List<WeakReference<ICachedObjectOwner>> _ownerObjects = new List<WeakReference<ICachedObjectOwner>>();
public Cache(ProjectCacheService cacheService, ProjectId key)
......@@ -169,9 +176,14 @@ public void Dispose()
_cacheService.DisableCaching(_key, this);
}
internal void CreateStrongReference(object key, object instance)
internal void CreateStrongReference(object key, object? instance)
{
if (!_cache.TryGetValue(key, out var o))
if (_cache == null)
{
throw new ObjectDisposedException(nameof(Cache));
}
if (!_cache.TryGetValue(key, out _))
{
_cache.Add(key, instance);
}
......
......@@ -46,7 +46,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Async
Private Function GetNewRootAsync(root As SyntaxNode, oldNode As SyntaxNode, semanticModel As SemanticModel, diagnostic As Diagnostic, document As Document, cancellationToken As CancellationToken) As Task(Of SyntaxNode)
Dim expression = TryCast(oldNode, ExpressionSyntax)
If expression Is Nothing Then
Return SpecializedTasks.Default(Of SyntaxNode)()
Return SpecializedTasks.Null(Of SyntaxNode)()
End If
Select Case diagnostic.Id
......@@ -63,7 +63,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeFixes.Async
Case BC42358
Return Task.FromResult(root.ReplaceNode(oldNode, ConverToAwaitExpression(expression, semanticModel, cancellationToken)))
Case Else
Return SpecializedTasks.Default(Of SyntaxNode)()
Return SpecializedTasks.Null(Of SyntaxNode)()
End Select
End Function
......
......@@ -133,7 +133,7 @@ public void Cancel()
{
_trackingPoint = textView.TextSnapshot.CreateTrackingPoint(0, PointTrackingMode.Negative);
_trackingSpan = textView.TextSnapshot.CreateTrackingSpan(new Span(), SpanTrackingMode.EdgeInclusive);
this.GetEventNameTask = SpecializedTasks.Default<string>();
this.GetEventNameTask = SpecializedTasks.Null<string>();
eventHookupSessionManager.CancelAndDismissExistingSessions();
}
}
......
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Roslyn.Utilities;
......@@ -8,7 +11,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal sealed partial class VisualStudioMetadataReferenceManager
{
private class MetadataCache
private sealed class MetadataCache
{
private const int InitialCapacity = 64;
private const int CapacityMultiplier = 2;
......@@ -16,11 +19,11 @@ private class MetadataCache
private readonly object _gate = new object();
// value is ValueSource so that how metadata is re-acquired back are different per entry.
private readonly Dictionary<FileKey, ValueSource<AssemblyMetadata>> _metadataCache = new Dictionary<FileKey, ValueSource<AssemblyMetadata>>();
private readonly Dictionary<FileKey, ValueSource<Optional<AssemblyMetadata>>> _metadataCache = new Dictionary<FileKey, ValueSource<Optional<AssemblyMetadata>>>();
private int _capacity = InitialCapacity;
public bool TryGetMetadata(FileKey key, out AssemblyMetadata metadata)
public bool TryGetMetadata(FileKey key, [NotNullWhen(true)]out AssemblyMetadata? metadata)
{
lock (_gate)
{
......@@ -28,7 +31,7 @@ public bool TryGetMetadata(FileKey key, out AssemblyMetadata metadata)
}
}
public bool TryGetSource(FileKey key, out ValueSource<AssemblyMetadata> source)
public bool TryGetSource(FileKey key, [NotNullWhen(true)]out ValueSource<Optional<AssemblyMetadata>>? source)
{
lock (_gate)
{
......@@ -36,11 +39,11 @@ public bool TryGetSource(FileKey key, out ValueSource<AssemblyMetadata> source)
}
}
private bool TryGetMetadata_NoLock(FileKey key, out AssemblyMetadata metadata)
private bool TryGetMetadata_NoLock(FileKey key, [NotNullWhen(true)]out AssemblyMetadata? metadata)
{
if (_metadataCache.TryGetValue(key, out var metadataSource))
{
metadata = metadataSource.GetValue();
metadata = metadataSource.GetValueOrNull();
return metadata != null;
}
......@@ -48,22 +51,35 @@ private bool TryGetMetadata_NoLock(FileKey key, out AssemblyMetadata metadata)
return false;
}
public bool TryGetOrAddMetadata(FileKey key, ValueSource<AssemblyMetadata> newMetadata, out AssemblyMetadata metadata)
/// <summary>
/// <para>Gets specified metadata from the cache, or retrieves metadata from given <paramref name="metadataSource"/>
/// and adds it to the cache if it's not there yet.</para>
///
/// <para><paramref name="metadataSource"/> is expected to to provide metadata at least until this method returns.</para>
/// </summary>
/// <returns>
/// True if the metadata is retrieved from <paramref name="metadataSource"/> source, false if it already exists in the cache.
/// </returns>
public bool GetOrAddMetadata(FileKey key, ValueSource<Optional<AssemblyMetadata>> metadataSource, out AssemblyMetadata metadata)
{
lock (_gate)
{
if (TryGetMetadata_NoLock(key, out metadata))
if (TryGetMetadata_NoLock(key, out var cachedMetadata))
{
metadata = cachedMetadata;
return false;
}
EnsureCapacity_NoLock();
metadata = newMetadata.GetValue();
Contract.ThrowIfNull(metadata);
var newMetadata = metadataSource.GetValueOrNull();
// the source is expected to keep the metadata alive at this point
Contract.ThrowIfNull(newMetadata);
// don't use "Add" since key might already exist with already released metadata
_metadataCache[key] = newMetadata;
_metadataCache[key] = metadataSource;
metadata = newMetadata;
return true;
}
}
......@@ -77,12 +93,12 @@ private void EnsureCapacity_NoLock()
using var pooledObject = SharedPools.Default<List<FileKey>>().GetPooledObject();
var keysToRemove = pooledObject.Object;
foreach (var kv in _metadataCache)
foreach (var (fileKey, metadataSource) in _metadataCache)
{
// metadata doesn't exist anymore. delete it from cache
if (!kv.Value.HasValue)
if (!metadataSource.TryGetValue(out _))
{
keysToRemove.Add(kv.Key);
keysToRemove.Add(fileKey);
}
}
......
// 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.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
......@@ -15,7 +16,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
internal sealed partial class VisualStudioMetadataReferenceManager
{
private class RecoverableMetadataValueSource : ValueSource<AssemblyMetadata>
private sealed class RecoverableMetadataValueSource : ValueSource<Optional<AssemblyMetadata>>
{
private readonly WeakReference<AssemblyMetadata> _weakValue;
private readonly List<ITemporaryStreamStorage> _storages;
......@@ -35,7 +36,22 @@ public IEnumerable<ITemporaryStreamStorage> GetStorages()
return _storages;
}
public override AssemblyMetadata GetValue(CancellationToken cancellationToken)
public override bool TryGetValue(out Optional<AssemblyMetadata> value)
{
if (_weakValue.TryGetTarget(out var target))
{
value = target;
return true;
}
value = default;
return false;
}
public override Task<Optional<AssemblyMetadata>> GetValueAsync(CancellationToken cancellationToken)
=> Task.FromResult(GetValue(cancellationToken));
public override Optional<AssemblyMetadata> GetValue(CancellationToken cancellationToken)
{
if (_weakValue.TryGetTarget(out var value))
{
......@@ -74,22 +90,6 @@ private ModuleMetadata GetModuleMetadata(ITemporaryStreamStorage storage)
_lifetimeMap.Add(metadata, stream);
return metadata;
}
public override bool TryGetValue(out AssemblyMetadata value)
{
if (_weakValue.TryGetTarget(out value))
{
return true;
}
value = default;
return false;
}
public override Task<AssemblyMetadata> GetValueAsync(CancellationToken cancellationToken)
{
return Task.FromResult(this.GetValue(cancellationToken));
}
}
}
}
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
......@@ -43,7 +46,7 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS
/// use of this field or derived types should be synchronized with <see cref="_readerWriterLock"/> to ensure
/// you don't grab the field and then use it while shutdown continues.
/// </summary>
private IVsSmartOpenScope SmartOpenScopeServiceOpt { get; set; }
private IVsSmartOpenScope? SmartOpenScopeServiceOpt { get; set; }
internal IVsFileChangeEx FileChangeService { get; }
......@@ -67,7 +70,7 @@ internal VisualStudioMetadataReferenceManager(IServiceProvider serviceProvider,
Assumes.Present(_temporaryStorageService);
}
internal IEnumerable<ITemporaryStreamStorage> GetStorages(string fullPath, DateTime snapshotTimestamp)
internal IEnumerable<ITemporaryStreamStorage>? GetStorages(string fullPath, DateTime snapshotTimestamp)
{
var key = new FileKey(fullPath, snapshotTimestamp);
// check existing metadata
......@@ -130,7 +133,7 @@ internal Metadata GetMetadata(string fullPath, DateTime snapshotTimestamp)
if (VsSmartScopeCandidate(key.FullPath) && TryCreateAssemblyMetadataFromMetadataImporter(key, out var newMetadata))
{
if (!_metadataCache.TryGetOrAddMetadata(key, new WeakConstantValueSource<AssemblyMetadata>(newMetadata), out metadata))
if (!_metadataCache.GetOrAddMetadata(key, new WeakValueSource<AssemblyMetadata>(newMetadata), out metadata))
{
newMetadata.Dispose();
}
......@@ -143,11 +146,14 @@ internal Metadata GetMetadata(string fullPath, DateTime snapshotTimestamp)
newMetadata = CreateAssemblyMetadataFromTemporaryStorage(key, storages);
// don't dispose assembly metadata since it shares module metadata
if (!_metadataCache.TryGetOrAddMetadata(key, new RecoverableMetadataValueSource(newMetadata, storages, s_lifetimeMap), out metadata))
if (!_metadataCache.GetOrAddMetadata(key, new RecoverableMetadataValueSource(newMetadata, storages, s_lifetimeMap), out metadata))
{
newMetadata.Dispose();
}
// guarantee that the metadata is alive while we add the source to the cache
GC.KeepAlive(newMetadata);
return metadata;
}
......@@ -159,7 +165,7 @@ private AssemblyMetadata CreateAssemblyMetadataFromTemporaryStorage(FileKey file
return CreateAssemblyMetadata(fileKey, moduleMetadata, storages, CreateModuleMetadataFromTemporaryStorage);
}
private ModuleMetadata CreateModuleMetadataFromTemporaryStorage(FileKey moduleFileKey, List<ITemporaryStreamStorage> storages)
private ModuleMetadata CreateModuleMetadataFromTemporaryStorage(FileKey moduleFileKey, List<ITemporaryStreamStorage>? storages)
{
GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storage, out var stream, out var pImage);
......@@ -169,10 +175,7 @@ private ModuleMetadata CreateModuleMetadataFromTemporaryStorage(FileKey moduleFi
s_lifetimeMap.Add(metadata, stream);
// hold onto storage if requested
if (storages != null)
{
storages.Add(storage);
}
storages?.Add(storage);
return metadata;
}
......@@ -224,7 +227,7 @@ private void StreamCopy(Stream source, Stream destination, int start, int length
var buffer = SharedPools.ByteArray.Allocate();
var read = 0;
int read;
var left = length;
while ((read = source.Read(buffer, 0, Math.Min(left, buffer.Length))) != 0)
{
......@@ -237,9 +240,9 @@ private void StreamCopy(Stream source, Stream destination, int start, int length
/// <exception cref="IOException"/>
/// <exception cref="BadImageFormatException" />
private bool TryCreateAssemblyMetadataFromMetadataImporter(FileKey fileKey, out AssemblyMetadata metadata)
private bool TryCreateAssemblyMetadataFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)]out AssemblyMetadata? metadata)
{
metadata = default;
metadata = null;
var manifestModule = TryCreateModuleMetadataFromMetadataImporter(fileKey);
if (manifestModule == null)
......@@ -247,11 +250,11 @@ private bool TryCreateAssemblyMetadataFromMetadataImporter(FileKey fileKey, out
return false;
}
metadata = CreateAssemblyMetadata(fileKey, manifestModule, null, CreateModuleMetadata);
metadata = CreateAssemblyMetadata(fileKey, manifestModule, storages: null, CreateModuleMetadata);
return true;
}
private ModuleMetadata TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey)
private ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey)
{
if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length))
{
......@@ -266,7 +269,7 @@ private ModuleMetadata TryCreateModuleMetadataFromMetadataImporter(FileKey modul
return metadata;
}
private ModuleMetadata CreateModuleMetadata(FileKey moduleFileKey, List<ITemporaryStreamStorage> storages)
private ModuleMetadata CreateModuleMetadata(FileKey moduleFileKey, List<ITemporaryStreamStorage>? storages)
{
var metadata = TryCreateModuleMetadataFromMetadataImporter(moduleFileKey);
if (metadata == null)
......@@ -278,7 +281,7 @@ private ModuleMetadata CreateModuleMetadata(FileKey moduleFileKey, List<ITempora
return metadata;
}
private bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, out IMetaDataInfo info, out IntPtr pImage, out long length)
private bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)]out IMetaDataInfo? info, out IntPtr pImage, out long length)
{
// We might not be able to use COM services to get this if VS is shutting down. We'll synchronize to make sure this
// doesn't race against
......@@ -315,12 +318,12 @@ private bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, out IMetaDat
/// <exception cref="IOException"/>
/// <exception cref="BadImageFormatException" />
private AssemblyMetadata CreateAssemblyMetadata(
FileKey fileKey, ModuleMetadata manifestModule, List<ITemporaryStreamStorage> storages,
Func<FileKey, List<ITemporaryStreamStorage>, ModuleMetadata> moduleMetadataFactory)
FileKey fileKey, ModuleMetadata manifestModule, List<ITemporaryStreamStorage>? storages,
Func<FileKey, List<ITemporaryStreamStorage>?, ModuleMetadata> moduleMetadataFactory)
{
var moduleBuilder = ArrayBuilder<ModuleMetadata>.GetInstance();
string assemblyDir = null;
string? assemblyDir = null;
foreach (var moduleName in manifestModule.GetModuleNames())
{
if (moduleBuilder.Count == 0)
......
......@@ -175,7 +175,7 @@ public Task<RemoteHostClient> TryGetRemoteHostClientAsync(CancellationToken canc
if (remoteClientTask == null)
{
// service is in shutdown mode or not enabled
return SpecializedTasks.Default<RemoteHostClient>();
return SpecializedTasks.Null<RemoteHostClient>();
}
return remoteClientTask;
......
......@@ -89,19 +89,20 @@ public static void PerformAction(this IExtensionManager extensionManager, object
Func<Task<T>?> function,
T defaultValue)
{
if (extensionManager.IsDisabled(extension))
{
return defaultValue;
}
try
{
if (!extensionManager.IsDisabled(extension))
var task = function();
if (task != null)
{
var task = function() ?? SpecializedTasks.Default<T>();
return await task.ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e) when (extensionManager.CanHandleException(extension, e))
catch (Exception e) when (!(e is OperationCanceledException) && extensionManager.CanHandleException(extension, e))
{
extensionManager.HandleException(extension, e);
}
......
......@@ -39,7 +39,7 @@ public Task<RemoteHostClient> TryGetRemoteHostClientAsync(CancellationToken canc
{
if (_lazyInstance == null)
{
return SpecializedTasks.Default<RemoteHostClient>();
return SpecializedTasks.Null<RemoteHostClient>();
}
return _lazyInstance.GetValueAsync(cancellationToken);
......
......@@ -82,7 +82,7 @@ public static string CreateClientId(string prefix)
{
if (!RemoteSupportedLanguages.IsSupported(project.Language))
{
return SpecializedTasks.Default<RemoteHostClient?>();
return SpecializedTasks.Null<RemoteHostClient>();
}
return TryGetClientAsync(project.Solution.Workspace, cancellationToken);
......@@ -93,7 +93,7 @@ public static string CreateClientId(string prefix)
var service = workspace.Services.GetService<IRemoteHostClientService>();
if (service == null)
{
return SpecializedTasks.Default<RemoteHostClient?>();
return SpecializedTasks.Null<RemoteHostClient>();
}
return service.TryGetRemoteHostClientAsync(cancellationToken);
......@@ -181,7 +181,7 @@ public NoOpClient(Workspace workspace)
public override Task<Connection?> TryCreateConnectionAsync(string serviceName, object? callbackTarget, CancellationToken cancellationToken)
{
return SpecializedTasks.Default<Connection?>();
return SpecializedTasks.Null<Connection>();
}
protected override void OnStarted()
......
// 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.Collections.Immutable;
......@@ -30,7 +32,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
return new SemanticModelService();
}
private class SemanticModelService : ISemanticModelService
private sealed class SemanticModelService : ISemanticModelService
{
private static readonly ConditionalWeakTable<Workspace, ConditionalWeakTable<BranchId, Dictionary<ProjectId, CompilationSet>>> s_map =
new ConditionalWeakTable<Workspace, ConditionalWeakTable<BranchId, Dictionary<ProjectId, CompilationSet>>>();
......@@ -40,8 +42,6 @@ private class SemanticModelService : ISemanticModelService
private readonly ReaderWriterLockSlim _gate = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
#nullable enable
public async Task<SemanticModel> GetSemanticModelForNodeAsync(Document document, SyntaxNode node, CancellationToken cancellationToken = default)
{
var syntaxFactsService = document.GetLanguageService<ISyntaxFactsService>();
......@@ -83,13 +83,16 @@ public async Task<SemanticModel> GetSemanticModelForNodeAsync(Document document,
// we have compilation set check whether it is something we can use
if (version.Equals(compilationSet.Version))
{
if (!compilationSet.Compilation.TryGetValue(out var oldCompilation))
if (!compilationSet.Compilation.TryGetValue(out var oldCompilationOpt) || !oldCompilationOpt.HasValue)
{
await AddVersionCacheAsync(document.Project, version, cancellationToken).ConfigureAwait(false);
// get the base one
return (await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false))!;
}
var oldCompilation = oldCompilationOpt.Value;
// first check whether the set has this document
if (!compilationSet.Trees.TryGetValue(document.Id, out var oldTree))
{
......@@ -163,18 +166,17 @@ private bool IsPrimaryBranch(Document document)
return document.Project.Solution.BranchId == document.Project.Solution.Workspace.PrimaryBranchId;
}
#nullable restore
private Task AddVersionCacheAsync(Project project, VersionStamp version, CancellationToken cancellationToken)
{
return UpdateVersionCacheAsync(project, version, primarySet: null, cancellationToken: cancellationToken);
}
private async Task UpdateVersionCacheAsync(Project project, VersionStamp version, CompilationSet primarySet, CancellationToken cancellationToken)
private async Task UpdateVersionCacheAsync(Project project, VersionStamp version, CompilationSet? primarySet, CancellationToken cancellationToken)
{
var versionMap = GetVersionMapFromBranch(project.Solution.Workspace, project.Solution.BranchId);
if (!AlreadyHasLatestCompilationSet(versionMap, project.Id, version, out var compilationSet) ||
!compilationSet.Compilation.TryGetValue(out var compilation))
!compilationSet.Compilation.TryGetValue(out var compilationOpt) ||
!compilationOpt.HasValue)
{
var newSet = await CompilationSet.CreateAsync(project, compilationSet ?? primarySet, cancellationToken).ConfigureAwait(false);
......@@ -205,18 +207,10 @@ private async Task UpdateVersionCacheAsync(Project project, VersionStamp version
private static readonly ConditionalWeakTable<Compilation, ConditionalWeakTable<SyntaxNode, WeakReference<SemanticModel>>>.CreateValueCallback s_createNodeMap =
_ => new ConditionalWeakTable<SyntaxNode, WeakReference<SemanticModel>>();
private static SemanticModel GetCachedSemanticModel(
ConditionalWeakTable<SyntaxNode, WeakReference<SemanticModel>> nodeMap, SyntaxNode newMember)
{
if (!nodeMap.TryGetValue(newMember, out var cached) || !cached.TryGetTarget(out var model))
{
return null;
}
private static SemanticModel? GetCachedSemanticModel(ConditionalWeakTable<SyntaxNode, WeakReference<SemanticModel>> nodeMap, SyntaxNode newMember)
=> nodeMap.TryGetValue(newMember, out var cached) && cached.TryGetTarget(out var model) ? model : null;
return model;
}
private static SemanticModel GetCachedSemanticModel(Compilation oldCompilation, SyntaxNode newMember)
private static SemanticModel? GetCachedSemanticModel(Compilation oldCompilation, SyntaxNode newMember)
{
var nodeMap = s_semanticModelMap.GetValue(oldCompilation, s_createNodeMap);
......@@ -380,17 +374,21 @@ private void ClearVersionMap(Workspace workspace, IReadOnlyList<ProjectId> proje
}
}
private class CompilationSet
private sealed class CompilationSet
{
private const int RebuildThreshold = 3;
public readonly VersionStamp Version;
public readonly ValueSource<Compilation> Compilation;
public readonly ValueSource<Optional<Compilation>> Compilation;
public readonly ImmutableDictionary<DocumentId, SyntaxTree> Trees;
public static async Task<CompilationSet> CreateAsync(Project project, CompilationSet oldCompilationSet, CancellationToken cancellationToken)
public static async Task<CompilationSet> CreateAsync(Project project, CompilationSet? oldCompilationSet, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// project must support compilations
Contract.ThrowIfNull(compilation);
var version = await project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false);
var map = GetTreeMap(project, compilation, oldCompilationSet, cancellationToken);
......@@ -398,14 +396,14 @@ public static async Task<CompilationSet> CreateAsync(Project project, Compilatio
return new CompilationSet(version, GetCompilation(project, compilation), map);
}
private CompilationSet(VersionStamp version, ValueSource<Compilation> compilation, ImmutableDictionary<DocumentId, SyntaxTree> map)
private CompilationSet(VersionStamp version, ValueSource<Optional<Compilation>> compilation, ImmutableDictionary<DocumentId, SyntaxTree> map)
{
this.Version = version;
this.Compilation = compilation;
this.Trees = map;
Version = version;
Compilation = compilation;
Trees = map;
}
private static ImmutableDictionary<DocumentId, SyntaxTree> GetTreeMap(Project project, Compilation compilation, CompilationSet oldCompilationSet, CancellationToken cancellationToken)
private static ImmutableDictionary<DocumentId, SyntaxTree> GetTreeMap(Project project, Compilation compilation, CompilationSet? oldCompilationSet, CancellationToken cancellationToken)
{
// enumerable count should take a quick path since ImmutableArray implements ICollection
var newTreeCount = compilation.SyntaxTrees.Count();
......@@ -463,7 +461,7 @@ private CompilationSet(VersionStamp version, ValueSource<Compilation> compilatio
private static ImmutableDictionary<DocumentId, SyntaxTree> AddOrUpdateNewTreeToOldMap(
Project newProject, Compilation newCompilation, CompilationSet oldSet, CancellationToken cancellationToken)
{
if (!oldSet.Compilation.TryGetValue(out var oldCompilation))
if (!oldSet.Compilation.TryGetValue(out var oldCompilationOpt) || !oldCompilationOpt.HasValue)
{
return ImmutableDictionary.CreateRange(GetNewTreeMap(newProject, newCompilation));
}
......@@ -473,7 +471,7 @@ private CompilationSet(VersionStamp version, ValueSource<Compilation> compilatio
{
cancellationToken.ThrowIfCancellationRequested();
if (oldCompilation.ContainsSyntaxTree(newTree))
if (oldCompilationOpt.Value.ContainsSyntaxTree(newTree))
{
continue;
}
......@@ -507,15 +505,15 @@ private CompilationSet(VersionStamp version, ValueSource<Compilation> compilatio
}
}
private static ValueSource<Compilation> GetCompilation(Project project, Compilation compilation)
private static ValueSource<Optional<Compilation>> GetCompilation(Project project, Compilation compilation)
{
var cache = project.Solution.Workspace.Services.GetService<IProjectCacheHostService>();
if (cache != null && project.Solution.BranchId == project.Solution.Workspace.PrimaryBranchId)
{
return new WeakConstantValueSource<Compilation>(cache.CacheObjectIfCachingEnabledForKey(project.Id, project, compilation));
return new WeakValueSource<Compilation>(cache.CacheObjectIfCachingEnabledForKey(project.Id, project, compilation));
}
return new ConstantValueSource<Compilation>(compilation);
return new ConstantValueSource<Optional<Compilation>>(compilation);
}
[Conditional("DEBUG")]
......
......@@ -235,7 +235,7 @@ public static bool IsAnonymousType([NotNullWhen(returnValue: true)] this INamedT
// local functions
static Task<Compilation?> GetCompilationOrNullAsync(Project? project, CancellationToken cancellationToken)
=> project?.GetCompilationAsync(cancellationToken) ?? SpecializedTasks.Default<Compilation?>();
=> project?.GetCompilationAsync(cancellationToken) ?? SpecializedTasks.Null<Compilation>();
}
......
// 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.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
......@@ -27,14 +29,14 @@ internal sealed class AsyncLazy<T> : ValueSource<T>
/// Null'ed out once we've computed the result and we've been asked to cache it. Otherwise,
/// it is kept around in case the value needs to be computed again.
/// </summary>
private Func<CancellationToken, Task<T>> _asynchronousComputeFunction;
private Func<CancellationToken, Task<T>>? _asynchronousComputeFunction;
/// <summary>
/// The underlying function that starts an synchronous computation of the resulting value.
/// The underlying function that starts a synchronous computation of the resulting value.
/// Null'ed out once we've computed the result and we've been asked to cache it, or if we
/// didn't get any synchronous function given to us in the first place.
/// </summary>
private Func<CancellationToken, T> _synchronousComputeFunction;
private Func<CancellationToken, T>? _synchronousComputeFunction;
/// <summary>
/// Whether or not we should keep the value around once we've computed it.
......@@ -44,7 +46,7 @@ internal sealed class AsyncLazy<T> : ValueSource<T>
/// <summary>
/// The Task that holds the cached result.
/// </summary>
private Task<T> _cachedResult;
private Task<T>? _cachedResult;
/// <summary>
/// Mutex used to protect reading and writing to all mutable objects and fields. Traces
......@@ -58,13 +60,13 @@ internal sealed class AsyncLazy<T> : ValueSource<T>
/// The hash set of all currently outstanding asynchronous requests. Null if there are no requests,
/// and will never be empty.
/// </summary>
private HashSet<Request> _requests;
private HashSet<Request>? _requests;
/// <summary>
/// If an asynchronous request is active, the CancellationTokenSource that allows for
/// cancelling the underlying computation.
/// </summary>
private CancellationTokenSource _asynchronousComputationCancellationSource;
private CancellationTokenSource? _asynchronousComputationCancellationSource;
/// <summary>
/// Whether a computation is active or queued on any thread, whether synchronous or
......@@ -98,7 +100,7 @@ public AsyncLazy(Func<CancellationToken, Task<T>> asynchronousComputeFunction, b
/// in the first place.</param>
/// <param name="cacheResult">Whether the result should be cached once the computation is
/// complete.</param>
public AsyncLazy(Func<CancellationToken, Task<T>> asynchronousComputeFunction, Func<CancellationToken, T> synchronousComputeFunction, bool cacheResult)
public AsyncLazy(Func<CancellationToken, Task<T>> asynchronousComputeFunction, Func<CancellationToken, T>? synchronousComputeFunction, bool cacheResult)
{
Contract.ThrowIfNull(asynchronousComputeFunction);
_asynchronousComputeFunction = asynchronousComputeFunction;
......@@ -161,7 +163,7 @@ private void AssertInvariants_NoLock()
#endregion
public override bool TryGetValue(out T result)
public override bool TryGetValue([MaybeNullWhen(false)]out T result)
{
// No need to lock here since this is only a fast check to
// see if the result is already computed.
......@@ -171,7 +173,8 @@ public override bool TryGetValue(out T result)
return true;
}
result = default;
// Suppressing nullable warning due to https://github.com/dotnet/roslyn/issues/40266
result = default!;
return false;
}
......@@ -185,7 +188,7 @@ public override T GetValue(CancellationToken cancellationToken)
return value;
}
Request request = null;
Request? request = null;
AsynchronousComputationToStart? newAsynchronousComputation = null;
using (TakeLock(cancellationToken))
......@@ -236,6 +239,8 @@ public override T GetValue(CancellationToken cancellationToken)
}
else
{
Contract.ThrowIfNull(_synchronousComputeFunction);
T result;
// We are the active computation, so let's go ahead and compute.
......@@ -350,6 +355,7 @@ public override Task<T> GetValueAsync(CancellationToken cancellationToken)
private AsynchronousComputationToStart RegisterAsynchronousComputation_NoLock()
{
Contract.ThrowIfTrue(_computationActive);
Contract.ThrowIfNull(_asynchronousComputeFunction);
_asynchronousComputationCancellationSource = new CancellationTokenSource();
_computationActive = true;
......@@ -364,12 +370,12 @@ private struct AsynchronousComputationToStart
public AsynchronousComputationToStart(Func<CancellationToken, Task<T>> asynchronousComputeFunction, CancellationTokenSource cancellationTokenSource)
{
this.AsynchronousComputeFunction = asynchronousComputeFunction;
this.CancellationTokenSource = cancellationTokenSource;
AsynchronousComputeFunction = asynchronousComputeFunction;
CancellationTokenSource = cancellationTokenSource;
}
}
private void StartAsynchronousComputation(AsynchronousComputationToStart computationToStart, Request requestToCompleteSynchronously, CancellationToken callerCancellationToken)
private void StartAsynchronousComputation(AsynchronousComputationToStart computationToStart, Request? requestToCompleteSynchronously, CancellationToken callerCancellationToken)
{
var cancellationToken = computationToStart.CancellationTokenSource.Token;
......@@ -496,7 +502,7 @@ private Task<T> GetCachedValueAndCacheThisValueIfNoneCached_NoLock(Task<T> task)
private void OnAsynchronousRequestCancelled(object state)
{
var request = (Request)state;
CancellationTokenSource cancellationTokenSource = null;
CancellationTokenSource? cancellationTokenSource = null;
using (TakeLock(CancellationToken.None))
{
......@@ -526,11 +532,7 @@ private void OnAsynchronousRequestCancelled(object state)
}
request.Cancel();
if (cancellationTokenSource != null)
{
cancellationTokenSource.Cancel();
}
cancellationTokenSource?.Cancel();
}
/// <remarks>
......
// 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.Collections.Immutable;
......@@ -17,49 +19,42 @@ internal static class SpecializedTasks
[Obsolete("Use Task.CompletedTask instead which is available in the framework.")]
public static readonly Task EmptyTask = Task.CompletedTask;
public static Task<T> Default<T>()
{
return Empty<T>.Default;
}
public static Task<T?> AsNullable<T>(this Task<T> task) where T : class
=> task!;
public static Task<T> DefaultOrResult<T>(T value)
{
if (EqualityComparer<T>.Default.Equals(value, default))
{
return Default<T>();
}
public static Task<T> Default<T>() where T : struct
=> TasksOfStruct<T>.Default;
return Task.FromResult(value);
}
public static Task<T?> Null<T>() where T : class
=> TasksOfClass<T>.Null;
public static Task<IReadOnlyList<T>> EmptyReadOnlyList<T>()
{
return Empty<T>.EmptyReadOnlyList;
}
=> EmptyTasks<T>.EmptyReadOnlyList;
public static Task<IList<T>> EmptyList<T>()
{
return Empty<T>.EmptyList;
}
=> EmptyTasks<T>.EmptyList;
public static Task<ImmutableArray<T>> EmptyImmutableArray<T>()
{
return Empty<T>.EmptyImmutableArray;
}
=> EmptyTasks<T>.EmptyImmutableArray;
public static Task<IEnumerable<T>> EmptyEnumerable<T>()
=> EmptyTasks<T>.EmptyEnumerable;
public static Task<T> FromResult<T>(T t) where T : class
=> FromResultCache<T>.FromResult(t);
private static class TasksOfStruct<T> where T : struct
{
return Empty<T>.EmptyEnumerable;
public static readonly Task<T> Default = Task.FromResult<T>(default);
}
public static Task<T> FromResult<T>(T t) where T : class
private static class TasksOfClass<T> where T : class
{
return FromResultCache<T>.FromResult(t);
public static readonly Task<T?> Null = Task.FromResult<T?>(null);
}
private static class Empty<T>
private static class EmptyTasks<T>
{
public static readonly Task<T> Default = Task.FromResult<T>(default);
public static readonly Task<IEnumerable<T>> EmptyEnumerable = Task.FromResult<IEnumerable<T>>(SpecializedCollections.EmptyEnumerable<T>());
public static readonly Task<ImmutableArray<T>> EmptyImmutableArray = Task.FromResult(ImmutableArray<T>.Empty);
public static readonly Task<IList<T>> EmptyList = Task.FromResult(SpecializedCollections.EmptyList<T>());
......
// 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.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
namespace Roslyn.Utilities
{
/// <summary>
/// A class that abstracts the accessing of a value
/// A class that abstracts the accessing of a value that is guaranteed to be available at some point.
/// </summary>
internal abstract class ValueSource<T>
{
public abstract bool TryGetValue(out T value);
public abstract bool TryGetValue([MaybeNullWhen(false)]out T value);
public abstract T GetValue(CancellationToken cancellationToken = default);
public abstract Task<T> GetValueAsync(CancellationToken cancellationToken = default);
}
internal static class ValueSourceExtensions
{
internal static T? GetValueOrNull<T>(this Optional<T> optional) where T : class
=> optional.Value;
internal static T GetValueOrDefault<T>(this Optional<T> optional) where T : struct
=> optional.Value;
public bool HasValue
{
get
{
return this.TryGetValue(out var tmp);
}
}
internal static T? GetValueOrNull<T>(this ValueSource<Optional<T>> optional, CancellationToken cancellationToken = default) where T : class
=> optional.GetValue(cancellationToken).GetValueOrNull();
public static readonly ConstantValueSource<T> Empty = new ConstantValueSource<T>(default);
internal static T GetValueOrDefault<T>(this ValueSource<Optional<T>> optional, CancellationToken cancellationToken = default) where T : struct
=> optional.GetValue(cancellationToken).GetValueOrDefault();
}
}
// 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.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
namespace Roslyn.Utilities
{
/// <summary>
/// A <see cref="ValueSource{T}"/> that keeps a weak reference to a value.
/// </summary>
internal sealed class WeakConstantValueSource<T> : ValueSource<T> where T : class
internal sealed class WeakValueSource<T> : ValueSource<Optional<T>>
where T : class
{
private readonly WeakReference<T> _weakValue;
public WeakConstantValueSource(T value)
public WeakValueSource(T value)
{
_weakValue = new WeakReference<T>(value);
}
public override T GetValue(CancellationToken cancellationToken)
public override bool TryGetValue(out Optional<T> value)
{
if (_weakValue != null)
if (_weakValue.TryGetTarget(out var target))
{
if (_weakValue.TryGetTarget(out var value))
{
return value;
}
value = target;
return true;
}
return default;
value = default;
return false;
}
public override bool TryGetValue(out T value)
public override Optional<T> GetValue(CancellationToken cancellationToken)
{
if (_weakValue != null)
if (_weakValue.TryGetTarget(out var target))
{
if (_weakValue.TryGetTarget(out value))
{
return true;
}
return target;
}
value = default;
return false;
return default;
}
public override Task<T> GetValueAsync(CancellationToken cancellationToken)
{
return Task.FromResult(this.GetValue(cancellationToken));
}
public override Task<Optional<T>> GetValueAsync(CancellationToken cancellationToken)
=> Task.FromResult(GetValue(cancellationToken));
}
}
// 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.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
......@@ -14,29 +17,25 @@ namespace Microsoft.CodeAnalysis.Host
/// The initial value comes from the <see cref="ValueSource{T}"/> specified in the constructor.
/// Derived types implement SaveAsync and RecoverAsync.
/// </summary>
internal abstract class RecoverableWeakValueSource<T> : ValueSource<T> where T : class
internal abstract class WeaklyCachedRecoverableValueSource<T> : ValueSource<T> where T : class
{
private SemaphoreSlim _gateDoNotAccessDirectly; // Lazily created. Access via the Gate property
private SemaphoreSlim? _lazyGate; // Lazily created. Access via the Gate property
private bool _saved;
private WeakReference<T> _weakInstance;
private WeakReference<T>? _weakReference;
private ValueSource<T> _recoverySource;
private static readonly WeakReference<T> s_noReference = new WeakReference<T>(null);
public RecoverableWeakValueSource(ValueSource<T> initialValue)
public WeaklyCachedRecoverableValueSource(ValueSource<T> initialValue)
{
_weakInstance = s_noReference;
_recoverySource = initialValue;
}
public RecoverableWeakValueSource(RecoverableWeakValueSource<T> savedSource)
public WeaklyCachedRecoverableValueSource(WeaklyCachedRecoverableValueSource<T> savedSource)
{
Contract.ThrowIfFalse(savedSource._saved);
Contract.ThrowIfFalse(savedSource.GetType() == this.GetType());
Contract.ThrowIfFalse(savedSource.GetType() == GetType());
_saved = true;
_weakInstance = s_noReference;
_recoverySource = new AsyncLazy<T>(this.RecoverAsync, this.Recover, cacheResult: false);
_recoverySource = new AsyncLazy<T>(RecoverAsync, Recover, cacheResult: false);
}
/// <summary>
......@@ -61,33 +60,40 @@ public RecoverableWeakValueSource(RecoverableWeakValueSource<T> savedSource)
private static Task s_latestTask = Task.CompletedTask;
private static readonly NonReentrantLock s_taskGuard = new NonReentrantLock();
private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _gateDoNotAccessDirectly, SemaphoreSlimFactory.Instance);
private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance);
public override bool TryGetValue(out T value)
public override bool TryGetValue([MaybeNullWhen(false)]out T value)
{
// it has 2 fields that can hold onto the value. if we only check weakInstance, we will
// It has 2 fields that can hold onto the value. if we only check weakInstance, we will
// return false for the initial case where weakInstance is set to s_noReference even if
// value can be retrieved from _recoverySource. so we check both here.
return _weakInstance.TryGetTarget(out value) ||
_recoverySource.TryGetValue(out value);
// Suppressing nullable warning due to https://github.com/dotnet/roslyn/issues/40266
var weakReference = _weakReference;
return weakReference != null && weakReference.TryGetTarget(out value) ||
_recoverySource.TryGetValue(out value!);
}
public override T GetValue(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (!_weakInstance.TryGetTarget(out var instance))
var weakReference = _weakReference;
if (weakReference == null || !weakReference.TryGetTarget(out var instance))
{
Task saveTask = null;
using (this.Gate.DisposableWait(cancellationToken))
Task? saveTask = null;
using (Gate.DisposableWait(cancellationToken))
{
if (!_weakInstance.TryGetTarget(out instance))
if (_weakReference == null || !_weakReference.TryGetTarget(out instance))
{
instance = _recoverySource.GetValue(cancellationToken);
saveTask = EnsureInstanceIsSaved(instance);
}
}
ResetRecoverySource(saveTask, instance);
if (saveTask != null)
{
ResetRecoverySource(saveTask, instance);
}
}
return instance;
......@@ -95,19 +101,23 @@ public override T GetValue(CancellationToken cancellationToken)
public override async Task<T> GetValueAsync(CancellationToken cancellationToken)
{
if (!_weakInstance.TryGetTarget(out var instance))
var weakReference = _weakReference;
if (weakReference == null || !weakReference.TryGetTarget(out var instance))
{
Task saveTask = null;
using (await this.Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
Task? saveTask = null;
using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (!_weakInstance.TryGetTarget(out instance))
if (_weakReference == null || !_weakReference.TryGetTarget(out instance))
{
instance = await _recoverySource.GetValueAsync(cancellationToken).ConfigureAwait(false);
saveTask = EnsureInstanceIsSaved(instance);
}
}
ResetRecoverySource(saveTask, instance);
if (saveTask != null)
{
ResetRecoverySource(saveTask, instance);
}
}
return instance;
......@@ -115,30 +125,27 @@ public override async Task<T> GetValueAsync(CancellationToken cancellationToken)
private void ResetRecoverySource(Task saveTask, T instance)
{
if (saveTask != null)
saveTask.SafeContinueWith(t =>
{
saveTask.SafeContinueWith(t =>
using (Gate.DisposableWait(CancellationToken.None))
{
using (this.Gate.DisposableWait(CancellationToken.None))
{
_recoverySource = new AsyncLazy<T>(this.RecoverAsync, this.Recover, cacheResult: false);
_recoverySource = new AsyncLazy<T>(RecoverAsync, Recover, cacheResult: false);
// Need to keep instance alive until recovery source is updated.
GC.KeepAlive(instance);
}
}, TaskScheduler.Default);
}
// Need to keep instance alive until recovery source is updated.
GC.KeepAlive(instance);
}
}, TaskScheduler.Default);
}
private Task EnsureInstanceIsSaved(T instance)
private Task? EnsureInstanceIsSaved(T instance)
{
if (_weakInstance == s_noReference)
if (_weakReference == null)
{
_weakInstance = new WeakReference<T>(instance);
_weakReference = new WeakReference<T>(instance);
}
else
{
_weakInstance.SetTarget(instance);
_weakReference.SetTarget(instance);
}
if (!_saved)
......@@ -148,14 +155,12 @@ private Task EnsureInstanceIsSaved(T instance)
{
// force all save tasks to be in sequence so we don't hog all the threads
s_latestTask = s_latestTask.SafeContinueWithFromAsync(t =>
this.SaveAsync(instance, CancellationToken.None), CancellationToken.None, TaskScheduler.Default);
SaveAsync(instance, CancellationToken.None), CancellationToken.None, TaskScheduler.Default);
return s_latestTask;
}
}
else
{
return null;
}
return null;
}
}
}
// 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.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Roslyn.Utilities;
......@@ -8,42 +11,43 @@
namespace Microsoft.CodeAnalysis.Host
{
/// <summary>
/// A value source that cache's its value weakly once obtained from its source.
/// A value source that caches its value weakly once obtained from its source.
/// The source must allow repeatable accesses.
/// </summary>
internal class CachedWeakValueSource<T> : ValueSource<T>
internal sealed class WeaklyCachedValueSource<T> : ValueSource<T>
where T : class
{
private SemaphoreSlim _gateDoNotAccessDirectly; // Lazily created. Access via the Gate property
private SemaphoreSlim? _lazyGate; // Lazily created. Access via the Gate property
private readonly ValueSource<T> _source;
private WeakReference<T> _reference;
private static readonly WeakReference<T> s_noReference = new WeakReference<T>(null);
private WeakReference<T>? _weakReference;
public CachedWeakValueSource(ValueSource<T> source)
public WeaklyCachedValueSource(ValueSource<T> source)
{
_source = source;
_reference = s_noReference;
_weakReference = null;
}
private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _gateDoNotAccessDirectly, SemaphoreSlimFactory.Instance);
private SemaphoreSlim Gate => LazyInitialization.EnsureInitialized(ref _lazyGate, SemaphoreSlimFactory.Instance);
public override bool TryGetValue(out T value)
public override bool TryGetValue([MaybeNullWhen(false)]out T value)
{
return _reference.TryGetTarget(out value)
|| _source.TryGetValue(out value);
// Suppressing nullable warning due to https://github.com/dotnet/roslyn/issues/40266
var weakReference = _weakReference;
return weakReference != null && weakReference.TryGetTarget(out value) ||
_source.TryGetValue(out value!);
}
public override T GetValue(CancellationToken cancellationToken = default)
{
if (!_reference.TryGetTarget(out var value))
var weakReference = _weakReference;
if (weakReference == null || !weakReference.TryGetTarget(out var value))
{
using (Gate.DisposableWait(cancellationToken))
{
if (!_reference.TryGetTarget(out value))
if (_weakReference == null || !_weakReference.TryGetTarget(out value))
{
value = _source.GetValue(cancellationToken);
_reference = new WeakReference<T>(value);
_weakReference = new WeakReference<T>(value);
}
}
}
......@@ -53,14 +57,15 @@ public override T GetValue(CancellationToken cancellationToken = default)
public override async Task<T> GetValueAsync(CancellationToken cancellationToken = default)
{
if (!_reference.TryGetTarget(out var value))
var weakReference = _weakReference;
if (weakReference == null || !weakReference.TryGetTarget(out var value))
{
using (await Gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
{
if (!_reference.TryGetTarget(out value))
if (_weakReference == null || !_weakReference.TryGetTarget(out value))
{
value = await _source.GetValueAsync(cancellationToken).ConfigureAwait(false);
_reference = new WeakReference<T>(value);
_weakReference = new WeakReference<T>(value);
}
}
}
......
// 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.Diagnostics.CodeAnalysis;
namespace Microsoft.CodeAnalysis.Host
{
......@@ -34,13 +37,15 @@ internal interface IProjectCacheHostService : IProjectCacheService
/// cache.
/// </summary>
/// <returns>The instance passed in is always returned</returns>
T CacheObjectIfCachingEnabledForKey<T>(ProjectId key, object owner, T instance) where T : class;
[return: NotNullIfNotNull("instance")]
T? CacheObjectIfCachingEnabledForKey<T>(ProjectId key, object owner, T? instance) where T : class;
/// <summary>
/// If caching is enabled for <see cref="ProjectId"/> key, <see cref="ICachedObjectOwner.CachedObject"/>
/// will be set to instance.
/// </summary>
/// <returns>The instance passed in is always returned</returns>
T CacheObjectIfCachingEnabledForKey<T>(ProjectId key, ICachedObjectOwner owner, T instance) where T : class;
[return: NotNullIfNotNull("instance")]
T? CacheObjectIfCachingEnabledForKey<T>(ProjectId key, ICachedObjectOwner owner, T? instance) where T : class;
}
}
......@@ -20,31 +20,31 @@ public void Dispose()
}
public Task<Checksum> ReadChecksumAsync(string name, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Checksum>();
=> SpecializedTasks.Null<Checksum>();
public Task<Checksum> ReadChecksumAsync(Project project, string name, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Checksum>();
=> SpecializedTasks.Null<Checksum>();
public Task<Checksum> ReadChecksumAsync(Document document, string name, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Checksum>();
=> SpecializedTasks.Null<Checksum>();
public Task<Stream> ReadStreamAsync(Document document, string name, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Stream>();
=> SpecializedTasks.Null<Stream>();
public Task<Stream> ReadStreamAsync(Project project, string name, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Stream>();
=> SpecializedTasks.Null<Stream>();
public Task<Stream> ReadStreamAsync(string name, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Stream>();
=> SpecializedTasks.Null<Stream>();
public Task<Stream> ReadStreamAsync(string name, Checksum checksum, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Stream>();
=> SpecializedTasks.Null<Stream>();
public Task<Stream> ReadStreamAsync(Project project, string name, Checksum checksum, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Stream>();
=> SpecializedTasks.Null<Stream>();
public Task<Stream> ReadStreamAsync(Document document, string name, Checksum checksum, CancellationToken cancellationToken)
=> SpecializedTasks.Default<Stream>();
=> SpecializedTasks.Null<Stream>();
public Task<bool> WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken)
=> SpecializedTasks.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;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using System.Threading;
......@@ -42,23 +45,22 @@ internal struct SyntaxTreeInfo
DiagnosticOptions = diagnosticOptions;
}
internal bool TryGetText(out SourceText text)
internal bool TryGetText([MaybeNullWhen(false)]out SourceText text)
{
if (this.TextSource.TryGetValue(out var textAndVersion))
if (TextSource.TryGetValue(out var textAndVersion))
{
text = textAndVersion.Text;
return true;
}
else
{
text = null;
return false;
}
// Suppressing nullable warning due to https://github.com/dotnet/roslyn/issues/40266
text = null!;
return false;
}
internal async Task<SourceText> GetTextAsync(CancellationToken cancellationToken)
{
var textAndVersion = await this.TextSource.GetValueAsync(cancellationToken).ConfigureAwait(false);
var textAndVersion = await TextSource.GetValueAsync(cancellationToken).ConfigureAwait(false);
return textAndVersion.Text;
}
......@@ -97,10 +99,10 @@ internal SyntaxTreeInfo WithDiagnosticOptions(ImmutableDictionary<string, Report
}
}
internal sealed class RecoverableSyntaxRoot<TRoot> : RecoverableWeakValueSource<TRoot>
internal sealed class RecoverableSyntaxRoot<TRoot> : WeaklyCachedRecoverableValueSource<TRoot>
where TRoot : SyntaxNode
{
private ITemporaryStreamStorage _storage;
private ITemporaryStreamStorage? _storage;
private readonly IRecoverableSyntaxTree<TRoot> _containingTree;
private readonly AbstractSyntaxTreeFactoryService _service;
......@@ -130,7 +132,7 @@ internal sealed class RecoverableSyntaxRoot<TRoot> : RecoverableWeakValueSource<
public RecoverableSyntaxRoot<TRoot> WithSyntaxTree(IRecoverableSyntaxTree<TRoot> containingTree)
{
// at this point, we should either have strongly held root or _storage should not be null
if (this.TryGetValue(out var root))
if (TryGetValue(out var root))
{
// we have strongly held root
return new RecoverableSyntaxRoot<TRoot>(_service, root, containingTree);
......@@ -151,7 +153,7 @@ protected override async Task SaveAsync(TRoot root, CancellationToken cancellati
root.SerializeTo(stream, cancellationToken);
stream.Position = 0;
_storage = _service.LanguageServices.WorkspaceServices.GetService<ITemporaryStorageService>().CreateTemporaryStreamStorage(cancellationToken);
_storage = _service.LanguageServices.WorkspaceServices.GetRequiredService<ITemporaryStorageService>().CreateTemporaryStreamStorage(cancellationToken);
await _storage.WriteStreamAsync(stream, cancellationToken).ConfigureAwait(false);
}
......@@ -170,7 +172,6 @@ protected override TRoot Recover(CancellationToken cancellationToken)
{
Contract.ThrowIfNull(_storage);
var tickCount = Environment.TickCount;
using (RoslynEventSource.LogInformationalBlock(FunctionId.Workspace_Recoverable_RecoverRoot, _containingTree.FilePath, cancellationToken))
{
using var stream = _storage.ReadStream(cancellationToken);
......
......@@ -164,7 +164,7 @@ public bool SupportsSemanticModel
// If the language doesn't support getting syntax trees for a document, then bail out immediately.
if (!this.SupportsSyntaxTree)
{
return SpecializedTasks.Default<SyntaxTree?>();
return SpecializedTasks.Null<SyntaxTree>();
}
// if we have a cached result task use it
......
......@@ -30,7 +30,9 @@ internal partial class DocumentState : TextDocumentState
private readonly HostLanguageServices _languageServices;
private readonly ParseOptions? _options;
private readonly ValueSource<AnalyzerConfigSet> _analyzerConfigSetSource;
private readonly ValueSource<TreeAndVersion> _treeSource;
// null if the document doesn't support syntax trees:
private readonly ValueSource<TreeAndVersion>? _treeSource;
private DocumentState(
HostLanguageServices languageServices,
......@@ -41,19 +43,13 @@ internal partial class DocumentState : TextDocumentState
ValueSource<AnalyzerConfigSet> analyzerConfigSetSource,
SourceText? sourceText,
ValueSource<TextAndVersion> textSource,
ValueSource<TreeAndVersion> treeSource)
ValueSource<TreeAndVersion>? treeSource)
: base(solutionServices, documentServiceProvider, attributes, sourceText, textSource)
{
_languageServices = languageServices;
_options = options;
_analyzerConfigSetSource = analyzerConfigSetSource;
// If this is document that doesn't support syntax, then don't even bother holding
// onto any tree source. It will never be used to get a tree, and can only hurt us
// by possibly holding onto data that might cause a slow memory leak.
_treeSource = this.SupportsSyntaxTree
? treeSource
: ValueSource<TreeAndVersion>.Empty;
_treeSource = treeSource;
}
public DocumentState(
......@@ -71,9 +67,9 @@ internal partial class DocumentState : TextDocumentState
// If this is document that doesn't support syntax, then don't even bother holding
// onto any tree source. It will never be used to get a tree, and can only hurt us
// by possibly holding onto data that might cause a slow memory leak.
if (!this.SupportsSyntaxTree)
if (languageServices.SyntaxTreeFactory == null)
{
_treeSource = ValueSource<TreeAndVersion>.Empty;
_treeSource = null;
}
else
{
......@@ -89,7 +85,7 @@ internal partial class DocumentState : TextDocumentState
}
internal bool SupportsSyntaxTree
=> _languageServices.SyntaxTreeFactory != null;
=> _treeSource != null;
public HostLanguageServices LanguageServices
=> _languageServices;
......@@ -316,9 +312,9 @@ private static bool TopLevelChanged(SyntaxTree oldTree, SourceText oldText, Synt
/// </summary>
public bool HasContentChanged(DocumentState oldState)
{
return oldState._treeSource != this._treeSource
|| oldState.sourceText != this.sourceText
|| oldState.TextAndVersionSource != this.TextAndVersionSource;
return oldState._treeSource != _treeSource
|| oldState.sourceText != sourceText
|| oldState.TextAndVersionSource != TextAndVersionSource;
}
/// <summary>
......@@ -326,8 +322,8 @@ public bool HasContentChanged(DocumentState oldState)
/// </summary>
public bool HasTextChanged(DocumentState oldState)
{
return (oldState.sourceText != this.sourceText
|| oldState.TextAndVersionSource != this.TextAndVersionSource);
return oldState.sourceText != sourceText
|| oldState.TextAndVersionSource != TextAndVersionSource;
}
public DocumentState UpdateParseOptions(ParseOptions options)
......@@ -350,23 +346,28 @@ private DocumentState SetParseOptions(ParseOptions options)
throw new ArgumentNullException(nameof(options));
}
if (!SupportsSyntaxTree)
{
throw new InvalidOperationException();
}
var newTreeSource = CreateLazyFullyParsedTree(
this.TextAndVersionSource,
this.Id.ProjectId,
GetSyntaxTreeFilePath(this.Attributes),
TextAndVersionSource,
Id.ProjectId,
GetSyntaxTreeFilePath(Attributes),
options,
_analyzerConfigSetSource,
_languageServices);
return new DocumentState(
this.LanguageServices,
this.solutionServices,
this.Services,
this.Attributes.With(sourceCodeKind: options.Kind),
LanguageServices,
solutionServices,
Services,
Attributes.With(sourceCodeKind: options.Kind),
options,
_analyzerConfigSetSource,
this.sourceText,
this.TextAndVersionSource,
sourceText,
TextAndVersionSource,
newTreeSource);
}
......@@ -385,13 +386,13 @@ public DocumentState UpdateName(string name)
// TODO: if FilePath is null, then this will change the name of the underlying tree, but we aren't producing a new tree in that case.
return new DocumentState(
_languageServices,
this.solutionServices,
this.Services,
this.Attributes.With(name: name),
solutionServices,
Services,
Attributes.With(name: name),
_options,
_analyzerConfigSetSource,
this.sourceText,
this.TextAndVersionSource,
sourceText,
TextAndVersionSource,
_treeSource);
}
......@@ -399,13 +400,13 @@ public DocumentState UpdateFolders(IList<string> folders)
{
return new DocumentState(
_languageServices,
this.solutionServices,
this.Services,
this.Attributes.With(folders: folders),
solutionServices,
Services,
Attributes.With(folders: folders),
_options,
_analyzerConfigSetSource,
this.sourceText,
this.TextAndVersionSource,
sourceText,
TextAndVersionSource,
_treeSource);
}
......@@ -415,25 +416,24 @@ public DocumentState UpdateFilePath(string filePath)
// TODO: it's overkill to fully reparse the tree if we had the tree already; all we have to do is update the
// file path and diagnostic options for that tree.
var newTreeSource = this.SupportsSyntaxTree
? CreateLazyFullyParsedTree(
this.TextAndVersionSource,
this.Id.ProjectId,
GetSyntaxTreeFilePath(newAttributes),
_options!,
_analyzerConfigSetSource,
_languageServices)
: ValueSource<TreeAndVersion>.Empty;
var newTreeSource = SupportsSyntaxTree ?
CreateLazyFullyParsedTree(
TextAndVersionSource,
Id.ProjectId,
GetSyntaxTreeFilePath(newAttributes),
_options!,
_analyzerConfigSetSource,
_languageServices) : null;
return new DocumentState(
_languageServices,
this.solutionServices,
this.Services,
solutionServices,
Services,
newAttributes,
_options,
_analyzerConfigSetSource,
this.sourceText,
this.TextAndVersionSource,
sourceText,
TextAndVersionSource,
newTreeSource);
}
......@@ -441,25 +441,24 @@ public DocumentState UpdateAnalyzerConfigSet(ValueSource<AnalyzerConfigSet> newA
{
// TODO: it's overkill to fully reparse the tree if we had the tree already; all we have to do is update the
// file path and diagnostic options for that tree.
var newTreeSource = SupportsSyntaxTree
? CreateLazyFullyParsedTree(
this.TextAndVersionSource,
this.Id.ProjectId,
GetSyntaxTreeFilePath(this.Attributes),
_options!,
newAnalyzerConfigSet,
_languageServices)
: ValueSource<TreeAndVersion>.Empty;
var newTreeSource = SupportsSyntaxTree ?
CreateLazyFullyParsedTree(
TextAndVersionSource,
Id.ProjectId,
GetSyntaxTreeFilePath(Attributes),
_options!,
newAnalyzerConfigSet,
_languageServices) : null;
return new DocumentState(
_languageServices,
this.solutionServices,
this.Services,
this.Attributes,
solutionServices,
Services,
Attributes,
_options,
newAnalyzerConfigSet,
this.sourceText,
this.TextAndVersionSource,
sourceText,
TextAndVersionSource,
newTreeSource);
}
......@@ -475,11 +474,11 @@ public new DocumentState UpdateText(TextAndVersion newTextAndVersion, Preservati
protected override TextDocumentState UpdateText(ValueSource<TextAndVersion> newTextSource, PreservationMode mode, bool incremental)
{
ValueSource<TreeAndVersion> newTreeSource;
ValueSource<TreeAndVersion>? newTreeSource;
if (!this.SupportsSyntaxTree)
if (_treeSource == null)
{
newTreeSource = ValueSource<TreeAndVersion>.Empty;
newTreeSource = null;
}
else if (incremental)
{
......@@ -489,8 +488,8 @@ protected override TextDocumentState UpdateText(ValueSource<TextAndVersion> newT
{
newTreeSource = CreateLazyFullyParsedTree(
newTextSource,
this.Id.ProjectId,
GetSyntaxTreeFilePath(this.Attributes),
Id.ProjectId,
GetSyntaxTreeFilePath(Attributes),
_options!,
_analyzerConfigSetSource,
_languageServices,
......@@ -498,10 +497,10 @@ protected override TextDocumentState UpdateText(ValueSource<TextAndVersion> newT
}
return new DocumentState(
this.LanguageServices,
this.solutionServices,
this.Services,
this.Attributes,
LanguageServices,
solutionServices,
Services,
Attributes,
_options,
_analyzerConfigSetSource,
sourceText: null,
......@@ -522,21 +521,21 @@ internal DocumentState UpdateText(TextLoader loader, SourceText? text, Preservat
// TODO: understand why this is being called this way at all. It seems we only have a text in a specific case
// when we are opening a file, when it seems this could have just called the other overload that took a
// TextAndVersion that could have just pinned the object directly.
if (text != null)
{
return new DocumentState(
this.LanguageServices,
this.solutionServices,
this.Services,
this.Attributes,
_options,
_analyzerConfigSetSource,
sourceText: text,
textSource: documentState.TextAndVersionSource,
treeSource: documentState._treeSource);
if (text == null)
{
return documentState;
}
return documentState;
return new DocumentState(
LanguageServices,
solutionServices,
Services,
Attributes,
_options,
_analyzerConfigSetSource,
sourceText: text,
textSource: documentState.TextAndVersionSource,
treeSource: documentState._treeSource);
}
internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode)
......@@ -546,6 +545,11 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode)
throw new ArgumentNullException(nameof(newRoot));
}
if (!SupportsSyntaxTree)
{
throw new InvalidOperationException();
}
var newTextVersion = GetNewerVersion();
var newTreeVersion = GetNewTreeVersionForUpdatedTree(newRoot, newTextVersion, mode);
......@@ -599,6 +603,8 @@ internal DocumentState UpdateTree(SyntaxNode newRoot, PreservationMode mode)
private VersionStamp GetNewTreeVersionForUpdatedTree(SyntaxNode newRoot, VersionStamp newTextVersion, PreservationMode mode)
{
RoslynDebug.Assert(_treeSource != null);
if (mode != PreservationMode.PreserveIdentity)
{
return newTextVersion;
......@@ -651,7 +657,7 @@ private VersionStamp GetNewTreeVersionForUpdatedTree(SyntaxNode newRoot, Version
// Uses CachedWeakValueSource so the document and tree will return the same SourceText instance across multiple accesses as long
// as the text is referenced elsewhere.
lazyTextAndVersion = new TreeTextSource(
new CachedWeakValueSource<SourceText>(
new WeaklyCachedValueSource<SourceText>(
new AsyncLazy<SourceText>(
// Build text from root, so recoverable tree won't cycle.
async cancellationToken => (await tree.GetRootAsync(cancellationToken).ConfigureAwait(false)).GetText(encoding),
......@@ -668,12 +674,12 @@ private VersionStamp GetNewTreeVersionForUpdatedTree(SyntaxNode newRoot, Version
private VersionStamp GetNewerVersion()
{
if (this.TextAndVersionSource.TryGetValue(out var textAndVersion))
if (TextAndVersionSource.TryGetValue(out var textAndVersion))
{
return textAndVersion!.Version.GetNewerVersion();
}
if (_treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
if (_treeSource != null && _treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
{
return treeAndVersion.Version.GetNewerVersion();
}
......@@ -684,10 +690,10 @@ private VersionStamp GetNewerVersion()
public bool TryGetSyntaxTree([NotNullWhen(returnValue: true)] out SyntaxTree? syntaxTree)
{
syntaxTree = null;
if (_treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
if (_treeSource != null && _treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
{
syntaxTree = treeAndVersion.Tree;
BindSyntaxTreeToId(syntaxTree, this.Id);
BindSyntaxTreeToId(syntaxTree, Id);
return true;
}
......@@ -697,6 +703,9 @@ public bool TryGetSyntaxTree([NotNullWhen(returnValue: true)] out SyntaxTree? sy
[PerformanceSensitive("https://github.com/dotnet/roslyn/issues/23582", OftenCompletesSynchronously = true)]
public async ValueTask<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancellationToken)
{
// operation should only be performed on documents that support syntax trees
RoslynDebug.Assert(_treeSource != null);
var treeAndVersion = await _treeSource.GetValueAsync(cancellationToken).ConfigureAwait(false);
// make sure there is an association between this tree and this doc id before handing it out
......@@ -706,6 +715,9 @@ public async ValueTask<SyntaxTree> GetSyntaxTreeAsync(CancellationToken cancella
internal SyntaxTree GetSyntaxTree(CancellationToken cancellationToken)
{
// operation should only be performed on documents that support syntax trees
RoslynDebug.Assert(_treeSource != null);
var treeAndVersion = _treeSource.GetValue(cancellationToken);
// make sure there is an association between this tree and this doc id before handing it out
......@@ -715,7 +727,7 @@ internal SyntaxTree GetSyntaxTree(CancellationToken cancellationToken)
public bool TryGetTopLevelChangeTextVersion(out VersionStamp version)
{
if (_treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
if (_treeSource != null && _treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
{
version = treeAndVersion.Version;
return true;
......@@ -729,9 +741,9 @@ public bool TryGetTopLevelChangeTextVersion(out VersionStamp version)
public override async Task<VersionStamp> GetTopLevelChangeTextVersionAsync(CancellationToken cancellationToken)
{
if (!this.SupportsSyntaxTree)
if (_treeSource == null)
{
return await this.GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
return await GetTextVersionAsync(cancellationToken).ConfigureAwait(false);
}
if (_treeSource.TryGetValue(out var treeAndVersion) && treeAndVersion != null)
......
......@@ -109,7 +109,7 @@ private TextAndVersion InitRecoverable(TextAndVersion textAndVersion)
return textAndVersion;
}
private sealed class RecoverableText : RecoverableWeakValueSource<SourceText>
private sealed class RecoverableText : WeaklyCachedRecoverableValueSource<SourceText>
{
private readonly RecoverableTextAndVersion _parent;
private ITemporaryTextStorage _storage;
......
// 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.Linq;
......@@ -22,40 +24,37 @@ private class State
/// <summary>
/// The base <see cref="State"/> that starts with everything empty.
/// </summary>
public static readonly State Empty = new State(compilation: ConstantValueSource<Compilation>.Empty, declarationOnlyCompilation: null);
public static readonly State Empty = new State(compilation: null, declarationOnlyCompilation: null);
/// <summary>
/// A strong reference to the declaration-only compilation. This compilation isn't used to produce symbols,
/// nor does it have any references. It just holds the declaration table alive.
/// </summary>
public Compilation DeclarationOnlyCompilation { get; }
public Compilation? DeclarationOnlyCompilation { get; }
/// <summary>
/// The best compilation that is available. May be an in-progress, full declaration, or a final compilation.
/// The best compilation that is available. May be an in-progress, full declaration, a final compilation, or <see langword="null"/>.
/// </summary>
public ValueSource<Compilation> Compilation { get; }
public ValueSource<Optional<Compilation>>? Compilation { get; }
/// <summary>
/// Specifies whether <see cref="FinalCompilation"/> and all compilations it depends on contain full information or not. This can return
/// null if the state isn't at the point where it would know, and it's necessary to transition to <see cref="FinalState"/> to figure that out.
/// <see langword="null"/> if the state isn't at the point where it would know, and it's necessary to transition to <see cref="FinalState"/> to figure that out.
/// </summary>
public virtual bool? HasSuccessfullyLoaded => null;
/// <summary>
/// The final compilation if available, otherwise an empty <see cref="ValueSource{Compilation}"/>.
/// The final compilation if available, otherwise <see langword="null"/>.
/// </summary>
public virtual ValueSource<Compilation> FinalCompilation
{
get { return ConstantValueSource<Compilation>.Empty; }
}
public virtual ValueSource<Optional<Compilation>>? FinalCompilation => null;
protected State(ValueSource<Compilation> compilation, Compilation declarationOnlyCompilation)
protected State(ValueSource<Optional<Compilation>>? compilation, Compilation? declarationOnlyCompilation)
{
this.Compilation = compilation;
this.DeclarationOnlyCompilation = declarationOnlyCompilation;
// Declaration-only compilations should never have any references
Contract.ThrowIfTrue(declarationOnlyCompilation != null && declarationOnlyCompilation.ExternalReferences.Any());
Compilation = compilation;
DeclarationOnlyCompilation = declarationOnlyCompilation;
}
public static State Create(
......@@ -72,13 +71,13 @@ protected State(ValueSource<Compilation> compilation, Compilation declarationOnl
: (State)new InProgressState(compilation, intermediateProjects);
}
public static ValueSource<Compilation> CreateValueSource(
public static ValueSource<Optional<Compilation>> CreateValueSource(
Compilation compilation,
SolutionServices services)
{
return services.SupportsCachingRecoverableObjects
? new WeakConstantValueSource<Compilation>(compilation)
: (ValueSource<Compilation>)new ConstantValueSource<Compilation>(compilation);
? new WeakValueSource<Compilation>(compilation)
: (ValueSource<Optional<Compilation>>)new ConstantValueSource<Optional<Compilation>>(compilation);
}
}
......@@ -93,7 +92,7 @@ private sealed class InProgressState : State
public InProgressState(
Compilation inProgressCompilation,
ImmutableArray<(ProjectState state, CompilationTranslationAction action)> intermediateProjects)
: base(compilation: new ConstantValueSource<Compilation>(inProgressCompilation), declarationOnlyCompilation: null)
: base(compilation: new ConstantValueSource<Optional<Compilation>>(inProgressCompilation), declarationOnlyCompilation: null)
{
Contract.ThrowIfNull(inProgressCompilation);
Contract.ThrowIfTrue(intermediateProjects.IsDefault);
......@@ -109,7 +108,7 @@ private sealed class InProgressState : State
private sealed class LightDeclarationState : State
{
public LightDeclarationState(Compilation declarationOnlyCompilation)
: base(compilation: ConstantValueSource<Compilation>.Empty, declarationOnlyCompilation: declarationOnlyCompilation)
: base(compilation: null, declarationOnlyCompilation: declarationOnlyCompilation)
{
}
}
......@@ -121,7 +120,7 @@ public LightDeclarationState(Compilation declarationOnlyCompilation)
private sealed class FullDeclarationState : State
{
public FullDeclarationState(Compilation declarationCompilation)
: base(new WeakConstantValueSource<Compilation>(declarationCompilation), declarationCompilation.Clone().RemoveAllReferences())
: base(new WeakValueSource<Compilation>(declarationCompilation), declarationCompilation.Clone().RemoveAllReferences())
{
}
}
......@@ -135,10 +134,10 @@ private sealed class FinalState : State
private readonly bool _hasSuccessfullyLoaded;
public override bool? HasSuccessfullyLoaded => _hasSuccessfullyLoaded;
public override ValueSource<Compilation> FinalCompilation => this.Compilation;
public override ValueSource<Optional<Compilation>>? FinalCompilation => Compilation;
public FinalState(ValueSource<Compilation> finalCompilationSource, bool hasSuccessfullyLoaded)
: base(finalCompilationSource, finalCompilationSource.GetValue().Clone().RemoveAllReferences())
public FinalState(ValueSource<Optional<Compilation>> finalCompilationSource, Compilation finalCompilation, bool hasSuccessfullyLoaded)
: base(finalCompilationSource, finalCompilation.Clone().RemoveAllReferences())
{
_hasSuccessfullyLoaded = hasSuccessfullyLoaded;
}
......
......@@ -421,9 +421,12 @@ private bool TryGetCompilationTracker(ProjectId projectId, [NotNullWhen(returnVa
}
private static readonly Func<ProjectId, SolutionState, CompilationTracker> s_createCompilationTrackerFunction = CreateCompilationTracker;
private static CompilationTracker CreateCompilationTracker(ProjectId projectId, SolutionState solution)
{
return new CompilationTracker(solution.GetProjectState(projectId));
var projectState = solution.GetProjectState(projectId);
Contract.ThrowIfNull(projectState);
return new CompilationTracker(projectState);
}
private CompilationTracker GetCompilationTracker(ProjectId projectId)
......@@ -1958,8 +1961,8 @@ public bool TryGetCompilation(ProjectId projectId, [NotNullWhen(returnValue: tru
public Task<Compilation?> GetCompilationAsync(ProjectState project, CancellationToken cancellationToken)
{
return project.SupportsCompilation
? this.GetCompilationTracker(project.Id).GetCompilationAsync(this, cancellationToken)
: SpecializedTasks.Default<Compilation?>();
? GetCompilationTracker(project.Id).GetCompilationAsync(this, cancellationToken).AsNullable()
: SpecializedTasks.Null<Compilation>();
}
/// <summary>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册