-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Conversation
…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 |
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: 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); |
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.
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.
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.
Restricting overrides of this method is not a breaking change for this type, so if the approach works then the approach works.
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.
Agreed no concern here since this was never practically inheritable.
src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs
Show resolved
Hide resolved
...edUtilitiesAndExtensions/Compiler/Core/Options/EditorConfig/EditorConfigStorageLocation`1.cs
Show resolved
Hide resolved
public new object? GetOption(OptionKey optionKey) | ||
=> base.GetOption(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.
This may warrant a good comment here explaining why this is here as a binary back-compat member.
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 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) |
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.
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...
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 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 |
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.
Agreed no concern here since this was never practically inheritable.
@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? |
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... |
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. |
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`.
…ion value
Fixes
InvalidCastException
from #42923We already ensured that we return the correct CodeStyle option value for
T OptionSet.GetOption<T>
overloads to fetch option. However, forobject? 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 asOptionSet
already has internal abstract methods, hence cannot be sub-typed outside Roslyn. I also verified none of our IVT partners sub-typeOptionSet
. 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 invokingGetOption<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.