-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move Options and CodeStyle APIs to shared layer #42323
Conversation
…nto the shared layer. The public types are renamed to end with a "2" suffix and are internal. These will be the core types that will replace the public Options types to avoid name clashes.
…ce and option set
…core impl types in shared layer with "2" suffix and shim public API types in Workspaces layer.
…ions to the shared layer, and I will not be doing it in this PR. That will be in a follow-up PR.
…d type. Also update options related helpers in shared analyzer layer.
…tyle types - this cleans up the `#if CODE_STYLE` mess from our shared layer analyzers.
… public CodeStyle and Options related types in Features layer. This should keep our IDE layer clean and onto a single Options and CodeStyle API.
…ons to shared layer.
… legacy test framework and the new test framework. This was needed to avoid type ambiguity from same internal options types from Workspaces and CodeStyle. Also added a bunch of test helper methods for new option types and ensured that we serialize naming styles in both tests frameworks
…layer. Should be much cleaner now.
{ | ||
internal interface ICodeStyleOption |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to shared layer
public class CodeStyleOption<T> : ICodeStyleOption, IEquatable<CodeStyleOption<T>> | ||
{ | ||
private readonly CodeStyleOption2<T> _codeStyleOptionImpl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the existing code from this type was copied directly into CodeStyleOption2<T>
being added in shared layer. This public type now just has a handle to the instance of CodeStyleOption2<T>
that has all the implementation. Very similar approach has been used for rest of the public types in this PR.
{ | ||
/// <inheritdoc cref="CodeStyleOptions2"/> | ||
public class CodeStyleOptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type now just has simple public fields that forward to CodeStyleOptions2
. All the deleted code below is just moved to CodeStyleOptions2
without any semantic changes.
public class NotificationOption | ||
{ | ||
public string Name { get; set; } | ||
private readonly NotificationOption2 _notificationOptionImpl; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now delegates to internal shared layer type NotificationOption2
.
@@ -90,119 +86,5 @@ string GetEditorConfigString(IOption option, IEditorConfigStorageLocation2 edito | |||
return editorConfigString; | |||
} | |||
} | |||
|
|||
public static void AppendNamingStylePreferencesToEditorConfig(NamingStylePreferences namingStylePreferences, string language, StringBuilder editorconfig) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The deleted code here is just moved to a separate partial declaration down in the shared layer. Unfortunately, I missed including that new file in this commit - it is in one of the following commits, without any semantic changes.
/// <summary> | ||
/// Marker interface for language specific options. | ||
/// </summary> | ||
internal interface ILanguageSpecificOption : IOptionWithGroup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an existing type just moved to shared layer.
/// <summary> | ||
/// Marker interface for language specific options. | ||
/// </summary> | ||
internal interface ILanguageSpecificOption<T> : ILanguageSpecificOption |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an new interface type added to allow being used as a common base interface for Option<T>
and Option2<T>
, so certain APIs can directly just use ILanguageSpecificOption<T>
as a parameter rather then have two similar overloads. This is not always feasible though so is not used everywhere.
} | ||
|
||
#if !CODE_STYLE | ||
public static implicit operator Option<T>(Option2<T> option) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New code which adds a conversion from internal type to public type.
namespace Microsoft.CodeAnalysis.Options | ||
{ | ||
[NonDefaultable] | ||
internal readonly struct OptionKey2 : IEquatable<OptionKey2> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a clone of OptionKey
as the code is very trivial. I have added implicit conversion to public type at the bottom of the file.
} | ||
|
||
#if !CODE_STYLE | ||
public static implicit operator OptionKey(OptionKey2 optionKey) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the reverse conversion is not possible because OptionKey.Option
has type IOption
, which can be any third party provided implementation of IOption
that cannot be represented by OptionKey2
/// Gets the current value of the specific option. | ||
/// </summary> | ||
[return: MaybeNull] | ||
T GetOption<T>(Option2<T> option); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that instead of adding a new overload for Option2<T>
and PerLanguageOption2<T>
, I tried to just have single overloads for ILanguageSpecificOption<T>
and IPerLanguageOption<T>
, but that caused overload resolution issues as we also need an overload that takes OptionKey
, which has implicit conversions from each of the option types and so there is no best match implicit conversion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment elsewhere: can we just have an extension method for this so we don't have to push the implementation everywhere?
src/Workspaces/CSharp/Portable/Formatting/CSharpFormattingOptions.cs
Outdated
Show resolved
Hide resolved
#endif | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.Formatting | ||
{ | ||
public static partial class CSharpFormattingOptions | ||
internal static partial class CSharpFormattingOptions2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally, one change that GitHub smartly recognized as file move + type rename instead of showing a new file addition!
@@ -419,7 +417,11 @@ static CSharpFormattingOptions() | |||
} | |||
} | |||
|
|||
#if CODE_STYLE | |||
internal enum LabelPositionOptions | |||
#else | |||
public enum LabelPositionOptions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not clone public enum types such as these into shared layer, as that is too much pain, especially to ensure the fields and values are always in sync. I think we should be fine for enums with this approach.
public static partial class FormattingOptions | ||
#endif | ||
{ | ||
public enum IndentStyle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracted into its own partial declaration - retained as a public type in workspaces, and internal enum in code style layer.
#if CODE_STYLE | ||
using Microsoft.CodeAnalysis.Internal.Options; | ||
using TOption = Microsoft.CodeAnalysis.Options.IOption2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A rare case where #if CODE_STYLE
in a helper methods file is helping avoid duplicate code, which needs to operate on core fields of IOption
and IOptions
.
@@ -176,30 +175,5 @@ private static string GetAssemblyQualifiedName(Type type) | |||
|
|||
return builder.ToImmutable(); | |||
} | |||
|
|||
public static NotificationOption ToNotificationOption(this ReportDiagnostic reportDiagnostic, DiagnosticSeverity defaultSeverity) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved to shared layer.
In theory, all of our code base should continue working on either set of types - outside of the shared layer, using either of these types should not cause any issues. I completely get your point though that is indeed adding an extra layer of confusion because you now have two available similar looking types to choose from. Let this change bake in for a week or so and I can ask the team how often they are hitting this hurdle and the common pain points and confusions, so the doc has appropriate content. In meantime, hopefully we can convince the compiler team to move Options down + update CodeStyle layer to newer Microsoft.CodeAnalysis, and I would be extremely happy to just delete the internal types added here and avoid anyone going through the pain of reading through this stuff.
Our existing unit tests caught quite a bit of stuff during the implementation to give me enough confidence, but I agree that there can be things missed out, so we would have to just keep an eye on bugs from dogfooding. Regarding future IVT risks, we should encourage everyone to move the external access layer for internal APIs being used - I was safely able to avoid any breaking changes for F# usage as they follow this model very nicely, other teams should follow suite if they wish to be resistant to such breaks.
I am going to file a separate follow-up issue with suitable context for compiler team for possibility of moving Options down to compiler layer (unless @sharwell already filed in the past which we can re-open). If that does not fly, we should consider your suggestion of packaging Workspaces assembly somehow with the NuGet package. I do feel the latter is quite an inferior solution due to the large number of compat/versioning/appdomain issues that come along with it, so hopefully it doesn't come down to it. |
...ditorFeatures/DiagnosticsTestUtilities/CodeActions/AbstractCodeActionOrUserDiagnosticTest.cs
Show resolved
Hide resolved
.../VisualBasicTest/Diagnostics/AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest.vb
Show resolved
Hide resolved
src/Workspaces/Core/Portable/ExternalAccess/Pythia/Api/PythiaOptions.cs
Outdated
Show resolved
Hide resolved
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Options/IOption2.cs
Outdated
Show resolved
Hide resolved
…rvice for different combinations of our internal and public option types. Also fix the issue discovered from these tests.
With @sharwell's help, I have added a matrix of tests for option setter/getter APIs, which hopefully increases our confidence on this PR, especially in terms of invalid cast exception failures. |
Have we at least done a smoke test with the F# and TypeScript experiences? Or want to have them do some testing too? |
internal interface IOption2 : IEquatable<IOption2?> | ||
#if !CODE_STYLE | ||
, IOption | ||
#endif | ||
{ | ||
OptionDefinition OptionDefinition { get; } | ||
|
||
#if CODE_STYLE | ||
string Feature { get; } | ||
string Name { get; } | ||
Type Type { get; } | ||
object? DefaultValue { get; } | ||
bool IsPerLanguage { get; } | ||
|
||
ImmutableArray<OptionStorageLocation2> StorageLocations { get; } | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file seems sufficiently magic as to need some comment in it somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will do in a follow-up PR to avoid re-running all the tests and risking another merge conflict resolution round :-). Hope that is fine.
hash = unchecked((hash * (int)0xA5555529) + this.Name.GetHashCode()); | ||
hash = unchecked((hash * (int)0xA5555529) + this.IsPerLanguage.GetHashCode()); | ||
|
||
if (!(this.DefaultValue is ICodeStyleOption)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the exception for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These will be different between CodeStyleOption and CodeStyleOption2. Theoretically, I think we don't even need to check for default value when checking equatability of options - I don't believe anyone will or should define options with same name, feature name and group, but just different default values. We can clean this up to remove the default value check even for non-code style options in future.
I am pretty confident on F# side due to them having defined all the options they access in their external access layer. I will verify the TS experience locally. |
I am going to send a follow-up PR to address some pending feedback, but am going to merge this one to avoid further merge conflicts from further delay as this PR touches lot of files... Thanks @sharwell and @jasonmalinowski for your help here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auto-approval
@mavasani Check with @allisonchou, she was recently testing TypeScript IVT changes knows how to do validation. |
F# does not have any IVTs anymore, except for VS layer. |
Strongly recommend reviewing commit by commit. I have tried to add descriptive comments to each commit for aid in review. Gist of the change:
2
suffix and are internal types, most of which have implicit conversions to the public type. For example, the shared layer has new internal typesIOption2
,Option2<T>
,PerLanguageOption2<T>
,CodeStyleOption<2>
,CodeStyleOptions2
and so on. These are the core types that will replace the public Options types in our shared analyzer and fixer layer, and possibly even all of Roslyn IDE, to avoid name clashes. The public types are now just thin shims that are forwarders to their internal counterparts and primarily for external consumption.FormattingOptions
andCSharpFormattingOptions
.#if CODE_STYLE
in this layer.BannedSymbols.txt
to Features layer to prevent accidental use of public CodeStyle and Options related types from Workspaces in analyzer/fixer layer. This should keep our IDE layer clean and stick to a single Options and CodeStyle API - you will get a warning if you use the wrong types in these layers.#if CODE_STYLE
directive in analyzer tests in shared layer