dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.36k stars 4.74k forks source link

Runtime constants in attributes #108416

Open timcassell opened 1 month ago

timcassell commented 1 month ago

Currently, attributes only allow compile-time constants as constructor arguments/properties. If we were allowed to use runtime constants, attributes would become much more powerful.

What is considered a runtime constant? Existing consts, obviously, but also any static readonly field (which the tier-1 JIT treats as consts), as well as static pure functions and properties (e.g. IntPtr.Size), and constant expressions (e.g. `IntPtr.Size 2`).

This would enable things like lambdas in attributes (https://github.com/dotnet/csharplang/discussions/343), improved explicit layouts (https://github.com/dotnet/csharplang/discussions/4652), enabling users to do things like

https://github.com/dotnet/runtime/blob/7b1e788c34ee9a44f6dc548c170c153dd7eab559/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueueSegment.cs#L339-L344

and more.


We don't currently have a way to declare a pure function. There is an old csharplang discussion about it (https://github.com/dotnet/csharplang/discussions/776), probably it's a new thing that would require runtime validation of purity.

KalleOlaviNiemitalo commented 1 month ago

"Removing readonly from a field" would then become a breaking change, if the field is static.

timcassell commented 1 month ago

"Removing readonly from a field" would then become a breaking change, if the field is static.

Hm. Personally, I would be ok with that. But I understand how that would be troublesome for the ecosystem. What about annotating a static readonly field such that it will be considered a runtime constant, and removing that annotation will be a breaking change instead (it would be invalid to have the annotation on a non-readonly field)? A similar annotation could be used for the functions/properties as well.

KalleOlaviNiemitalo commented 1 month ago

Or perhaps it could be limited to static fields that are not accessible from outside the assembly; thus internal, private, or private protected. That way, removing readonly could not break third-party assemblies. Having to define internal static readonly IntPtrSize = IntPtr.Size; just for use in attributes would be a bit annoying though.

Then, I wonder about reflection APIs. For System.Reflection.CustomAttributeTypedArgument, it might suffice if the object? Value property just returned the runtime-constant value without indicating how it was obtained. This would not work with Assembly.ReflectionOnlyLoad (because the initialisation of the static field could not be executed) but .NET Core doesn't support that anyway. For System.Reflection.Metadata.CustomAttributeTypedArgument\<TType> though, I think there should be a way to get the expression or at least the metadata token of the field.

timcassell commented 1 month ago

Actually, that might be just fine for the runtime to only support internal static readonly fields. The C# compiler doesn't have to have the same restriction, and could synthesize the field for you.

acaly commented 1 month ago

Or perhaps it could be limited to static fields that are not accessible from outside the assembly; thus internal, private, or private protected.

IMO it's not a good idea to add new meanings (or implications) to internal and others.

The C# compiler doesn't have to have the same restriction, and could synthesize the field for you.

This doesn't solve the breaking-change problem. The compiler may try to synthesize an internal static readonly from a public static readonly in another assembly. What if that readonly is removed? This will still be a valid program, but may give unpredictable results.

timcassell commented 1 month ago

The C# compiler doesn't have to have the same restriction, and could synthesize the field for you.

This doesn't solve the breaking-change problem. The compiler may try to synthesize an internal static readonly from a public static readonly in another assembly. What if that readonly is removed? This will still be a valid program, but may give unpredictable results.

Not having the same restriction doesn't mean the compiler must have no restrictions. It could have the requirement that used fields and properties be annotated with [RuntimeConstant], for example.

Also, even if that were to happen, I don't think it's a big deal. It would still be a runtime constant, evaluated only once.

acaly commented 1 month ago

Having [RuntimeConstant] is a much better choice IMO.

The point is not to allow arbitrary existing static readonly fields to be used in attributes (although they are treated as runtime constants by the runtime). This should be an opt-in feature. Only in this way the breaking-change problem can be solved.

In this case, is it possible to reuse the const keyword in C# to allow any type, effectively making them [RuntimeConstant] static readonly? This follows the recent trend of extending or redefining existing keywords for broader scenarios.