right now, Solution.Options is not actually part of snapshot but use Workspace.Options
we should make it as part of solution snapshot. which means when Workspace option is changed, new CurrentSolution should be created. and all normal process should begin from there such as workspace changed events and etc.
OptionSet should support checksum and support creating checksum without reading all options in to make creating checksum for solution efficient.
this depends on how @jasonmalinowski decide on options.
if we can endure prefetch perf cost of options (assume we make option async API) as we did for file content hashing for OOP, then adding options in solution would be quite straight forward. after this 1 time cost, updating option as time go will be incremental, so won't be big issue I believe.
but it is not all cost since there are these benefits (probably more)
ex)
code related to this can all be removed since option change will just update snapshot
http://sourceroslyn.io/#Microsoft.CodeAnalysis.Features/SolutionCrawler/WorkCoordinator.cs,162
http://sourceroslyn.io/#Microsoft.CodeAnalysis.Remote.Workspaces/Diagnostics/DiagnosticComputer.cs,76
no need to do special option serialization anymore
http://sourceroslyn.io/#Microsoft.CodeAnalysis.Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs,164
cache based on snapshot now accommodate options as well. previously, we might have a bug due to cache being only based on snapshot when result could be changed due to options.
and etc.
@mavasani FYI
Yes, we should just snapshot the options. If we have multiple sources or groups of options and some of them might take longer to retrieve then we could model them as a tree and fetch nodes on demand. But I would just start with a single bunch.
Workspace.Options.get and Solution.Options.get are public APIs that fetch the global options from IOptionService. Both these getters always return the same (equivalent) option set.Workspace.Options.set is the only public API that sets the global options in IOptionService, which is internal.Workspace.cs
/// <summary>
/// Gets or sets the set of all global options.
/// </summary>
public OptionSet Options
{
get
{
return _services.GetRequiredService<IOptionService>().GetOptions();
}
set
{
_services.GetRequiredService<IOptionService>().SetOptions(value);
}
}
Solution.cs
/// <summary>
/// Returns the options that should be applied to this solution. This is equivalent to <see cref="Workspace.Options" /> when the <see cref="Solution"/>
/// instance was created.
/// </summary>
public OptionSet Options
{
get
{
// TODO: actually make this a snapshot
return this.Workspace.Options;
}
}
Desired functionality changes:
Solution snapshot at the time the solution snapshot is created.API semantic changes:
Workspace fetches options attached to CurrentSolution snapshot.Solution fetches options created at the time that solution snapshot was created. Solution instance can differ from the current global options or options fetch from a different solution snapshot.Workspace updates the global option on IOptionService and then creates a new Solution instance with updated options, and updates Workspace.CurrentSolution to be this new solution instance.Solution to create a new solution snapshot with given options.How do we want to handle any semantic breaks for existing public APIs: Workspace.Options.get, Workspace.Options.set and Solution.Options.get?
GetOptions and SetOptions pairs and both Workspace and Solution, that are as per the new snapshot based semantics. I have an API proposal below.Workspace.Options.set API, as that is the only public setter API, in favor of new Workspace.SetOptions API that documents the new semantics, especially that underlying CurrentSolution will be been updated. Workspace.Options.get and Solution.Options.get are re-purposed to returns snapshot based options. If we take this route, there can still be silent semantic breaks:Workspace.Options.get and Solution.Options.get now no longer return equivalent options, so if user attempts to to fetch options from older solution snapshot, they get previous options.Workspace.TryApplyChanges might fail if the caller had done following:Workspace.TryApplyChanges with new solution created in first step above. This solution will have a lesser workspace version due to second step, hence TryApplyChanges will fail.Workspace.TryApplyChanges to be tolerant to option changes. This can be done by either of the below ways:WorkspaceVersion when changing options. Q: Can this have other bad side effects?WorkspaceVersion goes back in this case. This might be tricky to implement.Workspace.Options.get and Solution.Options.get may no longer return equivalent options andSolution.Options.get is repurposed for the new functionality.Workspace.cs
/// <summary>
/// Gets or sets the set of all global options.
/// </summary>
[Obsolete("Use GetOptions/SetOptions APIs")]
public OptionSet Options
{
get
{
return _services.GetRequiredService<IOptionService>().GetOptions();
}
set
{
_services.GetRequiredService<IOptionService>().SetOptions(value);
}
}
/// <summary>
/// Gets the options for the `CurrentSolution`s
/// </summary>
public OptionSet GetOptions() => this.CurrentSolution.GetOptions();
/// <summary>
/// Sets the global options and updates the underlying `CurrentSolution` to be a new `Solution` instance with the updated options.
/// </summary>
public OptionSet SetOptions()
{
if (_optionService.SetOptions(options))
{
UpdateCurrentSolutionOnOptionsChanged();
}
}
Solution.cs
/// <summary>
/// Returns the options that should be applied to this solution. This is equivalent to <see cref="Workspace.Options" /> when the <see cref="Solution"/>
/// instance was created.
/// </summary>
[Obsolete("Use GetOptions/WithOptions APIs")]
public OptionSet Options
{
get
{
// TODO: actually make this a snapshot
return this.Workspace.Options;
}
}
/// <summary>
/// Gets the options attached to this Solution instance at the time this Solution was created.
/// NOTE: The options returned may not be equivalent to `Workspace.Options`, which returns the options attached to `Workspace.CurrentSolution`
/// </summary>
public OptionSet GetOptions() => _state.Options;
/// <summary>
/// Creates a new solution instance with the specified <paramref name="options"/>.
/// </summary>
public Solution WithOptions(OptionSet options)
{
var newState = _state.WithOptions(options: options);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
Tagging @tmat @sharwell @jasonmalinowski @jinujoseph @vatsalyaagrawal - I would like to discuss the open design questions posted in https://github.com/dotnet/roslyn/issues/19284#issuecomment-566173145 in today's design meeting. I have a working implementation in https://github.com/dotnet/roslyn/pull/40289, but need a resolution on API design before proceeding.
@mavasani One question I do have (although you can save the answer for the design meeting rather than furiously typing it in the next 10 minutes): which compat issues have you observed while working on this? Is this a situation where we're mostly trying to predict compat issues or do we know of some breaking problems already in the wild?
Most helpful comment
Tagging @tmat @sharwell @jasonmalinowski @jinujoseph @vatsalyaagrawal - I would like to discuss the open design questions posted in https://github.com/dotnet/roslyn/issues/19284#issuecomment-566173145 in today's design meeting. I have a working implementation in https://github.com/dotnet/roslyn/pull/40289, but need a resolution on API design before proceeding.