Skip to content

No diagnostic when custom attributes ignored on unparenthesized tuple type #462

Open
@bbenoist

Description

@bbenoist

Hello,

After hours struggling to retrieve the values of some custom attributes assigned to an F# method return type annotation, I have found an unexpected behavior and would like to know why it did not worked as I thought.

Suppose that you have an F# method which returns a tuple value:

type HelloTupleWithoutParentheses =
    static member Format name : string * string =
            ("Hello " + name + "!", "Goodbye " + name + "!")

Then, you code a custom attribute containing a string describing each tuple field:

[<AttributeUsage(AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)>]
type ReturnParameterDescriptionAttribute(info:string) =
    inherit Attribute()
    member x.Info = info

If you apply it to the Format method:

type HelloTupleWithoutParentheses =
    static member Format name :
        [<ReturnParameterDescription("A string containing 'Hello <name>!'.")>]
        [<ReturnParameterDescription("A string containing 'Goodbye <name>!'.")>]
        string * string =
            ("Hello " + name + "!", "Goodbye " + name + "!")

The following reflection will not return anything:

let methodInfo = typeof<HelloTupleWithoutParentheses>.GetMethod("Format")
methodInfo.ReturnParameter.GetCustomAttributes(typeof<ReturnParameterDescriptionAttribute>, false)

Whereas it works correctly when the tuple type annotation is surrounded by parentheses:

type HelloTupleWithParentheses =
    static member Format name :
        [<ReturnParameterDescription("A string containing 'Hello <name>!'.")>]
        [<ReturnParameterDescription("A string containing 'Goodbye <name>!'.")>]
        (string * string) =
            ("Hello " + name + "!", "Goodbye " + name + "!")

Does anyone knows why the first attempt does not returns the custom attributes?
Am I missing something? Is it a parser bug?

Full example of my problem (try it in your web browser):

open System

[<AttributeUsage(AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)>]
type ReturnParameterDescriptionAttribute(info:string) =
    inherit Attribute()
    member x.Info = info

type HelloSimple =
    static member Format name :
        [<ReturnParameterDescription("A string containing 'Hello <name>!'.")>]
        string =
            "Hello " + name + "!"

type HelloTupleWithParentheses =
    static member Format name :
        [<ReturnParameterDescription("A string containing 'Hello <name>!'.")>]
        [<ReturnParameterDescription("A string containing 'Goodbye <name>!'.")>]
        (string * string) =
            ("Hello " + name + "!", "Goodbye " + name + "!")

type HelloTupleWithoutParentheses =
    static member Format name :
        [<ReturnParameterDescription("A string containing 'Hello <name>!'.")>]
        [<ReturnParameterDescription("A string containing 'Goodbye <name>!'.")>]
        string * string =
            ("Hello " + name + "!", "Goodbye " + name + "!")

// Print the descriptions for each implementation.
[| typeof<HelloSimple>; typeof<HelloTupleWithParentheses>; typeof<HelloTupleWithoutParentheses> |]
|> Array.map (fun t ->
        printfn "--- %s ---" t.Name
        t.GetMethod("Format").ReturnParameter.GetCustomAttributes(typeof<ReturnParameterDescriptionAttribute>, false)
        |> Array.map (fun attr -> (attr :?> ReturnParameterDescriptionAttribute).Info |> printfn "%s")
    )

Metadata

Metadata

Assignees

Labels

Area-Compiler-Syntaxlexfilter, indentation and parsingBugImpact-Low(Internal MS Team use only) Describes an issue with limited impact on existing code.

Type

Projects

Status

In Progress

Relationships

None yet

Development

No branches or pull requests

Issue actions