Skip to content

Commit b7c3e99

Browse files
committed
Reorder more sections
1 parent 692005c commit b7c3e99

File tree

1 file changed

+138
-132
lines changed

1 file changed

+138
-132
lines changed

docs/design/tools/illink/feature-check-attributes.md

Lines changed: 138 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,144 @@ interface IFeatureAttribute<TSelf> where TSelf : Attribute {
347347

348348
We could use this model even for feature switches similar to `StartupHookSupport`, which don't currently have a `StartupHoopSupportAttribute`. There's no need to actually annotate related APIs with the attribute if that is overkill for a small feature. In this case the attribute definition would just serve as metadata that allows us to define a feature switch in a uniform way.
349349

350+
This is fundamentally the same idea outlined in https://github.com/dotnet/designs/pull/261. The hope is that this document provides the motivation for using a unified representation for the attributes that are related to feature switches and trim/AOT analysis.
351+
352+
## Comparison with platform compatibility analyzer
353+
354+
The platform compatibility analyzer is semantically very similar to the behavior described here, except that it doesn't come with ILLink/ILCompiler support for removing removing branches that are unreachable when publishing for a given platform.
355+
356+
"Platforms" (instead of "features") are represented as strings, with optional versions.
357+
358+
- `SupportedOSPlatformAttribute` is similar to the `RequiresDynamicCodeAttribute`, etc, and will produce warnings if called in an unguarded context.
359+
360+
```csharp
361+
CallSomeAndroidAPI(); // warns if not targeting android
362+
363+
[SupportedOSPlatform("android")]
364+
static void CallSomeAndroidAPI() {
365+
// ...
366+
}
367+
```
368+
369+
- Platform checks are like feature switch properties, and can guard calls to annotated APIs:
370+
371+
```csharp
372+
if (OperatingSystem.IsAndroid())
373+
CallSomeAndroidAPI(); // no warning for guarded call
374+
```
375+
376+
The analyzer has built-in knowledge of fact that `IsAndroid` corresponds to the `SupportedOSPlatform("android")`. This is similar to the ILLink analyzer's current hard-coded knowledge of the fact that `IsDynamicCodeSupported` corresponds to `RequiresDynamicCodeAttribute`.
377+
378+
- Platform guards are like feature guards, allowing libraries to incroduce custom guards for existing platforms:
379+
380+
```csharp
381+
class Feature {
382+
[SupportedOSPlatformGuard("android")]
383+
public static bool IsSupported => SomeCondition() && OperatingSystem.IsAndroid();
384+
}
385+
```
386+
387+
```csharp
388+
if (Feature.IsSupported)
389+
CallSomeAndroidAPI(); // no warning for guarded call
390+
```
391+
392+
The platform compatibility analyzer also has some additional functionality, such as annotating _unsupported_ APIs, and including version numbers.
393+
394+
395+
## Possible future extensions
396+
397+
We might eventually want to extend the semantics in a few directions:
398+
399+
- Feature switches with inverted polarity (`false` means supported/available)
400+
401+
`GlobalizationMode.Invariant` is an example of this. `true` means that globalization support is not available.
402+
403+
This could be done by adding an extra boolean argument to the feature switch attribute constructor:
404+
405+
```csharp
406+
class GlobalizationMode {
407+
[FeatureSwitch("Globalization.Invariant", negativeCheck: true)]
408+
public static bool InvariantGlobalization => AppContext.TryGetSwitch("Globalization.Invariant", out bool value) ? value : false;
409+
}
410+
```
411+
412+
```csharp
413+
if (GlobaliazationMode.Invariant) {
414+
UseInvariantGlobalization();
415+
} else {
416+
UseGlobalization(); // no warning
417+
}
418+
419+
[RequiresGlobalizationSupport]
420+
static void UseGlobalization() { }
421+
```
422+
423+
- Feature guards with inverted polarity. This could work similarly to feature switches:
424+
```csharp
425+
class Feature {
426+
[FeatureGuard("RuntimeFeature.IsDynamicCodeSupported", negativeCheck: true)]
427+
public bool IsDynamicCodeUnsupported => !RuntimeFeature.IsDynamicCodeSupported;
428+
}
429+
```
430+
431+
- Feature attributes with inverted polarity
432+
433+
It would be possible to define an attribute that indicates _lack_ of support for a feature, similar to the `UnsupportedOSPlatformAttribute`. The attribute-based model should make it possible to differentiate these from the `Requires` attributes, for example with a different base class.
434+
435+
It's not clear whether we have a use case for such an attribute, so these examples aren't meant to suggest realistic names, but just the semantics:
436+
437+
```csharp
438+
class RequiresNotAttribute : Attribute {}
439+
440+
class RequiresNoDynamicCodeAttribute : RequiresNotAttribute {}
441+
```
442+
443+
- Versioning support for feature attributes/checks/guards
444+
445+
The model here would extend naturally to include support for version checks the same way that the platform compatibility analyzer does. Versions would likely be represented as strings because they are encodable in custom attributes:
446+
447+
```csharp
448+
class RequiresWithVersionAttribute : Attribute {
449+
public RequiresWithVersionAttribute(string version) {}
450+
}
451+
452+
class RequiresFooVersionAttribute : RequiresWithVersionAttribute {
453+
public RequiresFooVersionAttribute(string version) : base(version) {}
454+
}
455+
456+
class Foo {
457+
[FeatureSwitch<Requires>]
458+
public static bool IsSupportedWithVersionAtLeast(string version) => return VersionIsLessThanOrEquals(version, "2.0");
459+
460+
[RequiresFooVersion("2.0")]
461+
public static void Impl_2_0() {
462+
// Do some work
463+
}
464+
465+
[RequiresFooVersion("1.0")]
466+
public static void Impl_1_0() {
467+
// Breaking change was made in version 2.0, where this API is no longer supported.
468+
throw new NotSupportedException();
469+
}
470+
}
471+
```
472+
473+
Code that was originally built against the 1.0 version, and broken on the upgrade to the 2.0 version, could then be updated with a feature check like this:
474+
```csharp
475+
if (Foo.IsSupportedWithVersionAtLeast("2.0")) {
476+
Foo.Impl_2_0();
477+
} else {
478+
Foo.Impl_1_0();
479+
}
480+
```
481+
482+
Although it's not clear in practice where this would be useful. This is not meant as a realistic example.
483+
484+
485+
486+
<hr />
487+
350488
# Two models for custom feature checks
351489

352490
In practice, custom feature checks in dotnet/runtime broadly fall into two categories:
@@ -597,46 +735,6 @@ For example, in a trimmed app, the feature `RequiresUnreferencedCodeAttribute` i
597735
In a native AOT app, `"System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported"` is set to `false` by default because native AOT doesn't provide a JIT or interpreter, and calls to APIs annotated with `RequiresDynamicCodeAttribute` will produce warnings. There is also an independent feature switch `CanEmitObjectArrayDelegate` that is disabled by default because it depends on APIs marked `RequiresDynamicCode`.
598736

599737

600-
## Comparison with platform compatibility analyzer
601-
602-
The platform compatibility analyzer is semantically very similar to the behavior described here, except that it doesn't come with ILLink/ILCompiler support for removing removing branches that are unreachable when publishing for a given platform.
603-
604-
"Platforms" (instead of "features") are represented as strings, with optional versions.
605-
606-
- `SupportedOSPlatformAttribute` is similar to the `RequiresDynamicCodeAttribute`, etc, and will produce warnings if called in an unguarded context.
607-
608-
```csharp
609-
CallSomeAndroidAPI(); // warns if not targeting android
610-
611-
[SupportedOSPlatform("android")]
612-
static void CallSomeAndroidAPI() {
613-
// ...
614-
}
615-
```
616-
617-
- Platform checks are like feature switch properties, and can guard calls to annotated APIs:
618-
619-
```csharp
620-
if (OperatingSystem.IsAndroid())
621-
CallSomeAndroidAPI(); // no warning for guarded call
622-
```
623-
624-
The analyzer has built-in knowledge of fact that `IsAndroid` corresponds to the `SupportedOSPlatform("android")`. This is similar to the ILLink analyzer's current hard-coded knowledge of the fact that `IsDynamicCodeSupported` corresponds to `RequiresDynamicCodeAttribute`.
625-
626-
- Platform guards are like feature guards, allowing libraries to incroduce custom guards for existing platforms:
627-
628-
```csharp
629-
class Feature {
630-
[SupportedOSPlatformGuard("android")]
631-
public static bool IsSupported => SomeCondition() && OperatingSystem.IsAndroid();
632-
}
633-
```
634-
635-
```csharp
636-
if (Feature.IsSupported)
637-
CallSomeAndroidAPI(); // no warning for guarded call
638-
```
639-
640738
## Guarding features that don't have feature attributes
641739

642740
We've identified three components that a "feature" may or may not have:
@@ -742,97 +840,6 @@ It is possible via XML to define a new feature that has as string name and a che
742840

743841
Should we define an attribute-based model for defining feature switches?
744842

745-
## Extending the semantics
746-
747-
We might eventually want to extend the semantics in a few directions:
748-
749-
- Feature switches with inverted polarity (`false` means supported/available)
750-
751-
`GlobalizationMode.Invariant` is an example of this. `true` means that globalization support is not available.
752-
753-
This could be done by adding an extra boolean argument to the feature switch attribute constructor:
754-
755-
```csharp
756-
class GlobalizationMode {
757-
[FeatureSwitch("Globalization.Invariant", negativeCheck: true)]
758-
public static bool InvariantGlobalization => AppContext.TryGetSwitch("Globalization.Invariant", out bool value) ? value : false;
759-
}
760-
```
761-
762-
```csharp
763-
if (GlobaliazationMode.Invariant) {
764-
UseInvariantGlobalization();
765-
} else {
766-
UseGlobalization(); // no warning
767-
}
768-
769-
[RequiresGlobalizationSupport]
770-
static void UseGlobalization() { }
771-
```
772-
773-
- Feature guards with inverted polarity. This could work similarly to feature switches:
774-
```csharp
775-
class Feature {
776-
[FeatureGuard("RuntimeFeature.IsDynamicCodeSupported", negativeCheck: true)]
777-
public bool IsDynamicCodeUnsupported => !RuntimeFeature.IsDynamicCodeSupported;
778-
}
779-
```
780-
781-
- Feature attributes with inverted polarity
782-
783-
It would be possible to define an attribute that indicates _lack_ of support for a feature, similar to the `UnsupportedOSPlatformAttribute`. The attribute-based model should make it possible to differentiate these from the `Requires` attributes, for example with a different base class.
784-
785-
It's not clear whether we have a use case for such an attribute, so these examples aren't meant to suggest realistic names, but just the semantics:
786-
787-
```csharp
788-
class RequiresNotAttribute : Attribute {}
789-
790-
class RequiresNoDynamicCodeAttribute : RequiresNotAttribute {}
791-
```
792-
793-
- Versioning support for feature attributes/checks/guards
794-
795-
The model here would extend naturally to include support for version checks the same way that the platform compatibility analyzer does. Versions would likely be represented as strings because they are encodable in custom attributes:
796-
797-
```csharp
798-
class RequiresWithVersionAttribute : Attribute {
799-
public RequiresWithVersionAttribute(string version) {}
800-
}
801-
802-
class RequiresFooVersionAttribute : RequiresWithVersionAttribute {
803-
public RequiresFooVersionAttribute(string version) : base(version) {}
804-
}
805-
806-
class Foo {
807-
[FeatureSwitch<Requires>]
808-
public static bool IsSupportedWithVersionAtLeast(string version) => return VersionIsLessThanOrEquals(version, "2.0");
809-
810-
[RequiresFooVersion("2.0")]
811-
public static void Impl_2_0() {
812-
// Do some work
813-
}
814-
815-
[RequiresFooVersion("1.0")]
816-
public static void Impl_1_0() {
817-
// Breaking change was made in version 2.0, where this API is no longer supported.
818-
throw new NotSupportedException();
819-
}
820-
}
821-
```
822-
823-
Code that was originally built against the 1.0 version, and broken on the upgrade to the 2.0 version, could then be updated with a feature check like this:
824-
```csharp
825-
if (Foo.IsSupportedWithVersionAtLeast("2.0")) {
826-
Foo.Impl_2_0();
827-
} else {
828-
Foo.Impl_1_0();
829-
}
830-
```
831-
832-
Although it's not clear in practice where this would be useful. This is not meant as a realistic example.
833-
834-
835-
836843

837844

838845
<hr />
@@ -911,7 +918,6 @@ Do both!
911918

912919
This would allow us to get rid of ILLink.Substitutions.xml entirely.
913920

914-
915921
# Terminology
916922

917923
- "Incompatible with trimming/AOT": means it would produce trim/AOT warnings if enabled.

0 commit comments

Comments
 (0)