File: Options\SolutionOptionSet.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Options;
 
/// <summary>
/// Implements in-proc only storage for <see cref="Solution.Options"/>.
/// Supports tracking changed options.
/// Options that are not set in the option set are read from global options and cached.
/// </summary>
internal sealed class SolutionOptionSet : OptionSet
{
    private readonly ILegacyGlobalOptionService _legacyGlobalOptions;
 
    /// <summary>
    /// Cached values read from global options. Stores internal values of options.
    /// </summary>
    private ImmutableDictionary<OptionKey, object?> _values;
 
    /// <summary>
    /// Keys of options whose current value stored in <see cref="_values"/> differs from the value originally read from global options.
    /// </summary>
    private readonly ImmutableHashSet<OptionKey> _changedOptionKeys;
 
    private SolutionOptionSet(
        ILegacyGlobalOptionService globalOptions,
        ImmutableDictionary<OptionKey, object?> values,
        ImmutableHashSet<OptionKey> changedOptionKeys)
    {
        _legacyGlobalOptions = globalOptions;
        _values = values;
        _changedOptionKeys = changedOptionKeys;
    }
 
    internal SolutionOptionSet(ILegacyGlobalOptionService globalOptions)
        : this(globalOptions, values: ImmutableDictionary<OptionKey, object?>.Empty, changedOptionKeys: [])
    {
    }
 
    [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)]
    internal override object? GetInternalOptionValue(OptionKey optionKey)
    {
        if (_values.TryGetValue(optionKey, out var value))
        {
            return value;
        }
 
        value = (optionKey.Option is IOption2 internallyDefinedOption)
            ? _legacyGlobalOptions.GlobalOptions.GetOption<object?>(new OptionKey2(internallyDefinedOption, optionKey.Language))
            : _legacyGlobalOptions.GetExternallyDefinedOption(optionKey);
 
        return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value);
    }
 
    internal override OptionSet WithChangedOptionInternal(OptionKey optionKey, object? internalValue)
    {
        // Make sure we first load this in current optionset
        var currentInternalValue = GetInternalOptionValue(optionKey);
 
        // Check if the new value is the same as the current value.
        if (Equals(internalValue, currentInternalValue))
        {
            // Return a cloned option set as the public API 'WithChangedOption' guarantees a new option set is returned.
            return new SolutionOptionSet(_legacyGlobalOptions, _values, _changedOptionKeys);
        }
 
        return new SolutionOptionSet(
            _legacyGlobalOptions,
            _values.SetItem(optionKey, internalValue),
            _changedOptionKeys.Add(optionKey));
    }
 
    internal (ImmutableArray<KeyValuePair<OptionKey2, object?>> internallyDefined, ImmutableArray<KeyValuePair<OptionKey, object?>> externallyDefined) GetChangedOptions()
    {
        var internallyDefined = _changedOptionKeys.Where(key => key.Option is IOption2).SelectAsArray(key => KeyValuePairUtil.Create(new OptionKey2((IOption2)key.Option, key.Language), _values[key]));
        var externallyDefined = _changedOptionKeys.Where(key => key.Option is not IOption2).SelectAsArray(key => KeyValuePairUtil.Create(key, _values[key]));
        return (internallyDefined, externallyDefined);
    }
}