-
Couldn't load subscription status.
- Fork 5.2k
Description
Background and motivation
We've had a few times now in C# language design where the traditional method of requiring that a compiler understand a feature, a modreq, has been insufficient in one of a couple of ways:
- The context doesn't allow
modreqs to be applied, such as a type declaration. This is the case for ref structs. - The fact that
modreqs affect binary compatibility isn't desired. For example, the required members feature doesn't want to mark constructors with amodreqbecause that would break binary compat if all required members were later removed from a type, as themodreqis part of the signature.
For these cases, our strategy so far has been to use ObsoleteAttribute with a specific error message the compiler knows to ignore. This is not a great solution, however. If the user puts an obsolete attribute of their own set to warning, it overrides ours. Obsolete methods and types are also perfectly fine to use from an obsolete context. To address these deficiencies going forward, we'd like to add a new tool to our toolbox, a poison attribute of sorts, that allows the compiler to mark a type or other symbol that supports attributes as requiring an understanding of a specific feature. For equal flexibility with modopt vs modreq, I've also added an ability to say that this feature only applies to a single language, if that language wants to allow other languages free access, but prevent older versions of its own compiler from using the API without correct understanding.
The semantics of this attribute are:
- If
Languageis null, any compiler that does not understandFeatureNameis required to disallow usage of the member attributed with it. - If
Languageis not null, any compiler for that language that does not understandFeatureNameis required to disallow usage of the member attributed with it. Compilers for other languages are free to ignore the attribute.
API Proposal
namespace System.Runtime.CompilerServices
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
FeatureName = featureName;
}
public string FeatureName { get; }
public string? Language { get; init; }
}
}API Usage
ref struct S {}will be emitted as
[CompilerFeatureRequired("ref-struct")]
struct S {}Alternative Designs
We might also consider putting a list of features into the runtime, but that runs the risk of the runtime effectively becoming an arbiter of language features: if some third-party .NET language wanted to add their own strings, what would the process for that be?
Risks
It is important to note that this enforcement mechanism will only work going forward. Older compilers are going to have no idea that this is a thing and not block access. That being said, if we don't add something like this now, we'll never get to a point where we can solely rely on this attribute as the enforcement mechanism.