dotnet / runtime

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

[API Proposal]: Add Message property to ExperimentalAttribute #107963

Open jeffhandley opened 1 month ago

jeffhandley commented 1 month ago

Background and motivation

With our recent adoptions of the [Experimental] attribute, we found it would have been valuable to add a custom message for the experimental APIs to add context for why the API is marked as experimental. The desired experience would be similar to that of [Obsolete] where a custom message can be provided in addition to the custom diagnostic ID.

API Proposal

  [System.AttributeUsageAttribute(System.AttributeTargets.Assembly | System.AttributeTargets.Class |
   System.AttributeTargets.Constructor | System.AttributeTargets.Delegate | System.AttributeTargets.Enum |
   System.AttributeTargets.Event | System.AttributeTargets.Field | System.AttributeTargets.Interface |
   System.AttributeTargets.Method | System.AttributeTargets.Module | System.AttributeTargets.Property |
   System.AttributeTargets.Struct, Inherited=false)]
  public sealed partial class ExperimentalAttribute : System.Attribute
  {
      public ExperimentalAttribute(string diagnosticId) { }
+     public ExperimentalAttribute(string diagnosticId, string? message) { }
      public string DiagnosticId { get { throw null; } }
+     public string? Message { get { throw null; } }
      public string? UrlFormat { get { throw null; } set { } }
  }

API Usage

namespace System.Runtime.Intrinsics;

public abstract partial class X86Base
{
    [Experimental("SYSLIB5004",  "X86Base.DivRem is experimental since performance is not as optimized as T.DivRem",
                  UrlFormat = "https://aka.ms/dotnet-warnings/{0}")]
    public static (uint Quotient, uint Remainder) DivRem(uint lower, uint upper, uint divisor) { throw null; }
    {
    }
}

Alternative Designs

Create a new mechanism for registering the messages. Or keep the status quo and rely on the linked documentation to provide fuller context.

Risks

We will need to follow the model we applied with ObsoleteAttribute where the compiler respects whichever ExperimentalAttribute is defined for the assembly using it, where that type might have Message or might not. When applying this attribute down-level to targets before Message was introduced, libraries can opt to include an internal copy of the class that includes the property so the message can be applied.

We will need to discuss the format of the compiler message, localization, and determine if the default message should be emitted in addition to the custom message, or if only the custom message would be emitted. We should be able to follow the [Obsolete] attribute behavior.

dotnet-policy-service[bot] commented 1 month ago

Tagging subscribers to this area: @dotnet/area-system-runtime See info in area-owners.md if you want to be subscribed.

jeffhandley commented 1 month ago

/cc @333fred @jaredpar

Spawned from https://github.com/dotnet/runtime/pull/107905#discussion_r1763876212 by @stephentoub

terrajobst commented 1 month ago

When I designed the attribute I considered that but ultimately decided against it:

I'd say if you want to provide additional context, use DiagnosticId and UrlFormat. Whatever this points to can be updated, include sample code, etc.

jeffhandley commented 4 weeks ago

Thanks for sharing that background, @terrajobst. I can acknowledge all those points and I agree it's an option to keep the status quo and rely on the linked documentation to provide fuller context.

With the [Experimental] attributes we've applied so far, I instinctively expected to add a Message to the attribute as it would be visible in IntelliSense rather than requiring docs to be opened and read. For the DivRem example, it would be nice for message visible inside the IDE to reference the alternate API that isn't experimental. I kind of want to argue that the message from Roslyn is "too scary," but it's probably sufficiently scary.

'{0}' is for evaluation purposes only and is subject to change or removal in future updates.

I think it's a decision of whether we want to provide API-specific, non-localizable, non-serviceable, short messages in IntelliSense, or if we want users to always have to click the link and read the localizable, serviceable, detailed docs.

terrajobst commented 3 weeks ago

Video

namespace System;

public partial class ExperimentalAttribute
{
    public string? Message { get; set; }
}
jodydonetti commented 3 days ago

As the author of the original proposal from 5 years ago (which already contained the Message property) I'd like to chip in with my 2 cents.

I understand and agree with having an external url that allows us to support full blown localization plus the ability, in the online resource, to have longer explanations, rich examples, extra info and things like images/videos.

At the same time though, from a pragmatic perspective, we should also consider other things like:

If developers want, they can always use the external url and localize everything, add images and so on.

Having said that, having an optional english-only message that can be directly used to very quickly give a minimum of context on what is going on is, again imho, a very sensible thing to do that achieves the very concrete goal of providing some context without taking anything away.

Just my 2 cents, hope this helps.

ps: just as a sidenote for future similar developments, starting again today I would have opted for the other way around, meaning mandatory message (minimum effort and concrete immediate result) + optional external url. But still, this is a very welcome and concretely useful addition.