No compiler error for instance members on static class; produces invalid IL & causes runtime error
See original GitHub issueRepro steps
- Define a static class using the combination of
SealedAttribute
andAbstractClassAttribute
. - Add fields, instance members, even constructors:[^1] a. Fields (mutable or not), properties, methods, indexers. b. Abstract, concrete, override…
Examples
E.g., this
// This compiles and produces nonsense IL.
[<Sealed; AbstractClass>]
type T =
abstract A : int
abstract B : int with get, set
abstract C : i:int -> int
abstract D : i:int -> int
default _.D i = i + 3
member _.E = 3
val F : int
val mutable G : int
member _.H (i, j) = i + j
member _.Item with get i = 3 and set i value = ()
override _.ToString () = "🙃"
new () = { F = 3; G = 3 }
new (x, y) = { F = x; G = y }
generates this (SharpLab):
[Serializable]
[Sealed]
[AbstractClass]
[CompilationMapping(SourceConstructFlags.ObjectType)]
public static class T
{
internal int F@;
public int G;
[CompilationMapping(SourceConstructFlags.Field, 0)]
public int F
{
get
{
return F@;
}
}
public abstract override int A { get; }
public abstract override int B { get; set; }
public int E
{
get
{
return 3;
}
}
public int this[object i]
{
get
{
return 3;
}
set
{
}
}
public abstract override int C(int i);
public override int D(int i)
{
return i + 3;
}
public int H(int i, int j)
{
return i + j;
}
public override string ToString()
{
return "\ud83d\ude43";
}
public T()
{
F@ = 3;
G = 3;
}
public T(int x, int y)
{
F@ = x;
G = y;
}
}
There is also no error when making a class with a primary constructor static (SharpLab):
// Just throw [<Sealed; AbstractClass>] on the default
// SharpLab class and it still compiles!
[<Sealed; AbstractClass>]
type C() =
member _.M() = ()
There is no error for fieldless single-case unions, either (SharpLab):[^2][^3][^4][^5]
[<Sealed; AbstractClass>]
type U = U
let u = U
There is likewise no warning or error when adding SealedAttribute
to a struct fieldless single-case union (SharpLab):[^5]
[<Sealed; Struct>]
type U = U
Expected behavior
The code should not compile.
Actual behavior
The code compiles, and calling it results in a System.BadImageFormatException
with the message "Bad IL format."
at runtime.[^6]
> Unchecked.defaultof<T>.A;;
System.BadImageFormatException: Bad IL format.
at <StartupCode$FSI_0007>.$FSI_0007.main@()
Stopped due to error
Known workarounds
Don’t try weird things.
Related information
- .NET SDK 6.0.300.
[^1]: Not auto-properties: using member val
correctly gives FS3133
, presumably because the check for a primary constructor is separate. member val
is still let through if you do have a primary constructor (SharpLab).
[^2]: It looks like the compiler ignores the combination of Sealed
and AbstractClass
here and just adds the AbstractClass
attribute without actually making the class static.
[^3]: The compiler correctly gives FS0942
and FS0939
when a field is added (SharpLab) or a second case is added (SharpLab).
[^4]: The compiler does disallow this for records with FS0942
and FS0939
(SharpLab).
[^5]: If you add a field or a bar |
to either of these, the compiler does give you FS0942
and/or FS0939
.
[^6]: The compiler does disallow calling a constructor added to a static class with FS0759
.
Issue Analytics
- State:
- Created a year ago
- Reactions:2
- Comments:17 (15 by maintainers)
Top GitHub Comments
@abelbraaksma yes, that particular example doesn’t yield a
BadImageFormatException
because, I think, the method call is optimized away altogether by the F# compiler (x
is directly assigned the value42
).This should be relatively easy to add check for and fix, I’ll mark it as a good first issue.