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

Ensure that the public GetOption API returns the public CodeStyle opt… #42931

Merged
merged 8 commits into from
Apr 1, 2020

Conversation

mavasani
Copy link
Contributor

@mavasani mavasani commented Mar 31, 2020

…ion value

Fixes InvalidCastException from #42923
We already ensured that we return the correct CodeStyle option value for T OptionSet.GetOption<T> overloads to fetch option. However, for object? OptionSet.GetOption(OptionKey), we did not ensure that the public CodeStyleOption is returned, which could lead to a cast exception if an external consumer tries to cast the return value to CodeStyleOption.

NOTE: Even though theoretically this change introduces a breaking change for all public sub-types of OptionSet by making an abstract public method a non-abstract method, it is not really a breaking change as OptionSet already has internal abstract methods, hence cannot be sub-typed outside Roslyn. I also verified none of our IVT partners sub-type OptionSet. Another possible approach here is to revert the abstract to non-abstract breaking API change, and call out (T)GetOption(optionKey) as an unsupported/deprecated operation in favor of invoking GetOption<T>(optionKey), which provides type safe access to option value. Former was relying on internal implementation details, which were never part of the Options API contract.

Verified the repro case + also unit test failure for the modified test before the fix.

…ion value

Fixes dotnet#42923
We already ensured that we return the correct CodeStyle option value for `T OptionSet.GetOption<T>` overloads to fetch option. However, for `object? OptionSet.GetOption(OptionKey)`, we did not ensure that the public CodeStyleOption is returned, which could lead to a cast exception if an external consumer tries to cast the return value to CodeStyleOption.

NOTE: Even though theoretically this change introduces a breaking change for all public sub-types of `OptionSet` by making an abstract public method a non-abstract method, it is not really a breaking change as `OptionSet` already has internal abstract methods, hence cannot be sub-typed outside Roslyn. I also verified none of our IVT partners sub-type `OptionSet`.

Verified the repro case + also unit test failure for the modified test before the fix.
… for scenarios where caller wants a typed option value. Using `(T)GetOption(optionKey)` still works for public CodeStyleOption type, but is fragile approach that relied on internal implementation details. It should be discouraged in favor of `GetOption<T>(optionKey)`.
@@ -3,6 +3,10 @@ Microsoft.CodeAnalysis.Editing.DeclarationModifiers.IsVolatile.get -> bool
Microsoft.CodeAnalysis.Editing.DeclarationModifiers.WithIsExtern(bool isExtern) -> Microsoft.CodeAnalysis.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Editing.DeclarationModifiers.WithIsVolatile(bool isVolatile) -> Microsoft.CodeAnalysis.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Editing.SyntaxGenerator.ElementBindingExpression(params Microsoft.CodeAnalysis.SyntaxNode[] arguments) -> Microsoft.CodeAnalysis.SyntaxNode
Microsoft.CodeAnalysis.Options.OptionSet.GetOption(Microsoft.CodeAnalysis.Options.OptionKey optionKey) -> object
*REMOVED*override Microsoft.CodeAnalysis.Options.DocumentOptionSet.GetOption(Microsoft.CodeAnalysis.Options.OptionKey optionKey) -> object
*REMOVED*abstract Microsoft.CodeAnalysis.Options.OptionSet.GetOption(Microsoft.CodeAnalysis.Options.OptionKey optionKey) -> object
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: Even though theoretically this change introduces a breaking change for all public sub-types of OptionSet by making an abstract public method a non-abstract method, it is not really a breaking change as OptionSet already has internal abstract methods, hence cannot be sub-typed outside Roslyn. I also verified none of our IVT partners sub-type OptionSet.

internal abstract IEnumerable<OptionKey> GetChangedOptions(OptionSet optionSet);

Copy link
Contributor Author

@mavasani mavasani Mar 31, 2020

Choose a reason for hiding this comment

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

Another possible option here is to revert the abstract to non-abstract breaking API change, and call out (T)GetOption(optionKey) as an unsupported/deprecated operation in favor of invoking GetOption<T>(optionKey), which provides type safe access to option value. Former was relying on internal implementation details, which were never part of the Options API contract.

Copy link
Member

Choose a reason for hiding this comment

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

Restricting overrides of this method is not a breaking change for this type, so if the approach works then the approach works.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed no concern here since this was never practically inheritable.

@mavasani mavasani marked this pull request as ready for review March 31, 2020 17:27
@mavasani mavasani requested a review from a team as a code owner March 31, 2020 17:27
@jinujoseph jinujoseph modified the milestones: 16.6, 16.6.P3 Mar 31, 2020
Comment on lines +32 to +33
public new object? GetOption(OptionKey optionKey)
=> base.GetOption(optionKey);
Copy link
Member

Choose a reason for hiding this comment

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

This may warrant a good comment here explaining why this is here as a binary back-compat member.

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 add in a follow-up PR.

@@ -36,5 +36,16 @@ public static T GetOption<T>(OptionKey optionKey, Func<OptionKey, object?> getOp

return (T)value!;
}

public static object? GetPublicOption(OptionKey optionKey, Func<OptionKey, object?> getOption)
Copy link
Member

Choose a reason for hiding this comment

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

Should this be called GetOption to match the GetOption<> overload above? Or name the other one? The core code in OptionSet.cs threw me off in that one called "GetPublicOption" and the rest didn't and that seemed like an oversight...

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 rename and doc in a follow-up PR.

@@ -3,6 +3,10 @@ Microsoft.CodeAnalysis.Editing.DeclarationModifiers.IsVolatile.get -> bool
Microsoft.CodeAnalysis.Editing.DeclarationModifiers.WithIsExtern(bool isExtern) -> Microsoft.CodeAnalysis.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Editing.DeclarationModifiers.WithIsVolatile(bool isVolatile) -> Microsoft.CodeAnalysis.Editing.DeclarationModifiers
Microsoft.CodeAnalysis.Editing.SyntaxGenerator.ElementBindingExpression(params Microsoft.CodeAnalysis.SyntaxNode[] arguments) -> Microsoft.CodeAnalysis.SyntaxNode
Microsoft.CodeAnalysis.Options.OptionSet.GetOption(Microsoft.CodeAnalysis.Options.OptionKey optionKey) -> object
*REMOVED*override Microsoft.CodeAnalysis.Options.DocumentOptionSet.GetOption(Microsoft.CodeAnalysis.Options.OptionKey optionKey) -> object
*REMOVED*abstract Microsoft.CodeAnalysis.Options.OptionSet.GetOption(Microsoft.CodeAnalysis.Options.OptionKey optionKey) -> object
Copy link
Member

Choose a reason for hiding this comment

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

Agreed no concern here since this was never practically inheritable.

@mavasani mavasani merged commit a376b3e into dotnet:master Apr 1, 2020
@ghost ghost modified the milestones: 16.6.P3, Next Apr 1, 2020
@mavasani mavasani deleted the FixOptionCastException branch April 1, 2020 00:19
@sharwell sharwell modified the milestones: Next, temp, 16.6.P3 Apr 6, 2020
@sandyarmstrong
Copy link
Member

@mavasani you said "I also verified none of our IVT partners sub-type OptionSet", but somehow VSmac got missed in this verification. Is there a list we need to get on?

@mavasani
Copy link
Contributor Author

mavasani commented Apr 8, 2020

I looked at the internal VSO sources. Where is your repo? So you guys sub-type OptionSet? I am not sure that was intended to be a supported scenario...

@sandyarmstrong
Copy link
Member

I'll ping you with the URL. I understand that internal API usage is not supported but when I saw the comment about verification I just wanted to make sure VSmac was taken into account in the future. This API break is not a big deal for us, so no worries on that account.

mavasani added a commit to mavasani/roslyn that referenced this pull request Apr 16, 2020
dotnet#42931 added this API as we thought converting the base `OptionSet` type's abstract method to non-abstract and removing the override on `DocumentOptionSet` would be a binary breaking change for callers. However, based on the public API review meeting, it was identified that it would indeed not be a breaking change as the compiler would have emitted a callvirt to `OptionSet.GetOption`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants