Summary
The fix introduced in #518 (commit ceeb6bc, "Fix for issue #517") causes a regression: all generated property setters produce invalid IL, throwing System.InvalidProgramException: Common Language Runtime detected an invalid program at runtime when any property is set on a type-provider–generated type.
Root Cause
The change modifies the method body emitter in src/ProvidedTypes.fs (around line 16052) as follows:
// BEFORE (a54d92b):
let expectedState = if (transType minfo.ReturnType = ILType.Void) then ExpectedStackState.Empty else ExpectedStackState.Value
codeGen.EmitExpr (expectedState, expr)
ilg.Emit I_ret
// AFTER (ceeb6bc):
let retType = transType minfo.ReturnType
let retUnit = retType = ILType.Void || retType.QualifiedName = (transType (convTypeToTgt typeof<unit>)).QualifiedName
let expectedState = if retUnit then ExpectedStackState.Empty else ExpectedStackState.Value
codeGen.EmitExpr (expectedState, expr)
if retUnit then ilg.Emit(I_ldnull) // ← BUG: also fires for true void (ILType.Void) methods
ilg.Emit I_ret
The intent was to handle F# unit-returning methods correctly (they return obj null at the IL level). However, the retUnit condition is true for both unit-returning and void-returning methods (since ILType.Void already matched the original check). As a result, I_ldnull is emitted before I_ret for every void method — including property setters, which are declared with ReturnType = typeof<Void> (line 1208):
let setter = ... ProvidedMethod(..., typeof<Void>, setterCode, ...) :> MethodInfo
This pushes an extra value onto the evaluation stack before the ret instruction, which the CLR rejects as invalid IL.
Fix
The I_ldnull emission should only happen when the return type is F# unit, not when it is a true CLR void. The condition should distinguish the two cases:
let retVoid = retType = ILType.Void
let retUnit = not retVoid && retType.QualifiedName = (transType (convTypeToTgt typeof<unit>)).QualifiedName
let expectedState = if retVoid || retUnit then ExpectedStackState.Empty else ExpectedStackState.Value
codeGen.EmitExpr (expectedState, expr)
if retUnit then ilg.Emit(I_ldnull) // only for F# unit, not void
ilg.Emit I_ret
Reproduction
Any generative type provider that generates types with writable properties (property setters) will fail. A concrete reproducer comes from SwaggerProvider, which uses the SDK's github-sourced ProvidedTypes.fs directly via Paket:
// Using SwaggerProvider with the PetStore schema (OpenAPI v2/v3):
type PetStore = OpenApiClientProvider<"petstore.yaml">
let pet = PetStore.Pet()
pet.Name <- "Fido" // ← System.InvalidProgramException: Common Language Runtime detected an invalid program
// at PetStore.Pet.set_Name(String value)
Failing tests observed in SwaggerProvider CI (both Ubuntu and Windows):
Swagger.PetStore.Tests.Instantiate provided objects — at PetStore.Pet.set_Name(String value)
Swagger.PetStore.Tests.create types with Nullable properties — at PetStoreNullable.Tag.set_Name(String value)
Swagger.NullableDate.Tests.PersonDto can deserialize JSON with null birthDate — at TestApi.PersonDto.set_Id(String value)
Swashbuckle.ReturnControllersTests.Return FileDescription GET/POST Test — at WebAPI.FileDescription.set_Name(FSharpOption'1 value)
Swashbuckle.ReturnControllersTests/UpdateControllersTests (PointClass, FileDescription) — at WebAPI.PointClass.set_X(FSharpOption'1 value)
12 out of 127 integration tests fail on both Linux and Windows (SwaggerProvider CI run: 26537266056).
Affected versions
Summary
The fix introduced in #518 (commit
ceeb6bc, "Fix for issue #517") causes a regression: all generated property setters produce invalid IL, throwingSystem.InvalidProgramException: Common Language Runtime detected an invalid programat runtime when any property is set on a type-provider–generated type.Root Cause
The change modifies the method body emitter in
src/ProvidedTypes.fs(around line 16052) as follows:The intent was to handle F#
unit-returning methods correctly (they returnobj nullat the IL level). However, theretUnitcondition istruefor bothunit-returning andvoid-returning methods (sinceILType.Voidalready matched the original check). As a result,I_ldnullis emitted beforeI_retfor everyvoidmethod — including property setters, which are declared withReturnType = typeof<Void>(line 1208):This pushes an extra value onto the evaluation stack before the
retinstruction, which the CLR rejects as invalid IL.Fix
The
I_ldnullemission should only happen when the return type is F#unit, not when it is a true CLRvoid. The condition should distinguish the two cases:Reproduction
Any generative type provider that generates types with writable properties (property setters) will fail. A concrete reproducer comes from SwaggerProvider, which uses the SDK's
github-sourcedProvidedTypes.fsdirectly via Paket:Failing tests observed in SwaggerProvider CI (both Ubuntu and Windows):
Swagger.PetStore.Tests.Instantiate provided objects—at PetStore.Pet.set_Name(String value)Swagger.PetStore.Tests.create types with Nullable properties—at PetStoreNullable.Tag.set_Name(String value)Swagger.NullableDate.Tests.PersonDto can deserialize JSON with null birthDate—at TestApi.PersonDto.set_Id(String value)Swashbuckle.ReturnControllersTests.Return FileDescription GET/POST Test—at WebAPI.FileDescription.set_Name(FSharpOption'1 value)Swashbuckle.ReturnControllersTests/UpdateControllersTests(PointClass, FileDescription) —at WebAPI.PointClass.set_X(FSharpOption'1 value)12 out of 127 integration tests fail on both Linux and Windows (SwaggerProvider CI run: 26537266056).
Affected versions
ceeb6bceb234baa72d3b2a469e8b33cc764d88ab(PR Fix for issue #517 #518, "Fix for issue Generative type providers break on try/finally with unit method bodies, enum comparisons #517")a54d92b72be3b3ce47461c742d0ded8d2e6998c4(previous HEAD before PR Fix for issue #517 #518)