diff --git a/src/Compiler/AbstractIL/ilwrite.fs b/src/Compiler/AbstractIL/ilwrite.fs index 80bb791c25f..fb024b63f06 100644 --- a/src/Compiler/AbstractIL/ilwrite.fs +++ b/src/Compiler/AbstractIL/ilwrite.fs @@ -1192,7 +1192,7 @@ let canGenPropertyDef cenv (prop: ILPropertyDef) = // If we have GetMethod or SetMethod set (i.e. not None), try and see if we have MethodDefs for them. // NOTE: They can be not-None and missing MethodDefs if we skip generating them for reference assembly in the earlier pass. // Only generate property if we have at least getter or setter, otherwise, we skip. - [| prop.GetMethod; prop.SetMethod |] + [| prop.GetMethod; prop.SetMethod |] |> Array.choose id |> Array.map (TryGetMethodRefAsMethodDefIdx cenv) |> Array.exists (function | Ok _ -> true | _ -> false) @@ -1304,11 +1304,14 @@ and GenTypeDefPass2 pidx enc cenv (tdef: ILTypeDef) = // Now generate or assign index numbers for tables referenced by the maps. // Don't yet generate contents of these tables - leave that to pass3, as // code may need to embed these entries. - tdef.Implements |> List.iter (GenImplementsPass2 cenv env tidx) - props |> List.iter (GenPropertyDefPass2 cenv tidx) + tdef.Implements |> List.iter (GenImplementsPass2 cenv env tidx) events |> List.iter (GenEventDefPass2 cenv tidx) tdef.Fields.AsList() |> List.iter (GenFieldDefPass2 tdef cenv tidx) tdef.Methods |> Seq.iter (GenMethodDefPass2 tdef cenv tidx) + // Generation of property definitions for **ref assemblies** is checking existence of generated method definitions. + // Therefore, due to mutable state within "cenv", order of operations matters. + // Who could have thought that using shared mutable state can bring unexpected bugs...? + props |> List.iter (GenPropertyDefPass2 cenv tidx) tdef.NestedTypes.AsList() |> GenTypeDefsPass2 tidx (enc@[tdef.Name]) cenv with exn -> failwith ("Error in pass2 for type "+tdef.Name+", error: " + exn.Message) diff --git a/tests/fsharp/Compiler/CodeGen/EmittedIL/ReferenceAssemblyTests.fs b/tests/fsharp/Compiler/CodeGen/EmittedIL/ReferenceAssemblyTests.fs index 9020c5d85e3..8f9a21364e1 100644 --- a/tests/fsharp/Compiler/CodeGen/EmittedIL/ReferenceAssemblyTests.fs +++ b/tests/fsharp/Compiler/CodeGen/EmittedIL/ReferenceAssemblyTests.fs @@ -501,6 +501,115 @@ extends [runtime]System.Object }"""] + [] + let ``Properties are emitted for CliMutable records`` () = + FSharp """ +namespace ReferenceAssembly +type [] MyRecord = { MyId: int }""" + |> withOptions ["--refonly"] + |> compile + |> shouldSucceed + |> verifyIL [ + referenceAssemblyAttributeExpectedIL + " .property instance int32 MyId()"] + + [] + let ``Properties are emitted even for CliMutable records which are not last in a file`` () = + FSharp """ +namespace ReferenceAssembly +type [] MyRecord = { MyId: int } +type [] MySecondRecord = { MySecondId: string } +""" + |> withOptions ["--refonly"] + |> compile + |> shouldSucceed + |> verifyIL [ + referenceAssemblyAttributeExpectedIL + " .property instance int32 MyId()" + " .property instance string MySecondId()"] + + [] // Regression https://github.com/dotnet/fsharp/issues/14088 . + // Generated IL was assigning properties to the last record in file instead of where they are supposed to be + let ``Properties are emitted for equal records in the same file`` () = + FSharp """ +namespace Net7FSharpSnafu.Library + +open System + +type [] MyRecord = + { Name: string } + +type [] MySecondRecord = { Name: string } +""" + |> withOptions ["--refonly"] + |> compile + |> shouldSucceed + |> verifyIL [""" .class public auto ansi serializable sealed Net7FSharpSnafu.Library.MyRecord + extends [runtime]System.Object + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CLIMutableAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoComparisonAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.NoEqualityAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 02 00 00 00 00 00 ) + .method public hidebysig specialname instance string + get_Name() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } + + .method public hidebysig specialname instance void + set_Name(string 'value') cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } + + .method public specialname rtspecialname + instance void .ctor(string name) cil managed + { + + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } + + .method public specialname rtspecialname + instance void .ctor() cil managed + { + + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } + + .method public strict virtual instance string + ToString() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldnull + IL_0001: throw + } + + .property instance string Name() + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags, + int32) = ( 01 00 04 00 00 00 00 00 00 00 00 00 ) + .set instance void Net7FSharpSnafu.Library.MyRecord::set_Name(string) + .get instance string Net7FSharpSnafu.Library.MyRecord::get_Name() + } +} """] + [] let ``Properties, getters, setters are emitted for internal properties`` () = FSharp """ @@ -558,6 +667,11 @@ type MySecondaryAttribute() = IL_0001: throw } + .property instance int32 Prop1() + { + .set instance void ReferenceAssembly/MyAttribute::set_Prop1(int32) + .get instance int32 ReferenceAssembly/MyAttribute::get_Prop1() + } } .class auto ansi serializable nested public MySecondaryAttribute @@ -597,11 +711,6 @@ type MySecondaryAttribute() = IL_0001: throw } - .property instance int32 Prop1() - { - .set instance void ReferenceAssembly/MyAttribute::set_Prop1(int32) - .get instance int32 ReferenceAssembly/MyAttribute::get_Prop1() - } .property instance int32 Prop1() { .set instance void ReferenceAssembly/MySecondaryAttribute::set_Prop1(int32) @@ -805,6 +914,10 @@ type Person(name : string, age : int) = IL_0001: throw } + .property instance bool Something() + { + .get instance bool ReferenceAssembly/CustomAttribute::get_Something() + } } .class auto ansi serializable nested public Person @@ -865,10 +978,6 @@ type Person(name : string, age : int) = IL_0001: throw } - .property instance bool Something() - { - .get instance bool ReferenceAssembly/CustomAttribute::get_Something() - } .property instance string Name() { .custom instance void ReferenceAssembly/CustomAttribute::.ctor(bool) = ( 01 00 01 00 00 )