-
Notifications
You must be signed in to change notification settings - Fork 164
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
Add feature switch attribute design doc #305
base: main
Are you sure you want to change the base?
Conversation
|
||
- Feature attribute schemas | ||
|
||
We could consider unifying this model with the platform compatibility analyzer. One difference is that the `SupportedOSPlatformAttribute` takes a string indicating the platform name. We would likely need to extend the understanding of feature attributes to support treating "features" differently based on this string, effectively supporting feature attributes which define not a single feature, but a schema that allows representing a class of features. For example: |
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.
one slight complication with this is that we have some more complex logic in the platform compatibility checks, e.g. we know that MacCatalyst is a subset of iOS so checking OperatingSystem.IsIOS()
is enough to also check for MacCatalyst.
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 platform subsets (versions aside) are semantically almost the same idea as feature guards - IsIOS
is annotated with SupportedOSPlatformGuard("maccatalyst")
which effectively declares that iOS is a subset of MacCatalyst.
Co-authored-by: Alexander Köplinger <[email protected]>
Co-authored-by: Vitek Karas <[email protected]>
- Move IsDynamicCodeCompiled goal down - Mention challenge with arbitrary feature analysis - Add future extension of validating FeatureSwitch
- Clarify that SomeOtherCondition isn't a FeatureGuard
|
||
We will focus initially on a model where feature switches are booleans that return `true` if a feature is enabled. We aren't considering supporting version checks, or feature switches of the opposite polarity (where `true` means a feature is disabled/unsupported). We will consider what this might look like just enough to gain confidence that our model could be extended to support these cases in the future, but won't design this fully in the first iteration. | ||
|
||
## Feature guard attribute |
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.
It would help if you could list all the features you'll want to guard today. The only examples that currently exist seem to be around dynamic code. That doesn't seem to be too compelling, especially because those already have attributes.
Also, do you imagine that users can extend the set of features? Or are the features being guarded a closed set that is defined by the .NET platform?
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 added a section with example use cases.
Also, do you imagine that users can extend the set of features?
Yes, with some limitations. I'm suggesting that users should be able to define:
- guards for existing features supported by the trim analyzer
- new feature switches with trimming support (but not analyzer support, initially)
So the initial implementation would allow defining guards for RequiresDynamicCodesAttribute
and RequiresUnreferencedCodeAttribute
, and defining new features with trimming/AOT branch removal support but no analyzer warnings.
But the two seem naturally related, so my goal is for the existing analysis attributes to be defined in terms of a more general model, whether or not we actually teach the analyzer to produce warnings for arbitrary feature attributes.
|
||
```csharp | ||
class Feature { | ||
[FeatureGuard(typeof(RequiresDynamicCodeAttribute))] |
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.
Using a type seems unfortunate because that would require on attribute per feature and would disallow more complex shapes (eg an attribute with flags).
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 mentioned under "Feature attribute schemas" in the possible future extensions. I expanded a bit on the example to show what it might look like to define a feature switch where the feature attribute takes extra parameters.
I don't think we're going to be able to cover every possible attribute shape, so my goal is to start with a restricted model, while showing that it can be extended in various directions. I'm not attached to this particular API shape, but defining features in terms of types seemed like a pretty natural starting point. Happy to take other suggestions.
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 "Feature switches without feature attributes" has my favorite alternative so far - basically using the feature name string as the uniform representation.
Co-authored-by: Immo Landwerth <[email protected]>
- Move IsDynamicCodeCompiled goal down - Mention challenge with arbitrary feature analysis - Add future extension of validating FeatureSwitch
Personally, I think it makes the most sense to have a single identifier to associate guards, switches, and requirements to a feature. To me, using the feature name everywhere rather than an attribute makes the most sense, and I don't think we'd get an issue with correlation in the analyzer, trimmer, or Native AOT if we can change the existing Requires attributes. I think this generalizes well to other feature switches, and fits well with The trimmer and Native AOT could hard code the (new) "UnreferencedCode" and "DynamicCode" feature names as I also like the proposed class RequiresFeatureAttribute : Attribute
{
public string FeatureName { get; }
public NamedFeatureAttribute(string name) => FeatureName = name;
} // [FeatureName("MyLibrary.Features.MyFeature")]
// class RequiresMyFeature : RequiresFeatureAttribute { }
class RequiresMyFeature : RequiresFeatureAttribute
{
public RequiresMyFeature() : base("MyLibrary.Features.MyFeature") { }
}
class Features
{
[FeatureSwitch("MyLibrary.Features.MyFeature")]
public static bool MyFeature => AppContext.TryGetSwitch("MyLibrary.Features.MyFeature" out bool val) && val;
[FeatureSwitch("MyLibrary.Features.MyOtherFeature")]
public static bool MyOtherFeature => AppContext.TryGetSwitch("MyLibrary.Features.MyOtherFeature" out bool val) && val;
[FeatureGuard("MyLibrary.Features.MyOtherFeature")]
[FeatureGuard("MyLibrary.Features.MyFeature")]
public static bool BothFeatures => MyFeature && MyOtherFeature;
}
public class Program
{
[RequiresMyFeature]
public static void MyFeatureMethod() { }
[Requires("MyLibrary.Features.MyOtherFeature")]
public static void MyOtherFeatureMethod() { }
} |
Thanks @jtschuster, I incorporated those suggestions into the "Feature switches without feature attributes" section. I think the main tradeoff is that the string-based model requires giving a string name to every feature (which we may or may not want for |
[FeatureSwitch("MY_LIBRARY_FEATURE")] | ||
public static bool IsSupported => // ... | ||
|
||
[IfDefined("MY_LIBRARY_FEATURE")] |
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 had to resist calling this UnconditionalConditionalAttribute
😅
I'm going to play devil's advocate and only provide negative feedback ;-) For the proposal from Jackson - feature name model:
For the attribute based model:
If I had to pick one of these two, I would prefer the string based model. I honestly like the "Separate type to define the feature" more... but neither is perfect :-) |
To allow making string argument optional in the future
Per today's discussion:
|
- Add new examples for FeatureDependsOn - Update section about relationship between feature checks and guards - Update API shape with API review feedback - Update references to FeatureGuard - Remove implementation notes that were hard to keep updated
3841211
to
ab72f73
Compare
Adds a design for an attribute-based model for feature switches, to be supported in the trim/AOT analyzers and in ILLink/ILCompiler. There's significant overlap with @terrajobst's capability API analyzer draft in #261, but this approaches the problem more specifically with trimming support in mind.