fsharp icon indicating copy to clipboard operation
fsharp copied to clipboard

Access protected const from base class in attribute

Open gh3x78E opened this issue 8 years ago • 14 comments

Environment: I am using .Net Framework 4.6.1 together with the target F# runtime 4.1 (FSharp.Core, 4.4.1.0) in a F# class library which references XUnit (2.2.0, obtained via NuGet) in Visual Studio 2017 Community.

I have a C# base class with a constant

public class TestClassBase
{
  protected const string Foo = "Bar";
}

which I can consume in C# as follows

[Trait(Foo, "")]
public class MyCSharpTestClass : TestClassBase
{
}

How do I consume it in a F# class? Foo is not recognized by the compiler.

[<Trait(Foo, "")>]
type MyFSharpTestClass() =
  inherit TestClassBase()

The error I get is: FS0267 This is not a valid constant expression or custom attribute value.

[<Trait(Foo, "")>] yields: FS0039 The value or constructor 'Foo' is not defined. Maybe you want one of the following: floor.

[<Trait(this.Foo, "")>] yields FS0039 The value, namespace, type or module 'this' is not defined. Maybe you want one of the following: ThisAssembly.

[<Trait(base.Foo, "")>] yields FS0039 The value, namespace, type or module 'base' is not defined. Maybe you want one of the following: Base.

[<Trait(TestClassBase.Foo, "")>] yields FS0039 The field, constructor or member 'Foo' is not defined.

gh3x78E avatar Jun 01 '17 20:06 gh3x78E

@gh3x78E I think you need to change it to typeof<Foo>.

NatElkins avatar Jun 02 '17 17:06 NatElkins

[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
  inherit TestClassBase()

fails with

error FS1097: The struct or class field 'Foo' is not accessible from this code location

I guess the F# compiler sees the Attribute declaration as outside of the class, so that the access is not allowed.


Compare with

//[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
  inherit TestClassBase()
  let foo = TestClassBase.Foo

which compiles ok, and shows that accessing the protected member inside the class works.

0x53A avatar Jun 02 '17 17:06 0x53A

@NatElkins: Sorry, that does not work - and makes no sense.

gh3x78E avatar Jun 02 '17 18:06 gh3x78E

@0x53A I do not understand why the behaviour is different in C# and F#. In my opinion there should not be any difference in usage.

gh3x78E avatar Jun 02 '17 18:06 gh3x78E

@gh3x78E Oops. Sorry, skimmed your original post and thought you might've been trying to pass in a type rather than a string. In the case that your attribute was taking a type (not a string) and Foo was a type (rather than a field), it would've caused an error because in that case Foo would be viewed as a function that returned an instance of Foo.

NatElkins avatar Jun 02 '17 18:06 NatElkins

@gh3x78E This looks like a bug - thanks for the report.

As workaround please simply don't use "protected" in this case on your C# base class. In general, F# de-emphasizes the use of subclassing, and hence "protected" is seen as awkward for F# consumers, and can hit some corner-case issues like this.

dsyme avatar Jun 05 '17 10:06 dsyme

A different example of this issue appears to be in Microsoft Coyote (actor library written in C#). There the base class Actor contains a protected attribute to be used by inherited types. This works in C# but not in F#. The reasoning is to prevent scope pollution for C# users, since only Actor subclasses have access the the attributes defined in the Actor parent class. It would be great if it was somehow possible to override the access protection in client code, since this is no longer tagged as a bug. SO question.

bent-rasmussen avatar May 14 '20 12:05 bent-rasmussen

since this is no longer tagged as a bug

This is false. This issue is still tagged as a bug.

Happypig375 avatar May 14 '20 12:05 Happypig375

[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
  inherit TestClassBase()

fails with

error FS1097: The struct or class field 'Foo' is not accessible from this code location

I guess the F# compiler sees the Attribute declaration as outside of the class, so that the access is not allowed.

Compare with

//[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
  inherit TestClassBase()
  let foo = TestClassBase.Foo

which compiles ok, and shows that accessing the protected member inside the class works.

This no longer works inside functions. If you change to:

//[<Trait(TestClassBase.Foo, "")>]
type MyFSharpTestClass() =
  inherit TestClassBase()
  let foo () = TestClassBase.Foo

Common if you want to libraries like Akka. This is permitted in C# however

Swoorup avatar Jul 07 '20 19:07 Swoorup

Looking at the actual C# spec for this is, it's unclear to be whether the attribute declaration is part of the program text of the type: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/basic-concepts#declared-accessibility.

The spec is sufficiently vague in that you can read it either way. Though, from the examples above, clearly the implementation of the C# compiler considered it to be part of said program text, while F# does not.

Just saying ;).

abelbraaksma avatar Sep 27 '20 13:09 abelbraaksma

That means you can't interop with C# libraries easily.

Swoorup avatar Sep 27 '20 13:09 Swoorup

Bug in C# spec reported: https://github.com/dotnet/csharplang/discussions/3948. Looking a bit deeper into this makes it all rather fuzzy (protected member is disallowed, protected const member is not...), would be nice if some people can write up some definitive rules here that we can follow along in F#.

abelbraaksma avatar Sep 27 '20 14:09 abelbraaksma

The answer by @333fred (https://github.com/dotnet/csharplang/discussions/3948#discussioncomment-85803) suggests that this only works "by chance" in the C# compiler. More specifically, the protected const in the OP should not be accessible outside its own class (the attribute constructor call is outside the "program text" of the class) and F# here plays by the C# spec rules (but not the C# compiler).

I'm not saying we shouldn't fix this, but fixing it is basically following a bug (that'll never be fixed due to backwards compat issues) of the C# compiler. I've no idea what the policy is here (cc @dsyme).

abelbraaksma avatar Sep 28 '20 10:09 abelbraaksma

Interesting. In that case the C# folks should make an analyzer for it, since it breaks C#/F# interop. It is also interesting that this alleged bug has been used consciously as a feature in the wild (well at least once in a Microsoft library). :-)

bent-rasmussen avatar Sep 28 '20 11:09 bent-rasmussen