Please provide a succinct description of the issue.
Provide the steps required to reproduce the problem
dotnet new console -lang F#
/// Author: Zoltan Podlovics
/// License: Apache License, Version 2.0, https://www.apache.org/licenses/
/// Description: Single case struct discriminated union with the same case
/// name as the type silently converted to object type
open System
[<Struct>]
type StructDUSameName = StructDUSameName
[<Struct>]
type StructDUSameNamePipe = | StructDUSameNamePipe
[<Struct>]
type StructDUSameNameNewLine =
| StructDUSameNameNewLine
[<Struct>]
type StructDUIntSameName = StructDUIntSameName of int
[<Struct>]
type StructDUIntSameNamePipe = | StructDUIntSameNamePipe of int
[<Struct>]
type StructDUIntSameNameNewLine =
| StructDUIntSameNameNewLine of int
[<EntryPoint>]
let main argv =
printfn "sizeof<StructDUSameName>: %d" sizeof<StructDUSameName>
printfn "typeof<StructDUSameName>.IsValueType: %b" typeof<StructDUSameName>.IsValueType
printfn "sizeof<StructDUSameNamePipe>: %d" sizeof<StructDUSameNamePipe>
printfn "typeof<StructDUSameNamePipe>.IsValueType: %b" typeof<StructDUSameNamePipe>.IsValueType
printfn "sizeof<StructDUSameNameNewLine>: %d" sizeof<StructDUSameNameNewLine>
printfn "typeof<StructDUSameNameNewLine>.IsValueType: %b" typeof<StructDUSameNameNewLine>.IsValueType
printfn "sizeof<StructDUIntSameName>: %d" sizeof<StructDUIntSameName>
printfn "typeof<StructDUIntSameName>.IsValueType: %b" typeof<StructDUIntSameName>.IsValueType
printfn "sizeof<StructDUIntSameNamePipe>: %d" sizeof<StructDUIntSameNamePipe>
printfn "typeof<StructDUIntSameNamePipe>.IsValueType: %b" typeof<StructDUIntSameNamePipe>.IsValueType
printfn "sizeof<StructDUIntSameNameNewLine>: %d" sizeof<StructDUIntSameNameNewLine>
printfn "typeof<StructDUIntSameNameNewLine>.IsValueType: %b" typeof<StructDUIntSameNameNewLine>.IsValueType
0 // return an integer exit code
dotnet run -c Release
Execution result:
sizeof<StructDUSameName>: 8
typeof<StructDUSameName>.IsValueType: false
sizeof<StructDUSameNamePipe>: 1
typeof<StructDUSameNamePipe>.IsValueType: true
sizeof<StructDUSameNameNewLine>: 1
typeof<StructDUSameNameNewLine>.IsValueType: true
sizeof<StructDUIntSameName>: 4
typeof<StructDUIntSameName>.IsValueType: true
sizeof<StructDUIntSameNamePipe>: 4
typeof<StructDUIntSameNamePipe>.IsValueType: true
sizeof<StructDUIntSameNameNewLine>: 4
typeof<StructDUIntSameNameNewLine>.IsValueType: true
fsharpc --optimize+ Program.fs
mono Program.exe
Execution result:
sizeof<StructDUSameName>: 8
typeof<StructDUSameName>.IsValueType: false
sizeof<StructDUSameNamePipe>: 0
typeof<StructDUSameNamePipe>.IsValueType: true
sizeof<StructDUSameNameNewLine>: 0
typeof<StructDUSameNameNewLine>.IsValueType: true
sizeof<StructDUIntSameName>: 4
typeof<StructDUIntSameName>.IsValueType: true
sizeof<StructDUIntSameNamePipe>: 4
typeof<StructDUIntSameNamePipe>.IsValueType: true
sizeof<StructDUIntSameNameNewLine>: 4
typeof<StructDUIntSameNameNewLine>.IsValueType: true
Struct discriminated union not converted silently to object (non-value) type.
Single case struct discriminated union with the same case name as the type silently converted to object type. Any idea why the single case struct discriminated union size is different with .NET Core vs Mono?
Use the pipe character | before the discriminated union case name.
.NET Core 2.0 (production)
Mono JIT compiler version 5.2.0.215 (tarball Mon Aug 14 15:46:23 UTC 2017)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
TLS: __thread
SIGSEGV: altstack
Notifications: epoll
Architecture: amd64
Disabled: none
Misc: softdebug
LLVM: supported, not enabled.
GC: sgen (concurrent by default)
An intriguing report. I just ran it partially on VS2017 15.3.1.0 Preview 1 with latest nightlies on Windows 7 from FSI and I get similar peculiarities:
> [<Struct>] type S = S;;
[<Struct>]
type S = | S
> sizeof<S>;;
val it : int = 8
> [<Struct>] type S = | S;;
[<Struct>]
type S = | S
> sizeof<S>;;
val it : int = 1
> [<Struct>] type S = Y;;
[<Struct>]
type S = | Y
> sizeof<S>;;
val it : int = 8
Which also shows that it doesn't matter whether it is the same name, it seems to matter whether or not the pipeline character is there or not. Notice, however, that the type is recognized as the same, it just isn't stored the same.
@abelbraaksma you are right, I missed that non-same name case somehow, and the my conclusion was not fully precise.
Intrestingly the F# Interactive (wrongly) will print the as [<Struct>] type description after evaluation, however if you really check the types with typeof<MyType> it will have different BaseClass (System.Object vs System.ValueType) and IsValueType property (false vs true).
> type ClassDUSameName = ClassDUSameName;;
type ClassDUSameName = | ClassDUSameName
> type ClassDUSameName = | ClassDUSameName;;
type ClassDUSameName = | ClassDUSameName
> type ClassDU = ClassDUCase1;;
type ClassDU = | ClassDUCase1
> type ClassDU = | ClassDUCase1;;
type ClassDU = | ClassDUCase1
> type [<Struct>] StructDUSameName1 = StructDUSameName1;;
[<Struct>]
type StructDUSameName1 = | StructDUSameName1
> typeof<StructDUSameName1>.IsValueType;;
val it : bool = false
> type [<Struct>] StructDU1 = StructDU1Case1;;
[<Struct>]
type StructDU1 = | StructDU1Case1
> typeof<StructDU1>.IsValueType;;
val it : bool = false
@cartermp: can we label this with high priority? The analysis by @zpodlovics shows that structs are not always structs, meaning they end up on the heap. This has potentially serious effects and is directly counter to the syntax used.
Also, it may be a good idea to look into some types returning 0 for their size? Or should that be treated as a different issue? Sounds like a bug in the sizeof operator to me.
@abelbraaksma a more reasonable check would be to check whether the type is a value type or not.
The empty struct size seems to be one (1) on .NET Framework and .NET Core, as the sizeof
Please note, no mandatory field needed to have a correct non-zero sized runtime representation. I did a quick last day, the C# compiler in .NET Core 2.0 is perfectly happy with empty structs. Nested empty structures are also pefectly fine. No complaints (not even a warning) from the compiler.
Here is how to check a zero sized struct (probably there are better solutions now):
https://stackoverflow.com/questions/16611598/why-does-an-empty-struct-in-c-sharp-consume-memory
@zpodlovics @abelbraaksma I pushed a proposed fix. Thanks so much for your careful checking of many cases
Please note, no mandatory field needed to have a correct non-zero sized runtime representation.
Just to mention that without a field or size attribute peverify gives
[MD]: Error: Value class has neither fields nor size parameter. [token:0x0200000F]
[MD]: Error: Value class has neither fields nor size parameter. [token:0x02000011]
Fixed in #3521
Great work, @dsyme, tx. I'll have a look at this once the new nightly comes around (they seem to not be automatically build anymore?).
once the new nightly comes around (they seem to not be automatically build anymore?).
There was a glitch in the build, should be fixed by #3528