Skip to content

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

@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

No fields configured for Bug.

Projects

Status

In Progress

Relationships

None yet

Development

No branches or pull requests

Issue actions