F# compiler does not enforce attribute targets
Compile this code:
open System
[<AttributeUsage(AttributeTargets.Method)>]
type MethodOnlyAttribute() =
inherit System.Attribute()
[<MethodOnly>]
let abc () =
"abc"
[<MethodOnly>]
let def =
"def"
Expected behavior
Compilation should fail because def is not a method; it is a string.
Actual behavior
The code compiles.
Known workarounds
Take care not to use attributes incorrectly.
Related information
https://github.com/dotnet/fsharp/issues/4460 is not quite the same, but a fix will probably touch the same code.
- Operating system: Linux
- .NET Runtime kind: .NET Core, Fable
- Editing Tools: Visual Studio Code
So this one is kinda funky. F# functions emit as methods, so it's technically correct that you can use MethodOnly on an F# function.
However, in F# semantics there is a very loose distinction (often no distinction) between F# functions and values. F# supports "normal" functions like let add x y = x + y, but also function values like this to support partial application:
let add x = fun y -> x + y
let add' x y = x + y
And you can apply the attribute to that as well. The typechecker doesn't distinguish between the two functions (at least not how you might expect). The consequence of that is you can apply the attribute to something that emits as a property.
I would argue that this is a bug, but fixing it would also be a breaking change, so the best we can do is emit a warning.
Does the same funkiness apply for AttributeTargets that target types?
Such as:
- AttributeTargets.Class
- AttributeTargets.Interface
- AttributeTargets.Struct
I ran into this while consuming a C# library (I only have limited F# experience) that had attributes with these targets. E.g. the following situations compile but some wouldn't in C#:
open System
[<AttributeUsage(AttributeTargets.Class)>]
type ClassTargetAttribute() =
inherit Attribute()
[<AttributeUsage(AttributeTargets.Interface)>]
type InterfaceTargetAttribute() =
inherit Attribute()
[<AttributeUsage(AttributeTargets.Struct)>]
type StructTargetAttribute() =
inherit Attribute()
[<ClassTarget>] // Valid
[<InterfaceTarget>] // Invalid
[<StructTarget>] // Invalid
type C() =
member _.M() = ()
[<ClassTarget>] // Invalid
[<InterfaceTarget>] // Valid
[<StructTarget>] // Invalid
type I =
abstract member prop: string
[<ClassTarget>] // Valid
[<InterfaceTarget>] // Invalid
[<StructTarget>] // Invalid
type RecRef = {
prop: string
}
[<ClassTarget>] // Invalid
[<InterfaceTarget>] // Invalid
[<StructTarget>] // Valid
[<Struct>]
type RecStruct = {
prop: string
}
Yes, this should give a warning or error that the attribute is not applicable to def.
I will also list this as a spec clarification issue in https://github.com/fsharp/fsfoundation/issues/841 - we should indicate for each language element exactly which attribute targets are allowed. Some parts of the spec do record this but not all.
This trips us from time to time while writing tests in xunit where people forget to put unit after the let function
[<Fact>]
let ``When receiving should`` = ...
vs
[<Fact>]
let ``When receiving should`` () = ...
Would be very helpful with a compiler warning/error.
Yes, this should give a warning or error that the attribute is not applicable to
def.I will also list this as a spec clarification issue in fsharp/fsfoundation#841 - we should indicate for each language element exactly which attribute targets are allowed. Some parts of the spec do record this but not all.
Im adding the warning for this. @vzarytovskii @dsyme any plans on updating the lang specification about this? Want to help fixing all this inconsistencies but it is hard without knowing this
Yes, this should give a warning or error that the attribute is not applicable to
def.I will also list this as a spec clarification issue in fsharp/fsfoundation#841 - we should indicate for each language element exactly which attribute targets are allowed. Some parts of the spec do record this but not all.
Im adding the warning for this. @vzarytovskii @dsyme any plans on updating the lang specification about this? Want to help fixing all this inconsistencies but it is hard without knowing this
It should be an rfc, since we treat those as additions to specification.
Yes, this should give a warning or error that the attribute is not applicable to
def.I will also list this as a spec clarification issue in fsharp/fsfoundation#841 - we should indicate for each language element exactly which attribute targets are allowed. Some parts of the spec do record this but not all.
Im adding the warning for this. @vzarytovskii @dsyme any plans on updating the lang specification about this? Want to help fixing all this inconsistencies but it is hard without knowing this
It should be an rfc, since we treat those as additions to specification.
Yes RFC would be good to have that documented there . I can see 10+ issues related to AttributesTargets