dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.93k stars 788 forks source link

Internal error when using custom attribute and omitting optional argument #8353

Open chkn opened 4 years ago

chkn commented 4 years ago

There is an internal compiler error when you use a custom attribute and omit an optional argument tagged with System.Runtime.InteropServices.OptionalAttribute

Repro steps

module TestCase

open System
open System.Runtime.InteropServices

type FooAttribute(foo : string, bar : bool) =
    inherit Attribute()
    new([<Optional>] bar) = FooAttribute("", bar)

[<Foo>]
type Bar() = class end

Expected behavior

This should compile

Actual behavior

TestCase.fs(10,3): error FS0073: internal error: The type 'System.Boolean' may not be used as a custom attribute value

Known workarounds

Add a DefaultParameterValue attribute.

Related information

Provide any related information (optional):

abelbraaksma commented 4 years ago

I'm not sure if this is the cause of your error, the error itself seems out of place, but when you use attributes to declare optional parameters, I believe you also need DefaultParameterValue, and maybe even the ? to make it a proper optional? Some background: https://bugsquash.blogspot.com/2012/08/optional-parameters-interop-c-f.html?m=1

Normally, the compiler gives an error about not giving the default value, so I'd argue it's a bug.

chkn commented 4 years ago

I believe you also need DefaultParameterValue

Not according to the spec: https://github.com/fsharp/fslang-design/blob/master/FSharp-4.1/FS-1027-complete-optional-defaultparametervalue.md

Relevant quote:

specifying Optional without DefaultParameterValue - callers can then omit the argument and will choose a default value by convention - for primitive types and structs this is again the default constructor.

abelbraaksma commented 4 years ago

I would swear I had seen that error, but when I check my code where I used this, and I remove DefaultParameterValue, it just compiles. Perhaps I was confused with val in classes.

I did notice an oddity with CallerMemberName. If you don't give a default value, it doesn't work (it's always empty). This may well be a bug, I should post that separately.

I also noticed that your issue seems to be related to value types. If the optional argument is a string, it compiles correctly. Use an int or bool, you get the error. My guess: the optionality turns it into a reference type perhaps? (from the referenced RFC, I think it shouldn't).

If you do give it a DefaultParameterValue, then it compiles just fine with int or bool, which could be used as a workaround if you need this coding pattern (regardless of my wrong assumption in the previous post).

chkn commented 4 years ago

If you do give it a DefaultParameterValue, then it compiles just fine with int or bool, which could be used as a workaround

I could've sworn I tried this, but you're right, that does allow it to compile, thanks! It does make the code more verbose, however, so I do wish I could omit that attribute.