Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
bbenoist opened this issue May 21, 2015 · 5 comments
Open
Labels
Area-Compiler-Syntax lexfilter, indentation and parsing Bug Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Milestone

Comments

@bbenoist
Copy link

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")
    )
@latkin
Copy link
Contributor

latkin commented May 21, 2015

Bringing over some context from original issue at fsharp/fsharp:

@latkin said:
This looks like a parser bug. Maybe the parens are required, but if so you should get an error telling you something's wrong. As it stands your attributes are just silently ignored - they don't appear anywhere in the generated IL.

@bbenoist said:
The generated IL displayed by dotnetfiddle does not seems to include the attributes in any of the cases presented here. I also tested with this C# code and got the same results. Where is it supposed to be?

When you add parens, the attributes do appear. If you open your assembly with ILDASM you will see the attributes created in the method if you use parens:

.method public static class [mscorlib]System.Tuple`2<string,string> 
        Format(string name) cil managed
{
  .param [0]
  .custom instance void Test.ReturnParameterDescriptionAttribute::.ctor(string) = ( 01 00 24 41 20 73 74 72 69 6E 67 20 63 6F 6E 74   // ..$A string cont
                                                                                    61 69 6E 69 6E 67 20 27 48 65 6C 6C 6F 20 3C 6E   // aining 'Hello <n
                                                                                    61 6D 65 3E 21 27 2E 00 00 )                      // ame>!'...
  .custom instance void Test.ReturnParameterDescriptionAttribute::.ctor(string) = ( 01 00 26 41 20 73 74 72 69 6E 67 20 63 6F 6E 74   // ..&A string cont
                                                                                    61 69 6E 69 6E 67 20 27 47 6F 6F 64 62 79 65 20   // aining 'Goodbye 
                                                                                    3C 6E 61 6D 65 3E 21 27 2E 00 00 )                // <name>!'...

See http://stackoverflow.com/questions/30085515/getting-attribute-data-for-return-value-of-net-method for info on how to grab them via reflection.

@bbenoist
Copy link
Author

When you add parens, the attributes do appear. If you open your assembly with ILDASM you will see the attributes created in the method if you use parens.

OK, I will not trust the dotnetfiddle IL anymore 😞

See http://stackoverflow.com/questions/30085515/getting-attribute-data-for-return-value-of-net-method for info on how to grab them via reflection.

Thanks for pointing to an example including the MemberInfo.GetCustomAttributesData method. I was not yet aware of the differences with the MemberInfo.GetCustomAttributes method.

@latkin
Copy link
Contributor

latkin commented May 22, 2015

@bbenoist I typically use ILSpy, which seems to have the same problem. It's smart enough to show the attributes in the "C# View", yet doesn't show them in the "IL View". ILDasm is very clunky, but at least it's accurate.

@KevinRansom KevinRansom removed the pri-2 label Dec 4, 2015
@dsyme dsyme added Area-Compiler Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code. labels Jan 9, 2016
@cartermp cartermp added this to the Unknown milestone Aug 25, 2018
@cartermp cartermp modified the milestones: Unknown / not bug, Backlog May 23, 2019
@dsyme dsyme added Area-Compiler-Syntax lexfilter, indentation and parsing and removed Area-Compiler labels Mar 31, 2022
@dsyme dsyme changed the title Custom attributes on a method return type are not reflected when a tuple type annotation is not surrounded by parentheses No diagnostic when custom attributes ignored on unparenthesized tuple type Mar 31, 2022
@vzarytovskii vzarytovskii moved this to Not Planned in F# Compiler and Tooling Jun 17, 2022
@vzarytovskii vzarytovskii reopened this Jan 5, 2024
@github-project-automation github-project-automation bot moved this from Done to In Progress in F# Compiler and Tooling Jan 5, 2024
@edgarfgp
Copy link
Contributor

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

@brianrourkeboll Was wondering if wrapping the return type in extra parentheses during type checking when is a struct similar to your approach in 17017 would work here.

@brianrourkeboll
Copy link
Contributor

I wonder if it's a parsing thing here. Without parentheses around the type, the attributes aren't even colorized as attributes:

image

It looks like bare tuple types get special handling, and it doesn't look like the example code ends up in the same place it would if the tuple type were parenthesized:

fsharp/src/Compiler/pars.fsy

Lines 5730 to 5731 in a9037f4

topType:
| topTupleType RARROW topType

fsharp/src/Compiler/pars.fsy

Lines 5747 to 5748 in a9037f4

topTupleType:
| topAppType STAR topTupleTypeElements

fsharp/src/Compiler/pars.fsy

Lines 5773 to 5774 in a9037f4

topTupleTypeElements:
| topAppType STAR topTupleTypeElements

topAppType:

| attributes appType

fsharp/src/Compiler/pars.fsy

Lines 6024 to 6056 in a9037f4

appType:
| appType arrayTypeSuffix
{ SynType.Array($2, $1, lhs parseState) }
| appType HIGH_PRECEDENCE_BRACK_APP arrayTypeSuffix /* only HPA for "name[]" allowed here */
{ SynType.Array($3, $1, lhs parseState) }
| appType appTypeConPower
/* note: use "rhs parseState 1" to deal with parens in "(int) list" */
{ SynType.App($2, None, [$1], [], None, true, unionRanges (rhs parseState 1) $2.Range) }
| LPAREN appTypePrefixArguments rparen appTypeConPower
{ let args, commas = $2
if parseState.LexBuffer.SupportsFeature LanguageFeature.MLCompatRevisions then
mlCompatError (FSComp.SR.mlCompatMultiPrefixTyparsNoLongerSupported()) (unionRanges (rhs parseState 1) $4.Range)
else
mlCompatWarning (FSComp.SR.parsMultiArgumentGenericTypeFormDeprecated()) (unionRanges (rhs parseState 1) $4.Range)
SynType.App($4, None, args, commas, None, true, unionRanges (rhs parseState 1) $4.Range) }
| powerType
{ $1 }
| intersectionType
{ parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.ConstraintIntersectionOnFlexibleTypes (lhs parseState)
$1 }
| typar COLON_GREATER typ
{ let tp, typ = $1, $3
let m = lhs parseState
SynType.WithGlobalConstraints(SynType.Var(tp, rhs parseState 1), [SynTypeConstraint.WhereTyparSubtypeOfType(tp, typ, m)], m) }
| UNDERSCORE COLON_GREATER typ %prec COLON_GREATER
{ SynType.HashConstraint($3, lhs parseState) }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-Syntax lexfilter, indentation and parsing Bug Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code.
Projects
Archived in project
Development

No branches or pull requests

8 participants