Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #17447 -MethodAccessException on equality comparison of a record with private fields #17467

Merged
merged 2 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/9.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Fix reporting IsFromComputationExpression only for CE builder type constructors and let bindings. ([PR #17375](https://github.com/dotnet/fsharp/pull/17375))
* Optimize simple mappings in comprehensions when the body of the mapping has `let`-bindings and/or sequential expressions before a single yield. ([PR #17419](https://github.com/dotnet/fsharp/pull/17419))
* C# protected property can be assigned in F# inherit constructor call. ([Issue #13299](https://github.com/dotnet/fsharp/issues/13299), [PR #17391](https://github.com/dotnet/fsharp/pull/17391))
* MethodAccessException on equality comparison of a record with private fields. ([Issue #17447](https://github.com/dotnet/fsharp/issues/17447), [PR #17391](https://github.com/dotnet/fsharp/pull/17467))

### Added

Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Checking/AugmentWithHashCompare.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,7 @@ let MakeValsForEqualsAugmentation g (tcref: TyconRef) =
g
tcref
ty
vis
tcref.Accessibility
(if tcref.Deref.IsFSharpException then
None
else
Expand Down Expand Up @@ -1376,7 +1376,7 @@ let MakeValsForEqualityWithComparerAugmentation g (tcref: TyconRef) =
g
tcref
ty
vis
tcref.Accessibility
// This doesn't implement any interface.
None
"Equals"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,87 +7,239 @@ open FSharp.Test.Compiler

module GenericComparisonCrossAssembly =

[<Fact>]
let ``fslib``() =
[<InlineData(true)>] // RealSig
[<InlineData(false)>] // Regular
[<Theory>]
let ``fslib``(realsig) =
FSharpWithFileName "Program.fs"
"""
ValueSome (1, 2) = ValueSome (2, 3) |> ignore"""
|> withRealInternalSignature realsig
|> withOptimize
|> compileExeAndRun
|> shouldSucceed
|> verifyIL [ """
.method assembly specialname static void staticInitialization@() cil managed
{

.maxstack 8
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: newobj instance void class [runtime]System.Tuple`2<int32,int32>::.ctor(!0,
!1)
IL_0007: call valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<!0> valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>>::NewValueSome(!0)
IL_000c: stsfld valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>> Program::x@1
IL_0011: ldc.i4.2
IL_0012: ldc.i4.3
IL_0013: newobj instance void class [runtime]System.Tuple`2<int32,int32>::.ctor(!0,
!1)
IL_0018: call valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<!0> valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>>::NewValueSome(!0)
IL_001d: stsfld valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>> Program::y@1
IL_0022: ldsflda valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>> Program::x@1
IL_0027: call valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>> Program::get_y@1()
IL_002c: call class [runtime]System.Collections.IEqualityComparer [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives::get_GenericEqualityComparer()
IL_0031: call instance bool valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<class [runtime]System.Tuple`2<int32,int32>>::Equals(valuetype [FSharp.Core]Microsoft.FSharp.Core.FSharpValueOption`1<!0>,
class [runtime]System.Collections.IEqualityComparer)
IL_0036: stsfld bool Program::arg@1
IL_003b: ret
}
""" ]

[<Fact>]
let ``Another assembly``() =
let module1 =
[<InlineData(true)>] // RealSig
[<InlineData(false)>] // Regular
[<Theory>]
let ``Another Assembly - record with private fields`` (realsig) =
let library =
FSharpWithFileName "Module1.fs"
"""
module Module1

[<Struct>]
type Struct(v: int, u: int) =
member _.V = v
member _.U = u """
type Value =
private { value: uint32 }

static member Zero = { value = 0u }
static member Create(value: int) = { value = uint value } """
|> withRealInternalSignature realsig
|> withOptimize
|> asLibrary
|> withName "module1"

let module2 =
let mainModule =
FSharpWithFileName "Program.fs"
"""
Module1.Struct(1, 2) = Module1.Struct(2, 3) |> ignore"""
$"""
open Module1
Value.Zero = Value.Create 0 |> ignore"""

module2
|> withReferences [module1]
mainModule
|> withRealInternalSignature realsig
|> withReferences [ library ]
|> compileExeAndRun
|> shouldSucceed

[<InlineData(false, "private", "assembly")>] // Legacy, private WrapType, private visibility in IL
[<InlineData(false, "internal", "assembly")>] // RealSig, internal WrapType, assembly visibility in IL
[<InlineData(false, "public", "public")>] // Legacy, public WrapType, public visibility in IL
[<InlineData(true, "private", "private")>] // RealSig, private WrapType, private visibility in IL
[<InlineData(true, "internal", "assembly")>] // RealSig, internal WrapType, assembly visibility in IL
[<InlineData(true, "public", "public")>] // RealSig, public WrapType, public visibility in IL
[<Theory>]
let ``Generated typed Equals`` (realsig, typeScope, targetVisibility) =
let library =
FSharpWithFileName "Program.fs"
$"""
module Module1 =

[<Struct>]
type {typeScope} Struct(v: int, u: int) =
member _.V = v
member _.U = u"""

library
|> asExe
|> withNoWarn 988
|> withRealInternalSignature realsig
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
$"""
.method {targetVisibility} hidebysig instance bool
Equals(valuetype Program/Module1/Struct obj,
class [runtime]System.Collections.IEqualityComparer comp) cil managed
{{
.custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

.maxstack 8
IL_0000: ldarg.0
IL_0001: ldfld int32 Program/Module1/Struct::v
IL_0006: ldarga.s obj
IL_0008: ldfld int32 Program/Module1/Struct::v
IL_000d: bne.un.s IL_001f

IL_000f: ldarg.0
IL_0010: ldfld int32 Program/Module1/Struct::u
IL_0015: ldarga.s obj
IL_0017: ldfld int32 Program/Module1/Struct::u
IL_001c: ceq
IL_001e: ret

IL_001f: ldc.i4.0
IL_0020: ret
}} """ ]


[<InlineData(false, "private")>] // Legacy, private record fields, private visibility in IL
[<InlineData(false, "internal")>] // RealSig, internal record fields, assembly visibility in IL
[<InlineData(false, "public")>] // Legacy, public record fields, public visibility in IL
[<InlineData(true, "private")>] // RealSig, private record fields, private visibility in IL
[<InlineData(true, "internal")>] // RealSig, internal record fields, assembly visibility in IL
[<InlineData(true, "public")>] // RealSig, public record fields, public visibility in IL
[<Theory>]
let ``Record with various fields`` (realsig, fieldScope) =

let mainModule =
FSharpWithFileName "Program.fs"
$"""
module Module1 =
type Value =
{fieldScope} {{ value: uint32 }}

static member Zero = {{ value = 0u }}
static member Create(value: int) = {{ value = uint value }}

Value.Zero = Value.Create 0 |> ignore
printfn "Hello, World" """

mainModule
|> withRealInternalSignature realsig
|> compileExeAndRun
|> shouldSucceed
|> verifyIL [ """
.method assembly specialname static void staticInitialization@() cil managed
{

.maxstack 8
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: newobj instance void [module1]Module1/Struct::.ctor(int32,
int32)
IL_0007: stsfld valuetype [module1]Module1/Struct Program::x@1
IL_000c: ldc.i4.2
IL_000d: ldc.i4.3
IL_000e: newobj instance void [module1]Module1/Struct::.ctor(int32,
int32)
IL_0013: stsfld valuetype [module1]Module1/Struct Program::y@1
IL_0018: ldsflda valuetype [module1]Module1/Struct Program::x@1
IL_001d: call valuetype [module1]Module1/Struct Program::get_y@1()
IL_0022: call class [runtime]System.Collections.IEqualityComparer [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives::get_GenericEqualityComparer()
IL_0027: call instance bool [module1]Module1/Struct::Equals(valuetype [module1]Module1/Struct,
class [runtime]System.Collections.IEqualityComparer)
IL_002c: stsfld bool Program::arg@1
IL_0031: ret
}
""" ]
|> verifyIL [
"""
.method public hidebysig virtual final instance bool Equals(class Program/Module1/Value obj) cil managed
{
.custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

.maxstack 8
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0017

IL_0003: ldarg.1
IL_0004: brfalse.s IL_0015

IL_0006: ldarg.0
IL_0007: ldfld uint32 Program/Module1/Value::value@
IL_000c: ldarg.1
IL_000d: ldfld uint32 Program/Module1/Value::value@
IL_0012: ceq
IL_0014: ret

IL_0015: ldc.i4.0
IL_0016: ret

IL_0017: ldarg.1
IL_0018: ldnull
IL_0019: cgt.un
IL_001b: ldc.i4.0
IL_001c: ceq
IL_001e: ret
} """
"""
IL_0020: call class [runtime]System.Collections.IEqualityComparer [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives::get_GenericEqualityComparer()
IL_0025: callvirt instance bool Program/Module1/Value::Equals(class Program/Module1/Value,
class [runtime]System.Collections.IEqualityComparer)
""" ]


[<InlineData(false, "private", "assembly")>] // Legacy, private WrapType, private visibility in IL
[<InlineData(false, "internal", "assembly")>] // RealSig, internal WrapType, assembly visibility in IL
[<InlineData(false, "public", "public")>] // Legacy, public WrapType, public visibility in IL
[<InlineData(true, "private", "private")>] // RealSig, private WrapType, private visibility in IL
[<InlineData(true, "internal", "assembly")>] // RealSig, internal WrapType, assembly visibility in IL
[<InlineData(true, "public", "public")>] // RealSig, public WrapType, public visibility in IL
[<Theory>]
let ``scoped type arg`` (realsig, argScope, targetVisibility) =
let mainModule =
FSharpWithFileName "Program.fs"
$"""
module IPartialEqualityComparer =
open System.Collections.Generic

[<StructuralEquality; NoComparison>]
type {argScope} WrapType<'T> = Wrap of 'T
"""
mainModule
|> asExe
|> withNoWarn 988
|> withRealInternalSignature realsig
|> withOptimize
|> compile
|> shouldSucceed
|> verifyIL [
$"""
.method {targetVisibility} hidebysig instance bool
Equals(class Program/IPartialEqualityComparer/WrapType`1<!T> obj,
class [runtime]System.Collections.IEqualityComparer comp) cil managed
{{
.custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 )

.maxstack 5
.locals init (class Program/IPartialEqualityComparer/WrapType`1<!T> V_0,
class Program/IPartialEqualityComparer/WrapType`1<!T> V_1,
!T V_2,
!T V_3)
IL_0000: ldarg.0
IL_0001: brfalse.s IL_0027

IL_0003: ldarg.1
IL_0004: brfalse.s IL_0025

IL_0006: ldarg.0
IL_0007: pop
IL_0008: ldarg.0
IL_0009: stloc.0
IL_000a: ldarg.1
IL_000b: stloc.1
IL_000c: ldloc.0
IL_000d: ldfld !0 class Program/IPartialEqualityComparer/WrapType`1<!T>::item
IL_0012: stloc.2
IL_0013: ldloc.1
IL_0014: ldfld !0 class Program/IPartialEqualityComparer/WrapType`1<!T>::item
IL_0019: stloc.3
IL_001a: ldarg.2
IL_001b: ldloc.2
IL_001c: ldloc.3
IL_001d: tail.
IL_001f: call bool [FSharp.Core]Microsoft.FSharp.Core.LanguagePrimitives/HashCompare::GenericEqualityWithComparerIntrinsic<!T>(class [runtime]System.Collections.IEqualityComparer,
!!0,
!!0)
IL_0024: ret

IL_0025: ldc.i4.0
IL_0026: ret

IL_0027: ldarg.1
IL_0028: ldnull
IL_0029: cgt.un
IL_002b: ldc.i4.0
IL_002c: ceq
IL_002e: ret
}} """ ]
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ module GenericComparison =
|> verifyCompilation



// SOURCE=Hash01.fsx SCFLAGS="-a -g --optimize+" COMPILE_ONLY=1 POSTCMD="..\\CompareIL.cmd Hash01.dll" # Hash01.fs -
[<Theory; Directory(__SOURCE_DIRECTORY__, Includes=[|"Hash01.fsx"|])>]
let ``Hash01_fsx`` compilation =
Expand Down
6 changes: 6 additions & 0 deletions tests/FSharp.Test.Utilities/Compiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,12 @@ module rec Compiler =
| FS fs -> FS { fs with Options = fs.Options @ ["--realsig+"] }
| _ -> failwith "withRealInternalSignatureOn only supported by f#"

let withRealInternalSignature (realSig: bool) (cUnit: CompilationUnit) : CompilationUnit =
if realSig then
cUnit |> withRealInternalSignatureOn
else
cUnit |> withRealInternalSignatureOff

let asExe (cUnit: CompilationUnit) : CompilationUnit =
withOutputType CompileOutput.Exe cUnit

Expand Down
Loading