Skip to content
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

Merged
59 commits merged into from
Mar 24, 2020

Conversation

mavasani
Copy link
Contributor

@mavasani mavasani commented Mar 10, 2020

Strongly recommend reviewing commit by commit. I have tried to add descriptive comments to each commit for aid in review. Gist of the change:

  1. Move common Options and CodeStyle related public and internal types from the Workspaces layer into the shared layer. First commit of the PR 6692e76 has these core changes. The public types are renamed to end with a 2 suffix and are internal types, most of which have implicit conversions to the public type. For example, the shared layer has new internal types IOption2, 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.
  2. a2c78bd has very similar changes made to FormattingOptions and CSharpFormattingOptions.
  3. 3ba9025 shows how our exposed options transition to be based on these new option types.
  4. 7077431 moves our core analyzer base types to be based off these new types
  5. e182acd moves our shared analyzer layer to these types, a bunch of nice cleanup and no longer need of #if CODE_STYLE in this layer.
  6. 2143656 is similar to the previous commit, but applied to all the IDE analyzers in Features layer - very mechanical change.
  7. a9da22c adds a 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.
  8. Rest of the commits are pretty mechanical changes, though large scale that touch many files:
    1. 00b3593: Move EditorFeatures layer to use new types
    2. 6e1e5dd: Move VS layer to use new types
    3. f1b4348: CodeStyle layer test library split to avoid CodeStyle fixes test project having type ambiguity errors for shared option types from Workspaces and CodeStyle analyzer layer.
    4. e4c7e8e: Cleanup #if CODE_STYLE directive in analyzer tests in shared layer
    5. 7e3c47e: Biggest commit that moves our tests in different layers (Workspaces, Features, EditorFeatures and VS) to new options types
    6. c765e1f: Commit for all project file, resx moves and xlf file changes. Feel free to skip or skim through these.

mavasani added 22 commits March 10, 2020 10:50
…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.
…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.
… 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
{
internal interface ICodeStyleOption
Copy link
Contributor Author

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;
Copy link
Contributor Author

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
Copy link
Contributor Author

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;
Copy link
Contributor Author

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)
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

@mavasani mavasani Mar 10, 2020

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)
Copy link
Contributor Author

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>
Copy link
Contributor Author

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)
Copy link
Contributor Author

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);
Copy link
Contributor Author

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.

Copy link
Member

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?

#endif

namespace Microsoft.CodeAnalysis.CSharp.Formatting
{
public static partial class CSharpFormattingOptions
internal static partial class CSharpFormattingOptions2
Copy link
Contributor Author

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
Copy link
Contributor Author

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
Copy link
Contributor Author

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;
Copy link
Contributor Author

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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to shared layer.

@mavasani
Copy link
Contributor Author

Do we need a doc for a newbie to our repository to understand how this works? This PR does a great job using type safety and analyzers to keep some of the worst mistakes from happening, but I'd worry that the "how to use options" might not be obvious for somebody starting out, since we're more or less abandoning our public surface area. If somebody is trying to something that matches what's been done before it might be easy to just copy/paste and follow the pattern, but any "newer" code is decidedly less obvious how to write

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.

How do we test this safely? There's a number of IVT risks as well as a few places where a subtle change could pretty easily break something. Do we have a good plan in place?

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.

What's the next steps? We're still tracking thinking of possible better approaches, or there a good list of next steps? This moves us forward, but at the same time still feels like we're creating debt that needs to be cleaned up...

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.

…rvice for different combinations of our internal and public option types. Also fix the issue discovered from these tests.
@mavasani
Copy link
Contributor Author

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.

@jinujoseph jinujoseph modified the milestones: 16.6.P3, 16.6 Mar 22, 2020
@jasonmalinowski
Copy link
Member

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.

Have we at least done a smoke test with the F# and TypeScript experiences? Or want to have them do some testing too?

Comment on lines +12 to +27
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
Copy link
Member

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.

Copy link
Contributor Author

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))
Copy link
Member

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?

Copy link
Contributor Author

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.

@mavasani
Copy link
Contributor Author

Have we at least done a smoke test with the F# and TypeScript experiences? Or want to have them do some testing too?

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.

@mavasani
Copy link
Contributor Author

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.

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Auto-approval

@tmat
Copy link
Member

tmat commented Mar 24, 2020

@mavasani Check with @allisonchou, she was recently testing TypeScript IVT changes knows how to do validation.

@tmat
Copy link
Member

tmat commented Mar 24, 2020

F# does not have any IVTs anymore, except for VS layer.

@ghost ghost merged commit e16d390 into dotnet:master Mar 24, 2020
@mavasani mavasani mentioned this pull request Mar 24, 2020
@jinujoseph jinujoseph modified the milestones: 16.6, 16.6.P3 Mar 24, 2020
ghost pushed a commit that referenced this pull request Mar 24, 2020
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants