fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
340 stars 21 forks source link

Support using `nameof` to generate literal strings (specifically in Attribute parameter lists). #1348

Open baronfel opened 4 months ago

baronfel commented 4 months ago

I propose we allow the nameof function to work at compile time, specifically in attribute parameter expressions like the following:

/// Represents information about programming constructs like variables, classes,
/// interfaces etc.
type SymbolInformation =
  { 
    ....
    /// Tags for this symbol.
    Tags: SymbolTag [] option

    /// Indicates if this symbol is deprecated.
    /// @deprecated Use tags instead
    [<Obsolete("Use "+ nameof(Unchecked.defaultof<SymbolInformation>.Tags) + " instead")>]
    Deprecated: bool option
    ....
}

The existing way of approaching this problem in F# is to hard code member names in the strings, leaving them prone to drift out of date.

Pros and Cons

The advantages of making this adjustment to F# are more uniform behavior of the nameof helper.

The disadvantages of making this adjustment to F# are expansion of the set of data that is computable at compile time, user confusion around when certain functions can be used.

Extra information

Estimated cost (XS, S, M, L, XL, XXL): M

Related suggestions: https://github.com/fsharp/fslang-suggestions/issues/1347

Affidavit (please submit!)

Please tick these items by placing a cross in the box:

Please tick all that apply:

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

Tarmil commented 4 months ago

Interestingly, the following works:

    [<Obsolete("Use " + nameof SymbolInformation + " instead")>]

So my impression is that this is actually a bug when handling more complex content in nameof in attributes.

baronfel commented 4 months ago

Interesting catch - I was specifically trying to mimic some LSP Spec documentation that referred to another member, so maybe that means this is a much smaller request - extending the existing nameof replacement logic to recognize the Unchecked.defaultof<T>.member pattern?

brianrourkeboll commented 4 months ago
/// Represents information about programming constructs like variables, classes,
/// interfaces etc.
type SymbolInformation =
  { 
    ....
    /// Tags for this symbol.
    Tags: SymbolTag [] option

    /// Indicates if this symbol is deprecated.
    /// @deprecated Use tags instead
    [<Obsolete("Use "+ nameof(Unchecked.defaultof<SymbolInformation>.Tags) + " instead")>]
    Deprecated: bool option
    ....
}

I think it's actually just that the name resolution rules don't allow referring to the field name Tags in that context (in an attribute on another field in the same record type).

The rules for regular class members (as opposed to record fields) are a little bit more flexible, because members are already allowed to refer to each other or themselves in their implementations, and the following examples compile:

type SymbolInformation =
  { 
    /// Tags for this symbol.
    Tags: SymbolTag [] option
  }

  /// Indicates if this symbol is deprecated.
  /// @deprecated Use tags instead
  [<Obsolete("Use "+ nameof(Unchecked.defaultof<SymbolInformation>.Tags) + " instead")>]
  member _.Deprecated = Some true
[<AbstractClass>]
type SymbolInformation () =
    /// Tags for this symbol.
    abstract Tags: SymbolTag [] option

    /// Indicates if this symbol is deprecated.
    /// @deprecated Use tags instead
    [<Obsolete("Use "+ nameof(Unchecked.defaultof<SymbolInformation>.Tags) + " instead")>]
    abstract Deprecated: bool option