Skip to content

InvalidProgramException on generated property setters after fix for #517 (ceeb6bc) — I_ldnull emitted before ret for void-returning methods #519

Description

@sergey-tihon

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 objectsat PetStore.Pet.set_Name(String value)
  • Swagger.PetStore.Tests.create types with Nullable propertiesat PetStoreNullable.Tag.set_Name(String value)
  • Swagger.NullableDate.Tests.PersonDto can deserialize JSON with null birthDateat TestApi.PersonDto.set_Id(String value)
  • Swashbuckle.ReturnControllersTests.Return FileDescription GET/POST Testat 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions