Skip to content

Commit

Permalink
Fold DocumentSpecificOptionSet into DocumentOptionSet
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat committed May 13, 2022
1 parent c334a38 commit 871eae0
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 98 deletions.
74 changes: 65 additions & 9 deletions src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.ErrorReporting;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Options
{
Expand All @@ -15,28 +20,79 @@ namespace Microsoft.CodeAnalysis.Options
/// </summary>
public sealed class DocumentOptionSet : OptionSet
{
private readonly OptionSet _backingOptionSet;
private readonly OptionSet _underlyingOptions;
private readonly StructuredAnalyzerConfigOptions? _configOptions;
private ImmutableDictionary<OptionKey, object?> _values;
private readonly string _language;

internal DocumentOptionSet(OptionSet backingOptionSet, string language)
internal DocumentOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, string language)
: this(configOptions, underlyingOptions, language, ImmutableDictionary<OptionKey, object?>.Empty)
{
}

internal DocumentOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, string language, ImmutableDictionary<OptionKey, object?> values)
{
_backingOptionSet = backingOptionSet;
_language = language;
_configOptions = configOptions;
_underlyingOptions = underlyingOptions;
_values = values;
}

internal string Language => _language;

[PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)]
private protected override object? GetOptionCore(OptionKey optionKey)
=> _backingOptionSet.GetOption(optionKey);
{
// If we already know the document specific value, we're done
if (_values.TryGetValue(optionKey, out var value))
{
return value;
}

if (TryGetAnalyzerConfigOption(optionKey, out value))
{
// Cache and return
return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value);
}

// We don't have a document specific value, so forward
return _underlyingOptions.GetOption(optionKey);
}

private bool TryGetAnalyzerConfigOption(OptionKey option, out object? value)
{
if (_configOptions == null)
{
value = null;
return false;
}

var editorConfigPersistence = (IEditorConfigStorageLocation?)option.Option.StorageLocations.SingleOrDefault(static location => location is IEditorConfigStorageLocation);
if (editorConfigPersistence == null)
{
value = null;
return false;
}

try
{
return editorConfigPersistence.TryGetOption(_configOptions, option.Option.Type, out value);
}
catch (Exception e) when (FatalError.ReportAndCatch(e))
{
value = null;
return false;
}
}

public T GetOption<T>(PerLanguageOption<T> option)
=> _backingOptionSet.GetOption(option, _language);
=> GetOption(option, _language);

internal T GetOption<T>(PerLanguageOption2<T> option)
=> _backingOptionSet.GetOption(option, _language);
=> GetOption(option, _language);

public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value)
=> new DocumentOptionSet(_backingOptionSet.WithChangedOption(optionAndLanguage, value), _language);
=> new DocumentOptionSet(_configOptions, _underlyingOptions, _language, _values.SetItem(optionAndLanguage, value));

/// <summary>
/// Creates a new <see cref="DocumentOptionSet" /> that contains the changed value.
Expand All @@ -53,10 +109,10 @@ internal DocumentOptionSet WithChangedOption<T>(PerLanguageOption2<T> option, T
private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language)
{
Debug.Assert((language ?? _language) == _language, $"Use of a {nameof(DocumentOptionSet)} is not expected to differ from the language it was constructed with.");
return _backingOptionSet.AsAnalyzerConfigOptions(optionService, language ?? _language);
return base.CreateAnalyzerConfigOptions(optionService, language ?? _language);
}

internal override IEnumerable<OptionKey> GetChangedOptions(OptionSet optionSet)
=> _backingOptionSet.GetChangedOptions(optionSet);
=> GetChangedOptions(optionSet);
}
}
5 changes: 0 additions & 5 deletions src/Workspaces/Core/Portable/Options/IOptionService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,6 @@ internal interface IOptionService : IWorkspaceService

event EventHandler<OptionChangedEventArgs> OptionChanged;

/// <summary>
/// Returns the <see cref="OptionSet"/> that applies to a specific document, given that document and the global options.
/// </summary>
Task<OptionSet> GetUpdatedOptionSetForDocumentAsync(Document document, OptionSet optionSet, CancellationToken cancellationToken);

/// <summary>
/// Registers a workspace with the option service.
/// </summary>
Expand Down
81 changes: 0 additions & 81 deletions src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,87 +126,6 @@ public event EventHandler<OptionChangedEventArgs> OptionChanged
public void SetOptions(OptionSet optionSet) => _globalOptionService.SetOptions(optionSet);
public void RegisterWorkspace(Workspace workspace) => _globalOptionService.RegisterWorkspace(workspace);
public void UnregisterWorkspace(Workspace workspace) => _globalOptionService.UnregisterWorkspace(workspace);

public async Task<OptionSet> GetUpdatedOptionSetForDocumentAsync(Document document, OptionSet optionSet, CancellationToken cancellationToken)
{
var provider = (ProjectState.ProjectAnalyzerConfigOptionsProvider)document.Project.State.AnalyzerOptions.AnalyzerConfigOptionsProvider;
var options = await provider.GetOptionsAsync(document.DocumentState, cancellationToken).ConfigureAwait(false);
return new DocumentSpecificOptionSet(options, optionSet);
}

private sealed class DocumentSpecificOptionSet : OptionSet
{
private readonly OptionSet _underlyingOptions;
private readonly StructuredAnalyzerConfigOptions? _configOptions;
private ImmutableDictionary<OptionKey, object?> _values;

public DocumentSpecificOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions)
: this(configOptions, underlyingOptions, ImmutableDictionary<OptionKey, object?>.Empty)
{
}

public DocumentSpecificOptionSet(StructuredAnalyzerConfigOptions? configOptions, OptionSet underlyingOptions, ImmutableDictionary<OptionKey, object?> values)
{
_configOptions = configOptions;
_underlyingOptions = underlyingOptions;
_values = values;
}

[PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)]
private protected override object? GetOptionCore(OptionKey optionKey)
{
// If we already know the document specific value, we're done
if (_values.TryGetValue(optionKey, out var value))
{
return value;
}

if (TryGetAnalyzerConfigOption(optionKey, out value))
{
// Cache and return
return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value);
}

// We don't have a document specific value, so forward
return _underlyingOptions.GetOption(optionKey);
}

private bool TryGetAnalyzerConfigOption(OptionKey option, out object? value)
{
if (_configOptions == null)
{
value = null;
return false;
}

var editorConfigPersistence = (IEditorConfigStorageLocation?)option.Option.StorageLocations.SingleOrDefault(static location => location is IEditorConfigStorageLocation);
if (editorConfigPersistence == null)
{
value = null;
return false;
}

try
{
return editorConfigPersistence.TryGetOption(_configOptions, option.Option.Type, out value);
}
catch (Exception e) when (FatalError.ReportAndCatch(e))
{
value = null;
return false;
}
}

public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value)
=> new DocumentSpecificOptionSet(_configOptions, _underlyingOptions, _values.SetItem(optionAndLanguage, value));

internal override IEnumerable<OptionKey> GetChangedOptions(OptionSet optionSet)
{
// GetChangedOptions only needs to be supported for OptionSets that need to be compared during application,
// but that's already enforced it must be a full SerializableOptionSet.
throw new NotSupportedException();
}
}
}
}
}
7 changes: 4 additions & 3 deletions src/Workspaces/Core/Portable/Workspace/Solution/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -505,9 +505,10 @@ private void InitializeCachedOptions(OptionSet solutionOptions)
{
var newAsyncLazy = new AsyncLazy<DocumentOptionSet>(async c =>
{
var optionsService = Project.Solution.Workspace.Services.GetRequiredService<IOptionService>();
var documentOptionSet = await optionsService.GetUpdatedOptionSetForDocumentAsync(this, solutionOptions, c).ConfigureAwait(false);
return new DocumentOptionSet(documentOptionSet, Project.Language);
var provider = (ProjectState.ProjectAnalyzerConfigOptionsProvider)Project.State.AnalyzerOptions.AnalyzerConfigOptionsProvider;
var options = await provider.GetOptionsAsync(DocumentState, c).ConfigureAwait(false);

return new DocumentOptionSet(options, solutionOptions, Project.Language);
}, cacheResult: true);

Interlocked.CompareExchange(ref _cachedOptions, newAsyncLazy, comparand: null);
Expand Down

0 comments on commit 871eae0

Please sign in to comment.